131 lines
4.3 KiB
TypeScript
131 lines
4.3 KiB
TypeScript
|
|
import OpenAI from 'openai';
|
|||
|
|
|
|||
|
|
const openai = new OpenAI({
|
|||
|
|
apiKey: process.env.LLM_API_KEY || 'sk-placeholder',
|
|||
|
|
baseURL: process.env.LLM_BASE_URL || 'https://api.openai.com/v1',
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Per-user conversation history (in-memory; use Redis in production)
|
|||
|
|
const MAX_CACHE_USERS = 1000;
|
|||
|
|
const conversationCache = new Map<string, { messages: Array<{ role: 'user' | 'assistant'; content: string }>; lastAccess: number }>();
|
|||
|
|
|
|||
|
|
// Evict least-recently-used entries when cache exceeds limit
|
|||
|
|
function getHistory(userId: string): Array<{ role: 'user' | 'assistant'; content: string }> {
|
|||
|
|
const entry = conversationCache.get(userId);
|
|||
|
|
if (entry) {
|
|||
|
|
entry.lastAccess = Date.now();
|
|||
|
|
return entry.messages;
|
|||
|
|
}
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function setHistory(userId: string, messages: Array<{ role: 'user' | 'assistant'; content: string }>): void {
|
|||
|
|
if (conversationCache.size >= MAX_CACHE_USERS) {
|
|||
|
|
// Evict oldest entry
|
|||
|
|
let oldestKey = '';
|
|||
|
|
let oldestTime = Infinity;
|
|||
|
|
for (const [key, val] of conversationCache.entries()) {
|
|||
|
|
if (val.lastAccess < oldestTime) {
|
|||
|
|
oldestTime = val.lastAccess;
|
|||
|
|
oldestKey = key;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (oldestKey) conversationCache.delete(oldestKey);
|
|||
|
|
}
|
|||
|
|
conversationCache.set(userId, { messages, lastAccess: Date.now() });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function callAI(params: {
|
|||
|
|
systemPrompt: string;
|
|||
|
|
userMessage: string;
|
|||
|
|
userId: string;
|
|||
|
|
}): Promise<string> {
|
|||
|
|
const { systemPrompt, userMessage, userId } = params;
|
|||
|
|
|
|||
|
|
// Get recent conversation history (last 10 turns = 20 messages)
|
|||
|
|
const history = getHistory(userId);
|
|||
|
|
|
|||
|
|
const messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }> = [
|
|||
|
|
{ role: 'system', content: systemPrompt },
|
|||
|
|
...history.slice(-20),
|
|||
|
|
{ role: 'user', content: userMessage },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const completion = await openai.chat.completions.create({
|
|||
|
|
model: process.env.LLM_MODEL || 'gpt-4',
|
|||
|
|
messages,
|
|||
|
|
temperature: 0.7,
|
|||
|
|
max_tokens: 800,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const aiResponse = completion.choices[0]?.message?.content || '...';
|
|||
|
|
|
|||
|
|
// Update conversation cache
|
|||
|
|
const updatedHistory = [
|
|||
|
|
...history,
|
|||
|
|
{ role: 'user' as const, content: userMessage },
|
|||
|
|
{ role: 'assistant' as const, content: aiResponse },
|
|||
|
|
].slice(-20);
|
|||
|
|
setHistory(userId, updatedHistory);
|
|||
|
|
|
|||
|
|
return aiResponse;
|
|||
|
|
} catch (err: any) {
|
|||
|
|
console.error('[AI] Call failed:', err.message);
|
|||
|
|
throw new Error('AI服务暂时不可用');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function getWelcomeMessage(user: {
|
|||
|
|
nickname: string;
|
|||
|
|
role: string;
|
|||
|
|
aiCompanion: { name: string };
|
|||
|
|
}): string {
|
|||
|
|
const { nickname, role, aiCompanion } = user;
|
|||
|
|
const name = aiCompanion.name;
|
|||
|
|
|
|||
|
|
if (role === 'author') {
|
|||
|
|
return `早上好,${nickname}!我是${name},你的AI创作伙伴。\n\n今天想做什么?写作、看数据、还是找合作机会?`;
|
|||
|
|
} else if (role === 'editor') {
|
|||
|
|
return `早上好,${nickname}!我是${name},你的AI审稿助手。\n\n今天有新投稿等你审核,要看看吗?`;
|
|||
|
|
} else {
|
|||
|
|
return `早上好,${nickname}!我是${name},你的AI数据助手。\n\n今天的数据已更新,要看看趋势分析吗?`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function buildSystemPrompt(user: {
|
|||
|
|
nickname: string;
|
|||
|
|
role: string;
|
|||
|
|
}): string {
|
|||
|
|
const prompts: Record<string, string> = {
|
|||
|
|
author: `你是用户的AI创作伙伴,名字叫「笔灵」。
|
|||
|
|
用户信息:昵称=${user.nickname},角色=作者。
|
|||
|
|
你的职责:
|
|||
|
|
1. 帮助用户创作(提供灵感、扩写、优化文笔)
|
|||
|
|
2. 管理写作项目(打开文档、查看进度)
|
|||
|
|
3. 提醒和建议(写作时间、字数目标、市场趋势)
|
|||
|
|
4. 记住用户的写作风格和习惯
|
|||
|
|
语气:温暖、鼓励、专业。像一个懂你的创作搭档。`,
|
|||
|
|
|
|||
|
|
editor: `你是用户的AI审稿助手,名字叫「慧眼」。
|
|||
|
|
用户信息:昵称=${user.nickname},角色=编辑。
|
|||
|
|
你的职责:
|
|||
|
|
1. 帮助筛选和评估投稿
|
|||
|
|
2. 分析AI使用报告
|
|||
|
|
3. 提供审稿建议
|
|||
|
|
4. 管理审核工作流
|
|||
|
|
语气:专业、精准、高效。`,
|
|||
|
|
|
|||
|
|
operator: `你是用户的AI数据助手,名字叫「星图」。
|
|||
|
|
用户信息:昵称=${user.nickname},角色=运营。
|
|||
|
|
你的职责:
|
|||
|
|
1. 分析平台数据趋势
|
|||
|
|
2. 提供运营策略建议
|
|||
|
|
3. 管理跨平台合作
|
|||
|
|
4. 生成数据报告
|
|||
|
|
语气:敏锐、有洞察力、数据驱动。`,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return prompts[user.role] || prompts.author;
|
|||
|
|
}
|