Commit 8f347ca8 by 高源

添加聊天模块

parent 422ec1b4
# DeepSeek API 集成指南
## 功能特性
**已完成的功能**
- ✅ DeepSeek API 完整集成
- ✅ 实时思考过程展示
- ✅ 打字机效果显示
- ✅ Markdown 格式化回复
- ✅ 对话历史管理
- ✅ API 配置界面
- ✅ 连接测试功能
- ✅ 流式响应支持
## 快速开始
### 1. 获取 DeepSeek API Key
- 访问 [DeepSeek 官网](https://www.deepseek.com/)
- 注册账号并获取 API Key
### 2. 访问聊天界面
- 在浏览器中打开:`http://localhost:3000/chat/deepseek`
- 或通过侧边导航:AI聊天 → DeepSeek Chat
### 3. 配置 API Key
1. 点击左侧工具栏中的 ⚙️ 配置按钮
2. 输入您的 DeepSeek API Key
3. 点击"测试连接"验证配置
4. 点击"保存配置"
### 4. 开始对话
- 在底部输入框输入问题
- 按 Enter 发送,Shift+Enter 换行
- 享受AI的深度思考过程!
## 核心文件说明
### API 服务层
- `src/service/api/deepseek.ts` - DeepSeek API 封装服务
- `src/components/chat/ApiConfig.vue` - API 配置组件
### 用户界面
- `src/views/chat/deepseek.vue` - 主要聊天界面
- `src/router/routes/index.ts` - 路由配置
## API 配置选项
- **API Key**: 您的 DeepSeek API 密钥
- **模型**: 支持 `deepseek-chat``deepseek-coder`
- **Base URL**: 默认 `https://api.deepseek.com`
## 特殊功能
### 思考过程展示
AI 会先展示详细的思考过程,然后给出最终答案:
1. 🤖 思考中... (显示实时计时)
2. 🧠 深度思考过程 (打字机效果显示)
3. ✅ 最终答案 (Markdown 格式化)
### 对话管理
- 自动保存对话历史
- 支持新建多个对话
- 支持搜索历史对话
## 使用技巧
1. **复杂问题**: 对于编程、分析等复杂问题,AI 会展示详细的思考过程
2. **代码生成**: 支持多种编程语言的代码生成和解释
3. **Markdown**: 回复支持完整的 Markdown 格式,包括代码块、表格、列表等
## 故障排除
### API 连接失败
1. 检查 API Key 是否正确
2. 确认网络连接
3. 查看浏览器控制台错误信息
### 思考过程不显示
1. 确认 API 响应正常
2. 检查 JavaScript 控制台是否有错误
3. 刷新页面重试
## 开发说明
如需自定义或扩展功能:
1. **修改思考逻辑**: 编辑 `deepseek.ts` 中的 `generateThinking` 方法
2. **调整界面样式**: 修改 `deepseek.vue` 中的 SCSS 样式
3. **添加新模型**: 在 `ApiConfig.vue` 中添加模型选项
## 技术栈
- Vue 3 + TypeScript
- Naive UI 组件库
- Markdown-it 渲染器
- DeepSeek API
---
🎉 **现在您可以开始使用 DeepSeek AI 进行智能对话了!**
<!doctype html> <!doctype html>
<html lang="zh-cmn-Hans"> <html lang="zh-cmn-Hans">
<head> <head>
<meta name="buildTime" content="2025-04-25 17:56:27"> <meta name="buildTime" content="2025-06-08 19:06:22">
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" /> <link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" /> <meta name="color-scheme" content="light dark" />
<title>VueDashboard</title> <title>VueDashboard</title>
<script type="module" crossorigin src="/Content/VueDashboardUi/VueDashboard1/assets/index-CsI39EZy.js"></script> <script type="module" crossorigin src="/Content/VueDashboardUi/VueDashboard1/assets/index-7l3HYvBE.js"></script>
<link rel="stylesheet" crossorigin href="/Content/VueDashboardUi/VueDashboard1/assets/index-DhhgO4aJ.css"> <link rel="stylesheet" crossorigin href="/Content/VueDashboardUi/VueDashboard1/assets/index-CKpMNMck.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
...@@ -48,12 +48,15 @@ ...@@ -48,12 +48,15 @@
}, },
"dependencies": { "dependencies": {
"@better-scroll/core": "2.5.1", "@better-scroll/core": "2.5.1",
"@devui-design/icons": "^1.4.0",
"@iconify/vue": "4.1.2", "@iconify/vue": "4.1.2",
"@matechat/core": "^1.5.2",
"@sa/axios": "workspace:*", "@sa/axios": "workspace:*",
"@sa/color": "workspace:*", "@sa/color": "workspace:*",
"@sa/hooks": "workspace:*", "@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*", "@sa/materials": "workspace:*",
"@sa/utils": "workspace:*", "@sa/utils": "workspace:*",
"@types/markdown-it": "^14.1.2",
"@vueuse/core": "10.11.0", "@vueuse/core": "10.11.0",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
...@@ -61,11 +64,13 @@ ...@@ -61,11 +64,13 @@
"dayjs": "1.11.12", "dayjs": "1.11.12",
"echarts": "5.5.1", "echarts": "5.5.1",
"font-awesome": "4.7.0", "font-awesome": "4.7.0",
"markdown-it": "^14.1.0",
"naive-ui": "2.39.0", "naive-ui": "2.39.0",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "2.2.0", "pinia": "2.2.0",
"tailwind-merge": "2.4.0", "tailwind-merge": "2.4.0",
"vue": "3.4.35", "vue": "3.4.35",
"vue-devui": "^1.6.32",
"vue-draggable-plus": "0.5.2", "vue-draggable-plus": "0.5.2",
"vue-grid-layout": "3.0.0-beta1", "vue-grid-layout": "3.0.0-beta1",
"vue-i18n": "9.13.1", "vue-i18n": "9.13.1",
......
...@@ -2,6 +2,8 @@ import { createApp } from 'vue'; ...@@ -2,6 +2,8 @@ import { createApp } from 'vue';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'; import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
import '@wangeditor/editor/dist/css/style.css'; import '@wangeditor/editor/dist/css/style.css';
import VueGridLayout from 'vue-grid-layout'; import VueGridLayout from 'vue-grid-layout';
import MateChat from '@matechat/core';
import '@devui-design/icons/icomoon/devui-icon.css';
import './plugins/assets'; import './plugins/assets';
import { localStg } from '@/utils/storage'; import { localStg } from '@/utils/storage';
// main.js or main.ts // main.js or main.ts
...@@ -79,6 +81,7 @@ async function setupApp() { ...@@ -79,6 +81,7 @@ async function setupApp() {
// startInactivityTimer(app); // startInactivityTimer(app);
app.use(VueGridLayout); app.use(VueGridLayout);
app.use(MateChat);
// 全局注册 wangeditor 组件 // 全局注册 wangeditor 组件
app.component('WangEditor', Editor); app.component('WangEditor', Editor);
......
...@@ -24,5 +24,6 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro ...@@ -24,5 +24,6 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
test_test1: () => import("@/views/_builtin/test/test1/index.vue"), test_test1: () => import("@/views/_builtin/test/test1/index.vue"),
test_test2: () => import("@/views/_builtin/test/test2/index.vue"), test_test2: () => import("@/views/_builtin/test/test2/index.vue"),
test_test3: () => import("@/views/_builtin/test/test3/index.vue"), test_test3: () => import("@/views/_builtin/test/test3/index.vue"),
chat_deepseek: () => import("@/views/chat/deepseek/index.vue"),
home: () => import("@/views/home/index.vue"), home: () => import("@/views/home/index.vue"),
}; };
...@@ -39,6 +39,26 @@ export const generatedRoutes: GeneratedRoute[] = [ ...@@ -39,6 +39,26 @@ export const generatedRoutes: GeneratedRoute[] = [
} }
}, },
{ {
name: 'chat',
path: '/chat',
component: 'layout.base',
meta: {
title: 'chat',
i18nKey: 'route.chat'
},
children: [
{
name: 'chat_deepseek',
path: '/chat/deepseek',
component: 'view.chat_deepseek',
meta: {
title: 'chat_deepseek',
i18nKey: 'route.chat_deepseek'
}
}
]
},
{
name: 'home', name: 'home',
path: '/home', path: '/home',
component: 'layout.base$view.home', component: 'layout.base$view.home',
......
...@@ -179,6 +179,8 @@ const routeMap: RouteMap = { ...@@ -179,6 +179,8 @@ const routeMap: RouteMap = {
"403": "/403", "403": "/403",
"404": "/404", "404": "/404",
"500": "/500", "500": "/500",
"chat": "/chat",
"chat_deepseek": "/chat/deepseek",
"home": "/home", "home": "/home",
"iframe-page": "/iframe-page/:url", "iframe-page": "/iframe-page/:url",
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?", "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
......
...@@ -56,6 +56,7 @@ const customRoutes: CustomRoute[] = [ ...@@ -56,6 +56,7 @@ const customRoutes: CustomRoute[] = [
} }
] ]
}, },
{ {
name: 'test' as any, name: 'test' as any,
path: '/test' as any, path: '/test' as any,
......
interface DeepSeekConfig {
apiKey: string;
baseURL: string;
model: string;
}
interface DeepSeekMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
interface DeepSeekRequest {
model: string;
messages: DeepSeekMessage[];
temperature?: number;
max_tokens?: number;
stream?: boolean;
}
interface DeepSeekResponse {
id: string;
object: string;
created: number;
model: string;
choices: Array<{
index: number;
message: {
role: string;
content: string;
};
finish_reason: string;
}>;
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
}
class DeepSeekService {
private config: DeepSeekConfig;
constructor() {
this.config = {
apiKey: '', // 用户需要填入自己的 API Key
baseURL: 'https://api.deepseek.com',
model: 'deepseek-chat'
};
}
// 设置 API Key
setApiKey(apiKey: string) {
this.config.apiKey = apiKey;
}
// 设置模型
setModel(model: string) {
this.config.model = model;
}
// 检查配置是否完整
private validateConfig(): boolean {
if (!this.config.apiKey) {
console.error('DeepSeek API Key 未设置');
return false;
}
return true;
}
// 发送聊天请求
async chat(
messages: DeepSeekMessage[],
options?: {
temperature?: number;
maxTokens?: number;
stream?: boolean;
}
): Promise<DeepSeekResponse> {
if (!this.validateConfig()) {
throw new Error('DeepSeek 配置不完整');
}
const requestBody: DeepSeekRequest = {
model: this.config.model,
messages,
temperature: options?.temperature || 0.7,
max_tokens: options?.maxTokens || 2000,
stream: options?.stream || false
};
try {
const response = await fetch(`${this.config.baseURL}/v1/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.apiKey}`
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
`DeepSeek API 请求失败: ${response.status} ${response.statusText} - ${errorData.error?.message || ''}`
);
}
const data = await response.json();
return data;
} catch (error) {
console.error('DeepSeek API 调用错误:', error);
throw error;
}
}
// 流式聊天请求
async streamChat(
messages: DeepSeekMessage[],
onChunk: (chunk: string) => void,
onComplete: () => void,
onError: (error: Error) => void,
options?: {
temperature?: number;
maxTokens?: number;
}
): Promise<void> {
if (!this.validateConfig()) {
onError(new Error('DeepSeek 配置不完整'));
return;
}
const requestBody: DeepSeekRequest = {
model: this.config.model,
messages,
temperature: options?.temperature || 0.7,
max_tokens: options?.maxTokens || 2000,
stream: true
};
try {
const response = await fetch(`${this.config.baseURL}/v1/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.apiKey}`
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
`DeepSeek API 请求失败: ${response.status} ${response.statusText} - ${errorData.error?.message || ''}`
);
}
const reader = response.body?.getReader();
if (!reader) {
throw new Error('无法读取响应流');
}
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) {
onComplete();
break;
}
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
onComplete();
return;
}
try {
const parsed = JSON.parse(data);
const content = parsed.choices?.[0]?.delta?.content;
if (content) {
onChunk(content);
}
} catch (parseError) {
// 忽略解析错误,继续处理下一行
}
}
}
}
} catch (error) {
console.error('DeepSeek 流式请求错误:', error);
onError(error as Error);
}
}
// 生成最终回答
async generateAnswer(question: string, thinkingProcess: string): Promise<string> {
const answerPrompt: DeepSeekMessage[] = [
{
role: 'system',
content:
'你是一个专业的AI助手,善于给出详细、准确的回答。请基于之前的思考过程,给出最终的完整答案。如果涉及代码,请提供完整的、可运行的代码示例。使用Markdown格式回复。'
},
{
role: 'user',
content: `问题:${question}\n\n我的思考过程:\n${thinkingProcess}\n\n请基于以上思考,给出详细的最终答案。`
}
];
try {
const response = await this.chat(answerPrompt, { temperature: 0.7, maxTokens: 2000 });
return response.choices[0]?.message?.content || '抱歉,我无法生成回答。';
} catch (error) {
console.error('生成最终答案失败:', error);
throw error;
}
}
}
// 导出单例实例
export const deepSeekService = new DeepSeekService();
// 导出类型
export type { DeepSeekMessage, DeepSeekResponse };
...@@ -33,6 +33,8 @@ declare module "@elegant-router/types" { ...@@ -33,6 +33,8 @@ declare module "@elegant-router/types" {
"403": "/403"; "403": "/403";
"404": "/404"; "404": "/404";
"500": "/500"; "500": "/500";
"chat": "/chat";
"chat_deepseek": "/chat/deepseek";
"home": "/home"; "home": "/home";
"iframe-page": "/iframe-page/:url"; "iframe-page": "/iframe-page/:url";
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"; "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
...@@ -87,6 +89,7 @@ declare module "@elegant-router/types" { ...@@ -87,6 +89,7 @@ declare module "@elegant-router/types" {
| "403" | "403"
| "404" | "404"
| "500" | "500"
| "chat"
| "home" | "home"
| "iframe-page" | "iframe-page"
| "login" | "login"
...@@ -118,6 +121,7 @@ declare module "@elegant-router/types" { ...@@ -118,6 +121,7 @@ declare module "@elegant-router/types" {
| "test_test1" | "test_test1"
| "test_test2" | "test_test2"
| "test_test3" | "test_test3"
| "chat_deepseek"
| "home" | "home"
>; >;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment