diff --git a/package.json b/package.json index 697e3fbab4a666eaa87794daa705d245d06ddc85..d5996896ef02cdd8af49db2141f0c09ff2f52eb7 100644 --- a/package.json +++ b/package.json @@ -112,9 +112,10 @@ "@dcloudio/uni-mp-weixin": "3.0.0-4070620250821001", "@dcloudio/uni-mp-xhs": "3.0.0-4070620250821001", "@dcloudio/uni-quickapp-webview": "3.0.0-4070620250821001", - "abortcontroller-polyfill": "^1.7.8", "crypto-js": "^4.2.0", "dayjs": "1.11.10", + "highlight": "^0.2.4", + "markdown-it": "^14.1.0", "jsencrypt": "^3.5.4", "pinia": "2.0.36", "pinia-plugin-persistedstate": "3.2.1", diff --git a/src/api/ai/chat/index.ts b/src/api/ai/chat/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f077e5689c7d799f67e311c3117d7eb5bd2b10ab --- /dev/null +++ b/src/api/ai/chat/index.ts @@ -0,0 +1,44 @@ +import { http } from '@/http/http' + +export interface ChatConversationModel { + id: string + userId: number + title: string + pinned: boolean + roleId: number | null + modelId: number + model: string + modelName: string + systemMessage: string | null + temperature: number + maxTokens: number + maxContexts: number + createTime: number + roleAvatar: string | null + roleName: string | null + messageCount: number | null + transMap: Record +} + +/** 获取我的对话列表 */ +export async function getConversationList() { + return http.get('/ai/chat/conversation/my-list') +} + +/** 获取我的对话详情 */ +export async function getChatConversationMy(id: number) { + return http.get(`/ai/chat/conversation/get-my?id=${id}`) +} + +// 新增【我的】聊天对话 +export async function createChatConversationMy(data?: ChatConversationModel) { + return http.post('/ai/chat/conversation/create-my', data) +} +// 更新【我的】聊天对话 +export async function updateChatConversationMy(data: ChatConversationModel) { + return http.put(`/ai/chat/conversation/update-my`, data) +} +// 删除【我的】聊天对话 +export async function deleteChatConversationMy(id: number) { + return http.delete(`/ai/chat/conversation/delete-my?id=${id}`) +} diff --git a/src/api/ai/message/index.ts b/src/api/ai/message/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ed4d365bc92328aec3ebf532885d53cc368ed06 --- /dev/null +++ b/src/api/ai/message/index.ts @@ -0,0 +1,127 @@ +import { http } from '@/http/http' +import { useTokenStore } from '@/store/token' +import { getEnvBaseUrl } from '@/utils' +// 聊天VO +export interface ChatMessageVO { + id: number // 编号 + conversationId: number // 对话编号 + type: string // 消息类型 + userId: string // 用户编号 + roleId: string // 角色编号 + model: number // 模型标志 + modelId: number // 模型编号 + content: string // 聊天内容 + reasoningContent?: string // 推理内容 + attachmentUrls?: string[] // 附件 URL 数组 + tokens: number // 消耗 Token 数量 + segmentIds?: number[] // 段落编号 + segments?: { + id: number // 段落编号 + content: string // 段落内容 + documentId: number // 文档编号 + documentName: string // 文档名称 + }[] + webSearchPages?: { + name: string // 名称 + icon: string // 图标 + title: string // 标题 + url: string // URL + snippet: string // 内容的简短描述 + summary: string // 内容的文本摘要 + }[] + createTime: Date // 创建时间 + roleAvatar: string // 角色头像 + userAvatar: string // 用户头像 +} + +/** + * 获取对话的消息列表 + */ +export function getChatMessageListByConversationId(conversationId: number) { + return http.get(`/ai/chat/message/list-by-conversation-id?conversationId=${conversationId}`) +} + +// 发送 Stream 消息 +// @modify by panda 使用uni.request的sse支持,手动解释stream +export async function sendChatMessageStream( + conversationId: number, + content: string, + ctrl, + enableContext: boolean, + enableWebSearch: boolean, + onMessage, + onError, + onClose, + attachmentUrls?: string[], +) { + const tokenStore = useTokenStore() + const token = tokenStore.validToken + const baseUrl = getEnvBaseUrl() + let requestTask: any + try { + requestTask = uni.request({ + url: `${baseUrl}/ai/chat/message/send-stream`, + method: 'POST', + enableChunked: true, // 这个地方需要开 + responseType: 'arraybuffer', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'Accept': 'text/event-stream', + }, + data: JSON.stringify({ + conversationId, + content, + useContext: enableContext, + useSearch: enableWebSearch, + attachmentUrls: attachmentUrls || [], + }), + success() { + onClose() + }, + fail(err) { onError(err) }, + }) + } catch (error) { + onError(error) + } finally { + let buffer = '' // 用于存储不完整的消息片段 + requestTask.onChunkReceived((res) => { + const arrayBuffer = res.data + const uint8Array = new Uint8Array(arrayBuffer) + // 将 ArrayBuffer 转换为字符串 + const textChunk = new TextDecoder().decode(uint8Array) + + // --- 手动解析 SSE 格式 --- + buffer += textChunk + const messages = buffer.split('\n\n') + + // 最后一个消息可能不完整,保留在 buffer 中 + buffer = messages.pop() + + messages.forEach((msg) => { + if (msg.startsWith('data:')) { + const dataStr = msg.substring(5) // 去掉 "data: " + if (dataStr === '[DONE]') { + return + } + try { + const data = JSON.parse(dataStr) + if (onMessage) + onMessage(data) + } catch (e) { + onError(e) + } + } + }) + }) + + requestTask.offHeadersReceived(() => onClose()) + } + + return requestTask +} + +// 删除消息 +export function deleteChatMessage(id: number) { + return http.delete(`/ai/chat/message/delete?id=${id}`) +} diff --git a/src/api/ai/model/index.ts b/src/api/ai/model/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c4d2e50c10eed369bb6138c0643974c4de9ae43f --- /dev/null +++ b/src/api/ai/model/index.ts @@ -0,0 +1,20 @@ +import { http } from '@/http/http' + +export interface ModelVO { + id: number // 编号 + keyId: number // API 秘钥编号 + name: string // 模型名字 + model: string // 模型标识 + platform: string // 模型平台 + type: number // 模型类型 + sort: number // 排序 + status: number // 状态 + temperature?: number // 温度参数 + maxTokens?: number // 单条回复的最大 Token 数量 + maxContexts?: number // 上下文的最大 Message 数量 +} + +// 获得模型列表 +export async function getModelSimpleList(type?: number) { + return http.get('/ai/model/simple-list', { type }) +} diff --git a/src/components/markdown-view/index.vue b/src/components/markdown-view/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..9a97b7beb5da7ddec02cf9dfc9afb6e69f0b494f --- /dev/null +++ b/src/components/markdown-view/index.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/src/pages-ai/chat/components/chat/ChatLoading.vue b/src/pages-ai/chat/components/chat/ChatLoading.vue new file mode 100644 index 0000000000000000000000000000000000000000..a53df10e7811eef24fbe3ccaeb4e6453285767ce --- /dev/null +++ b/src/pages-ai/chat/components/chat/ChatLoading.vue @@ -0,0 +1,9 @@ + + diff --git a/src/pages-ai/chat/components/chat/ChatSettings.vue b/src/pages-ai/chat/components/chat/ChatSettings.vue new file mode 100644 index 0000000000000000000000000000000000000000..ef8458345b6fa6cbe4808b210cc9741a7a0fedca --- /dev/null +++ b/src/pages-ai/chat/components/chat/ChatSettings.vue @@ -0,0 +1,334 @@ + + + + + diff --git a/src/pages-ai/chat/components/chat/chat-item.vue b/src/pages-ai/chat/components/chat/chat-item.vue new file mode 100644 index 0000000000000000000000000000000000000000..ff4eaf77e0b5a5e424511b09f47ab31850d76a0c --- /dev/null +++ b/src/pages-ai/chat/components/chat/chat-item.vue @@ -0,0 +1,315 @@ + + + + + diff --git a/src/pages-ai/chat/components/message/MessageList.vue b/src/pages-ai/chat/components/message/MessageList.vue new file mode 100644 index 0000000000000000000000000000000000000000..9aa02dc23f7d59b80ab99a363c583582d91a8efb --- /dev/null +++ b/src/pages-ai/chat/components/message/MessageList.vue @@ -0,0 +1,296 @@ + + + + + diff --git a/src/pages-ai/chat/components/message/MessageListEmpty.vue b/src/pages-ai/chat/components/message/MessageListEmpty.vue new file mode 100644 index 0000000000000000000000000000000000000000..62ffe7258916d6ec9f86970ab5a942bbb768e778 --- /dev/null +++ b/src/pages-ai/chat/components/message/MessageListEmpty.vue @@ -0,0 +1,59 @@ + + + + + + diff --git a/src/pages-ai/chat/components/message/MessageLoading.vue b/src/pages-ai/chat/components/message/MessageLoading.vue new file mode 100644 index 0000000000000000000000000000000000000000..813410a5541e71d0237078073546b95506745088 --- /dev/null +++ b/src/pages-ai/chat/components/message/MessageLoading.vue @@ -0,0 +1,9 @@ + + diff --git a/src/pages-ai/chat/components/message/MessageReasoning.vue b/src/pages-ai/chat/components/message/MessageReasoning.vue new file mode 100644 index 0000000000000000000000000000000000000000..25da80c9e929a22162afe8597dbd48f1b69eae44 --- /dev/null +++ b/src/pages-ai/chat/components/message/MessageReasoning.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/pages-ai/chat/conversation.vue b/src/pages-ai/chat/conversation.vue new file mode 100644 index 0000000000000000000000000000000000000000..0ed270e425d4d4c402b2dd9c3cb90d946976f49b --- /dev/null +++ b/src/pages-ai/chat/conversation.vue @@ -0,0 +1,599 @@ + + + + + diff --git a/src/pages-ai/chat/index.vue b/src/pages-ai/chat/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..c293fd3a1e80c2347d47ebb8c8ef75ec02362b5f --- /dev/null +++ b/src/pages-ai/chat/index.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/src/pages/ai/index.vue b/src/pages/ai/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..424d75a91cd6bfeed9ea533bb829b0a24002afaf --- /dev/null +++ b/src/pages/ai/index.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/pages/ai/utils/constants.ts b/src/pages/ai/utils/constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..3fee920b84f421d89446c4e203a3ba379ec52b06 --- /dev/null +++ b/src/pages/ai/utils/constants.ts @@ -0,0 +1,25 @@ +/** + * AI 平台的枚举 + */ +export const AiPlatformEnum = { + TONG_YI: 'TongYi', // 阿里 + YI_YAN: 'YiYan', // 百度 + DEEP_SEEK: 'DeepSeek', // DeepSeek + ZHI_PU: 'ZhiPu', // 智谱 AI + XING_HUO: 'XingHuo', // 讯飞 + SiliconFlow: 'SiliconFlow', // 硅基流动 + OPENAI: 'OpenAI', + Ollama: 'Ollama', + STABLE_DIFFUSION: 'StableDiffusion', // Stability AI + MIDJOURNEY: 'Midjourney', // Midjourney + SUNO: 'Suno', // Suno AI +} + +export const AiModelTypeEnum = { + CHAT: 1, // 聊天 + IMAGE: 2, // 图像 + VOICE: 3, // 音频 + VIDEO: 4, // 视频 + EMBEDDING: 5, // 向量 + RERANK: 6, // 重排 +} diff --git a/src/pages/index/index.ts b/src/pages/index/index.ts index e75389fd4666f54b563495d304c1b913dd08e75d..bed4eeb9061dd0e92d66e45dc6fbc4c33e6f96a8 100644 --- a/src/pages/index/index.ts +++ b/src/pages/index/index.ts @@ -358,6 +358,21 @@ const menuGroupsData: MenuGroup[] = [ }, ], }, + { + key: 'ai', + name: 'AI功能', + menus: [ + { + key: 'aiChat', + name: 'AI对话', + icon: 'chat', + url: '/pages-ai/chat/index', + iconColor: '#597ef7', + permission: '', + }, + + ], + }, ] /** diff --git a/src/static/images/avatar.jpg b/src/static/images/avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d46a70a46430e31744420865138cc7eedb8b77e1 Binary files /dev/null and b/src/static/images/avatar.jpg differ diff --git a/src/static/images/gpt.svg b/src/static/images/gpt.svg new file mode 100644 index 0000000000000000000000000000000000000000..603e2e95a1fc9fde99f957b7180c0ae109e664f0 --- /dev/null +++ b/src/static/images/gpt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/tabbar/config.ts b/src/tabbar/config.ts index 4e09d2c8a879070078b1c942951adb837366abc0..4aad02715b9eb43acf78ea0a040cf5899c75d60b 100644 --- a/src/tabbar/config.ts +++ b/src/tabbar/config.ts @@ -111,6 +111,13 @@ export const customTabbarList: CustomTabBarItem[] = [ iconType: 'unocss', icon: 'i-carbon-chat', }, + // add by panda 25.12.17:添加 AI 对话 + { + text: '大模型', + pagePath: 'pages/ai/index', + iconType: 'unocss', + icon: 'i-carbon-ai', + }, { text: '我的', pagePath: 'pages/user/index', diff --git a/src/utils/index.ts b/src/utils/index.ts index df08cbe3f47b9f06a6aa0661785910f726000916..1881d5358aa3807cfd441f610193e5936a5f3657 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -224,7 +224,22 @@ export function navigateBackPlus(fallbackUrl?: string) { uni.reLaunch({ url: targetUrl }) } } - +/** + * 复制文本到剪贴板 + * @param text 要复制的文本 + */ +export function copyToClipboard(text: string) { + uni.setClipboardData({ + data: text, + success: () => { + uni.showToast({ + title: '复制成功!', + icon: 'success', + duration: 2000, + }) + }, + }) +} /** 获取 wd-navbar 导航栏高度 */ export function getNavbarHeight() { const systemInfo = uni.getSystemInfoSync() diff --git a/uno.config.ts b/uno.config.ts index 4a4401f313237413c5467131fcf18cccc9cd74bb..a1b406e9a68306642a286c045be6f59e3eaf0114 100644 --- a/uno.config.ts +++ b/uno.config.ts @@ -71,7 +71,7 @@ export default defineConfig({ }, ], // 动态图标需要在这里配置,或者写在vue页面中注释掉 - safelist: ['i-carbon-code', 'i-carbon-home', 'i-carbon-user', 'i-carbon-document', 'i-carbon-chat', 'i-carbon-user-avatar'], + safelist: ['i-carbon-code', 'i-carbon-home', 'i-carbon-user', 'i-carbon-document', 'i-carbon-chat', 'i-carbon-user-avatar', 'i-carbon-ai'], rules: [ [ 'p-safe', diff --git a/vite.config.ts b/vite.config.ts index b404002dd3158f245ffdb083f0cbf3dda616eccc..4e1a94b69ef56743d4f5581adbe166b3d03d9cb8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -77,6 +77,7 @@ export default defineConfig(({ command, mode }) => { 'src/pages-system', // “系统管理”模块 'src/pages-infra', // “基础设施”模块 'src/pages-bpm', // “工作流程”模块 + 'src/pages-ai', // “AI 功能”模块 ], dts: 'src/types/uni-pages.d.ts', }),