实现了AI助手

This commit is contained in:
2025-06-24 14:09:12 +08:00
parent 7d139b22db
commit 8a4ccd5ef2
31 changed files with 2883 additions and 252 deletions
+359
View File
@@ -0,0 +1,359 @@
import OpenAI from 'openai';
import { ComponentService } from '@/lib/services/component-service';
import { ConversationService } from '@/lib/services/conversation-service';
import { Component } from "@prisma/client";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const OPENAI_BASE_URL = process.env.OPENAI_BASE_URL;
if (!OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY is not set");
}
// 定义消息类型(兼容OpenAI格式)
type MessageParam = OpenAI.Chat.Completions.ChatCompletionMessageParam;
type FunctionCall = OpenAI.Chat.Completions.ChatCompletionMessageToolCall;
type Message = OpenAI.Chat.Completions.ChatCompletionMessage;
export class AIClient {
private openai: OpenAI;
private tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
{
type: "function",
function: {
name: "query-components",
description: "查询PC配件信息,支持按类型、品牌、价格范围、关键词等条件搜索。单次最多返回5个配件。",
parameters: {
type: "object",
properties: {
type: {
type: "string",
enum: ["CPU", "内存", "硬盘", "主板", "显卡", "机箱"],
description: "配件类型"
},
brand: {
type: "string",
description: "品牌名称"
},
minPrice: {
type: "number",
description: "最低价格"
},
maxPrice: {
type: "number",
description: "最高价格"
},
search: {
type: "string",
description: "搜索关键词,会在名称、品牌、型号、描述中搜索"
},
page: {
type: "integer",
description: "页码,默认为1"
},
}
}
}
},
{
type: "function",
function: {
name: "show-components",
description: "根据提供的配件ID列表,向用户以卡片形式展示一个或多个具体型号的配件。如果你提到了某些特定配件,请用此工具更好地向用户展示。",
parameters: {
type: "object",
properties: {
component_ids: {
type: "array",
description: "一个包含一个或多个要展示的配件ID的数组。这些ID来自`query-components`工具的查询结果。",
items: {
type: "string",
description: "单个配件的唯一ID"
}
}
},
required: ["component_ids"]
}
}
}
];
constructor() {
this.openai = new OpenAI({
apiKey: OPENAI_API_KEY,
baseURL: OPENAI_BASE_URL || "https://api.openai.com/v1"
});
}
// 查询配件
private async queryComponents(args: any): Promise<string> {
try {
const result = await ComponentService.queryComponents({
type: args.type,
brand: args.brand,
minPrice: args.minPrice,
maxPrice: args.maxPrice,
search: args.search,
page: args.page || 1,
limit: 5
})
if (result.components.length === 0) {
return "未找到符合条件的配件"
}
// 返回格式化的结果
return JSON.stringify(result.components.map(c => ({
id: c.id,
name: c.name,
brand: c.brand,
model: c.model,
price: c.price,
description: c.description,
stock: c.stock,
specifications: c.specifications,
})))
} catch (error) {
console.error('查询配件失败:', error)
return `查询配件时发生错误: ${error instanceof Error ? error.message : '未知错误'}`
}
}
async *processQuery(
query: string,
userId: string,
conversationId?: string
): AsyncGenerator<string, void, unknown> {
let messages: MessageParam[];
let currentConversationId = conversationId;
// 如果提供了对话ID,加载现有对话
if (conversationId) {
const conversation = await ConversationService.getConversation(conversationId, userId);
if (conversation) {
messages = [...conversation.messages as MessageParam[]];
// 添加新的用户消息
messages.push({
role: "user",
content: query
});
} else {
throw new Error('对话不存在或无权限访问');
}
} else {
// 创建新对话
currentConversationId = await ConversationService.createConversation(userId, query);
// 发送新对话ID给前端
yield `conversation_id:${currentConversationId}`;
// 获取新创建的对话消息
const conversation = await ConversationService.getConversation(currentConversationId, userId);
if (!conversation) {
throw new Error('创建对话失败');
}
messages = conversation.messages as MessageParam[];
}
try {
let maxIterations = 10; // 防止无限循环
let currentIteration = 0;
while (currentIteration < maxIterations) {
currentIteration++;
console.log("Send Req", messages);
const stream = await this.openai.chat.completions.create({
model: ["web-rev-claude-sonnet-4-20250514", "gemini-2.5-flash"][1],
max_tokens: 2048,
messages,
tools: this.tools,
stream: true,
});
let assistantMessage: MessageParam = {
role: "assistant",
content: "",
tool_calls: []
};
// 处理流式响应
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (!delta) continue;
// 处理文本内容
if (delta.content) {
assistantMessage.content += delta.content;
yield delta.content;
}
// 处理工具调用
if (delta.tool_calls) {
for (const toolCall of delta.tool_calls) {
const index = toolCall.index;
// 初始化工具调用
if (!assistantMessage.tool_calls) {
assistantMessage.tool_calls = [];
}
if (!assistantMessage.tool_calls[index]) {
assistantMessage.tool_calls[index] = {
id: toolCall.id || "",
type: "function",
function: {
name: toolCall.function?.name || "",
arguments: ""
}
};
yield `\n\n**使用工具**: ${toolCall.function?.name}\n\n\`\`\`\n`;
}
// 累积工具调用参数
if (toolCall.function?.arguments) {
assistantMessage.tool_calls[index].function.arguments += toolCall.function.arguments;
yield toolCall.function.arguments;
}
}
}
}
// 如果有工具调用,结束代码块
if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
yield `\n\`\`\``;
}
// 将助手消息添加到对话中
messages.push(assistantMessage);
// 如果没有工具调用,结束循环
if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
break;
}
// 处理工具调用
let needRerun = false;
for (const toolCall of assistantMessage.tool_calls) {
if (!toolCall.function.name) continue;
let toolArgs: any = {};
try {
toolArgs = JSON.parse(toolCall.function.arguments);
} catch (error) {
console.error('解析工具参数失败:', error);
continue;
}
let toolResult = "";
switch (toolCall.function.name) {
case "query-components":
toolResult = await this.queryComponents(toolArgs);
needRerun = true;
yield 'next_block';
break;
case "show-components":
const components = await ComponentService.getComponentsByIds(toolArgs.component_ids);
toolResult = `成功向用户展示了 ${components.map(c => c?.id).join(',')}`;
needRerun = true;
yield `show_card: ${JSON.stringify(components)}`;
break;
default:
console.error(`未知工具调用: ${toolCall.function.name}`);
toolResult = `未知工具: ${toolCall.function.name}`;
}
// 添加工具调用结果消息
messages.push({
role: "tool",
content: toolResult,
tool_call_id: toolCall.id
});
}
if (!needRerun) break;
}
if (currentIteration >= maxIterations) {
yield "\n\n⚠️ 达到最大处理轮次,对话结束。";
} // 保存更新后的对话到数据库
if (currentConversationId) {
// 转换消息格式以兼容现有的ConversationService
const compatibleMessages = messages.map((msg): any => {
if (msg.role === "tool") {
// 将工具消息转换为用户消息格式以保持兼容性
return {
role: "user",
content: `Tool result: ${msg.content}`
};
}
return {
role: msg.role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
};
});
await ConversationService.updateConversationMessages(currentConversationId, userId, compatibleMessages);
}
} catch (error) {
console.error('处理查询时发生错误:', error);
yield `\n\n❌ 抱歉,处理您的请求时遇到了问题: ${error instanceof Error ? error.message : '未知错误'}`; // 即使出错也要保存对话
if (currentConversationId) {
try {
const compatibleMessages = messages.map((msg): any => {
if (msg.role === "tool") {
return {
role: "user",
content: `Tool result: ${msg.content}`
};
}
return {
role: msg.role,
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
};
});
await ConversationService.updateConversationMessages(currentConversationId, userId, compatibleMessages);
} catch (saveError) {
console.error('保存对话失败:', saveError);
}
}
}
}
/**
* 获取用户的对话历史
*/
async getUserConversations(userId: string, page = 1, limit = 20) {
return await ConversationService.getUserConversations(userId, page, limit);
}
/**
* 获取特定对话的详情
*/
async getConversation(conversationId: string, userId: string) {
return await ConversationService.getConversation(conversationId, userId);
}
/**
* 删除对话
*/
async deleteConversation(conversationId: string, userId: string) {
return await ConversationService.deleteConversation(conversationId, userId);
}
/**
* 更新对话标题
*/
async updateConversationTitle(conversationId: string, userId: string, title: string) {
return await ConversationService.updateConversationTitle(conversationId, userId, title);
}
}
+399
View File
@@ -0,0 +1,399 @@
import { Anthropic } from "@anthropic-ai/sdk";
import {
Message,
MessageParam,
Tool
} from "@anthropic-ai/sdk/resources/messages/messages.mjs";
import { ComponentService } from '@/lib/services/component-service';
import { ConversationService } from '@/lib/services/conversation-service';
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
const ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL;
if (!ANTHROPIC_API_KEY) {
throw new Error("ANTHROPIC_API_KEY is not set");
}
export class AIClient {
private anthropic: Anthropic;
private tools: Tool[] = [
{
name: "query-components",
description: "查询PC配件信息,支持按类型、品牌、价格范围、关键词等条件搜索。单次最多返回5个配件。",
input_schema: {
type: "object",
properties: {
type: {
type: "string",
enum: ["CPU", "内存", "硬盘", "主板", "显卡", "机箱"],
description: "配件类型"
},
brand: {
type: "string",
description: "品牌名称"
},
minPrice: {
type: "number",
description: "最低价格"
},
maxPrice: {
type: "number",
description: "最高价格"
},
search: {
type: "string",
description: "搜索关键词,会在名称、品牌、型号、描述中搜索"
},
page: {
type: "integer",
description: "页码,默认为1"
},
}
}
}, {
name: "show-components",
description: "根据提供的配件ID列表,向用户以卡片形式展示一个或多个具体型号的配件。如果你提到了某些特定配件,请用此工具更好地向用户展示。",
input_schema: {
type: "object",
properties: {
component_ids: {
type: "array",
description: "一个包含一个或多个要展示的配件ID的数组。这些ID来自`query-components`工具的查询结果。",
items: {
type: "string",
description: "单个配件的唯一ID"
}
}
},
required: ["component_ids"]
}
}
];
constructor() {
this.anthropic = new Anthropic({
apiKey: ANTHROPIC_API_KEY,
baseURL: ANTHROPIC_BASE_URL,
});
}
// 查询配件
private async queryComponents(args: any): Promise<string> {
try {
const result = await ComponentService.queryComponents({
type: args.type,
brand: args.brand,
minPrice: args.minPrice,
maxPrice: args.maxPrice,
search: args.search,
page: args.page || 1,
limit: 5
})
if (result.components.length === 0) {
return "未找到符合条件的配件"
}
// 返回格式化的结果
return JSON.stringify(result.components.map(c => ({
id: c.id,
name: c.name,
brand: c.brand,
model: c.model,
price: c.price,
description: c.description,
stock: c.stock,
specifications: c.specifications,
})))
} catch (error) {
console.error('查询配件失败:', error)
return `查询配件时发生错误: ${error instanceof Error ? error.message : '未知错误'}`
}
}
async *processQuery(
query: string,
userId: string,
conversationId?: string
): AsyncGenerator<string, void, unknown> {
let messages: MessageParam[];
let currentConversationId = conversationId;
// 如果提供了对话ID,加载现有对话
if (conversationId) {
const conversation = await ConversationService.getConversation(conversationId, userId);
if (conversation) {
messages = [...conversation.messages];
// 添加新的用户消息
messages.push({
role: "user",
content: query
});
} else {
throw new Error('对话不存在或无权限访问');
}
} else {
// 创建新对话
currentConversationId = await ConversationService.createConversation(userId, query);
// 发送新对话ID给前端
yield `conversation_id:${currentConversationId}`;
// 获取新创建的对话消息
const conversation = await ConversationService.getConversation(currentConversationId, userId);
if (!conversation) {
throw new Error('创建对话失败');
}
messages = conversation.messages;
}
try {
let maxIterations = 20;
let currentIteration = 0;
while (currentIteration < maxIterations) {
currentIteration++;
console.log("Send Req", messages);
const response = await this.anthropic.messages.create({
model: ["claude-sonnet-4-20250514", "claude-3-5-haiku-20241022"][1],
max_tokens: 2048,
messages,
tools: this.tools,
stream: true,
});
let tempMsg: Message | null = null
// 处理流式响应
for await (const chunk of response) {
console.log(chunk);
switch (chunk.type) {
case "message_start":
tempMsg = {
id: chunk.message.id,
type: chunk.message.type,
role: chunk.message.role,
content: [],
model: chunk.message.model,
usage: chunk.message.usage,
stop_reason: chunk.message.stop_reason,
stop_sequence: chunk.message.stop_sequence,
}
break
case "message_delta":
tempMsg = {
...tempMsg || {},
stop_reason: chunk.delta.stop_reason,
stop_sequence: chunk.delta.stop_sequence,
// @ts-ignore
usage: chunk.usage,
}
break
case "message_stop":
break
case "content_block_start":
const contentBlock = chunk.content_block;
switch (contentBlock.type) {
case "text":
tempMsg?.content.push({
type: contentBlock.type,
text: contentBlock.text,
citations: contentBlock.citations || [],
})
yield contentBlock.text
break
case "tool_use":
tempMsg?.content.push({
type: contentBlock.type,
id: contentBlock.id,
name: contentBlock.name,
input: "",
});
yield `\n\n**使用工具**: ${contentBlock.name}\n\n\`\`\`\n`
break
}
break
case "content_block_delta":
const contentBlockDelta = chunk.delta;
const targetContent = tempMsg?.content[chunk.index];
switch (contentBlockDelta.type) {
case "text_delta":
if (targetContent && targetContent.type === "text") {
targetContent.text += contentBlockDelta.text;
yield contentBlockDelta.text;
} else console.error("文本块不匹配,无法追加内容", targetContent);
break
case "input_json_delta":
if (targetContent && targetContent.type === "tool_use") {
targetContent.input += contentBlockDelta.partial_json || "";
yield contentBlockDelta.partial_json || "";
} else console.error("工具调用块不匹配,无法追加输入", targetContent);
break
}
break
case "content_block_stop":
let type = tempMsg?.content[chunk.index].type;
if (type === "tool_use") {
yield `\n\`\`\``;
}
break
}
}
if (!tempMsg) {
console.error("没有接收到有效的消息内容");
yield "\n\n❌ 抱歉,处理您的请求时没有返回有效的消息内容。";
break;
}
messages.push({
role: tempMsg.role,
content: tempMsg.content.map(c => {
if (c.type === "text") {
return {
type: c.type,
text: c.text,
};
} else if (c.type === "tool_use") {
return {
id: c.id,
type: c.type,
name: c.name,
input: JSON.parse(c.input as string)
};
} else return c;
})
});
if (!tempMsg.content.find(c => c.type === "tool_use")) {
break
}
let tempUserMsg: MessageParam = {
role: "user",
content: []
}
let needRerun = false;
for (const toolUse of tempMsg.content.filter(c => c.type === "tool_use")) {
let toolArgs = JSON.parse(toolUse.input as string || "{}");
console.log(toolArgs);
switch (toolUse.name) {
case "query-components":
// @ts-ignore
tempUserMsg.content.push({
type: "tool_result",
tool_use_id: toolUse.id,
content: await this.queryComponents(toolArgs)
});
needRerun = true;
yield 'next_block'
break
case "show-components":
let components = await ComponentService.getComponentsByIds(toolArgs.component_ids)
// @ts-ignore
tempUserMsg.content.push({
type: "tool_result",
tool_use_id: toolUse.id,
content: `成功向用户展示了 ${JSON.stringify(
(await ComponentService.getComponentsByIds(toolArgs.component_ids)).map((c, index) => (c && {
id: c.id,
name: c.name,
brand: c.brand,
model: c.model,
price: c.price,
description: c.description,
stock: c.stock,
specifications: c.specifications,
} || { id: toolArgs.component_ids[index].id, name: "未知配件" })
)
)}`
});
needRerun = true;
yield `show_card: ${JSON.stringify(components)}`;
yield 'next_block'
break;
default:
console.error(`未知工具调用: ${toolUse.name}`);
}
// 继续处理下一个工具调用
}
// 将工具调用结果添加到消息列表中
messages.push(tempUserMsg);
if (!needRerun) break
} if (currentIteration >= maxIterations) {
yield "\n\n⚠️ 达到最大处理轮次,对话结束。";
}
// 保存更新后的对话到数据库
if (currentConversationId) {
await ConversationService.updateConversationMessages(currentConversationId, userId, messages);
}
} catch (error) {
console.error('处理查询时发生错误:', error);
yield `\n\n❌ 抱歉,处理您的请求时遇到了问题: ${error instanceof Error ? error.message : '未知错误'}`;
// 即使出错也要保存对话
if (currentConversationId) {
try {
await ConversationService.updateConversationMessages(currentConversationId, userId, messages);
} catch (saveError) {
console.error('保存对话失败:', saveError);
}
}
}
}
/**
* 获取用户的对话历史
*/
async getUserConversations(userId: string, page = 1, limit = 20) {
return await ConversationService.getUserConversations(userId, page, limit);
}
/**
* 获取特定对话的详情
*/
async getConversation(conversationId: string, userId: string) {
return await ConversationService.getConversation(conversationId, userId);
}
/**
* 删除对话
*/
async deleteConversation(conversationId: string, userId: string) {
return await ConversationService.deleteConversation(conversationId, userId);
}
/**
* 更新对话标题
*/
async updateConversationTitle(conversationId: string, userId: string, title: string) {
return await ConversationService.updateConversationTitle(conversationId, userId, title);
}
}
+37
View File
@@ -1,5 +1,7 @@
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'
import { prisma } from '@/lib/prisma'
import type { NextRequest } from 'next/server'
const JWT_SECRET = process.env.JWT_SECRET || 'your-jwt-secret-here'
@@ -22,3 +24,38 @@ export function verifyToken(token: string): any {
return null
}
}
export async function getUser(request: NextRequest) {
try {
const authHeader = request.headers.get('Authorization')
const token = authHeader?.replace('Bearer ', '')
if (!token) {
return null
}
const decoded = verifyToken(token)
if (!decoded || !decoded.userId) {
return null
}
const user = await prisma.user.findUnique({
where: { id: decoded.userId },
select: {
id: true,
email: true,
username: true,
name: true,
phone: true,
address: true,
isAdmin: true,
createdAt: true
}
})
return user
} catch (error) {
console.error('获取用户信息失败:', error)
return null
}
}
File diff suppressed because one or more lines are too long
+159
View File
@@ -0,0 +1,159 @@
import { prisma } from '@/lib/prisma'
export interface ComponentQueryParams {
type?: string
typeId?: string // 直接使用typeId
brand?: string
minPrice?: number
maxPrice?: number
search?: string
page?: number
limit?: number
}
export interface ComponentQueryResult {
components: Array<{
id: string
name: string
brand: string
model: string
price: number
description: string | null
stock: number
type: string
specifications: string | null
imageUrl: string | null
}>
pagination: {
page: number
limit: number
total: number
totalPages: number
hasNext: boolean
hasPrev: boolean
}
}
export class ComponentService {
/**
* 查询组件列表
*/
static async queryComponents(params: ComponentQueryParams): Promise<ComponentQueryResult> {
const {
type,
typeId,
brand,
minPrice,
maxPrice,
search,
page = 1,
limit = 12
} = params
const where: any = {}
// 优先使用typeId,如果没有但有type,则查找typeId
if (typeId) {
where.componentTypeId = typeId
} else if (type) {
const componentType = await prisma.componentType.findFirst({
where: {
name: {
contains: type,
mode: 'insensitive'
}
}
})
if (componentType) {
where.componentTypeId = componentType.id
}
}
if (brand) {
where.brand = {
contains: brand,
mode: 'insensitive'
}
}
if (minPrice || maxPrice) {
where.price = {}
if (minPrice) where.price.gte = minPrice
if (maxPrice) where.price.lte = maxPrice
}
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ brand: { contains: search, mode: 'insensitive' } },
{ model: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } }
]
}
const [components, total] = await Promise.all([
prisma.component.findMany({
where,
include: {
componentType: true
},
orderBy: {
createdAt: 'desc'
},
skip: (page - 1) * limit,
take: limit
}),
prisma.component.count({ where })
])
const totalPages = Math.ceil(total / limit)
return {
components: components.map(comp => ({
id: comp.id,
name: comp.name,
brand: comp.brand,
model: comp.model,
price: comp.price,
description: comp.description,
stock: comp.stock,
type: comp.componentType.name,
specifications: comp.specifications,
imageUrl: comp.imageUrl
})),
pagination: {
page,
limit,
total,
totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
}
}
/**
* 根据ID获取单个组件
*/
static async getComponentById(id: string) {
return await prisma.component.findUnique({
where: { id },
include: {
componentType: true
}
})
}
static async getComponentsByIds(componentIds: string[]) {
return await Promise.all(componentIds.map(id => ComponentService.getComponentById(id)))
}
/**
* 获取所有组件类型
*/
static async getComponentTypes() {
return await prisma.componentType.findMany({
orderBy: {
name: 'asc'
}
})
}
}
+163
View File
@@ -0,0 +1,163 @@
import { prisma } from '@/lib/prisma'
import type { MessageParam } from '@anthropic-ai/sdk/resources'
export interface ConversationData {
id: string
title: string | null
messages: MessageParam[]
createdAt: Date
updatedAt: Date
}
export class ConversationService {
/**
* 创建新对话
*/
static async createConversation(userId: string, initialMessage: string): Promise<string> {
// 生成对话标题(取用户消息的前30个字符)
const title = initialMessage.length > 30
? initialMessage.substring(0, 30) + '...'
: initialMessage
const systemMessage: MessageParam = {
role: "user",
content: `你是PC DIY商店的专业AI助手。你的主要职责是:
1. 帮助用户查找和推荐PC配件
2. 提供配件的价格、性能和兼容性信息
3. 根据用户预算和需求推荐最适合的配件
4. 解答关于PC装机的技术问题`
}
const assistantGreeting: MessageParam = {
role: "assistant",
content: "您好!我是PC DIY商店的AI助手,很高兴为您服务。请告诉我您需要什么帮助?"
}
const userMessage: MessageParam = {
role: "user",
content: initialMessage
}
const initialMessages = [systemMessage, assistantGreeting, userMessage]
const conversation = await prisma.conversation.create({
data: {
userId,
title,
messages: JSON.stringify(initialMessages)
}
})
return conversation.id
}
/**
* 获取对话
*/
static async getConversation(conversationId: string, userId: string): Promise<ConversationData | null> {
const conversation = await prisma.conversation.findFirst({
where: {
id: conversationId,
userId
}
})
if (!conversation) {
return null
}
return {
id: conversation.id,
title: conversation.title,
messages: JSON.parse(conversation.messages) as MessageParam[],
createdAt: conversation.createdAt,
updatedAt: conversation.updatedAt
}
}
/**
* 更新对话消息
*/
static async updateConversationMessages(
conversationId: string,
userId: string,
messages: MessageParam[]
): Promise<void> {
await prisma.conversation.updateMany({
where: {
id: conversationId,
userId
},
data: {
messages: JSON.stringify(messages),
updatedAt: new Date()
}
})
}
/**
* 获取用户的对话列表
*/
static async getUserConversations(userId: string, page = 1, limit = 20) {
const skip = (page - 1) * limit
const [conversations, total] = await Promise.all([
prisma.conversation.findMany({
where: { userId },
select: {
id: true,
title: true,
createdAt: true,
updatedAt: true
},
orderBy: { updatedAt: 'desc' },
skip,
take: limit
}),
prisma.conversation.count({
where: { userId }
})
])
return {
conversations,
total,
page,
totalPages: Math.ceil(total / limit)
}
}
/**
* 删除对话
*/
static async deleteConversation(conversationId: string, userId: string): Promise<boolean> {
const result = await prisma.conversation.deleteMany({
where: {
id: conversationId,
userId
}
})
return result.count > 0
}
/**
* 更新对话标题
*/
static async updateConversationTitle(
conversationId: string,
userId: string,
title: string
): Promise<boolean> {
const result = await prisma.conversation.updateMany({
where: {
id: conversationId,
userId
},
data: { title }
})
return result.count > 0
}
}