实现了AI助手
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { AIClient } from '@/lib/ai-assistant-openai'
|
||||
import { getUser } from '@/lib/auth'
|
||||
import { ComponentService } from '@/lib/services/component-service'
|
||||
import { Component } from '@prisma/client'
|
||||
|
||||
type Message = {
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
} | {
|
||||
role: 'card'
|
||||
components: Component[]
|
||||
}
|
||||
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
// 验证用户身份
|
||||
const user = await getUser(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ message: '请先登录' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const client = new AIClient()
|
||||
const conversation = await client.getConversation((await params).id, user.id)
|
||||
|
||||
if (!conversation) {
|
||||
return NextResponse.json(
|
||||
{ message: '对话不存在或无权限访问' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// 转换为Markdown
|
||||
const filteredMessages = conversation.messages.filter((m, index) => !(index == 0 || m.role == 'user' && typeof m.content != 'string'))
|
||||
|
||||
const res: Message[] = []
|
||||
let appendMessage: Message | null = null
|
||||
for (const message of filteredMessages) {
|
||||
let content: string
|
||||
|
||||
if (typeof message.content === "string") {
|
||||
content = message.content
|
||||
} else {
|
||||
const contentParts: string[] = []
|
||||
for (const part of message.content) {
|
||||
if (part.type === 'text') {
|
||||
contentParts.push(part.text)
|
||||
} else if (part.type === 'tool_use') {
|
||||
contentParts.push(`**工具调用**: ${part.name}\n\`\`\`\n${JSON.stringify(part.input, null, 2)}\n\`\`\``)
|
||||
if (part.name === "show-components") {
|
||||
const ids = (part.input as { component_ids: string[] }).component_ids
|
||||
const components = await ComponentService.getComponentsByIds(ids)
|
||||
appendMessage = {
|
||||
role: 'card',
|
||||
components: components.filter(c => !!c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
content = contentParts.join('\n\n')
|
||||
}
|
||||
|
||||
res.push({
|
||||
...message,
|
||||
content
|
||||
})
|
||||
if (appendMessage) {
|
||||
res.push(appendMessage)
|
||||
appendMessage = null
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(res)
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取对话详情错误:', error)
|
||||
return NextResponse.json(
|
||||
{ message: '获取对话详情失败' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
// 验证用户身份
|
||||
const user = await getUser(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ message: '请先登录' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const client = new AIClient()
|
||||
const success = await client.deleteConversation(params.id, user.id)
|
||||
|
||||
if (!success) {
|
||||
return NextResponse.json(
|
||||
{ message: '对话不存在或无权限删除' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: '对话删除成功' })
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除对话错误:', error)
|
||||
return NextResponse.json(
|
||||
{ message: '删除对话失败' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
// 验证用户身份
|
||||
const user = await getUser(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ message: '请先登录' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { title } = await request.json()
|
||||
if (!title || typeof title !== 'string') {
|
||||
return NextResponse.json(
|
||||
{ message: '请提供有效的标题' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const client = new AIClient()
|
||||
const success = await client.updateConversationTitle(params.id, user.id, title)
|
||||
|
||||
if (!success) {
|
||||
return NextResponse.json(
|
||||
{ message: '对话不存在或无权限修改' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: '标题更新成功' })
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新对话标题错误:', error)
|
||||
return NextResponse.json(
|
||||
{ message: '更新对话标题失败' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { AIClient } from '@/lib/ai-assistant-openai'
|
||||
import { getUser } from '@/lib/auth'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// 验证用户身份
|
||||
const user = await getUser(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ message: '请先登录' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const limit = parseInt(searchParams.get('limit') || '20')
|
||||
|
||||
const client = new AIClient()
|
||||
const conversations = await client.getUserConversations(user.id, page, limit)
|
||||
|
||||
return NextResponse.json(conversations)
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取对话历史错误:', error)
|
||||
return NextResponse.json(
|
||||
{ message: '获取对话历史失败' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { AIClient } from '@/lib/ai-assistant'
|
||||
import { getUser } from '@/lib/auth'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// 验证用户身份
|
||||
const user = await getUser(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ message: '请先登录' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { prompt, conversationId } = body
|
||||
|
||||
if (!prompt || !prompt.trim()) {
|
||||
return NextResponse.json(
|
||||
{ message: '请输入您的问题' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
let client = new AIClient() // 创建流式响应
|
||||
const encoder = new TextEncoder()
|
||||
let newConversationId: string | null = null
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
try {
|
||||
for await (const segment of client.processQuery(prompt, user.id, conversationId)) {
|
||||
// 检查是否是新对话ID
|
||||
if (segment.startsWith('conversation_id:')) {
|
||||
newConversationId = segment.substring('conversation_id:'.length)
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ conversationId: newConversationId })}\n\n`))
|
||||
continue
|
||||
}
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ content: segment })}\n\n`))
|
||||
}
|
||||
controller.enqueue(encoder.encode('data: [DONE]\n\n'))
|
||||
controller.close()
|
||||
} catch (error) {
|
||||
console.error('AI 处理错误:', error)
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify({
|
||||
error: error instanceof Error ? error.message : '处理请求时发生错误'
|
||||
})}\n\n`))
|
||||
controller.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
},
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('AI 查询错误:', error)
|
||||
return NextResponse.json(
|
||||
{ message: 'AI 服务暂时不可用,请稍后再试' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
message: '欢迎使用 PC DIY 商店 AI 助手!',
|
||||
examples: [
|
||||
'推荐一些价格在 1000-2000 元的显卡',
|
||||
'查找华硕品牌的主板',
|
||||
'什么是最新的 CPU 配件',
|
||||
'显示所有配件类型'
|
||||
]
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user