mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-06 13:06:09 +08:00
feat: 优化助手逻辑
This commit is contained in:
@@ -80,12 +80,8 @@ export class Agent {
|
||||
|
||||
const maxToolRounds = Math.max(0, this.config.maxToolRounds ?? 0)
|
||||
|
||||
// 当有 AssistantConfig 时,将其 systemPrompt/responseRules 映射为 PromptConfig
|
||||
const effectivePromptConfig: PromptConfig | undefined = this.assistantConfig
|
||||
? {
|
||||
roleDefinition: this.assistantConfig.systemPrompt,
|
||||
responseRules: this.assistantConfig.responseRules || '',
|
||||
}
|
||||
? { systemPrompt: this.assistantConfig.systemPrompt }
|
||||
: this.promptConfig
|
||||
|
||||
const systemPrompt = buildSystemPrompt(this.chatType, effectivePromptConfig, this.context.ownerInfo, this.locale)
|
||||
|
||||
@@ -64,15 +64,13 @@ function getFallbackRoleDefinition(chatType: 'group' | 'private', locale: string
|
||||
return agentT(`ai.agent.fallbackRoleDefinition.${chatType}`, locale)
|
||||
}
|
||||
|
||||
function getFallbackResponseRules(locale: string = 'zh-CN'): string {
|
||||
return agentT('ai.agent.fallbackResponseRules', locale)
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整的系统提示词
|
||||
*
|
||||
* 提示词配置主要来自前端 src/config/prompts.ts,通过 promptConfig 参数传递。
|
||||
* Fallback 仅在前端未传递配置时使用。
|
||||
*
|
||||
* 最终格式:{用户系统提示词}\n\n{系统锁定段(日期/owner/时间参数/通用指引)}
|
||||
*/
|
||||
export function buildSystemPrompt(
|
||||
chatType: 'group' | 'private' = 'group',
|
||||
@@ -80,14 +78,10 @@ export function buildSystemPrompt(
|
||||
ownerInfo?: OwnerInfo,
|
||||
locale: string = 'zh-CN'
|
||||
): string {
|
||||
const roleDefinition = promptConfig?.roleDefinition || getFallbackRoleDefinition(chatType, locale)
|
||||
const responseRules = promptConfig?.responseRules || getFallbackResponseRules(locale)
|
||||
const systemPrompt = promptConfig?.systemPrompt || getFallbackRoleDefinition(chatType, locale)
|
||||
const lockedSection = getLockedPromptSection(chatType, ownerInfo, locale)
|
||||
|
||||
return `${roleDefinition}
|
||||
return `${systemPrompt}
|
||||
|
||||
${lockedSection}
|
||||
|
||||
${agentT('ai.agent.responseRulesTitle', locale)}
|
||||
${responseRules}`
|
||||
${lockedSection}`
|
||||
}
|
||||
|
||||
@@ -64,8 +64,6 @@ export interface AgentResult {
|
||||
* 用户自定义提示词配置
|
||||
*/
|
||||
export interface PromptConfig {
|
||||
/** 角色定义(可编辑区) */
|
||||
roleDefinition: string
|
||||
/** 回答要求(可编辑区) */
|
||||
responseRules: string
|
||||
/** 系统提示词(角色定义 + 回答要求,统一为单一字段) */
|
||||
systemPrompt: string
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"id": "community_analyst",
|
||||
"name": "社群分析师",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"order": 2,
|
||||
"systemPrompt": "你是一位资深的社群运营分析师,擅长从聊天数据中挖掘社群运营洞察。\n你的核心能力是:\n- 分析成员活跃度排行和变化趋势\n- 识别群内热门话题和讨论焦点\n- 发现社群的活跃时段规律\n- 识别关键意见领袖(KOL)和活跃贡献者\n- 给出可执行的社群运营建议\n\n你的回答风格应该专业、数据驱动,用数据图表思维呈现分析结果。",
|
||||
"responseRules": "1. 每个分析结论都必须有数据支撑,引用具体数字\n2. 使用 Markdown 表格和列表清晰呈现排行和对比\n3. 在数据分析后给出可执行的运营建议\n4. 主动发现数据中的异常点和有趣模式\n5. 如果用户问题模糊,主动推荐合适的分析维度",
|
||||
"systemPrompt": "你是一位资深的社群运营分析师,擅长从聊天数据中挖掘社群运营洞察。\n你的核心能力是:\n- 分析成员活跃度排行和变化趋势\n- 识别群内热门话题和讨论焦点\n- 发现社群的活跃时段规律\n- 识别关键意见领袖(KOL)和活跃贡献者\n- 给出可执行的社群运营建议\n\n你的回答风格应该专业、数据驱动,用数据图表思维呈现分析结果。\n\n## 回答要求\n1. 每个分析结论都必须有数据支撑,引用具体数字\n2. 使用 Markdown 表格和列表清晰呈现排行和对比\n3. 在数据分析后给出可执行的运营建议\n4. 主动发现数据中的异常点和有趣模式\n5. 如果用户问题模糊,主动推荐合适的分析维度",
|
||||
"presetQuestions": [
|
||||
"本周最活跃的成员 Top 10 是谁?",
|
||||
"群里最近在讨论什么热门话题?",
|
||||
@@ -15,7 +14,7 @@
|
||||
"get_member_stats",
|
||||
"get_time_stats",
|
||||
"search_messages",
|
||||
"get_group_members",
|
||||
"get_members",
|
||||
"get_member_name_history",
|
||||
"search_sessions",
|
||||
"get_session_summaries",
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"id": "customer_service",
|
||||
"name": "客服助手",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"order": 4,
|
||||
"systemPrompt": "你是一位专业的客服对话分析专家,擅长从客服聊天记录中提炼有价值的信息。\n你的核心能力是:\n- 归纳和分类用户的常见问题(FAQ 提取)\n- 分析客服响应速度和服务质量\n- 识别未解决的问题和客户投诉\n- 提炼优秀的话术模板和应对策略\n- 发现服务流程中可以优化的环节\n\n你的回答风格应该专业、条理清晰,注重可操作性。",
|
||||
"responseRules": "1. 分析结果按优先级和重要性排列\n2. 归纳问题时使用分类标签,便于后续跟进\n3. 给出具体可执行的改进建议\n4. 引用原始对话作为证据支撑\n5. 使用结构化格式(表格、编号列表)呈现结果",
|
||||
"systemPrompt": "你是一位专业的客服对话分析专家,擅长从客服聊天记录中提炼有价值的信息。\n你的核心能力是:\n- 归纳和分类用户的常见问题(FAQ 提取)\n- 分析客服响应速度和服务质量\n- 识别未解决的问题和客户投诉\n- 提炼优秀的话术模板和应对策略\n- 发现服务流程中可以优化的环节\n\n你的回答风格应该专业、条理清晰,注重可操作性。\n\n## 回答要求\n1. 分析结果按优先级和重要性排列\n2. 归纳问题时使用分类标签,便于后续跟进\n3. 给出具体可执行的改进建议\n4. 引用原始对话作为证据支撑\n5. 使用结构化格式(表格、编号列表)呈现结果",
|
||||
"presetQuestions": [
|
||||
"最近客户最常问的问题有哪些?",
|
||||
"有没有未解决的客户问题?",
|
||||
@@ -15,7 +14,7 @@
|
||||
"search_messages",
|
||||
"get_recent_messages",
|
||||
"get_message_context",
|
||||
"get_group_members",
|
||||
"get_members",
|
||||
"get_conversation_between",
|
||||
"search_sessions",
|
||||
"get_session_messages",
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"id": "emotion_analyst",
|
||||
"name": "情感助手",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"order": 3,
|
||||
"systemPrompt": "你是一位善于洞察人际关系和情感变化的温暖助手。\n你的核心能力是:\n- 分析聊天中的情感色彩和情绪变化\n- 识别成员之间的互动关系和亲密度\n- 发现对话中的冲突、和解、默契等情感模式\n- 帮助用户回顾与特定好友的珍贵对话回忆\n- 从聊天记录中发现温暖的、有趣的、值得纪念的瞬间\n\n你的语气应该温暖、共情,像一位善解人意的朋友。",
|
||||
"responseRules": "1. 分析情感时要细腻具体,引用关键对话片段\n2. 保持温暖积极的语气,避免过度解读或负面判断\n3. 尊重隐私,不对敏感内容过度挖掘\n4. 用讲故事的方式呈现发现,让用户感到有温度\n5. 适当使用 emoji 增加亲和力",
|
||||
"systemPrompt": "你是一位善于洞察人际关系和情感变化的温暖助手。\n你的核心能力是:\n- 分析聊天中的情感色彩和情绪变化\n- 识别成员之间的互动关系和亲密度\n- 发现对话中的冲突、和解、默契等情感模式\n- 帮助用户回顾与特定好友的珍贵对话回忆\n- 从聊天记录中发现温暖的、有趣的、值得纪念的瞬间\n\n你的语气应该温暖、共情,像一位善解人意的朋友。\n\n## 回答要求\n1. 分析情感时要细腻具体,引用关键对话片段\n2. 保持温暖积极的语气,避免过度解读或负面判断\n3. 尊重隐私,不对敏感内容过度挖掘\n4. 用讲故事的方式呈现发现,让用户感到有温度\n5. 适当使用 emoji 增加亲和力",
|
||||
"presetQuestions": [
|
||||
"我和好友最近聊了些什么开心的事?",
|
||||
"群里谁和谁互动最频繁?",
|
||||
@@ -15,7 +14,7 @@
|
||||
"search_messages",
|
||||
"get_recent_messages",
|
||||
"get_message_context",
|
||||
"get_group_members",
|
||||
"get_members",
|
||||
"get_conversation_between",
|
||||
"get_member_stats",
|
||||
"mutual_interaction_pairs",
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"id": "general",
|
||||
"name": "通用分析助手",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"order": 1,
|
||||
"systemPrompt": "你是一个专业但风格轻松的聊天记录分析助手。\n你的任务是帮助用户理解和分析他们的聊天记录数据,同时可以适度使用网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。",
|
||||
"responseRules": "1. 基于工具返回的数据回答,不要编造信息\n2. 如果数据不足以回答问题,请说明\n3. 回答要简洁明了,使用 Markdown 格式\n4. 可以引用具体的发言作为证据\n5. 对于统计数据,可以适当总结趋势和特点\n6. 可以适度加入网络热梗、表情/颜文字(强度适中)\n7. 玩梗不得影响事实准确与结论清晰,避免低俗或冒犯性表达",
|
||||
"systemPrompt": "你是一个专业但风格轻松的聊天记录分析助手。\n你的任务是帮助用户理解和分析他们的聊天记录数据,同时可以适度使用网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。\n\n## 回答要求\n1. 基于工具返回的数据回答,不要编造信息\n2. 如果数据不足以回答问题,请说明\n3. 回答要简洁明了,使用 Markdown 格式\n4. 可以引用具体的发言作为证据\n5. 对于统计数据,可以适当总结趋势和特点\n6. 可以适度加入网络热梗、表情/颜文字(强度适中)\n7. 玩梗不得影响事实准确与结论清晰,避免低俗或冒犯性表达",
|
||||
"presetQuestions": [
|
||||
"最近大家都在聊什么?",
|
||||
"谁是群里最活跃的人?",
|
||||
|
||||
@@ -99,8 +99,9 @@ function loadAllAssistants(): void {
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const config = readJsonFile<AssistantConfig>(path.join(assistantsDir, file))
|
||||
if (config && config.id) {
|
||||
const raw = readJsonFile<AssistantConfig & { responseRules?: string }>(path.join(assistantsDir, file))
|
||||
if (raw && raw.id) {
|
||||
const config = migrateResponseRules(raw, path.join(assistantsDir, file))
|
||||
cachedAssistants.set(config.id, config)
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -109,6 +110,31 @@ function loadAllAssistants(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移旧数据:将 responseRules 合并到 systemPrompt,删除旧字段并持久化
|
||||
*/
|
||||
function migrateResponseRules(
|
||||
raw: AssistantConfig & { responseRules?: string },
|
||||
filePath: string
|
||||
): AssistantConfig {
|
||||
if (!raw.responseRules) return raw as AssistantConfig
|
||||
|
||||
const merged: AssistantConfig = {
|
||||
...raw,
|
||||
systemPrompt: `${raw.systemPrompt}\n\n## 回答要求\n${raw.responseRules}`,
|
||||
}
|
||||
delete (merged as Record<string, unknown>).responseRules
|
||||
|
||||
try {
|
||||
writeJsonFile(filePath, merged)
|
||||
aiLogger.info('AssistantManager', `Migrated responseRules for assistant: ${raw.id}`)
|
||||
} catch (error) {
|
||||
aiLogger.warn('AssistantManager', `Failed to persist migration for: ${raw.id}`, { error: String(error) })
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
// ==================== 查询 API ====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,10 +18,8 @@ export interface AssistantConfig {
|
||||
/** 助手显示名称 */
|
||||
name: string
|
||||
|
||||
/** 系统提示词(替代旧的 PromptConfig.roleDefinition) */
|
||||
/** 系统提示词(角色定义 + 回答要求,统一为单一字段) */
|
||||
systemPrompt: string
|
||||
/** 回答要求(替代旧的 PromptConfig.responseRules,可选) */
|
||||
responseRules?: string
|
||||
|
||||
/** 预设问题列表(前端展示,用户可点击直接发送) */
|
||||
presetQuestions: string[]
|
||||
|
||||
@@ -13,7 +13,7 @@ const schema = Type.Object({
|
||||
...timeParamProperties,
|
||||
})
|
||||
|
||||
/** 获取两个群成员之间的对话记录。适用于回答"A和B之间聊了什么"、"查看两人的对话"等问题。需要先通过 get_group_members 获取成员 ID。支持精确到分钟级别的时间查询。 */
|
||||
/** 获取两个群成员之间的对话记录。适用于回答"A和B之间聊了什么"、"查看两人的对话"等问题。需要先通过 get_members 获取成员 ID。支持精确到分钟级别的时间查询。 */
|
||||
export function createTool(context: ToolContext): AgentTool<typeof schema> {
|
||||
return {
|
||||
name: 'get_conversation_between',
|
||||
|
||||
@@ -5,16 +5,16 @@ import * as workerManager from '../../../worker/workerManager'
|
||||
import { isChineseLocale, t } from '../utils/format'
|
||||
|
||||
const schema = Type.Object({
|
||||
search: Type.Optional(Type.String({ description: 'ai.tools.get_group_members.params.search' })),
|
||||
limit: Type.Optional(Type.Number({ description: 'ai.tools.get_group_members.params.limit' })),
|
||||
search: Type.Optional(Type.String({ description: 'ai.tools.get_members.params.search' })),
|
||||
limit: Type.Optional(Type.Number({ description: 'ai.tools.get_members.params.limit' })),
|
||||
})
|
||||
|
||||
/** 获取群成员列表,包括成员的基本信息、别名和消息统计。适用于查询"群里有哪些人"、"某人的别名是什么"、"谁的QQ号是xxx"等问题。 */
|
||||
/** 获取成员列表,包括成员的基本信息、别名和消息统计。适用于查询"有哪些人"、"某人的别名是什么"、"谁的QQ号是xxx"等问题。 */
|
||||
export function createTool(context: ToolContext): AgentTool<typeof schema> {
|
||||
return {
|
||||
name: 'get_group_members',
|
||||
label: 'get_group_members',
|
||||
description: 'ai.tools.get_group_members.desc',
|
||||
name: 'get_members',
|
||||
label: 'get_members',
|
||||
description: 'ai.tools.get_members.desc',
|
||||
parameters: schema,
|
||||
execute: async (_toolCallId, params) => {
|
||||
const { sessionId, locale } = context
|
||||
|
||||
@@ -8,7 +8,7 @@ const schema = Type.Object({
|
||||
member_id: Type.Number({ description: 'ai.tools.get_member_name_history.params.member_id' }),
|
||||
})
|
||||
|
||||
/** 获取成员的昵称变更历史记录。适用于回答"某人以前叫什么名字"、"某人的昵称变化"、"某人曾用名"等问题。需要先通过 get_group_members 工具获取成员 ID。 */
|
||||
/** 获取成员的昵称变更历史记录。适用于回答"某人以前叫什么名字"、"某人的昵称变化"、"某人曾用名"等问题。需要先通过 get_members 工具获取成员 ID。 */
|
||||
export function createTool(context: ToolContext): AgentTool<typeof schema> {
|
||||
return {
|
||||
name: 'get_member_name_history',
|
||||
|
||||
@@ -7,7 +7,7 @@ export { createTool as createSearchMessages } from './search-messages'
|
||||
export { createTool as createGetRecentMessages } from './get-recent-messages'
|
||||
export { createTool as createGetMemberStats } from './get-member-stats'
|
||||
export { createTool as createGetTimeStats } from './get-time-stats'
|
||||
export { createTool as createGetGroupMembers } from './get-group-members'
|
||||
export { createTool as createGetMembers } from './get-group-members'
|
||||
export { createTool as createGetMemberNameHistory } from './get-member-name-history'
|
||||
export { createTool as createGetConversationBetween } from './get-conversation-between'
|
||||
export { createTool as createGetMessageContext } from './get-message-context'
|
||||
@@ -22,7 +22,7 @@ export const TS_TOOL_NAMES = [
|
||||
'get_recent_messages',
|
||||
'get_member_stats',
|
||||
'get_time_stats',
|
||||
'get_group_members',
|
||||
'get_members',
|
||||
'get_member_name_history',
|
||||
'get_conversation_between',
|
||||
'get_message_context',
|
||||
|
||||
@@ -35,11 +35,11 @@ const SQL_TOOL_DEFS: CustomSqlToolDef[] = [
|
||||
{
|
||||
name: 'peak_chat_hours_by_member',
|
||||
description:
|
||||
'分析指定成员在近 N 天内每小时的发言量分布,找出其最活跃的时段。需要先通过 get_group_members 获取 member_id。',
|
||||
'分析指定成员在近 N 天内每小时的发言量分布,找出其最活跃的时段。需要先通过 get_members 获取 member_id。',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
member_id: { type: 'number', description: '成员 ID(通过 get_group_members 获取)' },
|
||||
member_id: { type: 'number', description: '成员 ID(通过 get_members 获取)' },
|
||||
days: { type: 'number', description: '统计最近多少天的数据', default: 30 },
|
||||
},
|
||||
required: ['member_id'],
|
||||
@@ -58,11 +58,11 @@ const SQL_TOOL_DEFS: CustomSqlToolDef[] = [
|
||||
{
|
||||
name: 'member_activity_trend',
|
||||
description:
|
||||
'查看指定成员近 N 天的每日发言数量变化趋势。适用于观察某人是否变得更活跃或更沉默。需要先通过 get_group_members 获取 member_id。',
|
||||
'查看指定成员近 N 天的每日发言数量变化趋势。适用于观察某人是否变得更活跃或更沉默。需要先通过 get_members 获取 member_id。',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
member_id: { type: 'number', description: '成员 ID(通过 get_group_members 获取)' },
|
||||
member_id: { type: 'number', description: '成员 ID(通过 get_members 获取)' },
|
||||
days: { type: 'number', description: '查看最近多少天的趋势' },
|
||||
},
|
||||
required: ['member_id', 'days'],
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
createGetRecentMessages,
|
||||
createGetMemberStats,
|
||||
createGetTimeStats,
|
||||
createGetGroupMembers,
|
||||
createGetMembers,
|
||||
createGetMemberNameHistory,
|
||||
createGetConversationBetween,
|
||||
createGetMessageContext,
|
||||
@@ -37,7 +37,7 @@ const coreFactories: ToolFactory[] = [
|
||||
createGetRecentMessages,
|
||||
createGetMemberStats,
|
||||
createGetTimeStats,
|
||||
createGetGroupMembers,
|
||||
createGetMembers,
|
||||
createGetMemberNameHistory,
|
||||
createGetConversationBetween,
|
||||
createGetMessageContext,
|
||||
|
||||
@@ -60,7 +60,7 @@ export default {
|
||||
keywords:
|
||||
'List of search keywords, using OR logic to match messages containing any keyword. Pass an empty array [] to filter by sender only',
|
||||
sender_id:
|
||||
'Sender member ID, used to filter messages from a specific member. Can be obtained via the get_group_members tool',
|
||||
'Sender member ID, used to filter messages from a specific member. Can be obtained via the get_members tool',
|
||||
limit: 'Message count limit, default 1000, max 50000',
|
||||
year: 'Filter messages by year, e.g. 2024',
|
||||
month: 'Filter messages by month (1-12), use with year',
|
||||
@@ -98,7 +98,7 @@ export default {
|
||||
type: 'Statistics type: hourly (by hour), weekday (by day of week), daily (by date)',
|
||||
},
|
||||
},
|
||||
get_group_members: {
|
||||
get_members: {
|
||||
desc: 'Get group member list, including basic info, aliases, and message statistics. Suitable for queries like "who is in the group", "what is someone\'s alias", or "whose ID is xxx".',
|
||||
params: {
|
||||
search: 'Optional search keyword to filter by member nickname, alias, or platform ID',
|
||||
@@ -106,13 +106,13 @@ export default {
|
||||
},
|
||||
},
|
||||
get_member_name_history: {
|
||||
desc: 'Get member name change history. Suitable for questions like "what was someone\'s previous name", "name changes", or "former names". Requires member ID from get_group_members tool first.',
|
||||
desc: 'Get member name change history. Suitable for questions like "what was someone\'s previous name", "name changes", or "former names". Requires member ID from get_members tool first.',
|
||||
params: {
|
||||
member_id: 'Member database ID, can be obtained via get_group_members tool',
|
||||
member_id: 'Member database ID, can be obtained via get_members tool',
|
||||
},
|
||||
},
|
||||
get_conversation_between: {
|
||||
desc: 'Get conversation records between two group members. Suitable for questions like "what did A and B talk about" or "view the conversation between two people". Requires member IDs from get_group_members first. Supports minute-level time queries.',
|
||||
desc: 'Get conversation records between two group members. Suitable for questions like "what did A and B talk about" or "view the conversation between two people". Requires member IDs from get_members first. Supports minute-level time queries.',
|
||||
params: {
|
||||
member_id_1: 'Database ID of the first member',
|
||||
member_id_2: 'Database ID of the second member',
|
||||
@@ -205,9 +205,9 @@ Returned summaries are brief descriptions of each session, helping quickly locat
|
||||
fallback: 'No messages found in this time range',
|
||||
},
|
||||
peak_chat_hours_by_member: {
|
||||
desc: 'Analyze a specific member\'s hourly message distribution over the last N days to find their most active hours. Requires member_id from get_group_members.',
|
||||
desc: 'Analyze a specific member\'s hourly message distribution over the last N days to find their most active hours. Requires member_id from get_members.',
|
||||
params: {
|
||||
member_id: 'Member ID (from get_group_members)',
|
||||
member_id: 'Member ID (from get_members)',
|
||||
days: 'Number of recent days to analyze',
|
||||
},
|
||||
rowTemplate: '{hour}:00 — {msg_count} messages',
|
||||
@@ -215,9 +215,9 @@ Returned summaries are brief descriptions of each session, helping quickly locat
|
||||
fallback: 'This member has no messages in the specified time range',
|
||||
},
|
||||
member_activity_trend: {
|
||||
desc: 'View a specific member\'s daily message count trend over the last N days. Useful for observing whether someone is becoming more or less active. Requires member_id from get_group_members.',
|
||||
desc: 'View a specific member\'s daily message count trend over the last N days. Useful for observing whether someone is becoming more or less active. Requires member_id from get_members.',
|
||||
params: {
|
||||
member_id: 'Member ID (from get_group_members)',
|
||||
member_id: 'Member ID (from get_members)',
|
||||
days: 'Number of recent days to view',
|
||||
},
|
||||
rowTemplate: '{day}: {msg_count} messages',
|
||||
@@ -296,12 +296,12 @@ Returned summaries are brief descriptions of each session, helping quickly locat
|
||||
`,
|
||||
memberNotePrivate: `Member query strategy:
|
||||
- Private chats only have two participants, so the member list can be directly obtained
|
||||
- When the user refers to "the other party" or "he/she", get the other participant's information via get_group_members
|
||||
- When the user refers to "the other party" or "he/she", get the other participant's information via get_members
|
||||
`,
|
||||
memberNoteGroup: `Member query strategy:
|
||||
- When the user refers to specific group members (e.g., "what did John say", "Mary's messages"), first call get_group_members to get the member list
|
||||
- When the user refers to specific group members (e.g., "what did John say", "Mary's messages"), first call get_members to get the member list
|
||||
- Group members have three names: accountName (original nickname), groupNickname (group nickname), aliases (user-defined aliases)
|
||||
- The search parameter of get_group_members can be used for fuzzy searching these three names
|
||||
- The search parameter of get_members can be used for fuzzy searching these three names
|
||||
- Once a member is found, use their id field as the sender_id parameter for search_messages to retrieve their messages
|
||||
`,
|
||||
timeParamsIntro: 'Time parameters: combine year/month/day/hour based on user mention',
|
||||
@@ -312,16 +312,22 @@ Returned summaries are brief descriptions of each session, helping quickly locat
|
||||
'If year is not specified, defaults to {{year}}. If the month has not yet occurred, {{prevYear}} is used.',
|
||||
responseInstruction:
|
||||
"Based on the user's question, select appropriate tools to retrieve data, then provide an answer based on the data.",
|
||||
responseRulesTitle: 'Response requirements:',
|
||||
fallbackRoleDefinition: {
|
||||
group: `You are a professional group chat analysis assistant.
|
||||
Your task is to help users understand and analyze their group chat data.`,
|
||||
private: `You are a professional private chat analysis assistant.
|
||||
Your task is to help users understand and analyze their private chat data.`,
|
||||
},
|
||||
fallbackResponseRules: `1. Answer based on data returned by tools, do not fabricate information
|
||||
Your task is to help users understand and analyze their group chat data.
|
||||
|
||||
## Response Requirements
|
||||
1. Answer based on data returned by tools, do not fabricate information
|
||||
2. If data is insufficient to answer, please state so
|
||||
3. Keep answers concise and clear, use Markdown format`,
|
||||
private: `You are a professional private chat analysis assistant.
|
||||
Your task is to help users understand and analyze their private chat data.
|
||||
|
||||
## Response Requirements
|
||||
1. Answer based on data returned by tools, do not fabricate information
|
||||
2. If data is insufficient to answer, please state so
|
||||
3. Keep answers concise and clear, use Markdown format`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ export default {
|
||||
desc: '根据关键词搜索群聊记录。适用于用户想要查找特定话题、关键词相关的聊天内容。可以指定时间范围和发送者来筛选消息。支持精确到分钟级别的时间查询。',
|
||||
params: {
|
||||
keywords: '搜索关键词列表,会用 OR 逻辑匹配包含任一关键词的消息。如果只需要按发送者筛选,可以传空数组 []',
|
||||
sender_id: '发送者的成员 ID,用于筛选特定成员发送的消息。可以通过 get_group_members 工具获取成员 ID',
|
||||
sender_id: '发送者的成员 ID,用于筛选特定成员发送的消息。可以通过 get_members 工具获取成员 ID',
|
||||
limit: '返回消息数量限制,默认 1000,最大 50000',
|
||||
year: '筛选指定年份的消息,如 2024',
|
||||
month: '筛选指定月份的消息(1-12),需要配合 year 使用',
|
||||
@@ -91,7 +91,7 @@ export default {
|
||||
type: '统计类型:hourly(按小时)、weekday(按星期)、daily(按日期)',
|
||||
},
|
||||
},
|
||||
get_group_members: {
|
||||
get_members: {
|
||||
desc: '获取群成员列表,包括成员的基本信息、别名和消息统计。适用于查询"群里有哪些人"、"某人的别名是什么"、"谁的QQ号是xxx"等问题。',
|
||||
params: {
|
||||
search: '可选的搜索关键词,用于筛选成员昵称、别名或QQ号',
|
||||
@@ -99,13 +99,13 @@ export default {
|
||||
},
|
||||
},
|
||||
get_member_name_history: {
|
||||
desc: '获取成员的昵称变更历史记录。适用于回答"某人以前叫什么名字"、"某人的昵称变化"、"某人曾用名"等问题。需要先通过 get_group_members 工具获取成员 ID。',
|
||||
desc: '获取成员的昵称变更历史记录。适用于回答"某人以前叫什么名字"、"某人的昵称变化"、"某人曾用名"等问题。需要先通过 get_members 工具获取成员 ID。',
|
||||
params: {
|
||||
member_id: '成员的数据库 ID,可以通过 get_group_members 工具获取',
|
||||
member_id: '成员的数据库 ID,可以通过 get_members 工具获取',
|
||||
},
|
||||
},
|
||||
get_conversation_between: {
|
||||
desc: '获取两个群成员之间的对话记录。适用于回答"A和B之间聊了什么"、"查看两人的对话"等问题。需要先通过 get_group_members 获取成员 ID。支持精确到分钟级别的时间查询。',
|
||||
desc: '获取两个群成员之间的对话记录。适用于回答"A和B之间聊了什么"、"查看两人的对话"等问题。需要先通过 get_members 获取成员 ID。支持精确到分钟级别的时间查询。',
|
||||
params: {
|
||||
member_id_1: '第一个成员的数据库 ID',
|
||||
member_id_2: '第二个成员的数据库 ID',
|
||||
@@ -196,9 +196,9 @@ export default {
|
||||
fallback: '该时间范围内没有消息记录',
|
||||
},
|
||||
peak_chat_hours_by_member: {
|
||||
desc: '分析指定成员在近 N 天内每小时的发言量分布,找出其最活跃的时段。需要先通过 get_group_members 获取 member_id。',
|
||||
desc: '分析指定成员在近 N 天内每小时的发言量分布,找出其最活跃的时段。需要先通过 get_members 获取 member_id。',
|
||||
params: {
|
||||
member_id: '成员 ID(通过 get_group_members 获取)',
|
||||
member_id: '成员 ID(通过 get_members 获取)',
|
||||
days: '统计最近多少天的数据',
|
||||
},
|
||||
rowTemplate: '{hour}:00 — {msg_count} 条消息',
|
||||
@@ -206,9 +206,9 @@ export default {
|
||||
fallback: '该成员在指定时间范围内没有发言记录',
|
||||
},
|
||||
member_activity_trend: {
|
||||
desc: '查看指定成员近 N 天的每日发言数量变化趋势。适用于观察某人是否变得更活跃或更沉默。需要先通过 get_group_members 获取 member_id。',
|
||||
desc: '查看指定成员近 N 天的每日发言数量变化趋势。适用于观察某人是否变得更活跃或更沉默。需要先通过 get_members 获取 member_id。',
|
||||
params: {
|
||||
member_id: '成员 ID(通过 get_group_members 获取)',
|
||||
member_id: '成员 ID(通过 get_members 获取)',
|
||||
days: '查看最近多少天的趋势',
|
||||
},
|
||||
rowTemplate: '{day}:{msg_count} 条',
|
||||
@@ -287,12 +287,12 @@ export default {
|
||||
`,
|
||||
memberNotePrivate: `成员查询策略:
|
||||
- 私聊只有两个人,可以直接获取成员列表
|
||||
- 当用户提到"对方"、"他/她"时,通过 get_group_members 获取另一方信息
|
||||
- 当用户提到"对方"、"他/她"时,通过 get_members 获取另一方信息
|
||||
`,
|
||||
memberNoteGroup: `成员查询策略:
|
||||
- 当用户提到特定群成员(如"张三说过什么"、"小明的发言"等)时,应先调用 get_group_members 获取成员列表
|
||||
- 当用户提到特定群成员(如"张三说过什么"、"小明的发言"等)时,应先调用 get_members 获取成员列表
|
||||
- 群成员有三种名称:accountName(原始昵称)、groupNickname(群昵称)、aliases(用户自定义别名)
|
||||
- 通过 get_group_members 的 search 参数可以模糊搜索这三种名称
|
||||
- 通过 get_members 的 search 参数可以模糊搜索这三种名称
|
||||
- 找到成员后,使用其 id 字段作为 search_messages 的 sender_id 参数来获取该成员的发言
|
||||
`,
|
||||
timeParamsIntro: '时间参数:按用户提到的精度组合 year/month/day/hour',
|
||||
@@ -301,18 +301,26 @@ export default {
|
||||
timeParamExample3: '"10月1号下午3点" → year: {{year}}, month: 10, day: 1, hour: 15',
|
||||
defaultYearNote: '未指定年份默认{{year}}年,若该月份未到则用{{prevYear}}年',
|
||||
responseInstruction: '根据用户的问题,选择合适的工具获取数据,然后基于数据给出回答。',
|
||||
responseRulesTitle: '回答要求:',
|
||||
fallbackRoleDefinition: {
|
||||
group: `你是一个专业但风格轻松的群聊记录分析助手。
|
||||
你的任务是帮助用户理解和分析他们的群聊记录数据,同时可以适度使用 B 站/网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。`,
|
||||
private: `你是一个专业但风格轻松的私聊记录分析助手。
|
||||
你的任务是帮助用户理解和分析他们的私聊记录数据,同时可以适度使用 B 站/网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。`,
|
||||
},
|
||||
fallbackResponseRules: `1. 基于工具返回的数据回答,不要编造信息
|
||||
你的任务是帮助用户理解和分析他们的群聊记录数据,同时可以适度使用 B 站/网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。
|
||||
|
||||
## 回答要求
|
||||
1. 基于工具返回的数据回答,不要编造信息
|
||||
2. 如果数据不足以回答问题,请说明
|
||||
3. 回答要简洁明了,使用 Markdown 格式
|
||||
4. 可以适度加入 B 站/网络热梗、表情/颜文字(强度适中)
|
||||
5. 玩梗不得影响事实准确与结论清晰,避免低俗或冒犯性表达`,
|
||||
private: `你是一个专业但风格轻松的私聊记录分析助手。
|
||||
你的任务是帮助用户理解和分析他们的私聊记录数据,同时可以适度使用 B 站/网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。
|
||||
|
||||
## 回答要求
|
||||
1. 基于工具返回的数据回答,不要编造信息
|
||||
2. 如果数据不足以回答问题,请说明
|
||||
3. 回答要简洁明了,使用 Markdown 格式
|
||||
4. 可以适度加入 B 站/网络热梗、表情/颜文字(强度适中)
|
||||
5. 玩梗不得影响事实准确与结论清晰,避免低俗或冒犯性表达`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -176,8 +176,7 @@ export interface EmbeddingServiceConfigDisplay {
|
||||
|
||||
// 用户自定义提示词配置
|
||||
export interface PromptConfig {
|
||||
roleDefinition: string
|
||||
responseRules: string
|
||||
systemPrompt: string
|
||||
}
|
||||
|
||||
// ==================== AI API ====================
|
||||
@@ -679,7 +678,6 @@ export interface AssistantConfigFull {
|
||||
id: string
|
||||
name: string
|
||||
systemPrompt: string
|
||||
responseRules?: string
|
||||
presetQuestions: string[]
|
||||
allowedBuiltinTools?: string[]
|
||||
customSqlTools?: unknown[]
|
||||
|
||||
Vendored
+1
-3
@@ -659,8 +659,7 @@ interface ToolContext {
|
||||
|
||||
// 用户自定义提示词配置
|
||||
interface PromptConfig {
|
||||
roleDefinition: string
|
||||
responseRules: string
|
||||
systemPrompt: string
|
||||
}
|
||||
|
||||
interface AgentApi {
|
||||
@@ -694,7 +693,6 @@ interface AssistantConfigFull {
|
||||
id: string
|
||||
name: string
|
||||
systemPrompt: string
|
||||
responseRules?: string
|
||||
presetQuestions: string[]
|
||||
allowedBuiltinTools?: string[]
|
||||
customSqlTools?: unknown[]
|
||||
|
||||
@@ -58,7 +58,6 @@ interface ToolForm {
|
||||
const form = ref({
|
||||
name: '',
|
||||
systemPrompt: '',
|
||||
responseRules: '',
|
||||
presetQuestions: [] as string[],
|
||||
applicableChatType: '' as string,
|
||||
supportedLocales: [] as string[],
|
||||
@@ -146,7 +145,6 @@ function initEmptyForm() {
|
||||
id: '',
|
||||
name: '',
|
||||
systemPrompt: '',
|
||||
responseRules: '',
|
||||
presetQuestions: [],
|
||||
allowedBuiltinTools: [],
|
||||
customSqlTools: [],
|
||||
@@ -158,7 +156,6 @@ function initEmptyForm() {
|
||||
form.value = {
|
||||
name: '',
|
||||
systemPrompt: '',
|
||||
responseRules: '',
|
||||
presetQuestions: [],
|
||||
applicableChatType: '',
|
||||
supportedLocales: [],
|
||||
@@ -177,7 +174,6 @@ async function loadConfig(id: string) {
|
||||
form.value = {
|
||||
name: config.value.name,
|
||||
systemPrompt: config.value.systemPrompt,
|
||||
responseRules: config.value.responseRules || '',
|
||||
presetQuestions: [...config.value.presetQuestions],
|
||||
applicableChatType: config.value.applicableChatTypes?.[0] || '',
|
||||
supportedLocales: [...(config.value.supportedLocales || [])],
|
||||
@@ -202,7 +198,6 @@ async function handleSave() {
|
||||
const payload = {
|
||||
name: form.value.name,
|
||||
systemPrompt: form.value.systemPrompt,
|
||||
responseRules: form.value.responseRules,
|
||||
presetQuestions: [...form.value.presetQuestions],
|
||||
applicableChatTypes: form.value.applicableChatType
|
||||
? ([form.value.applicableChatType] as ('group' | 'private')[])
|
||||
@@ -430,19 +425,6 @@ function closeModal() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('ai.assistant.config.responseRules') }}
|
||||
</label>
|
||||
<UTextarea
|
||||
v-model="form.responseRules"
|
||||
:rows="4"
|
||||
autoresize
|
||||
class="w-full font-mono text-sm"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('ai.assistant.config.chatType') }}
|
||||
|
||||
@@ -61,10 +61,11 @@ function handleConfigure(id: string) {
|
||||
|
||||
<!-- 助手卡片可滚动区域 -->
|
||||
<div class="max-h-[40vh] overflow-y-auto pr-1">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div class="assistant-grid">
|
||||
<AssistantCard
|
||||
v-for="assistant in filteredAssistants"
|
||||
:key="assistant.id"
|
||||
class="assistant-grid-item"
|
||||
:assistant="assistant"
|
||||
@select="handleSelect"
|
||||
@configure="handleConfigure"
|
||||
@@ -85,3 +86,19 @@ function handleConfigure(id: string) {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 使用换行 flex 布局,让不足一整行的卡片(含最后一行剩余项)自动居中 */
|
||||
.assistant-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 单卡宽度在 170~220px 之间收缩,最小窗口下可稳定维持约 3 列 */
|
||||
.assistant-grid-item {
|
||||
min-width: 170px;
|
||||
flex: 0 1 220px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -204,7 +204,7 @@ function formatToolParams(tool: ToolBlockContent): string {
|
||||
return t(`ai.chat.message.toolParams.timeStats.${typeKey}`) || String(params.type)
|
||||
}
|
||||
|
||||
if (name === 'get_group_members') {
|
||||
if (name === 'get_members') {
|
||||
if (params.search) {
|
||||
return `${t('ai.chat.message.toolParams.search')}: ${params.search}`
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import { ref, computed, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { PromptPreset, PresetApplicableType } from '@/types/ai'
|
||||
import {
|
||||
getDefaultRoleDefinition,
|
||||
getDefaultResponseRules,
|
||||
getDefaultSystemPrompt,
|
||||
getLockedPromptSectionPreview,
|
||||
getOriginalBuiltinPreset,
|
||||
type LocaleType,
|
||||
@@ -13,32 +12,26 @@ import { usePromptStore } from '@/stores/prompt'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
mode: 'add' | 'edit'
|
||||
preset: PromptPreset | null
|
||||
}>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
saved: []
|
||||
}>()
|
||||
|
||||
// Store
|
||||
const promptStore = usePromptStore()
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
name: '',
|
||||
roleDefinition: '',
|
||||
responseRules: '',
|
||||
systemPrompt: '',
|
||||
supportGroup: true,
|
||||
supportPrivate: true,
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const isBuiltIn = computed(() => props.preset?.isBuiltIn ?? false)
|
||||
const isEditMode = computed(() => props.mode === 'edit')
|
||||
const isModified = computed(() => {
|
||||
@@ -52,12 +45,9 @@ const modalTitle = computed(() => {
|
||||
})
|
||||
|
||||
const canSave = computed(() => {
|
||||
return formData.value.name.trim() && formData.value.roleDefinition.trim() && formData.value.responseRules.trim()
|
||||
return formData.value.name.trim() && formData.value.systemPrompt.trim()
|
||||
})
|
||||
|
||||
/**
|
||||
* 将 applicableTo 转换为勾选状态
|
||||
*/
|
||||
function applicableToCheckboxes(applicableTo?: PresetApplicableType): { group: boolean; private: boolean } {
|
||||
if (!applicableTo || applicableTo === 'common') {
|
||||
return { group: true, private: true }
|
||||
@@ -68,37 +58,29 @@ function applicableToCheckboxes(applicableTo?: PresetApplicableType): { group: b
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将勾选状态转换为 applicableTo
|
||||
*/
|
||||
function checkboxesToApplicableTo(group: boolean, private_: boolean): PresetApplicableType {
|
||||
if (group && private_) return 'common'
|
||||
if (group) return 'group'
|
||||
if (private_) return 'private'
|
||||
return 'common' // 默认全选
|
||||
return 'common'
|
||||
}
|
||||
|
||||
// 监听打开状态,初始化表单
|
||||
watch(
|
||||
() => props.open,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
if (props.preset) {
|
||||
// 编辑模式:加载现有预设
|
||||
const checkboxes = applicableToCheckboxes(props.preset.applicableTo)
|
||||
formData.value = {
|
||||
name: props.preset.name,
|
||||
roleDefinition: props.preset.roleDefinition,
|
||||
responseRules: props.preset.responseRules,
|
||||
systemPrompt: props.preset.systemPrompt,
|
||||
supportGroup: checkboxes.group,
|
||||
supportPrivate: checkboxes.private,
|
||||
}
|
||||
} else {
|
||||
// 添加模式:重置为默认
|
||||
formData.value = {
|
||||
name: '',
|
||||
roleDefinition: getDefaultRoleDefinition(locale.value as LocaleType),
|
||||
responseRules: getDefaultResponseRules(locale.value as LocaleType),
|
||||
systemPrompt: getDefaultSystemPrompt(locale.value as LocaleType),
|
||||
supportGroup: true,
|
||||
supportPrivate: true,
|
||||
}
|
||||
@@ -107,40 +89,32 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
/** 关闭弹窗 */
|
||||
function closeModal() {
|
||||
emit('update:open', false)
|
||||
}
|
||||
|
||||
/** 保存提示词预设 */
|
||||
function handleSave() {
|
||||
if (!canSave.value) return
|
||||
|
||||
const applicableTo = checkboxesToApplicableTo(formData.value.supportGroup, formData.value.supportPrivate)
|
||||
|
||||
if (isEditMode.value && props.preset) {
|
||||
// 更新现有预设(支持内置和自定义)
|
||||
const updates: {
|
||||
name: string
|
||||
roleDefinition: string
|
||||
responseRules: string
|
||||
systemPrompt: string
|
||||
applicableTo?: PresetApplicableType
|
||||
} = {
|
||||
name: formData.value.name.trim(),
|
||||
roleDefinition: formData.value.roleDefinition.trim(),
|
||||
responseRules: formData.value.responseRules.trim(),
|
||||
systemPrompt: formData.value.systemPrompt.trim(),
|
||||
}
|
||||
// 内置预设不更新 applicableTo
|
||||
if (!isBuiltIn.value) {
|
||||
updates.applicableTo = applicableTo
|
||||
}
|
||||
promptStore.updatePromptPreset(props.preset.id, updates)
|
||||
} else {
|
||||
// 添加新预设
|
||||
promptStore.addPromptPreset({
|
||||
name: formData.value.name.trim(),
|
||||
roleDefinition: formData.value.roleDefinition.trim(),
|
||||
responseRules: formData.value.responseRules.trim(),
|
||||
systemPrompt: formData.value.systemPrompt.trim(),
|
||||
applicableTo,
|
||||
})
|
||||
}
|
||||
@@ -149,38 +123,26 @@ function handleSave() {
|
||||
closeModal()
|
||||
}
|
||||
|
||||
/** 重置内置预设为原始值 */
|
||||
function handleReset() {
|
||||
if (!props.preset || !isBuiltIn.value) return
|
||||
|
||||
const original = getOriginalBuiltinPreset(props.preset.id, locale.value as LocaleType)
|
||||
if (original) {
|
||||
// 重置表单为原始值
|
||||
formData.value = {
|
||||
name: original.name,
|
||||
roleDefinition: original.roleDefinition,
|
||||
responseRules: original.responseRules,
|
||||
systemPrompt: original.systemPrompt,
|
||||
supportGroup: true,
|
||||
supportPrivate: true,
|
||||
}
|
||||
// 清除覆盖
|
||||
promptStore.resetBuiltinPreset(props.preset.id)
|
||||
}
|
||||
}
|
||||
|
||||
// 完整提示词预览(使用群聊模式作为示例)
|
||||
const previewContent = computed(() => {
|
||||
// 获取锁定的系统部分(用于预览,默认使用群聊模式)
|
||||
const lockedSection = getLockedPromptSectionPreview('group', undefined, locale.value as LocaleType)
|
||||
return `${formData.value.systemPrompt}
|
||||
|
||||
// 组合完整提示词
|
||||
const responseRulesLabel = locale.value === 'zh-CN' ? '回答要求:' : 'Response requirements:'
|
||||
return `${formData.value.roleDefinition}
|
||||
|
||||
${lockedSection}
|
||||
|
||||
${responseRulesLabel}
|
||||
${formData.value.responseRules}`
|
||||
${lockedSection}`
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -238,29 +200,15 @@ ${formData.value.responseRules}`
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 角色定义 -->
|
||||
<!-- 系统提示词 -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('settings.aiPrompt.modal.roleDefinition') }}
|
||||
{{ t('settings.aiPrompt.modal.systemPrompt') }}
|
||||
</label>
|
||||
<UTextarea
|
||||
v-model="formData.roleDefinition"
|
||||
:rows="8"
|
||||
:placeholder="t('settings.aiPrompt.modal.roleDefinitionPlaceholder')"
|
||||
class="w-120 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 回答要求 -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('settings.aiPrompt.modal.responseRules') }}
|
||||
<span class="font-normal text-gray-500">{{ t('settings.aiPrompt.modal.responseRulesHint') }}</span>
|
||||
</label>
|
||||
<UTextarea
|
||||
v-model="formData.responseRules"
|
||||
:rows="5"
|
||||
:placeholder="t('settings.aiPrompt.modal.responseRulesPlaceholder')"
|
||||
v-model="formData.systemPrompt"
|
||||
:rows="12"
|
||||
:placeholder="t('settings.aiPrompt.modal.systemPromptPlaceholder')"
|
||||
class="w-120 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
@@ -280,7 +228,6 @@ ${formData.value.responseRules}`
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="mt-6 flex justify-end gap-2">
|
||||
<!-- 内置预设:显示重置按钮 -->
|
||||
<UButton v-if="isBuiltIn && isModified" variant="outline" color="warning" @click="handleReset">
|
||||
<UIcon name="i-heroicons-arrow-path" class="mr-1 h-4 w-4" />
|
||||
{{ t('settings.aiPrompt.modal.resetToDefault') }}
|
||||
|
||||
@@ -78,8 +78,7 @@ async function loadRemotePresets() {
|
||||
async function handlePreview(preset: RemotePresetData) {
|
||||
previewError.value = ''
|
||||
|
||||
// 如果已有内容,直接显示
|
||||
if (preset.roleDefinition && preset.responseRules) {
|
||||
if (preset.systemPrompt) {
|
||||
previewPreset.value = preset
|
||||
return
|
||||
}
|
||||
@@ -112,9 +111,8 @@ function closePreview() {
|
||||
async function handleAddPreset(preset: RemotePresetData) {
|
||||
addingPresetId.value = preset.id
|
||||
|
||||
// 如果还没有内容,先下载
|
||||
let fullPreset = preset
|
||||
if (!preset.roleDefinition || !preset.responseRules) {
|
||||
if (!preset.systemPrompt) {
|
||||
const fetched = await promptStore.fetchPresetContent(preset)
|
||||
if (!fetched) {
|
||||
addingPresetId.value = null
|
||||
@@ -282,24 +280,14 @@ watch(
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div v-else-if="previewPreset?.roleDefinition" class="max-h-[60vh] overflow-y-auto space-y-4">
|
||||
<div v-else-if="previewPreset?.systemPrompt" class="max-h-[60vh] overflow-y-auto space-y-4">
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-semibold text-gray-700 dark:text-gray-300">
|
||||
{{ t('settings.aiPrompt.importPreset.roleDefinition') }}
|
||||
{{ t('settings.aiPrompt.importPreset.systemPrompt') }}
|
||||
</p>
|
||||
<div class="rounded-lg bg-gray-50 p-3 dark:bg-gray-800">
|
||||
<p class="whitespace-pre-wrap text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ previewPreset.roleDefinition }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-semibold text-gray-700 dark:text-gray-300">
|
||||
{{ t('settings.aiPrompt.importPreset.responseRules') }}
|
||||
</p>
|
||||
<div class="rounded-lg bg-gray-50 p-3 dark:bg-gray-800">
|
||||
<p class="whitespace-pre-wrap text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ previewPreset.responseRules }}
|
||||
{{ previewPreset.systemPrompt }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -97,12 +97,8 @@ export function useAIChat(
|
||||
const assistantStore = useAssistantStore()
|
||||
const { activePreset, aiGlobalSettings } = storeToRefs(promptStore)
|
||||
|
||||
// 获取当前聊天类型对应的提示词配置(使用统一的激活预设)
|
||||
const currentPromptConfig = computed(() => {
|
||||
return {
|
||||
roleDefinition: activePreset.value.roleDefinition,
|
||||
responseRules: activePreset.value.responseRules,
|
||||
}
|
||||
return { systemPrompt: activePreset.value.systemPrompt }
|
||||
})
|
||||
|
||||
// 状态
|
||||
@@ -540,10 +536,7 @@ export function useAIChat(
|
||||
chatType,
|
||||
currentAssistantId
|
||||
? undefined
|
||||
: {
|
||||
roleDefinition: currentPromptConfig.value.roleDefinition,
|
||||
responseRules: currentPromptConfig.value.responseRules,
|
||||
},
|
||||
: { systemPrompt: currentPromptConfig.value.systemPrompt },
|
||||
locale,
|
||||
maxHistoryRounds,
|
||||
currentAssistantId
|
||||
|
||||
+32
-42
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* 本文件集中管理所有 AI 提示词相关的配置:
|
||||
* - 内置预设定义(统一版本,不再区分群聊/私聊)
|
||||
* - 默认角色定义/回答要求
|
||||
* - 默认系统提示词
|
||||
* - 锁定部分说明(用于前端预览)
|
||||
*
|
||||
* 注意:群聊/私聊的差异化内容(如成员查询策略)由后端 agent.ts 根据运行时 chatType 自动处理。
|
||||
@@ -20,11 +20,11 @@ export type LocaleType = 'zh-CN' | 'en-US'
|
||||
const i18nContent = {
|
||||
'zh-CN': {
|
||||
presetName: '默认分析助手',
|
||||
// 默认角色定义:适中幽默,允许 B 站/网络热梗与表情
|
||||
roleDefinition: `你是一个专业但风格轻松的聊天记录分析助手。
|
||||
你的任务是帮助用户理解和分析他们的聊天记录数据,同时可以适度使用 B 站/网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。`,
|
||||
// 默认回答要求:强调严谨优先,适度玩梗
|
||||
responseRules: `1. 基于工具返回的数据回答,不要编造信息
|
||||
systemPrompt: `你是一个专业但风格轻松的聊天记录分析助手。
|
||||
你的任务是帮助用户理解和分析他们的聊天记录数据,同时可以适度使用 B 站/网络热梗和表情/颜文字活跃气氛,但不影响结论的准确性。
|
||||
|
||||
## 回答要求
|
||||
1. 基于工具返回的数据回答,不要编造信息
|
||||
2. 如果数据不足以回答问题,请说明
|
||||
3. 回答要简洁明了,使用 Markdown 格式
|
||||
4. 可以引用具体的发言作为证据
|
||||
@@ -44,13 +44,13 @@ const i18nContent = {
|
||||
`,
|
||||
memberNote: {
|
||||
group: `成员查询策略:
|
||||
- 当用户提到特定群成员(如"张三说过什么"、"小明的发言"等)时,应先调用 get_group_members 获取成员列表
|
||||
- 当用户提到特定群成员(如"张三说过什么"、"小明的发言"等)时,应先调用 get_members 获取成员列表
|
||||
- 群成员有三种名称:accountName(原始昵称)、groupNickname(群昵称)、aliases(用户自定义别名)
|
||||
- 通过 get_group_members 的 search 参数可以模糊搜索这三种名称
|
||||
- 通过 get_members 的 search 参数可以模糊搜索这三种名称
|
||||
- 找到成员后,使用其 id 字段作为 search_messages 的 sender_id 参数来获取该成员的发言`,
|
||||
private: `成员查询策略:
|
||||
- 私聊只有两个人,可以直接获取成员列表
|
||||
- 当用户提到"对方"、"他/她"时,通过 get_group_members 获取另一方信息`,
|
||||
- 当用户提到"对方"、"他/她"时,通过 get_members 获取另一方信息`,
|
||||
},
|
||||
currentDatePrefix: '当前日期是',
|
||||
timeParamsTemplate: (year: number, prevYear: number) =>
|
||||
@@ -60,14 +60,15 @@ const i18nContent = {
|
||||
- "10月1号下午3点" → year: ${year}, month: 10, day: 1, hour: 15
|
||||
未指定年份默认${year}年,若该月份未到则用${prevYear}年`,
|
||||
conclusion: '根据用户的问题,选择合适的工具获取数据,然后基于数据给出回答。',
|
||||
responseRulesLabel: '回答要求:',
|
||||
},
|
||||
},
|
||||
'en-US': {
|
||||
presetName: 'Default Analysis Assistant',
|
||||
roleDefinition: `You are a professional chat analysis assistant.
|
||||
Your task is to help users understand and analyze their chat records.`,
|
||||
responseRules: `1. Answer based on data returned by tools, do not fabricate information
|
||||
systemPrompt: `You are a professional chat analysis assistant.
|
||||
Your task is to help users understand and analyze their chat records.
|
||||
|
||||
## Response Requirements
|
||||
1. Answer based on data returned by tools, do not fabricate information
|
||||
2. If data is insufficient to answer the question, explain
|
||||
3. Keep answers concise and clear, use Markdown format
|
||||
4. Quote specific messages as evidence when possible
|
||||
@@ -85,13 +86,13 @@ Your task is to help users understand and analyze their chat records.`,
|
||||
`,
|
||||
memberNote: {
|
||||
group: `Member query strategy:
|
||||
- When the user mentions a specific group member (e.g., "what did John say", "Mary's messages"), first call get_group_members to get the member list
|
||||
- When the user mentions a specific group member (e.g., "what did John say", "Mary's messages"), first call get_members to get the member list
|
||||
- Group members have three name types: accountName (original nickname), groupNickname (group nickname), aliases (user-defined aliases)
|
||||
- Use the search parameter of get_group_members to fuzzy search across all three name types
|
||||
- Use the search parameter of get_members to fuzzy search across all three name types
|
||||
- After finding the member, use their id field as the sender_id parameter for search_messages to get their messages`,
|
||||
private: `Member query strategy:
|
||||
- Private chats have only two people, you can directly get the member list
|
||||
- When the user mentions "the other person" or "he/she", use get_group_members to get the other party's information`,
|
||||
- When the user mentions "the other person" or "he/she", use get_members to get the other party's information`,
|
||||
},
|
||||
currentDatePrefix: 'The current date is',
|
||||
timeParamsTemplate: (year: number, prevYear: number) =>
|
||||
@@ -102,7 +103,6 @@ Your task is to help users understand and analyze their chat records.`,
|
||||
Default to ${year} if year not specified, use ${prevYear} if the month hasn't arrived yet`,
|
||||
conclusion:
|
||||
"Based on the user's question, select appropriate tools to retrieve data, then provide an answer based on the data.",
|
||||
responseRulesLabel: 'Response requirements:',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -120,21 +120,22 @@ export const DEFAULT_PRIVATE_PRESET_ID = DEFAULT_PRESET_ID
|
||||
// ==================== 默认提示词内容 ====================
|
||||
|
||||
/**
|
||||
* 获取默认角色定义
|
||||
* 获取默认系统提示词
|
||||
* @param locale 语言设置
|
||||
*/
|
||||
export function getDefaultRoleDefinition(locale: LocaleType = 'zh-CN'): string {
|
||||
export function getDefaultSystemPrompt(locale: LocaleType = 'zh-CN'): string {
|
||||
const content = i18nContent[locale] || i18nContent['zh-CN']
|
||||
return content.roleDefinition
|
||||
return content.systemPrompt
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认回答要求
|
||||
* @param locale 语言设置
|
||||
*/
|
||||
/** @deprecated 使用 getDefaultSystemPrompt 代替 */
|
||||
export function getDefaultRoleDefinition(locale: LocaleType = 'zh-CN'): string {
|
||||
return getDefaultSystemPrompt(locale)
|
||||
}
|
||||
|
||||
/** @deprecated responseRules 已合并到 systemPrompt */
|
||||
export function getDefaultResponseRules(locale: LocaleType = 'zh-CN'): string {
|
||||
const content = i18nContent[locale] || i18nContent['zh-CN']
|
||||
return content.responseRules
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,8 +159,7 @@ export function getBuiltinPresets(locale: LocaleType = 'zh-CN'): PromptPreset[]
|
||||
const BUILTIN_DEFAULT: PromptPreset = {
|
||||
id: DEFAULT_PRESET_ID,
|
||||
name: getBuiltinPresetName(locale),
|
||||
roleDefinition: getDefaultRoleDefinition(locale),
|
||||
responseRules: getDefaultResponseRules(locale),
|
||||
systemPrompt: getDefaultSystemPrompt(locale),
|
||||
isBuiltIn: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
@@ -204,7 +204,6 @@ export function getLockedPromptSectionPreview(
|
||||
const content = i18nContent[locale] || i18nContent['zh-CN']
|
||||
const now = new Date()
|
||||
|
||||
// 根据语言格式化日期
|
||||
const dateLocale = locale === 'zh-CN' ? 'zh-CN' : 'en-US'
|
||||
const currentDate = now.toLocaleDateString(dateLocale, {
|
||||
year: 'numeric',
|
||||
@@ -214,10 +213,7 @@ export function getLockedPromptSectionPreview(
|
||||
})
|
||||
|
||||
const chatContext = content.lockedSection.chatContext[chatType]
|
||||
|
||||
// Owner 说明(当用户设置了"我是谁"时)
|
||||
const ownerNote = ownerInfo ? content.lockedSection.ownerNoteTemplate(ownerInfo.displayName, chatContext) : ''
|
||||
|
||||
const memberNote = content.lockedSection.memberNote[chatType]
|
||||
const year = now.getFullYear()
|
||||
const prevYear = year - 1
|
||||
@@ -233,26 +229,20 @@ ${content.lockedSection.conclusion}`
|
||||
|
||||
/**
|
||||
* 构建完整提示词预览(用于前端展示)
|
||||
* @param roleDefinition 角色定义
|
||||
* @param responseRules 回答要求
|
||||
* @param systemPrompt 系统提示词
|
||||
* @param chatType 聊天类型(用于展示对应的锁定部分)
|
||||
* @param ownerInfo Owner 信息(可选)
|
||||
* @param locale 语言设置
|
||||
*/
|
||||
export function buildPromptPreview(
|
||||
roleDefinition: string,
|
||||
responseRules: string,
|
||||
systemPrompt: string,
|
||||
chatType: 'group' | 'private' = 'group',
|
||||
ownerInfo?: OwnerInfoPreview,
|
||||
locale: LocaleType = 'zh-CN'
|
||||
): string {
|
||||
const content = i18nContent[locale] || i18nContent['zh-CN']
|
||||
const lockedSection = getLockedPromptSectionPreview(chatType, ownerInfo, locale)
|
||||
|
||||
return `${roleDefinition}
|
||||
return `${systemPrompt}
|
||||
|
||||
${lockedSection}
|
||||
|
||||
${content.lockedSection.responseRulesLabel}
|
||||
${responseRules}`
|
||||
${lockedSection}`
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"get_recent_messages": "Get Recent Messages",
|
||||
"get_member_stats": "Get Member Stats",
|
||||
"get_time_stats": "Get Time Stats",
|
||||
"get_group_members": "Get Members",
|
||||
"get_members": "Get Members",
|
||||
"get_member_name_history": "Get Nickname History",
|
||||
"get_conversation_between": "Get Conversation",
|
||||
"get_message_context": "Get Message Context",
|
||||
@@ -214,7 +214,6 @@
|
||||
},
|
||||
"name": "Name",
|
||||
"systemPrompt": "System Prompt",
|
||||
"responseRules": "Response Rules",
|
||||
"chatType": "Chat Type",
|
||||
"chatTypeAll": "All",
|
||||
"chatTypeGroup": "Group",
|
||||
@@ -286,7 +285,7 @@
|
||||
"get_recent_messages": "Get recent messages",
|
||||
"get_message_context": "Get message context",
|
||||
"get_conversation_between": "Get conversation between two members",
|
||||
"get_group_members": "Get group members",
|
||||
"get_members": "Get group members",
|
||||
"get_member_stats": "Get member statistics",
|
||||
"get_member_name_history": "Get member name history",
|
||||
"get_time_stats": "Get time statistics",
|
||||
|
||||
@@ -227,11 +227,8 @@
|
||||
"applicableToHint": " (Check to enable for corresponding analysis type)",
|
||||
"groupChat": "Group Chat",
|
||||
"privateChat": "Private Chat",
|
||||
"roleDefinition": "Role Definition",
|
||||
"roleDefinitionPlaceholder": "Define the AI assistant's role and tasks...",
|
||||
"responseRules": "Response Rules",
|
||||
"responseRulesHint": " (Guide how AI should respond)",
|
||||
"responseRulesPlaceholder": "Define AI response format and requirements...",
|
||||
"systemPrompt": "System Prompt",
|
||||
"systemPromptPlaceholder": "Define the AI assistant's role, tasks and response requirements...",
|
||||
"preview": "Full Prompt Preview",
|
||||
"previewHint": " (Preview shows group chat mode, actual will adjust based on analysis type)",
|
||||
"resetToDefault": "Reset to Default",
|
||||
@@ -250,8 +247,7 @@
|
||||
"add": "Add",
|
||||
"added": "Added",
|
||||
"preview": "Preview",
|
||||
"roleDefinition": "Role Definition",
|
||||
"responseRules": "Response Rules",
|
||||
"systemPrompt": "System Prompt",
|
||||
"noDescription": "No description",
|
||||
"fetchingContent": "Loading content...",
|
||||
"fetchError": "Failed to load content"
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"get_recent_messages": "获取最近消息",
|
||||
"get_member_stats": "获取成员统计",
|
||||
"get_time_stats": "获取时间分布",
|
||||
"get_group_members": "获取成员列表",
|
||||
"get_members": "获取成员列表",
|
||||
"get_member_name_history": "获取昵称历史",
|
||||
"get_conversation_between": "获取对话记录",
|
||||
"get_message_context": "获取上下文",
|
||||
@@ -214,7 +214,6 @@
|
||||
},
|
||||
"name": "名称",
|
||||
"systemPrompt": "系统提示词",
|
||||
"responseRules": "回答要求",
|
||||
"chatType": "适用聊天类型",
|
||||
"chatTypeAll": "全部",
|
||||
"chatTypeGroup": "群聊",
|
||||
@@ -286,7 +285,7 @@
|
||||
"get_recent_messages": "获取最近消息",
|
||||
"get_message_context": "获取消息上下文",
|
||||
"get_conversation_between": "获取两人对话",
|
||||
"get_group_members": "获取群成员列表",
|
||||
"get_members": "获取群成员列表",
|
||||
"get_member_stats": "获取成员统计",
|
||||
"get_member_name_history": "获取成员改名历史",
|
||||
"get_time_stats": "获取时间统计",
|
||||
|
||||
@@ -227,11 +227,8 @@
|
||||
"applicableToHint": "(勾选后可在对应分析类型中使用)",
|
||||
"groupChat": "群聊分析",
|
||||
"privateChat": "私聊分析",
|
||||
"roleDefinition": "角色定义",
|
||||
"roleDefinitionPlaceholder": "定义 AI 助手的角色和任务...",
|
||||
"responseRules": "回答要求",
|
||||
"responseRulesHint": "(指导 AI 如何回答)",
|
||||
"responseRulesPlaceholder": "定义 AI 回答的格式和要求...",
|
||||
"systemPrompt": "系统提示词",
|
||||
"systemPromptPlaceholder": "定义 AI 助手的角色、任务和回答要求...",
|
||||
"preview": "完整提示词预览",
|
||||
"previewHint": "(预览为群聊模式,实际会根据分析类型自动调整)",
|
||||
"resetToDefault": "重置为默认",
|
||||
@@ -250,8 +247,7 @@
|
||||
"add": "添加",
|
||||
"added": "已添加",
|
||||
"preview": "预览",
|
||||
"roleDefinition": "角色定义",
|
||||
"responseRules": "回复规则",
|
||||
"systemPrompt": "系统提示词",
|
||||
"noDescription": "暂无描述",
|
||||
"fetchingContent": "正在加载内容...",
|
||||
"fetchError": "加载内容失败"
|
||||
|
||||
@@ -22,7 +22,6 @@ export interface AssistantConfigFull {
|
||||
id: string
|
||||
name: string
|
||||
systemPrompt: string
|
||||
responseRules?: string
|
||||
presetQuestions: string[]
|
||||
allowedBuiltinTools?: string[]
|
||||
customSqlTools?: unknown[]
|
||||
|
||||
+44
-54
@@ -18,10 +18,8 @@ export interface RemotePresetData {
|
||||
path: string
|
||||
/** 简短描述(索引中提供,用于列表展示) */
|
||||
description?: string
|
||||
/** 角色定义(从 Markdown 文件解析后填充) */
|
||||
roleDefinition?: string
|
||||
/** 回复规则(从 Markdown 文件解析后填充) */
|
||||
responseRules?: string
|
||||
/** 系统提示词(从 Markdown 文件解析后填充) */
|
||||
systemPrompt?: string
|
||||
/** 适用场景:common(通用)、group(仅群聊)、private(仅私聊) */
|
||||
chatType?: 'common' | 'group' | 'private'
|
||||
}
|
||||
@@ -38,7 +36,7 @@ export const usePromptStore = defineStore(
|
||||
|
||||
const customPromptPresets = ref<PromptPreset[]>([])
|
||||
const builtinPresetOverrides = ref<
|
||||
Record<string, { name?: string; roleDefinition?: string; responseRules?: string; updatedAt?: number }>
|
||||
Record<string, { name?: string; systemPrompt?: string; updatedAt?: number }>
|
||||
>({})
|
||||
const aiPromptSettings = ref<AIPromptSettings>({
|
||||
activePresetId: DEFAULT_PRESET_ID,
|
||||
@@ -157,15 +155,13 @@ export const usePromptStore = defineStore(
|
||||
*/
|
||||
function addPromptPreset(preset: {
|
||||
name: string
|
||||
roleDefinition: string
|
||||
responseRules: string
|
||||
systemPrompt: string
|
||||
applicableTo?: 'common' | 'group' | 'private'
|
||||
}) {
|
||||
const newPreset: PromptPreset = {
|
||||
id: `custom-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
name: preset.name,
|
||||
roleDefinition: preset.roleDefinition,
|
||||
responseRules: preset.responseRules,
|
||||
systemPrompt: preset.systemPrompt,
|
||||
isBuiltIn: false,
|
||||
applicableTo: preset.applicableTo || 'common',
|
||||
createdAt: Date.now(),
|
||||
@@ -182,8 +178,7 @@ export const usePromptStore = defineStore(
|
||||
presetId: string,
|
||||
updates: {
|
||||
name?: string
|
||||
roleDefinition?: string
|
||||
responseRules?: string
|
||||
systemPrompt?: string
|
||||
applicableTo?: 'common' | 'group' | 'private'
|
||||
}
|
||||
) {
|
||||
@@ -192,8 +187,7 @@ export const usePromptStore = defineStore(
|
||||
builtinPresetOverrides.value[presetId] = {
|
||||
...builtinPresetOverrides.value[presetId],
|
||||
name: updates.name,
|
||||
roleDefinition: updates.roleDefinition,
|
||||
responseRules: updates.responseRules,
|
||||
systemPrompt: updates.systemPrompt,
|
||||
updatedAt: Date.now(),
|
||||
}
|
||||
return
|
||||
@@ -254,8 +248,7 @@ export const usePromptStore = defineStore(
|
||||
const copySuffix = locale.value === 'zh-CN' ? '(副本)' : '(Copy)'
|
||||
return addPromptPreset({
|
||||
name: `${source.name} ${copySuffix}`,
|
||||
roleDefinition: source.roleDefinition,
|
||||
responseRules: source.responseRules,
|
||||
systemPrompt: source.systemPrompt,
|
||||
})
|
||||
}
|
||||
return null
|
||||
@@ -281,27 +274,11 @@ export const usePromptStore = defineStore(
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Markdown 文件内容,使用 `---` 分隔 roleDefinition 和 responseRules
|
||||
* @param content Markdown 文件内容
|
||||
* @returns { roleDefinition, responseRules }
|
||||
* 解析 Markdown 文件内容为完整的系统提示词
|
||||
* 旧格式使用 `---` 分隔角色定义和回答要求,现统一为单一字段
|
||||
*/
|
||||
function parseMarkdownContent(content: string): { roleDefinition: string; responseRules: string } {
|
||||
// 使用 `---` 独立成行作为分隔符
|
||||
const separator = /\n---\n/
|
||||
const parts = content.split(separator)
|
||||
|
||||
if (parts.length >= 2) {
|
||||
return {
|
||||
roleDefinition: parts[0].trim(),
|
||||
responseRules: parts.slice(1).join('\n---\n').trim(),
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有分隔符,整个内容作为 roleDefinition
|
||||
return {
|
||||
roleDefinition: content.trim(),
|
||||
responseRules: '',
|
||||
}
|
||||
function parseMarkdownContent(content: string): { systemPrompt: string } {
|
||||
return { systemPrompt: content.trim() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,10 +315,9 @@ export const usePromptStore = defineStore(
|
||||
*/
|
||||
async function fetchPresetContent(
|
||||
preset: RemotePresetData
|
||||
): Promise<(RemotePresetData & { roleDefinition: string; responseRules: string }) | null> {
|
||||
// 如果已经有内容,直接返回
|
||||
if (preset.roleDefinition && preset.responseRules) {
|
||||
return preset as RemotePresetData & { roleDefinition: string; responseRules: string }
|
||||
): Promise<(RemotePresetData & { systemPrompt: string }) | null> {
|
||||
if (preset.systemPrompt) {
|
||||
return preset as RemotePresetData & { systemPrompt: string }
|
||||
}
|
||||
|
||||
const mdUrl = `${REMOTE_PRESET_BASE_URL}${preset.path}`
|
||||
@@ -351,16 +327,12 @@ export const usePromptStore = defineStore(
|
||||
return null
|
||||
}
|
||||
|
||||
const { roleDefinition, responseRules } = parseMarkdownContent(mdResult.data)
|
||||
if (!roleDefinition || !responseRules) {
|
||||
const { systemPrompt } = parseMarkdownContent(mdResult.data)
|
||||
if (!systemPrompt) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...preset,
|
||||
roleDefinition,
|
||||
responseRules,
|
||||
}
|
||||
return { ...preset, systemPrompt }
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
@@ -372,20 +344,17 @@ export const usePromptStore = defineStore(
|
||||
* @returns 是否添加成功
|
||||
*/
|
||||
function addRemotePreset(preset: RemotePresetData): boolean {
|
||||
// 检查是否已添加
|
||||
if (fetchedRemotePresetIds.value.includes(preset.id)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
// 将远程 chatType 映射为本地 applicableTo
|
||||
const applicableTo = preset.chatType || 'common'
|
||||
|
||||
const newPreset: PromptPreset = {
|
||||
id: preset.id,
|
||||
name: preset.name,
|
||||
roleDefinition: preset.roleDefinition || '',
|
||||
responseRules: preset.responseRules || '',
|
||||
systemPrompt: preset.systemPrompt || '',
|
||||
isBuiltIn: false,
|
||||
applicableTo,
|
||||
createdAt: now,
|
||||
@@ -421,26 +390,47 @@ export const usePromptStore = defineStore(
|
||||
|
||||
// 如果存在旧字段,进行迁移
|
||||
if (oldSettings.activeGroupPresetId && !oldSettings.activePresetId) {
|
||||
// 优先使用群聊预设,因为使用频率更高
|
||||
const oldGroupId = oldSettings.activeGroupPresetId
|
||||
// 如果是旧的内置预设 ID,映射到新的统一 ID
|
||||
if (oldGroupId === 'builtin-group-default' || oldGroupId === 'builtin-private-default') {
|
||||
aiPromptSettings.value.activePresetId = DEFAULT_PRESET_ID
|
||||
} else {
|
||||
aiPromptSettings.value.activePresetId = oldGroupId
|
||||
}
|
||||
// 清理旧字段
|
||||
delete (aiPromptSettings.value as Record<string, unknown>).activeGroupPresetId
|
||||
delete (aiPromptSettings.value as Record<string, unknown>).activePrivatePresetId
|
||||
}
|
||||
|
||||
// 迁移自定义预设中的 chatType 字段
|
||||
for (const preset of customPromptPresets.value) {
|
||||
const oldPreset = preset as PromptPreset & { chatType?: string }
|
||||
if (oldPreset.chatType) {
|
||||
delete oldPreset.chatType
|
||||
}
|
||||
}
|
||||
|
||||
// 迁移旧 roleDefinition + responseRules → systemPrompt
|
||||
for (const preset of customPromptPresets.value) {
|
||||
const legacy = preset as unknown as { roleDefinition?: string; responseRules?: string; systemPrompt?: string }
|
||||
if (legacy.roleDefinition && !legacy.systemPrompt) {
|
||||
preset.systemPrompt = legacy.responseRules
|
||||
? `${legacy.roleDefinition}\n\n## 回答要求\n${legacy.responseRules}`
|
||||
: legacy.roleDefinition
|
||||
delete (preset as Record<string, unknown>).roleDefinition
|
||||
delete (preset as Record<string, unknown>).responseRules
|
||||
}
|
||||
}
|
||||
|
||||
// 迁移 builtinPresetOverrides 中的旧字段
|
||||
for (const [id, override] of Object.entries(builtinPresetOverrides.value)) {
|
||||
const legacy = override as unknown as { roleDefinition?: string; responseRules?: string; systemPrompt?: string }
|
||||
if (legacy.roleDefinition && !legacy.systemPrompt) {
|
||||
override.systemPrompt = legacy.responseRules
|
||||
? `${legacy.roleDefinition}\n\n## 回答要求\n${legacy.responseRules}`
|
||||
: legacy.roleDefinition
|
||||
delete (override as Record<string, unknown>).roleDefinition
|
||||
delete (override as Record<string, unknown>).responseRules
|
||||
builtinPresetOverrides.value[id] = override
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化时执行迁移
|
||||
|
||||
+1
-2
@@ -26,8 +26,7 @@ export type PresetApplicableType = 'group' | 'private' | 'common'
|
||||
export interface PromptPreset {
|
||||
id: string
|
||||
name: string // 预设名称
|
||||
roleDefinition: string // 角色定义(可编辑)
|
||||
responseRules: string // 回答要求(可编辑)
|
||||
systemPrompt: string // 系统提示词(角色定义 + 回答要求,统一为单一字段)
|
||||
isBuiltIn: boolean // 是否内置(内置不可删除)
|
||||
applicableTo?: PresetApplicableType // 适用场景,默认 'common'
|
||||
createdAt: number
|
||||
|
||||
Reference in New Issue
Block a user