feat: 优化助手逻辑

This commit is contained in:
digua
2026-03-10 21:35:53 +08:00
parent 60be78b767
commit 6edb3e3b95
33 changed files with 246 additions and 333 deletions
+1 -5
View File
@@ -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)
+5 -11
View File
@@ -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}`
}
+2 -4
View File
@@ -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": [
"最近大家都在聊什么?",
"谁是群里最活跃的人?",
+28 -2
View File
@@ -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 ====================
/**
+1 -3
View File
@@ -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',
+2 -2
View File
@@ -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'],
+2 -2
View File
@@ -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,
+24 -18
View File
@@ -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`,
},
},
},
+26 -18
View File
@@ -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. 玩梗不得影响事实准确与结论清晰,避免低俗或冒犯性表达`,
},
},
},
+1 -3
View File
@@ -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[]
+1 -3
View File
@@ -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>
+2 -9
View File
@@ -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
View File
@@ -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}`
}
+2 -3
View File
@@ -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",
+3 -7
View File
@@ -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"
+2 -3
View File
@@ -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": "获取时间统计",
+3 -7
View File
@@ -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": "加载内容失败"
-1
View File
@@ -22,7 +22,6 @@ export interface AssistantConfigFull {
id: string
name: string
systemPrompt: string
responseRules?: string
presetQuestions: string[]
allowedBuiltinTools?: string[]
customSqlTools?: unknown[]
+44 -54
View File
@@ -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
View File
@@ -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