新增: Phase2 阶段收尾(Sprint 1-20)
重构:删 5 零引用 crate(df-evolve/plugin/stages/task/traceability)+ 清死模块、ai.rs 拆 11 子 module、ai.ts 拆 6 composable、i18n 拆目录 功能:知识库全栈(df-project/scan + CRUD + 时间线 + 前端)、Settings 拆分、appSettings KV 迁移、模型池、LLM 并发 Semaphore 修复:审批持久化根治、ConditionEngine 默认拒绝、NodeRegistry unimplemented 清除、promote 补偿删除、工具结果截断 50KB、路径校验防 symlink 逃逸 文档:B-03 人工审批设计、决策记录三分档、规格契约自检、经验记录、todo 看板、PROGRESS 更新 详见 PROGRESS.md。src-tauri/儿童每日打卡应用/ 与本项目无关,已排除。
This commit is contained in:
@@ -1,79 +1,156 @@
|
||||
import { reactive, computed } from 'vue'
|
||||
import { knowledgeApi } from '../api'
|
||||
import type {
|
||||
KnowledgeRecord,
|
||||
KnowledgeDetailPayload,
|
||||
KnowledgeEventRecord,
|
||||
CreateKnowledgeInput,
|
||||
UpdateKnowledgeInput,
|
||||
KnowledgeConfig,
|
||||
} from '../api/types'
|
||||
|
||||
export interface KnowledgeItem {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
category: string
|
||||
tags: string[]
|
||||
reuseCount: number
|
||||
score: number
|
||||
updatedAt: string
|
||||
}
|
||||
// ── 全局响应式状态(单例) ──
|
||||
|
||||
const state = reactive({
|
||||
items: [
|
||||
// 审查规则
|
||||
{ id: 1, title: 'SQL 注入防护检查', description: '所有 SQL 拼接必须使用参数化查询,禁止字符串拼接用户输入', category: 'review', tags: ['安全', 'SQL', 'Go'], reuseCount: 23, score: 95, updatedAt: '3 天前' },
|
||||
{ id: 2, title: '错误处理规范', description: '禁止吞掉 error,必须向上传播或记录日志', category: 'review', tags: ['Go', '规范', '错误处理'], reuseCount: 18, score: 88, updatedAt: '1 周前' },
|
||||
{ id: 3, title: 'API 响应格式一致性', description: '统一使用 { code, message, data } 结构,HTTP 状态码语义正确', category: 'review', tags: ['API', '规范'], reuseCount: 15, score: 82, updatedAt: '5 天前' },
|
||||
// Prompt 模板
|
||||
{ id: 4, title: 'SQL 查询生成 Prompt', description: '根据自然语言生成 SQL,包含表结构上下文和示例输出格式', category: 'prompt', tags: ['SQL', 'LLM', '模板'], reuseCount: 142, score: 91, updatedAt: '昨天' },
|
||||
{ id: 5, title: '代码审查 Prompt', description: 'AI 代码审查专用 Prompt,涵盖安全、性能、可维护性维度', category: 'prompt', tags: ['代码审查', 'LLM'], reuseCount: 87, score: 85, updatedAt: '3 天前' },
|
||||
{ id: 6, title: 'API 文档生成 Prompt', description: '从 Handler 代码自动生成 API 文档的 Prompt 模板', category: 'prompt', tags: ['文档', 'LLM', '自动生成'], reuseCount: 34, score: 78, updatedAt: '1 周前' },
|
||||
// 踩坑经验
|
||||
{ id: 7, title: 'Go context 传递陷阱', description: 'goroutine 中必须传递 context 而非创建新的,否则超时控制失效', category: 'pitfall', tags: ['Go', '并发', 'context'], reuseCount: 12, score: 92, updatedAt: '2 天前' },
|
||||
{ id: 8, title: 'Vue 3 作用域坑', description: 'v-for 内 ref 绑定不会自动响应式,需使用数组形式', category: 'pitfall', tags: ['Vue', '前端', '响应式'], reuseCount: 8, score: 75, updatedAt: '1 周前' },
|
||||
{ id: 9, title: 'Docker 网络模式选择', description: 'host 模式在 Mac/Win 上无效,必须用端口映射', category: 'pitfall', tags: ['Docker', '网络'], reuseCount: 6, score: 70, updatedAt: '2 周前' },
|
||||
// 诊断知识
|
||||
{ id: 10, title: 'MySQL 慢查询诊断流程', description: 'EXPLAIN → 索引检查 → 慢查询日志分析 → 优化建议', category: 'diagnosis', tags: ['MySQL', '性能', '诊断'], reuseCount: 31, score: 89, updatedAt: '4 天前' },
|
||||
{ id: 11, title: 'Go 内存泄漏排查', description: 'pprof heap 分析 → goroutine 泄漏检查 → GC 调优', category: 'diagnosis', tags: ['Go', '内存', 'pprof'], reuseCount: 9, score: 83, updatedAt: '1 周前' },
|
||||
{ id: 12, title: '前端白屏诊断', description: 'Console 错误 → 网络请求 → 路由配置 → 构建产物检查', category: 'diagnosis', tags: ['前端', '调试', 'Vue'], reuseCount: 14, score: 80, updatedAt: '5 天前' },
|
||||
// 部署经验
|
||||
{ id: 13, title: 'Go 二进制热更新', description: 'kill + mv + nohup 启动,无需 systemd 的轻量部署方案', category: 'deploy', tags: ['Go', '部署', 'Linux'], reuseCount: 19, score: 86, updatedAt: '昨天' },
|
||||
{ id: 14, title: 'SCP 上传最佳实践', description: '使用 ssh-proxy upload 代替 scp,支持配置化管理', category: 'deploy', tags: ['SCP', '部署', '工具'], reuseCount: 11, score: 72, updatedAt: '3 天前' },
|
||||
{ id: 15, title: 'Nginx 反向代理配置', description: 'u-ask 的 Nginx 配置模板,含 WebSocket 支持和 gzip', category: 'deploy', tags: ['Nginx', '配置', '反向代理'], reuseCount: 7, score: 77, updatedAt: '1 周前' },
|
||||
] as KnowledgeItem[],
|
||||
|
||||
categories: [
|
||||
{ key: 'all', label: '全部', icon: '📦' },
|
||||
{ key: 'review', label: '审查规则', icon: '🔍' },
|
||||
{ key: 'prompt', label: 'Prompt模板', icon: '💬' },
|
||||
{ key: 'pitfall', label: '踩坑经验', icon: '⚠️' },
|
||||
{ key: 'diagnosis', label: '诊断知识', icon: '🩺' },
|
||||
{ key: 'deploy', label: '部署经验', icon: '🚀' },
|
||||
],
|
||||
items: [] as KnowledgeRecord[],
|
||||
candidates: [] as KnowledgeRecord[],
|
||||
config: null as KnowledgeConfig | null,
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
})
|
||||
|
||||
export function useKnowledgeStore() {
|
||||
const items = computed(() => state.items)
|
||||
// 7 种知识类型(snake_case,与后端 KnowledgeKind 对齐)
|
||||
// 仅存 key + icon;展示文案(label)走 i18n: $t('knowledge.kind.<key>')
|
||||
export const KNOWLEDGE_KINDS = [
|
||||
{ key: 'review_rule', icon: '🔍' },
|
||||
{ key: 'prompt_template', icon: '💬' },
|
||||
{ key: 'pitfall', icon: '⚠️' },
|
||||
{ key: 'architecture_pattern', icon: '🏛️' },
|
||||
{ key: 'diagnosis', icon: '🩺' },
|
||||
{ key: 'deployment_note', icon: '🚀' },
|
||||
{ key: 'workflow_optimization', icon: '⚡' },
|
||||
] as const
|
||||
|
||||
const getByCategory = (category: string) =>
|
||||
computed(() =>
|
||||
category === 'all'
|
||||
? state.items
|
||||
: state.items.filter(i => i.category === category)
|
||||
)
|
||||
|
||||
const search = (query: string) =>
|
||||
computed(() => {
|
||||
if (!query.trim()) return state.items
|
||||
const q = query.toLowerCase()
|
||||
return state.items.filter(i =>
|
||||
i.title.toLowerCase().includes(q) ||
|
||||
i.tags.some(t => t.toLowerCase().includes(q)) ||
|
||||
i.description.toLowerCase().includes(q)
|
||||
)
|
||||
})
|
||||
|
||||
const getCategoryCount = (key: string) =>
|
||||
key === 'all' ? state.items.length : state.items.filter(i => i.category === key).length
|
||||
|
||||
return {
|
||||
items,
|
||||
categories: computed(() => state.categories),
|
||||
getByCategory,
|
||||
search,
|
||||
getCategoryCount,
|
||||
/** 解析 tags JSON 字符串为 string[] */
|
||||
export function parseTags(tags: string | null): string[] {
|
||||
if (!tags) return []
|
||||
try {
|
||||
const arr = JSON.parse(tags)
|
||||
return Array.isArray(arr) ? arr : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export function useKnowledgeStore() {
|
||||
// ── 加载列表(默认排除 archived) ──
|
||||
async function loadList(status?: string) {
|
||||
state.loading = true
|
||||
state.error = null
|
||||
try {
|
||||
state.items = await knowledgeApi.list(status ?? null)
|
||||
} catch (e: any) {
|
||||
state.error = e?.toString() ?? '加载知识库失败'
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── 加载审核收件箱(候选列表) ──
|
||||
async function loadCandidates() {
|
||||
try {
|
||||
state.candidates = await knowledgeApi.listCandidates()
|
||||
} catch (e: any) {
|
||||
state.error = e?.toString() ?? '加载收件箱失败'
|
||||
}
|
||||
}
|
||||
|
||||
async function search(query: string, kind?: string) {
|
||||
state.loading = true
|
||||
state.error = null
|
||||
try {
|
||||
state.items = await knowledgeApi.search({ query, kind })
|
||||
} catch (e: any) {
|
||||
state.error = e?.toString() ?? '检索失败'
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function create(input: CreateKnowledgeInput) {
|
||||
const record = await knowledgeApi.create(input)
|
||||
// 手动录入默认 candidate,刷新收件箱
|
||||
await loadCandidates()
|
||||
return record
|
||||
}
|
||||
|
||||
async function updateStatus(id: string, status: string) {
|
||||
await knowledgeApi.updateStatus(id, status)
|
||||
// 从 items 和 candidates 中同步移除
|
||||
state.items = state.items.filter(k => k.id !== id)
|
||||
state.candidates = state.candidates.filter(k => k.id !== id)
|
||||
}
|
||||
|
||||
async function archive(id: string) {
|
||||
await knowledgeApi.archive(id)
|
||||
state.items = state.items.filter(k => k.id !== id)
|
||||
state.candidates = state.candidates.filter(k => k.id !== id)
|
||||
}
|
||||
|
||||
// ── 配置 ──
|
||||
async function loadConfig() {
|
||||
try {
|
||||
state.config = await knowledgeApi.getConfig()
|
||||
} catch (e: any) {
|
||||
state.error = e?.toString() ?? '加载配置失败'
|
||||
}
|
||||
}
|
||||
|
||||
async function saveConfig(config: KnowledgeConfig) {
|
||||
await knowledgeApi.saveConfig(config)
|
||||
state.config = config
|
||||
}
|
||||
|
||||
async function extractNow() {
|
||||
await knowledgeApi.extractNow()
|
||||
await loadCandidates()
|
||||
}
|
||||
|
||||
// ── 生命线:详情 / 编辑 / 事件查询(按需调用,不入全局 state) ──
|
||||
async function getDetail(id: string): Promise<KnowledgeDetailPayload> {
|
||||
return knowledgeApi.getDetail(id)
|
||||
}
|
||||
|
||||
async function update(id: string, input: UpdateKnowledgeInput): Promise<KnowledgeRecord> {
|
||||
const updated = await knowledgeApi.update(id, input)
|
||||
// 同步刷新内存中的列表/收件箱条目
|
||||
const patch = (arr: KnowledgeRecord[]) => {
|
||||
const idx = arr.findIndex(k => k.id === id)
|
||||
if (idx >= 0) arr[idx] = { ...arr[idx], ...updated }
|
||||
}
|
||||
patch(state.items)
|
||||
patch(state.candidates)
|
||||
return updated
|
||||
}
|
||||
|
||||
async function getEvents(knowledgeId: string, eventType?: string, limit?: number): Promise<KnowledgeEventRecord[]> {
|
||||
return knowledgeApi.events(knowledgeId, eventType, limit)
|
||||
}
|
||||
|
||||
// 候选数量(供侧栏 badge 用)
|
||||
const candidateCount = computed(() => state.candidates.length)
|
||||
|
||||
return reactive({
|
||||
// 状态用 getter 实时读 state(防快照不跟随,同 project store 模式)
|
||||
get items() { return state.items },
|
||||
get candidates() { return state.candidates },
|
||||
get config() { return state.config },
|
||||
get loading() { return state.loading },
|
||||
get error() { return state.error },
|
||||
candidateCount,
|
||||
// actions
|
||||
loadList, loadCandidates, search, create, updateStatus, archive,
|
||||
loadConfig, saveConfig, extractNow,
|
||||
getDetail, update, getEvents,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user