diff --git a/src/components/ai/ChatInput.vue b/src/components/ai/ChatInput.vue index 42bc265..9e779e1 100644 --- a/src/components/ai/ChatInput.vue +++ b/src/components/ai/ChatInput.vue @@ -119,7 +119,7 @@ import { ref, computed, nextTick, watch } from 'vue' import { useI18n } from 'vue-i18n' import { useAiStore } from '../../stores/ai' import { useProjectStore } from '../../stores/project' -import type { AiMessage, ContentPart, SkillInfo, ProjectRecord, TaskRecord } from '../../api/types' +import type { AiMessage, ContentPart, SkillInfo, ProjectRecord, TaskRecord, IdeaRecord } from '../../api/types' const props = defineProps<{ /** UX-09: 编辑末条 user 消息态;父持有,handleSend 据此走 editMessage 而非 sendMessage */ @@ -324,7 +324,7 @@ function clearSkill() { // 触发:输入框光标前最近的 @ 后无空白/换行 → 激活;Esc/光标移开/选中插入 → 关闭。 // Enter 不冲突:popover 开时 Enter 插入选中(覆盖发送),关时 Enter 发送(原逻辑)。 interface MentionItem { - type: 'project' | 'task' + type: 'project' | 'task' | 'idea' /** 扁平后唯一 key(用于 key/导航扁平索引定位) */ uid: string /** 实体原始 id(供后端解析,目前仅插入 [类型:名] 文本,id 未透传) */ @@ -342,6 +342,7 @@ const mentionStart = ref(-1) const mentionGroupLabel = computed(() => ({ project: t('aiChat.mentionGroupProject'), task: t('aiChat.mentionGroupTask'), + idea: t('aiChat.mentionGroupIdea'), })) /** 当前 @ 查询文本(@ 之后的串,不含 @) */ @@ -378,19 +379,33 @@ const mentionItems = computed(() => { desc: tk.description || '', typeLabel: t('aiChat.mentionGroupTask'), })) - return [...projectItems, ...taskItems] + const ideas = (projectsStore.ideas || []) as IdeaRecord[] + const ideaItems: MentionItem[] = ideas + .filter(it => match(it.title) || match(it.description || '')) + .slice(0, 20) + .map(it => ({ + type: 'idea' as const, + uid: 'i-' + it.id, + id: it.id, + name: it.title, + desc: it.description || '', + typeLabel: t('aiChat.mentionGroupIdea'), + })) + return [...projectItems, ...taskItems, ...ideaItems] }) /** 联想候选总数(未过滤,用于区分"无匹配" vs "空") */ const mentionTotal = computed(() => ((projectsStore.projects || []) as ProjectRecord[]).length - + ((projectsStore.tasks || []) as TaskRecord[]).length, + + ((projectsStore.tasks || []) as TaskRecord[]).length + + ((projectsStore.ideas || []) as IdeaRecord[]).length, ) /** 按类型分组(模板按组渲染组标题) */ const mentionGroups = computed(() => [ { key: 'project', label: mentionGroupLabel.value.project, items: mentionItems.value.filter(i => i.type === 'project') }, { key: 'task', label: mentionGroupLabel.value.task, items: mentionItems.value.filter(i => i.type === 'task') }, + { key: 'idea', label: mentionGroupLabel.value.idea, items: mentionItems.value.filter(i => i.type === 'idea') }, ]) /** 扁平下标(组内 item 在扁平列表中的位置,供键盘导航 ↑↓/高亮) */ @@ -405,7 +420,9 @@ function selectMention(item: MentionItem) { const after = inputText.value.slice(cursorAfter) const label = item.type === 'project' ? `[${t('aiChat.mentionGroupProject')}: ${item.name}]` - : `[${t('aiChat.mentionGroupTask')}: ${item.name}]` + : item.type === 'task' + ? `[${t('aiChat.mentionGroupTask')}: ${item.name}]` + : `[${t('aiChat.mentionGroupIdea')}: ${item.name}]` inputText.value = before + label + ' ' + after mentionOpen.value = false mentionStart.value = -1 diff --git a/src/i18n/en/aiChat.ts b/src/i18n/en/aiChat.ts index 6caf7b9..811c327 100644 --- a/src/i18n/en/aiChat.ts +++ b/src/i18n/en/aiChat.ts @@ -90,6 +90,7 @@ export default { mentionHint: "{'@'} to mention project/task", mentionGroupProject: 'Projects', mentionGroupTask: 'Tasks', + mentionGroupIdea: 'Ideas', mentionNoMatch: 'No matching entity', mentionEmpty: 'No project/task to mention', diff --git a/src/i18n/zh-CN/aiChat.ts b/src/i18n/zh-CN/aiChat.ts index d913ff5..802a648 100644 --- a/src/i18n/zh-CN/aiChat.ts +++ b/src/i18n/zh-CN/aiChat.ts @@ -90,6 +90,7 @@ export default { mentionHint: "{'@'} 引用项目/任务", mentionGroupProject: '项目', mentionGroupTask: '任务', + mentionGroupIdea: '灵感', mentionNoMatch: '无匹配实体', mentionEmpty: '暂无可引用的项目/任务',