From 8a4ccd5ef24eba07bd91baa23bf70a6c038ee90f Mon Sep 17 00:00:00 2001 From: feie9456 Date: Tue, 24 Jun 2025 14:09:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86AI=E5=8A=A9=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dev-tools/data/8.json | 1 + .dev-tools/fetchDat.js | 41 +- AI_ASSISTANT_README.md | 162 +++++++ app/admin/components/page.tsx | 142 ++++--- app/admin/orders/page.tsx | 2 +- app/ai-assistant/page.tsx | 394 +++++++++++++++++ app/api/ai/conversations/[id]/route.ts | 167 ++++++++ app/api/ai/conversations/route.ts | 32 ++ app/api/ai/route.ts | 80 ++++ app/api/components/[id]/route.ts | 11 +- app/api/components/route.ts | 67 +-- app/build/page.tsx | 199 ++++----- app/globals.css | 160 +++++++ app/layout.tsx | 2 +- bun.lock | 201 +++++++++ components/Navbar.tsx | 13 +- lib/ai-assistant-openai.ts | 359 ++++++++++++++++ lib/ai-assistant.ts | 399 ++++++++++++++++++ lib/auth.ts | 37 ++ lib/markdown-it.ts | 8 + lib/services/component-service.ts | 159 +++++++ lib/services/conversation-service.ts | 163 +++++++ package.json | 7 + .../migration.sql | 14 + prisma/schema.prisma | 18 +- requirements.md | 54 +++ 第一章-概述.md | 27 ++ 第三章-系统设计.md | 43 ++ 第二章-需求分析.md | 51 +++ 第五章-总结.md | 49 +++ 第四章-系统实现.md | 73 ++++ 31 files changed, 2883 insertions(+), 252 deletions(-) create mode 100644 .dev-tools/data/8.json create mode 100644 AI_ASSISTANT_README.md create mode 100644 app/ai-assistant/page.tsx create mode 100644 app/api/ai/conversations/[id]/route.ts create mode 100644 app/api/ai/conversations/route.ts create mode 100644 app/api/ai/route.ts create mode 100644 lib/ai-assistant-openai.ts create mode 100644 lib/ai-assistant.ts create mode 100644 lib/markdown-it.ts create mode 100644 lib/services/component-service.ts create mode 100644 lib/services/conversation-service.ts create mode 100644 prisma/migrations/20250623095809_add_ai_conversations_json/migration.sql create mode 100644 requirements.md create mode 100644 第一章-概述.md create mode 100644 第三章-系统设计.md create mode 100644 第二章-需求分析.md create mode 100644 第五章-总结.md create mode 100644 第四章-系统实现.md diff --git a/.dev-tools/data/8.json b/.dev-tools/data/8.json new file mode 100644 index 0000000..2c576f7 --- /dev/null +++ b/.dev-tools/data/8.json @@ -0,0 +1 @@ +[{"name":"技嘉Z390M GAMING主板1151支持8代9代 I9 9900K 技嘉Z390M GAMING","brand":"技嘉","model":"Z390M GAMING","price":427,"description":"技嘉Z390M GAMING主板,支持Intel 8代和9代处理器,兼容I9 9900K。","imageUrl":"https://img10.360buyimg.com/n7/jfs/t20260424/280862/19/21900/86848/68099c8cF661274d4/61c1bab2541e2ed1.png","stock":14,"typeName":"主板","specifications":"{\"chipset\":\"Intel Z390\",\"socket\":\"LGA1151\",\"compatibleCPUs\":\"Intel 8th/9th Gen (e.g., i9 9900K)\"}"},{"name":"研域A610M1迷你ITX工控主板12/13/14代LGA1700针双网口6串口工业台式机CPU套装电脑mini小主板PCIE x16 H610芯片 双网6串I210AT网卡A610M1","brand":"研域","model":"A610M1","price":849,"description":"研域A610M1迷你ITX工控主板,H610芯片组,支持Intel 12/13/14代LGA1700处理器,双网口6串口,PCIE x16。","imageUrl":"https://img12.360buyimg.com/n7/jfs/t1/272903/10/18528/145710/67f73f3eF7963dbb7/9c3944277e3a3884.jpg","stock":10,"typeName":"主板","specifications":"{\"chipset\":\"Intel H610\",\"socket\":\"LGA1700\",\"formFactor\":\"Mini-ITX\",\"compatibleCPUs\":\"Intel 12th/13th/14th Gen\",\"features\":\"Dual LAN (I210AT), 6 COM ports, PCIe x16\"}"},{"name":"微星微星MSI充新微星B450M PRO-VDH MORTAR 主板AM4替A3... 微星B450M MORTAR MAX迫击","brand":"微星","model":"B450M MORTAR MAX","price":478.62,"description":"微星B450M MORTAR MAX迫击炮主板,AM4接口,可替代B450M PRO-VDH MORTAR。","imageUrl":"https://img11.360buyimg.com/n7/jfs/t1/120656/3/46019/183868/66f84b30Fdd3d3cd6/a3eee3f6fa7659db.jpg","stock":14,"typeName":"主板","specifications":"{\"chipset\":\"AMD B450\",\"socket\":\"AM4\",\"formFactor\":\"Micro-ATX\"}"},{"name":"升技B760ITX D4电脑主板支持12/13/14代处理器,装甲散热,支持三屏输出 M.2 B760ITX D4 雪山白","brand":"升技","model":"B760ITX D4 雪山白","price":479,"description":"升技B760ITX D4雪山白电脑主板,支持Intel 12/13/14代处理器,具备装甲散热和三屏输出,含M.2接口。","imageUrl":"https://img10.360buyimg.com/n7/jfs/t1/6856/38/28446/102266/6539ce43Fe8362ecf/370d8875f2fc8fc8.jpg","stock":10,"typeName":"主板","specifications":"{\"chipset\":\"Intel B760\",\"socket\":\"LGA1700\",\"formFactor\":\"Mini-ITX\",\"memoryType\":\"DDR4\",\"compatibleCPUs\":\"Intel 12th/13th/14th Gen\",\"features\":\"Armor散热, Three-screen output, M.2\"}"},{"name":"GIGABYTE技嘉B560M AORUS ELITE/PRO拆机主板 支持处理器10-11代CPU 电脑 技嘉B560M AORUS PRO AX","brand":"技嘉","model":"B560M AORUS PRO AX","price":469,"description":"技嘉B560M AORUS PRO AX拆机主板,支持Intel 10-11代CPU。","imageUrl":"https://img14.360buyimg.com/n7/jfs/t1/246338/22/15148/129034/66dea4a5F979c55d3/0043e2e6afd87f25.png","stock":30,"typeName":"主板","specifications":"{\"chipset\":\"Intel B560\",\"socket\":\"LGA1200\",\"compatibleCPUs\":\"Intel 10th/11th Gen\"}"}] \ No newline at end of file diff --git a/.dev-tools/fetchDat.js b/.dev-tools/fetchDat.js index f52f328..5d69d30 100644 --- a/.dev-tools/fetchDat.js +++ b/.dev-tools/fetchDat.js @@ -1,34 +1,39 @@ let result = []; let richResult = [] -$0.querySelectorAll("li").forEach((ele, index) => { +Array.from($0.querySelectorAll("li")) + .filter(ele => + !(ele.querySelector(".p-promo-flag") && ele.querySelector(".p-promo-flag").textContent.includes("广告"))) + .forEach((ele, index) => { let price = parseFloat(ele.querySelector("strong").textContent.trim().slice(1)); let title = ele.querySelector(".p-name").textContent.trim(); let img = ele.querySelector("img").src; result.push([index, title, price]); richResult.push({ - index, - title: title, - price: price, - img: img + index, + title: title, + price: price, + img: img }); -}) + }) + +console.log(JSON.stringify(result)); -console.log(JSON.stringify(result, null, 2)); let data = [] + let res = data.map(item => { - return { - name: item.name, - brand: item.brand, - model: item.model, - price: item.price, - description: item.description, - imageUrl: richResult.find(item_ => item_.index == item.index).img, - stock: Math.random() * 50 | 0 + 10, - typeName: item.typeName, - specifications: item.specifications - }; + return { + name: item.name, + brand: item.brand, + model: item.model, + price: item.price, + description: item.description, + imageUrl: richResult.find(item_ => item_.index == item.index).img, + stock: Math.random() * 50 | 0 + 10, + typeName: item.typeName, + specifications: typeof item.specifications == "string" ? item.specifications : JSON.stringify(item.specifications) + }; }).filter(item => item.imageUrl).filter(item => item.typeName == "主板") console.log(JSON.stringify(res)); diff --git a/AI_ASSISTANT_README.md b/AI_ASSISTANT_README.md new file mode 100644 index 0000000..0810c26 --- /dev/null +++ b/AI_ASSISTANT_README.md @@ -0,0 +1,162 @@ +# PC DIY 商店 AI 助手使用说明 + +## 功能概述 + +AI 助手集成了 Anthropic 的 Claude AI,可以帮助用户: + +1. **查询配件信息** - 按类型、品牌、价格范围搜索配件 +2. **获取配件类型** - 查看所有可用的配件类型 +3. **智能推荐** - 基于用户需求提供配件推荐 +4. **价格对比** - 帮助用户比较不同配件的价格和性能 + +## 环境配置 + +### 1. 安装依赖 + +确保已安装 Anthropic SDK: + +```bash +bun add @anthropic-ai/sdk +``` + +### 2. 环境变量 + +在 `.env.local` 文件中添加: + +```env +ANTHROPIC_API_KEY=your_anthropic_api_key_here +NEXT_PUBLIC_APP_URL=http://localhost:3000 +``` + +### 3. 获取 Anthropic API Key + +1. 访问 [Anthropic Console](https://console.anthropic.com/) +2. 注册/登录账户 +3. 创建 API Key +4. 将 API Key 添加到环境变量中 + +## API 使用 + +### 发送查询请求 + +```javascript +const response = await fetch('/api/ai', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: '推荐一些价格在1000-2000元的显卡' + }), +}); + +const data = await response.json(); +console.log(data.response); +``` + +### 支持的工具调用 + +#### 1. query-components +查询配件信息,支持以下参数: + +- `type`: 配件类型ID +- `brand`: 品牌名称 +- `minPrice`: 最低价格 +- `maxPrice`: 最高价格 +- `search`: 搜索关键词 +- `page`: 页码(默认1) +- `limit`: 每页数量(默认12) + +#### 2. get-component-types +获取所有配件类型列表,无需参数。 + +## 使用示例 + +### 示例查询 + +1. **查询配件类型** + ``` + "显示所有配件类型" + "有哪些配件类型可以选择?" + ``` + +2. **搜索特定配件** + ``` + "推荐一些价格在1000-2000元的显卡" + "查找华硕品牌的主板" + "有什么高性能的CPU推荐" + ``` + +3. **价格筛选** + ``` + "500元以下的内存条有哪些" + "3000元左右的显卡推荐" + ``` + +4. **品牌筛选** + ``` + "华硕的所有配件" + "英特尔的CPU产品" + ``` + +### 页面访问 + +访问 `/ai-assistant` 页面可以使用图形界面与 AI 助手交互。 + +## 技术架构 + +### 文件结构 + +``` +lib/ + ai-assistant.ts # AI 助手核心逻辑 +app/ + api/ai/route.ts # AI API 端点 + ai-assistant/page.tsx # AI 助手前端页面 +components/ + Navbar.tsx # 导航栏(已添加AI助手链接) +``` + +### 核心组件 + +1. **MCPClient 类** - 封装 Anthropic API 调用 +2. **工具调用处理** - 处理 AI 对数据库的查询需求 +3. **消息处理** - 管理对话上下文和工具结果 + +## 注意事项 + +1. **API Key 安全** - 确保 API Key 不要提交到版本控制系统 +2. **错误处理** - AI 助手包含完整的错误处理机制 +3. **费用控制** - Anthropic API 按 token 计费,建议设置使用限制 +4. **网络依赖** - 需要稳定的网络连接到 Anthropic 服务 + +## 故障排除 + +### 常见问题 + +1. **API Key 错误** + - 检查环境变量是否正确设置 + - 确认 API Key 是否有效 + +2. **网络连接问题** + - 检查网络连接 + - 确认 Anthropic 服务是否可访问 + +3. **数据库连接问题** + - 确保数据库正常运行 + - 检查 Prisma 配置 + +### 调试方法 + +1. 查看浏览器控制台错误 +2. 检查服务器端日志 +3. 验证 API 响应格式 + +## 后续扩展 + +可以考虑添加: + +1. **用户偏好记忆** - 记住用户的配件偏好 +2. **配置推荐** - 根据预算推荐完整的装机配置 +3. **性能对比** - 添加配件性能参数对比功能 +4. **库存提醒** - 监控配件库存状态 diff --git a/app/admin/components/page.tsx b/app/admin/components/page.tsx index 2358076..fe074d6 100644 --- a/app/admin/components/page.tsx +++ b/app/admin/components/page.tsx @@ -51,15 +51,29 @@ function AdminComponentsPage() { specifications: '' }) + // Helper function to get authenticated headers + const getAuthHeaders = (): HeadersInit => { + const token = localStorage.getItem('token') + const headers: HeadersInit = { + 'Content-Type': 'application/json', + } + + if (token) { + headers['Authorization'] = `Bearer ${token}` + } + + return headers + } useEffect(() => { loadData() }, []) - const loadData = async () => { try { + const headers = getAuthHeaders() + const [componentsRes, typesRes] = await Promise.all([ - fetch('/api/components?limit=100'), - fetch('/api/component-types') + fetch('/api/admin/components?limit=100', { headers }), + fetch('/api/component-types', { headers }) ]) if (componentsRes.ok) { @@ -77,22 +91,19 @@ function AdminComponentsPage() { setIsLoading(false) } } - const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - + try { - const url = editingComponent + const url = editingComponent ? `/api/components/${editingComponent.id}` : '/api/components' - + const method = editingComponent ? 'PUT' : 'POST' - + const response = await fetch(url, { method, - headers: { - 'Content-Type': 'application/json', - }, + headers: getAuthHeaders(), body: JSON.stringify(formData), }) @@ -109,13 +120,13 @@ function AdminComponentsPage() { alert('操作失败') } } - const handleDelete = async (id: string) => { if (!confirm('确定要删除这个配件吗?')) return try { const response = await fetch(`/api/components/${id}`, { method: 'DELETE', + headers: getAuthHeaders(), }) if (response.ok) { @@ -170,10 +181,10 @@ function AdminComponentsPage() { } const parseFile = async (file: File) => { const text = await file.text() - + try { let data: any[] = [] - + if (file.name.endsWith('.json')) { const parsed = JSON.parse(text) data = Array.isArray(parsed) ? parsed : [parsed] @@ -183,7 +194,7 @@ function AdminComponentsPage() { alert('CSV文件格式错误') return } - + const headers = lines[0].split(',').map(h => h.trim()) data = lines.slice(1) .filter(line => line.trim()) @@ -196,7 +207,7 @@ function AdminComponentsPage() { return obj }) } - + setBatchData(data) validateBatchData(data) } catch (error) { @@ -212,7 +223,7 @@ function AdminComponentsPage() { data.forEach((item, index) => { const itemErrors: string[] = [] - + // 验证必需字段 if (!item.name || typeof item.name !== 'string' || item.name.trim() === '') { itemErrors.push('缺少商品名称') @@ -223,26 +234,26 @@ function AdminComponentsPage() { if (!item.model || typeof item.model !== 'string' || item.model.trim() === '') { itemErrors.push('缺少型号') } - + // 验证价格 const price = typeof item.price === 'string' ? parseFloat(item.price) : item.price if (isNaN(price) || price < 0) { itemErrors.push('价格必须是有效的正数') } - + // 验证库存 const stock = typeof item.stock === 'string' ? parseInt(item.stock) : item.stock if (isNaN(stock) || stock < 0) { itemErrors.push('库存必须是有效的非负整数') } - + // 验证配件类型 const hasTypeId = item.componentTypeId && item.componentTypeId.trim() !== '' const hasTypeName = (item.typeName || item.type) && (item.typeName || item.type).trim() !== '' if (!hasTypeId && !hasTypeName) { itemErrors.push('缺少配件类型信息 (componentTypeId, typeName 或 type)') } - + if (itemErrors.length > 0) { invalid.push({ ...item, _errors: itemErrors, _index: index + 1 }) errors.push(`第${index + 1}行: ${itemErrors.join(', ')}`) @@ -268,31 +279,29 @@ function AdminComponentsPage() { } setBatchImportLoading(true) - + try { const response = await fetch('/api/components/batch', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: getAuthHeaders(), body: JSON.stringify({ components: validData }), }) const result = await response.json() - + if (response.ok && result.success) { const { summary, results } = result - + // 显示详细的导入结果 const failedItems = results.filter((r: any) => !r.success) let message = `导入完成!\n成功: ${summary.successful}\n失败: ${summary.failed}` - + if (summary.newTypesCreated > 0) { message += `\n新创建配件类型: ${summary.newTypesCreated}` } - + if (failedItems.length > 0 && failedItems.length <= 5) { message += '\n\n失败项目:' failedItems.forEach((item: any, index: number) => { @@ -302,9 +311,9 @@ function AdminComponentsPage() { message += `\n\n失败项目过多,请检查数据格式或查看控制台详情` console.log('批量导入失败项目:', failedItems) } - + alert(message) - + if (summary.successful > 0) { await loadData() setShowBatchModal(false) @@ -318,7 +327,7 @@ function AdminComponentsPage() { alert(`批量导入失败: ${result.message || '未知错误'}`) console.error('Batch import error:', result) } - + } catch (error) { alert('批量导入失败:网络或服务器错误') console.error('Batch import error:', error) @@ -348,7 +357,7 @@ function AdminComponentsPage() { headers.join(','), ...sampleData.map(item => headers.map(header => `"${item[header as keyof typeof item] || ''}"`).join(',')) ].join('\n') - + const blob = new Blob([csvContent], { type: 'text/csv' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') @@ -383,27 +392,27 @@ function AdminComponentsPage() { return (
-

配件管理

-
- - -
+

配件管理

+
+ +
+
{/* Search */}
@@ -444,7 +453,7 @@ function AdminComponentsPage() { {filteredComponents.map((component) => ( - +
{component.imageUrl ? ( @@ -459,11 +468,11 @@ function AdminComponentsPage() {
)}
-
-
+
+
{component.name}
-
+
{component.brand} {component.model}
@@ -476,11 +485,10 @@ function AdminComponentsPage() { ¥{component.price.toLocaleString()} - 0 - ? 'bg-green-100 text-green-800' - : 'bg-red-100 text-red-800' - }`}> + 0 + ? 'bg-green-100 text-green-800' + : 'bg-red-100 text-red-800' + }`}> {component.stock} @@ -506,12 +514,12 @@ function AdminComponentsPage() { {/* Modal */} {showModal && ( -
+

{editingComponent ? '编辑配件' : '添加配件'}

- +
@@ -628,10 +636,10 @@ function AdminComponentsPage() { {/* Batch Import Modal */} {showBatchModal && ( -
+

批量导入配件

- +
)} -
+
+
+ +
+ {isLoadingConversations ? ( +
加载中...
+ ) : conversations.length === 0 ? ( +
暂无对话历史
+ ) : ( + conversations.map((conversation) => ( +
+
loadConversation(conversation.id)} + className="flex-1" + > +
+ {conversation.title || '新对话'} +
+
+ {new Date(conversation.updatedAt).toLocaleDateString()} +
+
+ +
+ )) + )} +
+
+ + {/* 主聊天区域 */} +
+ {/* 聊天标题 */} +
+

PC DIY 智能助手

+

我可以帮助您选择和了解PC配件

+
+ + {/* 消息列表 */} +
+ {messages.length === 0 ? ( +
+
+ 👋 您好!我是PC DIY商店的智能助手 +
+
+ 您可以询问关于PC配件的任何问题,比如: +
    +
  • • "推荐一些价格在1000-2000元的显卡"
  • +
  • • "查找华硕品牌的主板"
  • +
  • • "什么是最新的CPU配件"
  • +
+
+
+ ) : (messages.map((message, index) => ( +
+ {message.role == "card" + ?
+ {message.components.map((component) => ( + + )) + } +
+ :
+
+
+
+
} +
+ )) + )} + {isLoading && ( +
+
+
+
+ AI正在思考... +
+
+
+ )} +
+
+ + {/* 输入区域 */} +
+
+