diff --git a/.vscode/settings.json b/.vscode/settings.json index 34e6347..829a7d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -71,5 +71,6 @@ }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "cSpell.words": ["Chatdoc"] } diff --git a/change/@zhishuyun-hub-8b85a325-d18a-4b5a-9d43-5818fc9bb392.json b/change/@zhishuyun-hub-8b85a325-d18a-4b5a-9d43-5818fc9bb392.json new file mode 100644 index 0000000..943cb7a --- /dev/null +++ b/change/@zhishuyun-hub-8b85a325-d18a-4b5a-9d43-5818fc9bb392.json @@ -0,0 +1,7 @@ +{ + "type": "major", + "comment": "add chatdoc feature", + "packageName": "@zhishuyun/hub", + "email": "cqc@germey.cn", + "dependentChangeType": "patch" +} diff --git a/src/components/chat/Message.vue b/src/components/chat/Message.vue index 47ca3cf..3e4c20d 100644 --- a/src/components/chat/Message.vue +++ b/src/components/chat/Message.vue @@ -33,7 +33,7 @@ import AnsweringMark from './AnsweringMark.vue'; import copy from 'copy-to-clipboard'; import { ElAlert, ElButton } from 'element-plus'; import MarkdownRenderer from '@/components/common/MarkdownRenderer.vue'; -import { IApplication, IChatMessage, IChatMessageState, ROLE_ASSISTANT } from '@/operators'; +import { IApplication, IChatMessage, IChatMessageState } from '@/operators'; import CopyToClipboard from '../common/CopyToClipboard.vue'; import { ERROR_CODE_API_ERROR, @@ -43,7 +43,8 @@ import { ERROR_CODE_TIMEOUT, ERROR_CODE_TOO_MANY_REQUESTS, ERROR_CODE_UNKNOWN, - ERROR_CODE_USED_UP + ERROR_CODE_USED_UP, + ROLE_ASSISTANT } from '@/constants'; import message from '@/i18n/zh/common/message'; import { ROUTE_CONSOLE_APPLICATION_BUY } from '@/router'; @@ -125,9 +126,6 @@ export default defineComponent({ } }); } - // onStop() { - // this.$emit('stop'); - // } } }); diff --git a/src/components/chatdoc/AnsweringMark.vue b/src/components/chatdoc/AnsweringMark.vue new file mode 100644 index 0000000..91e9102 --- /dev/null +++ b/src/components/chatdoc/AnsweringMark.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/components/chatdoc/Conversations.vue b/src/components/chatdoc/Conversations.vue new file mode 100644 index 0000000..ec9851c --- /dev/null +++ b/src/components/chatdoc/Conversations.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/components/chatdoc/CreateRepository.vue b/src/components/chatdoc/CreateRepository.vue new file mode 100644 index 0000000..178ee1d --- /dev/null +++ b/src/components/chatdoc/CreateRepository.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src/components/chatdoc/InputBox.vue b/src/components/chatdoc/InputBox.vue new file mode 100644 index 0000000..b5c0f7d --- /dev/null +++ b/src/components/chatdoc/InputBox.vue @@ -0,0 +1,155 @@ + + + + + + + diff --git a/src/components/chatdoc/Message.vue b/src/components/chatdoc/Message.vue new file mode 100644 index 0000000..23941d6 --- /dev/null +++ b/src/components/chatdoc/Message.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/src/components/chatdoc/SidePanel.vue b/src/components/chatdoc/SidePanel.vue new file mode 100644 index 0000000..c2f70a4 --- /dev/null +++ b/src/components/chatdoc/SidePanel.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/components/chatdoc/UploadDocument.vue b/src/components/chatdoc/UploadDocument.vue new file mode 100644 index 0000000..a5d598a --- /dev/null +++ b/src/components/chatdoc/UploadDocument.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/src/components/common/Navigator.vue b/src/components/common/Navigator.vue index 2c9f983..90b53e9 100644 --- a/src/components/common/Navigator.vue +++ b/src/components/common/Navigator.vue @@ -76,6 +76,10 @@ import { defineComponent } from 'vue'; import { ElButton, ElTooltip, ElMenu, ElMenuItem } from 'element-plus'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import { + ROUTE_CHATDOC_INDEX, + ROUTE_CHATDOC_CONVERSATION, + ROUTE_CHATDOC_MANAGE, + ROUTE_CHATDOC_SETTING, ROUTE_CHAT_CONVERSATION, ROUTE_CHAT_CONVERSATION_NEW, ROUTE_CONSOLE_ROOT, @@ -106,7 +110,6 @@ export default defineComponent({ }, displayName: this.$t('common.nav.chat'), icon: 'fa-regular fa-comment', - image: 'https://cdn.zhishuyun.com/9ad12c99b2.png/thumb_100x100', routes: [ROUTE_CHAT_CONVERSATION, ROUTE_CHAT_CONVERSATION_NEW] }); } @@ -117,11 +120,21 @@ export default defineComponent({ }, displayName: this.$t('common.nav.midjourney'), icon: 'fa-solid fa-palette', - image: 'https://cdn.zhishuyun.com/83ee211091.png/thumb_100x100', routes: [ROUTE_MIDJOURNEY_INDEX, ROUTE_MIDJOURNEY_HISTORY] }); } + if (this.$config.navigation?.chatdoc) { + links.push({ + route: { + name: ROUTE_CHATDOC_INDEX + }, + displayName: this.$t('common.nav.chatdoc'), + icon: 'fa-solid fa-file-lines', + routes: [ROUTE_CHATDOC_INDEX, ROUTE_CHATDOC_CONVERSATION, ROUTE_CHATDOC_MANAGE, ROUTE_CHATDOC_SETTING] + }); + } + return { links, activeIndex: this.$route.name as string @@ -173,6 +186,7 @@ export default defineComponent({ await this.$store.dispatch('resetAll'); await this.$store.dispatch('chat/resetAll'); await this.$store.dispatch('midjourney/resetAll'); + await this.$store.dispatch('chatdoc/resetAll'); }, onConsole() { this.$router.push({ name: ROUTE_CONSOLE_ROOT }); diff --git a/src/config.ts b/src/config.ts index c152edf..8c30a1c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -43,6 +43,11 @@ export default { */ midjourney: true, + /** + * Show chatdoc entry in left navigation. + */ + chatdoc: true, + /** * Show console entry in left navigation. */ diff --git a/src/constants/action.ts b/src/constants/action.ts new file mode 100644 index 0000000..cc2601f --- /dev/null +++ b/src/constants/action.ts @@ -0,0 +1,6 @@ +export const ACTION_CREATE = 'create'; +export const ACTION_UPDATE = 'update'; +export const ACTION_DELETE = 'delete'; +export const ACTION_RETRIEVE = 'retrieve'; +export const ACTION_RETRIEVE_BATCH = 'retrieve_batch'; +export const ACTION_RETRIEVE_ALL = 'retrieve_all'; diff --git a/src/constants/index.ts b/src/constants/index.ts index 3777092..3e85f69 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,2 +1,4 @@ export * from './errorCode'; export * from './endpoint'; +export * from './role'; +export * from './action'; diff --git a/src/constants/role.ts b/src/constants/role.ts new file mode 100644 index 0000000..badc457 --- /dev/null +++ b/src/constants/role.ts @@ -0,0 +1,3 @@ +export const ROLE_SYSTEM = 'system'; +export const ROLE_ASSISTANT = 'assistant'; +export const ROLE_USER = 'user'; diff --git a/src/i18n/zh/chatdoc/button.ts b/src/i18n/zh/chatdoc/button.ts new file mode 100644 index 0000000..77d211c --- /dev/null +++ b/src/i18n/zh/chatdoc/button.ts @@ -0,0 +1,3 @@ +export default { + uploadDocuments: '上传文档' +}; diff --git a/src/i18n/zh/chatdoc/field.ts b/src/i18n/zh/chatdoc/field.ts new file mode 100644 index 0000000..4d69050 --- /dev/null +++ b/src/i18n/zh/chatdoc/field.ts @@ -0,0 +1,13 @@ +export default { + fileName: '文件名', + createdAt: '创建时间', + fileExtension: '文件类型', + fileUrl: '文件链接', + fileSize: '文件大小', + state: '状态', + stateProcessing: '学习中', + stateCompleted: '学习完成', + stateFailed: '学习失败', + name: '名称', + description: '描述' +}; diff --git a/src/i18n/zh/chatdoc/index.ts b/src/i18n/zh/chatdoc/index.ts new file mode 100644 index 0000000..ea8125c --- /dev/null +++ b/src/i18n/zh/chatdoc/index.ts @@ -0,0 +1,7 @@ +import message from './message'; +import title from './title'; +import nav from './nav'; +import field from './field'; +import button from './button'; + +export default { message, title, nav, field, button }; diff --git a/src/i18n/zh/chatdoc/message.ts b/src/i18n/zh/chatdoc/message.ts new file mode 100644 index 0000000..ded4d27 --- /dev/null +++ b/src/i18n/zh/chatdoc/message.ts @@ -0,0 +1,31 @@ +export default { + introductionForKnowledge: '知识库中可添加文档,在机器人回答时,可运用库中的知识进行回复。', + introductionForRepository: + '知识库是一组文档的集合,您可以在知识库中添加文档,机器人在回答时,会从知识库中的文档中进行回复。', + uploadDocumentsExceed: '上传文档数量超过限制', + uploadDocumentsError: '上传文档失败', + uploadDocumentsSuccess: '上传文档成功', + createDocumentSuccess: '学习文档成功', + startCreateDocument: '开始学习文档...', + createDocumentError: '学习文档失败', + dragOrClickToUpload: '拖拽或点击上传文档', + learningDocument: '学习中,请稍后...', + startNewChat: '开始新会话', + errorApiError: '回答失败,请稍后重试', + errorBadRequest: '请求内容不规范,请重新提问', + errorNoConversation: '对话内容不存在或者已经过期,请发起新的会话', + errorContentTooLarge: '问题内容过长,请缩短后重试', + errorTooManyRequests: '您的操作过于频繁,请稍后重试', + errorUsedUp: '您的套餐次数已经用完,请购买更多次数继续使用', + errorUnknown: '服务器出现未知错误,请稍后重试或联系客服', + errorTimeout: '回答问题超时,请稍后重试', + errorNotApplied: '您尚未申请该服务,请先申请再继续提问', + confirmDelete: '确定删除', + howToUse: '按 Shift+Enter 键可以换行', + createRepositorySuccess: '创建知识库成功', + createRepositoryFailed: '创建知识库失败', + deleteRepositorySuccess: '删除知识库成功', + deleteDocumentSuccess: '删除文档成功', + currentRepository: '当前知识库', + nameRequired: '名称不能为空' +}; diff --git a/src/i18n/zh/chatdoc/nav.ts b/src/i18n/zh/chatdoc/nav.ts new file mode 100644 index 0000000..074d904 --- /dev/null +++ b/src/i18n/zh/chatdoc/nav.ts @@ -0,0 +1,5 @@ +export default { + chat: '对话', + setting: '设置', + manage: '管理' +}; diff --git a/src/i18n/zh/chatdoc/title.ts b/src/i18n/zh/chatdoc/title.ts new file mode 100644 index 0000000..55b480a --- /dev/null +++ b/src/i18n/zh/chatdoc/title.ts @@ -0,0 +1,5 @@ +export default { + manage: '管理知识库', + createRepository: '创建知识库', + repositories: '知识库列表' +}; diff --git a/src/i18n/zh/common/button.ts b/src/i18n/zh/common/button.ts index c7edd2b..e01182b 100644 --- a/src/i18n/zh/common/button.ts +++ b/src/i18n/zh/common/button.ts @@ -29,5 +29,6 @@ export default { buyMore: '购买更多', update: '更新', copy: '复制', - stop: '停止' + stop: '停止', + create: '创建' }; diff --git a/src/i18n/zh/common/nav.ts b/src/i18n/zh/common/nav.ts index efb0f05..9383130 100644 --- a/src/i18n/zh/common/nav.ts +++ b/src/i18n/zh/common/nav.ts @@ -15,5 +15,6 @@ export default { console: '控制台', newChat: '新建对话', logOut: '退出登录', - distribution: '分销赚钱' + distribution: '分销赚钱', + chatdoc: 'AI 知识库' }; diff --git a/src/i18n/zh/index.ts b/src/i18n/zh/index.ts index 4a68916..5dc5217 100644 --- a/src/i18n/zh/index.ts +++ b/src/i18n/zh/index.ts @@ -8,9 +8,11 @@ import console from './console'; import order from './order'; import distribution from './distribution'; import user from './user'; +import chatdoc from './chatdoc'; export default { chat, + chatdoc, user, order, console, diff --git a/src/layouts/Chatdoc.vue b/src/layouts/Chatdoc.vue new file mode 100644 index 0000000..0dc27c0 --- /dev/null +++ b/src/layouts/Chatdoc.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/src/operators/chat/constants.ts b/src/operators/chat/constants.ts index 072fb28..c5a25f3 100644 --- a/src/operators/chat/constants.ts +++ b/src/operators/chat/constants.ts @@ -1,9 +1,5 @@ import { IChatModel } from './models'; -export const ROLE_SYSTEM = 'system'; -export const ROLE_ASSISTANT = 'assistant'; -export const ROLE_USER = 'user'; - export const CHAT_MODEL_NAME_CHATGPT = 'chatgpt'; export const CHAT_MODEL_NAME_CHATGPT4 = 'chatgpt4'; export const CHAT_MODEL_NAME_CHATGPT_16K = 'chatgpt-16k'; diff --git a/src/operators/chat/models.ts b/src/operators/chat/models.ts index fc04a9d..905ad2f 100644 --- a/src/operators/chat/models.ts +++ b/src/operators/chat/models.ts @@ -1,13 +1,11 @@ +import { ROLE_ASSISTANT, ROLE_SYSTEM, ROLE_USER } from '@/constants'; import { CHAT_MODEL_NAME_CHATGPT, CHAT_MODEL_NAME_CHATGPT4, CHAT_MODEL_NAME_CHATGPT4_BROWSING, CHAT_MODEL_NAME_CHATGPT4_VISION, CHAT_MODEL_NAME_CHATGPT_16K, - CHAT_MODEL_NAME_CHATGPT_BROWSING, - ROLE_ASSISTANT, - ROLE_SYSTEM, - ROLE_USER + CHAT_MODEL_NAME_CHATGPT_BROWSING } from './constants'; export type IChatModelName = @@ -25,7 +23,7 @@ export interface IChatModel { description: string; } -export interface IError { +interface IError { code: string; detail?: string; } diff --git a/src/operators/chatdoc/constants.ts b/src/operators/chatdoc/constants.ts new file mode 100644 index 0000000..60bf284 --- /dev/null +++ b/src/operators/chatdoc/constants.ts @@ -0,0 +1,4 @@ +export const API_ID_CHATDOC_REPOSITORIES = 'e22e9e3f-5594-4c48-9284-c1847c22c65f'; +export const API_ID_CHATDOC_DOCUMENTS = 'afc7917f-d89f-4dc9-95c2-863936b02cad'; +export const API_ID_CHATDOC_CONVERSATIONS = 'afc7917f-d89f-4dc9-95c2-863936b02cad'; +export const API_ID_CHATDOC_CHAT = '099eafc8-8167-4557-b8f2-47867ca76e24'; diff --git a/src/operators/chatdoc/index.ts b/src/operators/chatdoc/index.ts new file mode 100644 index 0000000..4a6cbae --- /dev/null +++ b/src/operators/chatdoc/index.ts @@ -0,0 +1,2 @@ +export * from './models'; +export * from './operator'; diff --git a/src/operators/chatdoc/models.ts b/src/operators/chatdoc/models.ts new file mode 100644 index 0000000..ce07606 --- /dev/null +++ b/src/operators/chatdoc/models.ts @@ -0,0 +1,76 @@ +import { ACTION_CREATE, ACTION_DELETE, ACTION_UPDATE, ROLE_ASSISTANT, ROLE_SYSTEM, ROLE_USER } from '@/constants'; + +export interface IError { + code: string; + detail?: string; +} + +export interface IChatdocRepository { + id: string; + name?: string; + description?: string; + deleting?: boolean; + editing?: boolean; + documents?: IChatdocDocument[]; + conversations?: IChatdocConversation[]; +} + +export interface IChatdocDocument { + id: string; + repository_id: string; + file_url: string; + file_name: string; +} + +export interface IChatdocConversation { + id: string; + repository_id: string; + messages: IChatdocMessage[]; + editing?: boolean; + deleting?: boolean; +} + +export interface IChatdocMessage { + content?: string; + error?: IError; + state?: IChatdocMessageState; + role?: typeof ROLE_SYSTEM | typeof ROLE_ASSISTANT | typeof ROLE_USER; +} + +export enum IChatdocMessageState { + PENDING = 'pending', + ANSWERING = 'answering', + FINISHED = 'finished', + FAILED = 'failed' +} + +export interface IChatdocChatRequest { + repository_id: string; + messages: IChatdocMessage[]; + temperature?: number; + knowledge_fallback?: boolean; +} + +export interface IChatdocDocumentRequest extends IChatdocDocument { + action: typeof ACTION_CREATE | typeof ACTION_UPDATE | typeof ACTION_DELETE; + callback_url?: string; +} + +export interface IChatdocDocumentResponse extends IChatdocDocument {} + +export type IChatdocConversationsResponse = IChatdocConversation[]; + +export interface IChatdocRepositoryRequest extends IChatdocRepository { + action: typeof ACTION_CREATE | typeof ACTION_UPDATE | typeof ACTION_DELETE; +} + +export interface IChatdocRepositoryResponse extends IChatdocRepository {} +export type IChatdocRepositoriesResponse = IChatdocRepository[]; +export type IChatdocDocumentsResponse = IChatdocDocument[]; + +export interface IChatdocChatResponse { + answer: string; + delta_answer: string; + repository_id?: string; + conversation_id?: string; +} diff --git a/src/operators/chatdoc/operator.ts b/src/operators/chatdoc/operator.ts new file mode 100644 index 0000000..3837b3d --- /dev/null +++ b/src/operators/chatdoc/operator.ts @@ -0,0 +1,300 @@ +import axios, { AxiosProgressEvent, AxiosResponse } from 'axios'; +import { + IChatdocChatResponse, + IChatdocConversation, + IChatdocConversationsResponse, + IChatdocDocumentResponse, + IChatdocDocumentsResponse, + IChatdocRepositoriesResponse, + IChatdocRepositoryResponse +} from './models'; +import { ACTION_RETRIEVE_ALL, ACTION_UPDATE, BASE_URL_API } from '@/constants'; +import { ACTION_CREATE, ACTION_DELETE, ACTION_RETRIEVE, ACTION_RETRIEVE_BATCH } from '@/constants'; + +class ChatdocOperator { + async createRepository( + payload: { + name: string; + description?: string; + }, + options: { token: string } + ): Promise> { + return await axios.post( + `/chatdoc/repositories`, + { + action: ACTION_CREATE, + name: payload.name, + description: payload.description + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async deleteRepository(id: string, options: { token: string }): Promise> { + return await axios.post( + `/chatdoc/repositories`, + { + action: ACTION_DELETE, + id + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async getRepository(id: string, options: { token: string }): Promise> { + return await axios.post( + `/chatdoc/repositories`, + { + action: ACTION_RETRIEVE, + id + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async getAllRepositories(options: { token: string }): Promise> { + return await axios.post( + `/chatdoc/repositories`, + { + action: ACTION_RETRIEVE_ALL + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async getAllDocuments( + repositoryId: string, + options: { token: string } + ): Promise> { + return await axios.post( + `/chatdoc/documents`, + { + action: ACTION_RETRIEVE_ALL, + repository_id: repositoryId + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async getAllConversations(repositoryId: string): Promise> { + return await axios.post( + `/chatdoc/conversations`, + { + action: ACTION_RETRIEVE_ALL, + repository_id: repositoryId + }, + { + headers: { + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async getRepositories(ids: string[]): Promise> { + return await axios.post( + `/chatdoc/repositories`, + { + action: ACTION_RETRIEVE_BATCH, + ids + }, + { + headers: { + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async createDocument( + payload: { + repositoryId: string; + fileUrl: string; + fileName: string; + }, + options: { token: string } + ): Promise> { + return await axios.post( + `/chatdoc/documents`, + { + action: ACTION_CREATE, + repository_id: payload.repositoryId, + file_url: payload.fileUrl, + file_name: payload.fileName + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async retrieveDocument(id: string, options: { token: string }): Promise> { + return await axios.post( + `/chatdoc/documents`, + { + action: ACTION_RETRIEVE, + id + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async retrieveConversation(id: string, options: { token: string }): Promise> { + return await axios.post( + `/chatdoc/conversations`, + { + action: ACTION_RETRIEVE, + id + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async deleteDocument(id: string, options: { token: string }): Promise> { + return await axios.post( + `/chatdoc/documents`, + { + action: ACTION_DELETE, + id + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async updateConversation(payload: IChatdocConversation) { + return await axios.post( + `/chatdoc/conversations`, + { + action: ACTION_UPDATE, + id: payload.id, + messages: payload.messages + }, + { + headers: { + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async deleteConversation(id: string): Promise> { + return await axios.post( + `/chatdoc/conversations`, + { + action: ACTION_DELETE, + id + }, + { + headers: { + accept: 'application/json', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API + } + ); + } + + async chat( + payload: { repositoryId: string; question: string; conversationId?: string; knowledgeFallback?: boolean }, + options: { token: string; stream: (response: IChatdocChatResponse) => void } + ): Promise> { + return await axios.post( + `/chatdoc/chat`, + { + repository_id: payload.repositoryId, + question: payload.question, + conversation_id: payload.conversationId, + stateful: true + }, + { + headers: { + authorization: `Bearer ${options.token}`, + accept: 'application/x-ndjson', + 'content-type': 'application/json' + }, + baseURL: BASE_URL_API, + responseType: 'stream', + onDownloadProgress: ({ event }: AxiosProgressEvent) => { + const response = event.target.response; + const lines = response.split('\r\n').filter((line: string) => !!line); + const lastLine = lines[lines.length - 1]; + if (lastLine) { + const jsonData = JSON.parse(lastLine); + if (options?.stream) { + options?.stream(jsonData as IChatdocChatResponse); + } + } + } + } + ); + } +} + +export const chatdocOperator = new ChatdocOperator(); diff --git a/src/operators/index.ts b/src/operators/index.ts index d207241..d8e4c03 100644 --- a/src/operators/index.ts +++ b/src/operators/index.ts @@ -7,3 +7,4 @@ export * from './usage'; export * from './api'; export * from './order'; export * from './distribution'; +export * from './chatdoc'; diff --git a/src/operators/midjourney/operator.ts b/src/operators/midjourney/operator.ts index be991e6..334df2c 100644 --- a/src/operators/midjourney/operator.ts +++ b/src/operators/midjourney/operator.ts @@ -1,4 +1,4 @@ -import axios, { AxiosResponse } from 'axios'; +import axios, { AxiosProgressEvent, AxiosResponse } from 'axios'; import { IMidjourneyImagineRequest, IMidjourneyImagineResponse, IMidjourneyImagineTask } from './models'; import { BASE_URL_API } from '@/constants'; @@ -54,7 +54,7 @@ class MidjourneyOperator { }, baseURL: options.endpoint, responseType: 'stream', - onDownloadProgress: (event) => { + onDownloadProgress: ({ event }: AxiosProgressEvent) => { const response = event.target.response; const lines = response.split('\r\n').filter((line: string) => !!line); const lastLine = lines[lines.length - 1]; diff --git a/src/pages/chat/Conversation.vue b/src/pages/chat/Conversation.vue index d674f1e..3086e92 100644 --- a/src/pages/chat/Conversation.vue +++ b/src/pages/chat/Conversation.vue @@ -39,9 +39,8 @@ import axios from 'axios'; import { defineComponent } from 'vue'; import Message from '@/components/chat/Message.vue'; +import { ROLE_ASSISTANT, ROLE_USER } from '@/constants'; import { - ROLE_ASSISTANT, - ROLE_USER, IChatModel, IApplication, IChatMessageState, diff --git a/src/pages/chatdoc/Conversation.vue b/src/pages/chatdoc/Conversation.vue new file mode 100644 index 0000000..d999f95 --- /dev/null +++ b/src/pages/chatdoc/Conversation.vue @@ -0,0 +1,291 @@ + + + + + diff --git a/src/pages/chatdoc/Index.vue b/src/pages/chatdoc/Index.vue new file mode 100644 index 0000000..303f22f --- /dev/null +++ b/src/pages/chatdoc/Index.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/src/pages/chatdoc/Manage.vue b/src/pages/chatdoc/Manage.vue new file mode 100644 index 0000000..a70686a --- /dev/null +++ b/src/pages/chatdoc/Manage.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/src/pages/chatdoc/Setting.vue b/src/pages/chatdoc/Setting.vue new file mode 100644 index 0000000..c4cf7fe --- /dev/null +++ b/src/pages/chatdoc/Setting.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/plugins/font-awesome.ts b/src/plugins/font-awesome.ts index 128feb1..781d6fd 100644 --- a/src/plugins/font-awesome.ts +++ b/src/plugins/font-awesome.ts @@ -51,13 +51,16 @@ import { faCube as faSolidCube, faShieldHalved as faSolidShieldHalved, faCubes as faSolidCubes, + faFileLines as faSolidFileLines, faLink as faSolidLink, faWandMagic as faSolidWandMagic } from '@fortawesome/free-solid-svg-icons'; + library.add(faRegularCopy); library.add(faSolidStore); library.add(faSolidChevronRight); library.add(faSolidCube); +library.add(faSolidFileLines); library.add(faSolidCompass); library.add(faSolidPaperclip); library.add(faSolidXmark); diff --git a/src/router/chatdoc.ts b/src/router/chatdoc.ts new file mode 100644 index 0000000..a9f858a --- /dev/null +++ b/src/router/chatdoc.ts @@ -0,0 +1,42 @@ +import { + ROUTE_CHATDOC_CONVERSATION, + ROUTE_CHATDOC_CONVERSATION_NEW, + ROUTE_CHATDOC_INDEX, + ROUTE_CHATDOC_MANAGE, + ROUTE_CHATDOC_SETTING +} from './constants'; + +export default { + path: '/chatdoc', + meta: { + auth: true + }, + component: () => import('@/layouts/Main.vue'), + children: [ + { + path: '', + name: ROUTE_CHATDOC_INDEX, + component: () => import('@/pages/chatdoc/Index.vue') + }, + { + path: 'repository/:repositoryId/conversation', + name: ROUTE_CHATDOC_CONVERSATION_NEW, + component: () => import('@/pages/chatdoc/Conversation.vue') + }, + { + path: 'repository/:repositoryId/conversation/:conversationId', + name: ROUTE_CHATDOC_CONVERSATION, + component: () => import('@/pages/chatdoc/Conversation.vue') + }, + { + path: 'repository/:repositoryId/manage', + name: ROUTE_CHATDOC_MANAGE, + component: () => import('@/pages/chatdoc/Manage.vue') + }, + { + path: 'repository/:repositoryId', + name: ROUTE_CHATDOC_SETTING, + component: () => import('@/pages/chatdoc/Setting.vue') + } + ] +}; diff --git a/src/router/constants.ts b/src/router/constants.ts index 14ac189..b7d54b1 100644 --- a/src/router/constants.ts +++ b/src/router/constants.ts @@ -9,6 +9,12 @@ export const ROUTE_CHAT_CONVERSATION_NEW = 'chat-conversation-new'; export const ROUTE_MIDJOURNEY_INDEX = 'midjourney-index'; export const ROUTE_MIDJOURNEY_HISTORY = 'midjourney-history'; +export const ROUTE_CHATDOC_INDEX = 'chatdoc-index'; +export const ROUTE_CHATDOC_CONVERSATION = 'chatdoc-conversation'; +export const ROUTE_CHATDOC_CONVERSATION_NEW = 'chatdoc-conversation-new'; +export const ROUTE_CHATDOC_SETTING = 'chatdoc-setting'; +export const ROUTE_CHATDOC_MANAGE = 'chatdoc-knowledge'; + export const ROUTE_CONSOLE_ROOT = 'console-root'; export const ROUTE_CONSOLE_ORDER_LIST = 'console-order-list'; export const ROUTE_CONSOLE_ORDER_DETAIL = 'console-order-detail'; diff --git a/src/router/index.ts b/src/router/index.ts index b2207dd..da3e5a8 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -4,6 +4,7 @@ import console from './console'; import chat from './chat'; import midjourney from './midjourney'; import distribution from './distribution'; +import chatdoc from './chatdoc'; import { ROUTE_CHAT_CONVERSATION_NEW, ROUTE_INDEX } from './constants'; @@ -16,6 +17,7 @@ const routes = [ } }, console, + chatdoc, auth, chat, midjourney, diff --git a/src/router/midjourney.ts b/src/router/midjourney.ts index c25dd2a..89d7932 100644 --- a/src/router/midjourney.ts +++ b/src/router/midjourney.ts @@ -2,6 +2,9 @@ import { ROUTE_MIDJOURNEY_HISTORY, ROUTE_MIDJOURNEY_INDEX } from './constants'; export default { path: '/midjourney', + meta: { + auth: true + }, component: () => import('@/layouts/Main.vue'), children: [ { diff --git a/src/store/chatdoc/actions.ts b/src/store/chatdoc/actions.ts new file mode 100644 index 0000000..21fb9ca --- /dev/null +++ b/src/store/chatdoc/actions.ts @@ -0,0 +1,274 @@ +import { IApplication, apiUsageOperator, applicationOperator } from '@/operators'; +import { IRootState, Status } from '../common/models'; +import { ActionContext } from 'vuex'; +import { log } from '@/utils/log'; +import { IChatdocState } from './models'; +import { IChatdocConversation, IChatdocDocument, IChatdocRepository } from '@/operators/chatdoc/models'; +import { chatdocOperator } from '@/operators/chatdoc/operator'; +import { + API_ID_CHATDOC_CHAT, + API_ID_CHATDOC_DOCUMENTS, + API_ID_CHATDOC_REPOSITORIES +} from '@/operators/chatdoc/constants'; + +export const setApplications = async ({ commit }: any, payload: IApplication[]): Promise => { + commit('setApplications', payload); +}; + +export const setRepository = async ({ commit }: any, payload: { repository: IChatdocRepository }): Promise => { + commit('setRepository', payload); +}; + +export const getApplications = async ({ + commit, + rootState +}: ActionContext): Promise => { + log(getApplications, 'start to get application for chat'); + commit('setGetApplicationsStatus', Status.Request); + const { data: applications } = await applicationOperator.getAll({ + user_id: rootState.user.id, + api_id: [API_ID_CHATDOC_REPOSITORIES, API_ID_CHATDOC_CHAT, API_ID_CHATDOC_DOCUMENTS] + }); + log(getApplications, 'get application for chat success', applications); + commit('setGetApplicationsStatus', Status.Success); + commit('setApplications', applications?.items); + return applications.items; +}; + +export const resetAll = ({ commit }: ActionContext): void => { + commit('resetAll'); +}; + +export const getRepositories = async ({ + commit, + state +}: ActionContext): Promise => { + log(getRepositories, 'start to get repositories'); + commit('setGetRepositoriesStatus', Status.Request); + const applications = state.applications; + console.log('applications', applications); + const application = applications?.find( + (application: IApplication) => application.api?.id === API_ID_CHATDOC_REPOSITORIES + ); + console.log('application', application); + const token = application?.credential?.token; + if (!token) { + commit('setRepositories', undefined); + return []; + } + const repositories = ( + await chatdocOperator.getAllRepositories({ + token + }) + ).data; + commit('setGetRepositoriesStatus', Status.Success); + log(getRepositories, 'get repositories success', repositories); + commit('setRepositories', repositories); + return repositories; +}; + +export const deleteRepository = async ( + { state }: ActionContext, + payload: { + id: string; + } +): Promise => { + log(deleteRepository, 'start to delete repository'); + const applications = state.applications; + console.log('applications', applications); + const application = applications?.find( + (application: IApplication) => application.api?.id === API_ID_CHATDOC_REPOSITORIES + ); + console.log('application', application); + const token = application?.credential?.token; + if (!token) { + return Promise.reject('no token'); + } + const repository = ( + await chatdocOperator.deleteRepository(payload.id, { + token + }) + ).data; + log(deleteRepository, 'delete repository success', repository); + return repository; +}; + +export const deleteDocument = async ( + { state }: ActionContext, + payload: { + id: string; + } +): Promise => { + log(deleteDocument, 'start to delete document'); + const applications = state.applications; + console.log('applications', applications); + const application = applications?.find( + (application: IApplication) => application.api?.id === API_ID_CHATDOC_DOCUMENTS + ); + console.log('application', application); + const token = application?.credential?.token; + if (!token) { + return Promise.reject('no token'); + } + const document = ( + await chatdocOperator.deleteDocument(payload.id, { + token + }) + ).data; + log(deleteRepository, 'delete document success', document); + return document; +}; + +export const createRepository = async ( + { state }: ActionContext, + payload: { + name: string; + description?: string; + } +): Promise => { + log(createRepository, 'start to create repository'); + const applications = state.applications; + console.log('applications', applications); + const application = applications?.find( + (application: IApplication) => application.api?.id === API_ID_CHATDOC_REPOSITORIES + ); + console.log('application', application); + const token = application?.credential?.token; + if (!token) { + return Promise.reject('no token'); + } + const repository = ( + await chatdocOperator.createRepository(payload, { + token + }) + ).data; + log(createRepository, 'create repository success', repository); + return repository; +}; + +export const createDocument = async ( + { state }: ActionContext, + payload: { + repositoryId: string; + fileUrl: string; + fileName: string; + } +): Promise => { + log(createDocument, 'start to create document'); + const applications = state.applications; + console.log('applications', applications); + const application = applications?.find( + (application: IApplication) => application.api?.id === API_ID_CHATDOC_DOCUMENTS + ); + console.log('application', application); + const token = application?.credential?.token; + if (!token) { + return Promise.reject('no token'); + } + const document = ( + await chatdocOperator.createDocument(payload, { + token + }) + ).data; + log(createDocument, 'create document success', document); + return document; +}; + +export const getDocuments = async ( + { commit, state }: ActionContext, + payload: { repositoryId: string } +): Promise => { + log(getRepositories, 'start to get documents'); + const applications = state.applications; + console.log('applications', applications); + const application = applications?.find( + (application: IApplication) => application.api?.id === API_ID_CHATDOC_DOCUMENTS + ); + console.log('application for getDocuments', application); + const token = application?.credential?.token; + if (!token) { + commit('setRepository', { + id: payload.repositoryId, + documents: [] + }); + return []; + } + const documents = ( + await chatdocOperator.getAllDocuments(payload.repositoryId, { + token + }) + ).data; + log(getRepositories, 'get documents success', documents); + commit('setRepository', { + id: payload.repositoryId, + documents: documents + }); + return documents; +}; + +export const getConversations = async ( + { commit, state }: ActionContext, + payload: { repositoryId: string } +): Promise => { + log(getConversations, 'start to get conversations'); + const applications = state.applications; + console.log('applications', applications); + const application = applications?.find((application: IApplication) => application.api?.id === API_ID_CHATDOC_CHAT); + console.log('application for getConversations', application); + const token = application?.credential?.token; + if (!token) { + commit('setRepository', { + id: payload.repositoryId, + conversations: [] + }); + return []; + } + const conversations = (await chatdocOperator.getAllConversations(payload.repositoryId)).data; + log(getConversations, 'get conversations success', conversations); + commit('setRepository', { + id: payload.repositoryId, + conversations: conversations + }); + return conversations; +}; + +export const getRepository = async ( + { commit, state }: ActionContext, + payload: { id: string } +): Promise => { + log(getRepository, 'start to get repository'); + // commit('setGetRepositoryStatus', Status.Request); + const applications = state.applications; + const application = applications?.find( + (application: IApplication) => application.api?.id === API_ID_CHATDOC_REPOSITORIES + ); + const token = application?.credential?.token; + if (!token) { + commit('setRepository', { id: payload.id }); + return Promise.reject('no token'); + } + const repository = ( + await chatdocOperator.getRepository(payload.id, { + token + }) + ).data; + // commit('setGetRepositoryStatus', Status.Success); + log(getRepository, 'get repository success', repository); + commit('setRepository', repository); + return repository; +}; + +export default { + createRepository, + setApplications, + getApplications, + getRepositories, + deleteRepository, + getRepository, + setRepository, + getConversations, + getDocuments, + resetAll, + createDocument, + deleteDocument +}; diff --git a/src/store/chatdoc/index.ts b/src/store/chatdoc/index.ts new file mode 100644 index 0000000..5e3daa3 --- /dev/null +++ b/src/store/chatdoc/index.ts @@ -0,0 +1,14 @@ +import { Module } from 'vuex'; +import actions from './actions'; +import mutations from './mutations'; +import state from './state'; +import { IChatdocState } from './models'; + +export const chatdoc: Module = { + namespaced: true, + state, + mutations, + actions +}; + +export default chatdoc; diff --git a/src/store/chatdoc/models.ts b/src/store/chatdoc/models.ts new file mode 100644 index 0000000..1c1b842 --- /dev/null +++ b/src/store/chatdoc/models.ts @@ -0,0 +1,10 @@ +import { IApplication } from '@/operators'; +import { Status } from '../common/models'; +import { IChatdocRepository } from '@/operators/chatdoc/models'; + +export interface IChatdocState { + applications: IApplication[] | undefined; + getApplicationsStatus: Status | undefined; + repositories: IChatdocRepository[] | undefined; + getRepositoriesStatus: Status | undefined; +} diff --git a/src/store/chatdoc/mutations.ts b/src/store/chatdoc/mutations.ts new file mode 100644 index 0000000..f22f1f8 --- /dev/null +++ b/src/store/chatdoc/mutations.ts @@ -0,0 +1,74 @@ +import { IApplication, IChatConversation, IChatModel } from '@/operators'; +import { IChatdocState } from './models'; +import { Status } from '../common/models'; +import { IChatdocRepository } from '@/operators/chatdoc/models'; +import { log } from '@/utils'; + +export const resetAll = (state: IChatdocState): void => { + state.applications = []; + state.getApplicationsStatus = Status.None; + state.repositories = []; +}; + +export const setApplications = (state: IChatdocState, payload: IApplication[]): void => { + state.applications = payload; +}; + +export const setGetApplicationsStatus = (state: IChatdocState, payload: Status): void => { + state.getApplicationsStatus = payload; +}; + +export const setRepositories = (state: IChatdocState, payload: IChatdocRepository[]): void => { + const currentRepositories = state.repositories; + if (currentRepositories) { + // merge current repositories into payload + payload.forEach((repository: IChatdocRepository) => { + const index = currentRepositories.findIndex((item: IChatdocRepository) => item.id === repository.id); + if (index !== -1) { + payload[index] = { + ...currentRepositories[index], + ...repository + }; + } + }); + state.repositories = payload; + return; + } else { + // set the repositories + state.repositories = payload; + } +}; + +export const setRepository = (state: IChatdocState, payload: IChatdocRepository): void => { + log(setRepository, 'mutation', payload); + // find the repository and set it + const repository = payload; + const repositories = state.repositories; + if (!repositories) { + log(setRepository, 'no repositories'); + return; + } + const index = repositories.findIndex((item: IChatdocRepository) => item.id === repository.id); + if (index === -1) { + log(setRepository, 'no repository found'); + return; + } + log(setRepository, 'set repository for index', index, repository); + repositories[index] = { + ...repositories[index], + ...repository + }; +}; + +export const setGetRepositoriesStatus = (state: IChatdocState, payload: Status): void => { + state.getRepositoriesStatus = payload; +}; + +export default { + setApplications, + setGetApplicationsStatus, + setRepositories, + setRepository, + setGetRepositoriesStatus, + resetAll +}; diff --git a/src/store/chatdoc/persist.ts b/src/store/chatdoc/persist.ts new file mode 100644 index 0000000..aae5063 --- /dev/null +++ b/src/store/chatdoc/persist.ts @@ -0,0 +1 @@ +export default ['chatdoc.repositories', 'chatdoc.applications']; diff --git a/src/store/chatdoc/state.ts b/src/store/chatdoc/state.ts new file mode 100644 index 0000000..bd2d267 --- /dev/null +++ b/src/store/chatdoc/state.ts @@ -0,0 +1,10 @@ +import { IChatdocState } from './models'; + +export default (): IChatdocState => { + return { + applications: undefined, + getApplicationsStatus: undefined, + repositories: undefined, + getRepositoriesStatus: undefined + }; +}; diff --git a/src/store/common/models.ts b/src/store/common/models.ts index 073fe91..82b04b6 100644 --- a/src/store/common/models.ts +++ b/src/store/common/models.ts @@ -1,6 +1,7 @@ import { IUser } from '@/operators'; import { IMidjourneyState } from '../midjourney/models'; import { IChatState } from '../chat/models'; +import { IChatdocState } from '../chatdoc/models'; export enum Status { Request = 'Request', @@ -28,4 +29,5 @@ export interface ICommonState { export interface IRootState extends ICommonState { midjourney: IMidjourneyState; chat: IChatState; + chatdoc: IChatdocState; } diff --git a/src/store/index.ts b/src/store/index.ts index 3a41d69..afeb697 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,20 +2,23 @@ import { createStore } from 'vuex'; import createPersistedState from 'vuex-persistedstate'; import midjourney from './midjourney'; import chat from './chat'; +import chatdoc from './chatdoc'; import root from './common'; import persistChat from './chat/persist'; import persistMidjourney from './midjourney/persist'; +import persistChatdoc from './chatdoc/persist'; import persistRoot from './common/persist'; const store = createStore({ ...root, modules: { midjourney, - chat + chat, + chatdoc }, plugins: [ createPersistedState({ - paths: [...persistRoot, ...persistChat, ...persistMidjourney] + paths: [...persistRoot, ...persistChat, ...persistMidjourney, ...persistChatdoc] }) ] });