diff --git a/locales/en.yaml b/locales/en.yaml index a9d8bc0..4c114a8 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -70,6 +70,8 @@ panel: menus: pureHome: Home pureProject: Project Management + aiChat: AI Assistant + knowledgeBase: Knowledge Base pureRiskWorkorder: Risk & Workorder pureRiskAssessment: Risk Assessment pureWorkorderManagement: Workorder Management diff --git a/locales/zh-CN.yaml b/locales/zh-CN.yaml index 308d64f..7e2af84 100644 --- a/locales/zh-CN.yaml +++ b/locales/zh-CN.yaml @@ -70,6 +70,8 @@ panel: menus: pureHome: 首页 pureProject: 项目管理 + aiChat: AI助手 + knowledgeBase: 知识库 pureRiskWorkorder: 风险与工单 pureRiskAssessment: 风险评估 pureWorkorderManagement: 工单管理 diff --git a/src/api/ai-chat.ts b/src/api/ai-chat.ts new file mode 100644 index 0000000..411005a --- /dev/null +++ b/src/api/ai-chat.ts @@ -0,0 +1,231 @@ +import { http } from "@/utils/http"; + +/** 通用响应结果 */ +type Result = { + code: number; + message: string; + data?: T; +}; + +// ==================== 数据结构定义 ==================== + +/** 会话信息 */ +export interface ChatSessionVO { + sessionId: string; // UUID格式 + sessionTitle: string; + projectId: number; + projectName: string; + timelineNodeId?: number; + timelineNodeName?: string; + lastMessageTime: string; // ISO 8601格式 + messageCount: number; + createTime: string; +} + +/** 引用文档 */ +export interface ReferencedDocVO { + id: number; + docId: string; + title: string; + docType: string; + fileType: string; + sourceType: string; + score: number; // 相似度分数 0-1 + content: string; // 内容摘要 +} + +/** 对话消息 */ +export interface ChatMessageVO { + id: number; + role: "user" | "assistant" | "system"; + content: string; + referencedDocs?: ReferencedDocVO[]; + model?: string; + tokensUsed?: number; + responseTime?: number; + messageIndex: number; + createTime: string; +} + +/** 知识库文档 */ +export interface KbDocumentVO { + id: number; + docId: string; // UUID格式 + title: string; + docType: string; // report/document/text/data/other + fileType: string; // pdf/doc/txt/md等 + fileSize: number; // 字节 + filePath: string; + sourceType: string; // upload/project/risk等 + chunkCount: number; // 分块数量 + status: "pending" | "processing" | "active" | "error"; + createByName: string; + createTime: string; +} + +/** 文档状态 */ +export type DocumentStatus = "pending" | "processing" | "active" | "error"; + +// ==================== SSE事件数据类型 ==================== + +/** SSE start 事件数据 */ +export interface SSEStartData { + sessionId: string; + isNewSession: boolean; +} + +/** SSE chunk 事件数据 */ +export interface SSEChunkData { + content: string; +} + +/** SSE references 事件数据 */ +export interface SSEReferencesData { + docs: ReferencedDocVO[]; +} + +/** SSE complete 事件数据 */ +export interface SSECompleteData { + messageId: number; + tokensUsed: number; +} + +/** SSE error 事件数据 */ +export interface SSEErrorData { + message: string; +} + +// ==================== 请求参数类型 ==================== + +/** 创建会话请求参数 */ +export interface CreateSessionRequest { + projectId: number; + timelineNodeId?: number | null; + firstMessage?: string; + sessionTitle?: string; +} + +/** SSE对话请求参数 */ +export interface SSEChatParams { + sessionId?: string; // UUID格式,为空则新建会话 + projectId: number; + timelineNodeId?: number; + message: string; + useRag?: boolean; // 默认true + useTextToSql?: boolean; // 默认false + contextWindow?: number; // 默认10 +} + +/** 上传文件请求参数 */ +export interface UploadDocumentParams { + projectId: number; + file: File; +} + +// ==================== AI对话接口 ==================== + +/** 新建会话 */ +export const createChatSession = (data: CreateSessionRequest) => { + return http.request>( + "post", + "/api/v1/ai/chat/session", + { + data + } + ); +}; + +/** 获取会话列表 */ +export const getChatSessions = (projectId: number) => { + return http.request>( + "get", + "/api/v1/ai/chat/sessions", + { + params: { projectId } + } + ); +}; + +/** 获取会话历史消息 */ +export const getSessionMessages = (sessionId: string) => { + return http.request>( + "get", + `/api/v1/ai/chat/session/${sessionId}/messages` + ); +}; + +/** 删除会话 */ +export const deleteChatSession = (sessionId: string) => { + return http.request>( + "delete", + `/api/v1/ai/chat/session/${sessionId}` + ); +}; + +/** 构建SSE URL */ +export const buildSSEUrl = (params: SSEChatParams): string => { + const queryParams = new URLSearchParams(); + + if (params.sessionId) { + queryParams.append("sessionId", params.sessionId); + } + queryParams.append("projectId", String(params.projectId)); + if (params.timelineNodeId !== undefined) { + queryParams.append("timelineNodeId", String(params.timelineNodeId)); + } + queryParams.append("message", params.message); + if (params.useRag !== undefined) { + queryParams.append("useRag", String(params.useRag)); + } + if (params.useTextToSql !== undefined) { + queryParams.append("useTextToSql", String(params.useTextToSql)); + } + if (params.contextWindow !== undefined) { + queryParams.append("contextWindow", String(params.contextWindow)); + } + + return `/api/v1/ai/chat/sse?${queryParams.toString()}`; +}; + +// ==================== 知识库接口 ==================== + +/** 上传文档 */ +export const uploadDocument = (params: UploadDocumentParams) => { + const formData = new FormData(); + formData.append("projectId", String(params.projectId)); + formData.append("file", params.file); + + return http.request>("post", "/api/v1/ai/kb/upload", { + data: formData, + headers: { + "Content-Type": undefined // 让浏览器自动设置 multipart/form-data + } + }); +}; + +/** 获取文档列表 */ +export const getDocuments = (projectId: number) => { + return http.request>( + "get", + "/api/v1/ai/kb/documents", + { + params: { projectId } + } + ); +}; + +/** 删除文档 */ +export const deleteDocument = (docId: string) => { + return http.request>( + "delete", + `/api/v1/ai/kb/document/${docId}` + ); +}; + +/** 重新索引文档 */ +export const reindexDocument = (docId: string) => { + return http.request>( + "post", + `/api/v1/ai/kb/document/${docId}/reindex` + ); +}; diff --git a/src/router/enums.ts b/src/router/enums.ts index a4a044b..fc1716b 100644 --- a/src/router/enums.ts +++ b/src/router/enums.ts @@ -3,38 +3,42 @@ const home = 0, // 平台规定只有 home 路由的 rank 才能为 0 ,所以后端在返回 rank 的时候需要从非 0 开始 chatai = 1, project = 2, - vueflow = 3, - ganttastic = 4, - components = 5, - able = 6, - table = 7, - form = 8, - list = 9, - result = 10, - error = 11, - frame = 12, - nested = 13, - permission = 14, - monitor = 16, - tabs = 17, - about = 18, - codemirror = 19, - markdown = 20, - editor = 21, - flowchart = 22, - formdesign = 23, - board = 24, - ppt = 25, - mind = 26, - guide = 27, - menuoverflow = 28, - riskWorkorder = 29, + aiChat = 3, + knowledgeBase = 4, + vueflow = 5, + ganttastic = 6, + components = 7, + able = 8, + table = 9, + form = 10, + list = 11, + result = 12, + error = 13, + frame = 14, + nested = 15, + permission = 16, + monitor = 17, + tabs = 18, + about = 19, + codemirror = 20, + markdown = 21, + editor = 22, + flowchart = 23, + formdesign = 24, + board = 25, + ppt = 26, + mind = 27, + guide = 28, + menuoverflow = 29, + riskWorkorder = 30, system = 99; export { home, chatai, project, + aiChat, + knowledgeBase, vueflow, ganttastic, components, diff --git a/src/router/index.ts b/src/router/index.ts index e50c436..ef28ac3 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -47,6 +47,8 @@ const modules: Record = import.meta.glob( "./modules/home.ts", "./modules/risk-assessment.ts", "./modules/workorder-management.ts", + "./modules/knowledge-base.ts", + "./modules/ai-chat.ts", "!./modules/**/remaining.ts" ], { diff --git a/src/router/modules/ai-chat.ts b/src/router/modules/ai-chat.ts new file mode 100644 index 0000000..afd6d24 --- /dev/null +++ b/src/router/modules/ai-chat.ts @@ -0,0 +1,22 @@ +import { $t } from "@/plugins/i18n"; +import { aiChat } from "@/router/enums"; + +export default { + path: "/ai-chat", + redirect: "/ai-chat/index", + meta: { + icon: "ri:robot-line", + title: $t("menus.aiChat") || "AI助手", + rank: aiChat + }, + children: [ + { + path: "/ai-chat/index", + name: "AiChat", + component: () => import("@/views/ai-chat/index.vue"), + meta: { + title: $t("menus.aiChat") || "AI助手" + } + } + ] +} satisfies RouteConfigsTable; diff --git a/src/router/modules/knowledge-base.ts b/src/router/modules/knowledge-base.ts new file mode 100644 index 0000000..5b54217 --- /dev/null +++ b/src/router/modules/knowledge-base.ts @@ -0,0 +1,22 @@ +import { $t } from "@/plugins/i18n"; +import { knowledgeBase } from "@/router/enums"; + +export default { + path: "/knowledge-base", + redirect: "/knowledge-base/index", + meta: { + icon: "ri:database-2-line", + title: $t("menus.knowledgeBase") || "知识库", + rank: knowledgeBase + }, + children: [ + { + path: "/knowledge-base/index", + name: "KnowledgeBase", + component: () => import("@/views/knowledge-base/index.vue"), + meta: { + title: $t("menus.knowledgeBase") || "知识库" + } + } + ] +} satisfies RouteConfigsTable; diff --git a/src/views/ai-chat/index.vue b/src/views/ai-chat/index.vue new file mode 100644 index 0000000..42d4d96 --- /dev/null +++ b/src/views/ai-chat/index.vue @@ -0,0 +1,787 @@ + + + + + diff --git a/src/views/knowledge-base/index.vue b/src/views/knowledge-base/index.vue new file mode 100644 index 0000000..b048bc6 --- /dev/null +++ b/src/views/knowledge-base/index.vue @@ -0,0 +1,508 @@ + + + + +