dsh-data-domain-knowledge 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/cordis.patch.yml +410 -0
- package/eval/README.md +76 -0
- package/eval/evaluate-retrieval.mjs +121 -0
- package/eval/queries.example.jsonl +2 -0
- package/lib/client.js +111415 -0
- package/lib/types/config-schema.d.ts +35 -0
- package/lib/types/config-schema.d.ts.map +1 -0
- package/lib/types/config-schema.js +34 -0
- package/lib/types/config-schema.js.map +1 -0
- package/lib/types/convert/core-loader.d.ts +32 -0
- package/lib/types/convert/core-loader.d.ts.map +1 -0
- package/lib/types/convert/core-loader.js +60 -0
- package/lib/types/convert/core-loader.js.map +1 -0
- package/lib/types/convert/host.d.ts +88 -0
- package/lib/types/convert/host.d.ts.map +1 -0
- package/lib/types/convert/host.js +479 -0
- package/lib/types/convert/host.js.map +1 -0
- package/lib/types/convert/ingest.d.ts +125 -0
- package/lib/types/convert/ingest.d.ts.map +1 -0
- package/lib/types/convert/ingest.js +228 -0
- package/lib/types/convert/ingest.js.map +1 -0
- package/lib/types/convert/settings-schema.d.ts +18 -0
- package/lib/types/convert/settings-schema.d.ts.map +1 -0
- package/lib/types/convert/settings-schema.js +26 -0
- package/lib/types/convert/settings-schema.js.map +1 -0
- package/lib/types/convert/types.d.ts +46 -0
- package/lib/types/convert/types.d.ts.map +1 -0
- package/lib/types/convert/types.js +41 -0
- package/lib/types/convert/types.js.map +1 -0
- package/lib/types/diagnosis/cards.d.ts +71 -0
- package/lib/types/diagnosis/cards.d.ts.map +1 -0
- package/lib/types/diagnosis/cards.js +482 -0
- package/lib/types/diagnosis/cards.js.map +1 -0
- package/lib/types/diagnosis/problem-classes.d.ts +51 -0
- package/lib/types/diagnosis/problem-classes.d.ts.map +1 -0
- package/lib/types/diagnosis/problem-classes.js +66 -0
- package/lib/types/diagnosis/problem-classes.js.map +1 -0
- package/lib/types/diagnosis/schema.d.ts +65 -0
- package/lib/types/diagnosis/schema.d.ts.map +1 -0
- package/lib/types/diagnosis/schema.js +208 -0
- package/lib/types/diagnosis/schema.js.map +1 -0
- package/lib/types/diagnosis/seed-source.d.ts +20 -0
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -0
- package/lib/types/diagnosis/seed-source.js +96 -0
- package/lib/types/diagnosis/seed-source.js.map +1 -0
- package/lib/types/diagnosis/seed.d.ts +50 -0
- package/lib/types/diagnosis/seed.d.ts.map +1 -0
- package/lib/types/diagnosis/seed.js +242 -0
- package/lib/types/diagnosis/seed.js.map +1 -0
- package/lib/types/diagnosis/store.d.ts +81 -0
- package/lib/types/diagnosis/store.d.ts.map +1 -0
- package/lib/types/diagnosis/store.js +409 -0
- package/lib/types/diagnosis/store.js.map +1 -0
- package/lib/types/diagnosis/types.d.ts +124 -0
- package/lib/types/diagnosis/types.d.ts.map +1 -0
- package/lib/types/diagnosis/types.js +13 -0
- package/lib/types/diagnosis/types.js.map +1 -0
- package/lib/types/directory-picker.d.ts +94 -0
- package/lib/types/directory-picker.d.ts.map +1 -0
- package/lib/types/directory-picker.js +110 -0
- package/lib/types/directory-picker.js.map +1 -0
- package/lib/types/extract-model.d.ts +76 -0
- package/lib/types/extract-model.d.ts.map +1 -0
- package/lib/types/extract-model.js +193 -0
- package/lib/types/extract-model.js.map +1 -0
- package/lib/types/extract.d.ts +121 -0
- package/lib/types/extract.d.ts.map +1 -0
- package/lib/types/extract.js +549 -0
- package/lib/types/extract.js.map +1 -0
- package/lib/types/fts.d.ts +96 -0
- package/lib/types/fts.d.ts.map +1 -0
- package/lib/types/fts.js +184 -0
- package/lib/types/fts.js.map +1 -0
- package/lib/types/index.d.ts +39 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +1112 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/intent/engine.d.ts +108 -0
- package/lib/types/intent/engine.d.ts.map +1 -0
- package/lib/types/intent/engine.js +501 -0
- package/lib/types/intent/engine.js.map +1 -0
- package/lib/types/intent/host.d.ts +88 -0
- package/lib/types/intent/host.d.ts.map +1 -0
- package/lib/types/intent/host.js +389 -0
- package/lib/types/intent/host.js.map +1 -0
- package/lib/types/intent/preset-tools.d.ts +37 -0
- package/lib/types/intent/preset-tools.d.ts.map +1 -0
- package/lib/types/intent/preset-tools.js +45 -0
- package/lib/types/intent/preset-tools.js.map +1 -0
- package/lib/types/intent/settings-schema.d.ts +59 -0
- package/lib/types/intent/settings-schema.d.ts.map +1 -0
- package/lib/types/intent/settings-schema.js +57 -0
- package/lib/types/intent/settings-schema.js.map +1 -0
- package/lib/types/intent/store.d.ts +75 -0
- package/lib/types/intent/store.d.ts.map +1 -0
- package/lib/types/intent/store.js +139 -0
- package/lib/types/intent/store.js.map +1 -0
- package/lib/types/intent/tools.d.ts +63 -0
- package/lib/types/intent/tools.d.ts.map +1 -0
- package/lib/types/intent/tools.js +108 -0
- package/lib/types/intent/tools.js.map +1 -0
- package/lib/types/intent/types.d.ts +189 -0
- package/lib/types/intent/types.d.ts.map +1 -0
- package/lib/types/intent/types.js +62 -0
- package/lib/types/intent/types.js.map +1 -0
- package/lib/types/knowledge-bases.d.ts +266 -0
- package/lib/types/knowledge-bases.d.ts.map +1 -0
- package/lib/types/knowledge-bases.js +1107 -0
- package/lib/types/knowledge-bases.js.map +1 -0
- package/lib/types/layout.d.ts +262 -0
- package/lib/types/layout.d.ts.map +1 -0
- package/lib/types/layout.js +312 -0
- package/lib/types/layout.js.map +1 -0
- package/lib/types/migrate.d.ts +110 -0
- package/lib/types/migrate.d.ts.map +1 -0
- package/lib/types/migrate.js +280 -0
- package/lib/types/migrate.js.map +1 -0
- package/lib/types/recall-rank.d.ts +158 -0
- package/lib/types/recall-rank.d.ts.map +1 -0
- package/lib/types/recall-rank.js +261 -0
- package/lib/types/recall-rank.js.map +1 -0
- package/lib/types/runtime.d.ts +568 -0
- package/lib/types/runtime.d.ts.map +1 -0
- package/lib/types/runtime.js +3045 -0
- package/lib/types/runtime.js.map +1 -0
- package/lib/types/settings-schema.d.ts +117 -0
- package/lib/types/settings-schema.d.ts.map +1 -0
- package/lib/types/settings-schema.js +92 -0
- package/lib/types/settings-schema.js.map +1 -0
- package/lib/types/settings-volatile.d.ts +102 -0
- package/lib/types/settings-volatile.d.ts.map +1 -0
- package/lib/types/settings-volatile.js +114 -0
- package/lib/types/settings-volatile.js.map +1 -0
- package/lib/types/types.d.ts +799 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js +387 -0
- package/lib/types/types.js.map +1 -0
- package/lib/types/wiki-types.d.ts +68 -0
- package/lib/types/wiki-types.d.ts.map +1 -0
- package/lib/types/wiki-types.js +322 -0
- package/lib/types/wiki-types.js.map +1 -0
- package/package.json +101 -0
- package/preset/tita-data-agent/agent.cordis.yml +376 -0
- package/preset/tita-data-agent/preset.yml +3 -0
- package/python/requirements.txt +8 -0
- package/scripts/doctor.mjs +70 -0
- package/scripts/e2e-pipeline.mjs +355 -0
- package/scripts/install-agent-preset.mjs +92 -0
- package/scripts/setup-embedding.sh +34 -0
- package/scripts/sync-agent-preset.mjs +188 -0
- package/src/client/AgentPresetCard.tsx +247 -0
- package/src/client/DirectoryPicker.tsx +357 -0
- package/src/client/InfoHint.tsx +65 -0
- package/src/client/IntentLogicTab.tsx +185 -0
- package/src/client/IntentTab.tsx +715 -0
- package/src/client/KnowledgeBaseTab.tsx +1454 -0
- package/src/client/KnowledgeGraphView.tsx +306 -0
- package/src/client/KnowledgePanel.tsx +381 -0
- package/src/client/KnowledgeStepRail.tsx +60 -0
- package/src/client/NodeSettings.tsx +392 -0
- package/src/client/PreviewSections.tsx +116 -0
- package/src/client/RecallPanel.tsx +399 -0
- package/src/client/TypeDefinitionDrawer.tsx +215 -0
- package/src/client/WikiFlowCard.tsx +226 -0
- package/src/client/WikiTypeEditor.tsx +332 -0
- package/src/client/agent-preset.ts +121 -0
- package/src/client/arch/AgentArchitectureGraph.tsx +219 -0
- package/src/client/arch/NodeLogicPanel.tsx +133 -0
- package/src/client/arch/ProblemDetailPanel.tsx +88 -0
- package/src/client/arch/model.ts +691 -0
- package/src/client/architecture.css +191 -0
- package/src/client/architecture.tsx +530 -0
- package/src/client/attribution/AttributionGraph.tsx +104 -0
- package/src/client/attribution/AttributionTab.tsx +332 -0
- package/src/client/attribution/CaseView.tsx +102 -0
- package/src/client/attribution/GraphStage.tsx +87 -0
- package/src/client/attribution/RunView.tsx +184 -0
- package/src/client/attribution/StageChrome.tsx +403 -0
- package/src/client/attribution/ViewSwitch.tsx +107 -0
- package/src/client/attribution/graph-options.ts +381 -0
- package/src/client/attribution/model.ts +462 -0
- package/src/client/config-scope.ts +119 -0
- package/src/client/convert/ConvertPanel.tsx +594 -0
- package/src/client/convert/styles.css +138 -0
- package/src/client/convert/styles.ts +49 -0
- package/src/client/format.ts +39 -0
- package/src/client/graph/layout.ts +181 -0
- package/src/client/graph.css +113 -0
- package/src/client/index.ts +98 -0
- package/src/client/problem-domains.ts +114 -0
- package/src/client/styles.css +940 -0
- package/src/client/styles.ts +11 -0
- package/src/client/useFitScale.ts +68 -0
- package/src/config-schema.ts +45 -0
- package/src/convert/core-loader.ts +68 -0
- package/src/convert/host.ts +579 -0
- package/src/convert/ingest.ts +367 -0
- package/src/convert/settings-schema.ts +27 -0
- package/src/convert/types.ts +65 -0
- package/src/diagnosis/cards.ts +498 -0
- package/src/diagnosis/problem-classes.ts +117 -0
- package/src/diagnosis/schema.ts +260 -0
- package/src/diagnosis/seed-source.ts +107 -0
- package/src/diagnosis/seed.ts +291 -0
- package/src/diagnosis/store.ts +432 -0
- package/src/diagnosis/types.ts +152 -0
- package/src/directory-picker.ts +164 -0
- package/src/extract-model.ts +227 -0
- package/src/extract.ts +632 -0
- package/src/fts.ts +203 -0
- package/src/index.ts +1091 -0
- package/src/intent/README.md +63 -0
- package/src/intent/engine.ts +527 -0
- package/src/intent/host.ts +462 -0
- package/src/intent/preset-tools.ts +48 -0
- package/src/intent/settings-schema.ts +63 -0
- package/src/intent/store.ts +143 -0
- package/src/intent/tools.ts +139 -0
- package/src/intent/types.ts +245 -0
- package/src/knowledge-bases.ts +1160 -0
- package/src/layout.ts +350 -0
- package/src/migrate.ts +262 -0
- package/src/python/embedding_worker.py +471 -0
- package/src/recall-rank.ts +299 -0
- package/src/runtime.ts +3069 -0
- package/src/settings-schema.ts +94 -0
- package/src/settings-volatile.ts +122 -0
- package/src/types.ts +1140 -0
- package/src/wiki-types.ts +357 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 根路径的选择与预览(0029)。
|
|
3
|
+
*
|
|
4
|
+
* 三件事,一个文件:
|
|
5
|
+
*
|
|
6
|
+
* 1. `DirectoryPickerField` —— 设置里的**那个**路径字段。它是一个只读显示 + 一个
|
|
7
|
+
* 「选择文件夹」按钮,不是一个输入框:路径是磁盘事实,让人手打只会得到拼错的
|
|
8
|
+
* 路径,而这一处错误决定整个 Agent 的东西存在哪。
|
|
9
|
+
* 2. `DirectoryBrowser` —— 宿主只能「列一层 / 建子目录」时(浏览器在远端)的页面内
|
|
10
|
+
* 浏览框。本机部署走系统对话框,这个框不会出现;但它是远端唯一能用的形态,所以
|
|
11
|
+
* 不能只有一句「不支持」。
|
|
12
|
+
* 3. `LayoutPreview` —— 把服务端算出来的四段布局画出来。目录结构本身就是这个方案
|
|
13
|
+
* 要交代的东西,所以它显式地摆在设置里,而不是等用户去磁盘上发现。
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'
|
|
19
|
+
import { Button, Input, cn } from '@tita-data-agent/ui/components'
|
|
20
|
+
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@tita-data-agent/ui/overlays'
|
|
21
|
+
import { AlertCircle, Check, ChevronRight, FolderOpen, FolderPlus, Home, LoaderCircle } from '@tita-data-agent/ui/icons'
|
|
22
|
+
|
|
23
|
+
interface Envelope<T> { ok?: boolean; data?: T; warnings?: string[]; error?: { code?: string; message?: string; suggestion?: string } }
|
|
24
|
+
|
|
25
|
+
async function call<T>(url: string, init?: RequestInit): Promise<T> {
|
|
26
|
+
const response = await fetch(url, init)
|
|
27
|
+
const text = await response.text()
|
|
28
|
+
if (!text.trim()) throw new Error(`服务端返回空响应(HTTP ${response.status})`)
|
|
29
|
+
let payload: Envelope<T>
|
|
30
|
+
try {
|
|
31
|
+
payload = JSON.parse(text) as Envelope<T>
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error(`服务端返回了无效响应(HTTP ${response.status})`)
|
|
34
|
+
}
|
|
35
|
+
if (!response.ok || payload.ok === false) {
|
|
36
|
+
const base = payload.error?.message ?? `请求失败(${response.status})`
|
|
37
|
+
const error = new Error(payload.error?.suggestion ? `${base} —— ${payload.error.suggestion}` : base) as Error & { code?: string }
|
|
38
|
+
error.code = payload.error?.code ?? (response.status === 404 ? 'NOT_FOUND' : undefined)
|
|
39
|
+
throw error
|
|
40
|
+
}
|
|
41
|
+
return payload.data as T
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 接口不存在时**不要**把服务端的原话(「knowledge endpoint 不存在」)甩给用户。
|
|
46
|
+
*
|
|
47
|
+
* 它只会在一种情况下出现:客户端已经更新、宿主进程还在跑旧代码 —— 也就是「改了
|
|
48
|
+
* 插件但没重启 DSH」。这句话对用户没有任何可操作性,而真正要做的动作只有一个。
|
|
49
|
+
* 所以把它翻译成那一个动作。
|
|
50
|
+
*/
|
|
51
|
+
function explain(cause: unknown, fallback: string): string {
|
|
52
|
+
const code = (cause as { code?: string } | undefined)?.code
|
|
53
|
+
if (code === 'NOT_FOUND') return '目录布局接口还没生效 —— 插件代码更新了,但宿主进程还在跑旧的,重启 DSH 后即可。'
|
|
54
|
+
return cause instanceof Error ? cause.message : fallback
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 一个目录行。与服务端 `directory-picker.ts` 的 `DirectoryEntry` 同形。 */
|
|
58
|
+
interface DirectoryEntry { name: string; path: string; hidden: boolean }
|
|
59
|
+
interface DirectoryListing { path: string; home: string; crumbs: DirectoryEntry[]; entries: DirectoryEntry[]; truncated: boolean }
|
|
60
|
+
/** `/knowledge/layout` 的返回。 */
|
|
61
|
+
export interface AgentLayoutView {
|
|
62
|
+
agentRoot: string
|
|
63
|
+
domainKnowledge: string
|
|
64
|
+
/** 0029:记忆那一段(版本快照、反馈、访问审计),按知识库分格。 */
|
|
65
|
+
memory: string
|
|
66
|
+
/** 意图记录那一段。 */
|
|
67
|
+
intent: string
|
|
68
|
+
tmp: string
|
|
69
|
+
/** 0029:家当那一段(模型 / Python 环境 / 注册表)。 */
|
|
70
|
+
resource: string
|
|
71
|
+
migration?: MigrationView
|
|
72
|
+
resourceMigration?: MigrationView
|
|
73
|
+
/** 三个「家当」路径**解析后**的值:设置里空着 = 自动,这里给的是它实际指向哪。 */
|
|
74
|
+
resources?: { python: string; embeddingModel: string; rerankModel: string }
|
|
75
|
+
/**
|
|
76
|
+
* 没落在 `resource/` 下的家当(服务端算的,见 `index.ts` 的 `outsideResource`)。
|
|
77
|
+
*
|
|
78
|
+
* `outside` = 是绝对路径但在别处(旧家当目录),迁移会把它搬进来;
|
|
79
|
+
* `bare` = 根本不是一个路径(`python3`、`BAAI/bge-reranker-base`),迁移搬不了它。
|
|
80
|
+
*/
|
|
81
|
+
resourcesOutsideResource?: Array<{ key: string; value: string; kind: 'outside' | 'bare' }>
|
|
82
|
+
/**
|
|
83
|
+
* 设置分区注册失败的原因。
|
|
84
|
+
*
|
|
85
|
+
* 设置层在**登记命名空间**时就会解析一次 schema,所以 schema 里一个错误会让整个
|
|
86
|
+
* 分区注册不上:面板显示「设置服务不可用」、迁移不执行、存下来的设置一个都不生效。
|
|
87
|
+
* 有这句话,界面才能说「为什么」,而不是只显示症状。
|
|
88
|
+
*/
|
|
89
|
+
settingsError?: string
|
|
90
|
+
pickerKind: string | null
|
|
91
|
+
/**
|
|
92
|
+
* preset 的**真源目录**(`<包根>/preset/tita-data-agent/`)。
|
|
93
|
+
*
|
|
94
|
+
* 提示词的唯一真源就在这个目录里的 `agent.cordis.yml`。0.1.7 起 DSH 那边 preset
|
|
95
|
+
* 不再是一个目录(它是组合里的一条声明行),所以「打开 preset 目录」打开的是
|
|
96
|
+
* 这个随插件分发的真源目录 —— 服务端算好给出来,客户端不拼路径。
|
|
97
|
+
*/
|
|
98
|
+
presetDir?: string
|
|
99
|
+
/** 真源目录在不在。不在时界面如实说,而不是给一个弹空目录的按钮。 */
|
|
100
|
+
presetDirExists?: boolean
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface MigrationView {
|
|
104
|
+
status: 'skipped' | 'moved' | 'failed'
|
|
105
|
+
from?: string
|
|
106
|
+
to?: string
|
|
107
|
+
moved?: string[]
|
|
108
|
+
reason?: string
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 读一次布局。设置面板与目录选择都用它。 */
|
|
112
|
+
export function useAgentLayout(refreshKey: unknown): { layout: AgentLayoutView | null; error: string | null; reload: () => void } {
|
|
113
|
+
const [layout, setLayout] = useState<AgentLayoutView | null>(null)
|
|
114
|
+
const [error, setError] = useState<string | null>(null)
|
|
115
|
+
const [nonce, setNonce] = useState(0)
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
let alive = true
|
|
118
|
+
void call<AgentLayoutView>('/knowledge/layout')
|
|
119
|
+
.then(next => { if (alive) { setLayout(next); setError(null) } })
|
|
120
|
+
.catch(cause => { if (alive) setError(explain(cause, '读取目录布局失败')) })
|
|
121
|
+
return () => { alive = false }
|
|
122
|
+
}, [refreshKey, nonce])
|
|
123
|
+
return { layout, error, reload: useCallback(() => setNonce(value => value + 1), []) }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ─────────────────────────────────────────────────────────── 字段 */
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 根路径字段。
|
|
130
|
+
*
|
|
131
|
+
* 值的来源只有两个:用户选了一个目录,或者什么都没选(空串 = 用默认值)。
|
|
132
|
+
* 所以这里的 `<Input>` 是 `readOnly` —— 不是「暂时不给编辑」,是**这个字段本来
|
|
133
|
+
* 就不接受手打**。
|
|
134
|
+
*/
|
|
135
|
+
export function DirectoryPickerField({ label, value, onChange, disabled, pickerKind, onError }: {
|
|
136
|
+
/** 字段名。由调用方从 `spec.label` 传进来 —— 写死一份就等于同一件事有两个出处。 */
|
|
137
|
+
label: string
|
|
138
|
+
value: string
|
|
139
|
+
onChange: (next: string) => void
|
|
140
|
+
disabled: boolean
|
|
141
|
+
/** 宿主提供哪种交互;`null` 表示没挂目录选择器。 */
|
|
142
|
+
pickerKind: string | null
|
|
143
|
+
onError: (message: string | null) => void
|
|
144
|
+
}): ReactNode {
|
|
145
|
+
const [busy, setBusy] = useState(false)
|
|
146
|
+
const [browsing, setBrowsing] = useState(false)
|
|
147
|
+
|
|
148
|
+
const choose = async (): Promise<void> => {
|
|
149
|
+
setBusy(true); onError(null)
|
|
150
|
+
try {
|
|
151
|
+
const picked = await call<{ kind: 'native' | 'browse'; path?: string | null }>('/knowledge/pick-directory', { method: 'POST' })
|
|
152
|
+
if (picked.kind === 'browse') setBrowsing(true)
|
|
153
|
+
else if (picked.path) onChange(picked.path)
|
|
154
|
+
} catch (cause) {
|
|
155
|
+
onError(explain(cause, '打开文件夹对话框失败'))
|
|
156
|
+
} finally {
|
|
157
|
+
setBusy(false)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return <div className="tita-settingField">
|
|
162
|
+
<span>{label}</span>
|
|
163
|
+
<div className="tita-pickerRow">
|
|
164
|
+
{/* 只读而不是 disabled:disabled 的输入框读不出「这是当前值」,只读的能选中复制。 */}
|
|
165
|
+
<Input readOnly value={value} placeholder="未设置(使用默认位置)" aria-label={label} />
|
|
166
|
+
<Button variant="outline" onClick={() => void choose()} disabled={disabled || busy}>
|
|
167
|
+
{busy ? <LoaderCircle size={14} className="tita-spin" /> : <FolderOpen size={14} />}
|
|
168
|
+
{busy ? '等待选择…' : '选择文件夹'}
|
|
169
|
+
</Button>
|
|
170
|
+
</div>
|
|
171
|
+
<small className="tita-settingHint">
|
|
172
|
+
{pickerKind === 'browse'
|
|
173
|
+
? '浏览器不在宿主那台机器上,所以这里会在页面内打开一个目录浏览框。'
|
|
174
|
+
: pickerKind === 'native'
|
|
175
|
+
? '会打开系统文件夹对话框;取消对话框不会改动当前值。'
|
|
176
|
+
: '宿主没有提供目录选择器。若你刚更新过插件,重启 DSH 后这里会变成系统文件夹对话框。'}
|
|
177
|
+
</small>
|
|
178
|
+
{browsing && <DirectoryBrowser
|
|
179
|
+
onClose={() => setBrowsing(false)}
|
|
180
|
+
onPick={next => { onChange(next); setBrowsing(false) }}
|
|
181
|
+
/>}
|
|
182
|
+
</div>
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ─────────────────────────────────────────────────────── 页面内浏览 */
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 页面内目录浏览框(browse 形态)。
|
|
189
|
+
*
|
|
190
|
+
* 一列到底而不是 Miller 双列:这里只服务一个目的 —— 选一个根路径。双列在
|
|
191
|
+
* 「要找的那个目录就在下一层」时更快,但多出一整列的宽度与一套选中态;而这个
|
|
192
|
+
* 框是**兜底**形态,简单比快更重要。
|
|
193
|
+
*/
|
|
194
|
+
function DirectoryBrowser({ onPick, onClose }: { onPick: (path: string) => void; onClose: () => void }): ReactNode {
|
|
195
|
+
const [listing, setListing] = useState<DirectoryListing | null>(null)
|
|
196
|
+
const [error, setError] = useState<string | null>(null)
|
|
197
|
+
const [loading, setLoading] = useState(true)
|
|
198
|
+
const [showHidden, setShowHidden] = useState(false)
|
|
199
|
+
const [newName, setNewName] = useState('')
|
|
200
|
+
const [creating, setCreating] = useState(false)
|
|
201
|
+
/** 请求序号:快速连点目录时,只让最后一次的结果落地。 */
|
|
202
|
+
const seq = useRef(0)
|
|
203
|
+
|
|
204
|
+
const open = useCallback((path?: string): void => {
|
|
205
|
+
const ticket = ++seq.current
|
|
206
|
+
setLoading(true); setError(null)
|
|
207
|
+
void call<DirectoryListing>(`/knowledge/browse-directory${path ? `?path=${encodeURIComponent(path)}` : ''}`)
|
|
208
|
+
.then(next => { if (ticket === seq.current) setListing(next) })
|
|
209
|
+
.catch(cause => { if (ticket === seq.current) setError(cause instanceof Error ? cause.message : String(cause)) })
|
|
210
|
+
.finally(() => { if (ticket === seq.current) setLoading(false) })
|
|
211
|
+
}, [])
|
|
212
|
+
|
|
213
|
+
useEffect(() => { open() }, [open])
|
|
214
|
+
|
|
215
|
+
const create = async (): Promise<void> => {
|
|
216
|
+
if (!listing || !newName.trim()) return
|
|
217
|
+
setCreating(true); setError(null)
|
|
218
|
+
try {
|
|
219
|
+
const created = await call<{ path: string }>('/knowledge/create-directory', {
|
|
220
|
+
method: 'POST',
|
|
221
|
+
headers: { 'content-type': 'application/json' },
|
|
222
|
+
body: JSON.stringify({ path: listing.path, name: newName }),
|
|
223
|
+
})
|
|
224
|
+
setNewName('')
|
|
225
|
+
open(created.path)
|
|
226
|
+
} catch (cause) {
|
|
227
|
+
setError(cause instanceof Error ? cause.message : String(cause))
|
|
228
|
+
} finally {
|
|
229
|
+
setCreating(false)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const rows = (listing?.entries ?? []).filter(entry => showHidden || !entry.hidden)
|
|
234
|
+
|
|
235
|
+
return <Dialog open onOpenChange={next => { if (!next) onClose() }}>
|
|
236
|
+
<DialogContent className="tita-browseDialog">
|
|
237
|
+
<DialogHeader><DialogTitle>选择文件夹</DialogTitle></DialogHeader>
|
|
238
|
+
|
|
239
|
+
<nav className="tita-browseCrumbs" aria-label="路径">
|
|
240
|
+
<button type="button" className="tita-browseCrumb" onClick={() => open(listing?.home)} title="回到主目录">
|
|
241
|
+
<Home size={12} />
|
|
242
|
+
</button>
|
|
243
|
+
{(listing?.crumbs ?? []).map((crumb, index) => <span key={crumb.path} className="tita-browseCrumbWrap">
|
|
244
|
+
<ChevronRight size={11} aria-hidden />
|
|
245
|
+
<button
|
|
246
|
+
type="button"
|
|
247
|
+
className={cn('tita-browseCrumb', index === (listing?.crumbs.length ?? 0) - 1 && 'tita-browseCrumbOn')}
|
|
248
|
+
onClick={() => open(crumb.path)}
|
|
249
|
+
>{crumb.name}</button>
|
|
250
|
+
</span>)}
|
|
251
|
+
</nav>
|
|
252
|
+
|
|
253
|
+
<div className="tita-browsePath" title={listing?.path ?? ''}>{listing?.path ?? '…'}</div>
|
|
254
|
+
|
|
255
|
+
{error && <p className="tita-browseError" role="alert"><AlertCircle size={13} />{error}</p>}
|
|
256
|
+
|
|
257
|
+
<ul className="tita-browseList">
|
|
258
|
+
{loading && !listing && <li className="tita-browseEmpty">正在读取…</li>}
|
|
259
|
+
{!loading && rows.length === 0 && <li className="tita-browseEmpty">这一层没有子文件夹。</li>}
|
|
260
|
+
{rows.map(entry => <li key={entry.path}>
|
|
261
|
+
<button type="button" className="tita-browseRow" onDoubleClick={() => open(entry.path)} onClick={() => open(entry.path)}>
|
|
262
|
+
<FolderOpen size={14} />{entry.name}
|
|
263
|
+
</button>
|
|
264
|
+
</li>)}
|
|
265
|
+
</ul>
|
|
266
|
+
|
|
267
|
+
{listing?.truncated && <p className="tita-browseNote">这一层文件夹太多,只列出了前一部分。</p>}
|
|
268
|
+
|
|
269
|
+
<div className="tita-browseCreate">
|
|
270
|
+
<Input value={newName} placeholder="在此新建文件夹" onChange={event => setNewName(event.target.value)} onKeyDown={event => { if (event.key === 'Enter') void create() }} />
|
|
271
|
+
<Button variant="outline" onClick={() => void create()} disabled={creating || !newName.trim()}><FolderPlus size={14} />新建</Button>
|
|
272
|
+
<label className="tita-browseHidden"><input type="checkbox" checked={showHidden} onChange={event => setShowHidden(event.target.checked)} />显示隐藏项</label>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<DialogFooter>
|
|
276
|
+
<Button variant="ghost" onClick={onClose}>取消</Button>
|
|
277
|
+
<Button variant="default" disabled={!listing} onClick={() => { if (listing) onPick(listing.path) }}>
|
|
278
|
+
<Check size={14} />选择这个文件夹
|
|
279
|
+
</Button>
|
|
280
|
+
</DialogFooter>
|
|
281
|
+
</DialogContent>
|
|
282
|
+
</Dialog>
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* ─────────────────────────────────────────────────────── 布局预览 */
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 根路径下会长出来的段。顺序即它们出现的顺序,理由见 `layout.ts`。
|
|
289
|
+
*
|
|
290
|
+
* **`output` 不在这张表里**:交付物跟着工作项目走,落在工作区的 `output/<日期>/`,
|
|
291
|
+
* 不落在这个根路径下。把不属于这里的东西画进这张表,用户就会照着去这个目录找他的报告。
|
|
292
|
+
*/
|
|
293
|
+
const SEGMENTS: Array<{ key: 'domainKnowledge' | 'memory' | 'intent' | 'tmp' | 'resource'; name: string; note: string; keep: string }> = [
|
|
294
|
+
{ key: 'domainKnowledge', name: 'domain_knowledge/', note: '知识树:每个知识库一格,里面是 SourceRaw、wiki 与编译产物', keep: '必须备份' },
|
|
295
|
+
{ key: 'memory', name: 'memory/', note: '记忆:版本快照、反馈与访问审计,按知识库分格', keep: '删掉会丢历史版本' },
|
|
296
|
+
{ key: 'intent', name: 'intent/', note: '意图记录:每轮「用户要什么」的留痕', keep: '可随时清空' },
|
|
297
|
+
{ key: 'tmp', name: 'tmp/', note: '运行期临时文件:转换工作区与任务状态,二级按日期分桶', keep: '删掉只影响未完成的任务' },
|
|
298
|
+
{ key: 'resource', name: 'resource/', note: 'Agent 跑起来所需要的家当:模型、Python 环境、知识库注册表', keep: '删掉会失去语义检索' },
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
/** 家当条目的中文名。路径本身没法读,用户看的是「哪个模型不在家当目录里」。 */
|
|
302
|
+
const ASSET_LABELS: Record<string, string> = {
|
|
303
|
+
python: 'Python 环境',
|
|
304
|
+
embeddingModel: '嵌入模型',
|
|
305
|
+
rerankModel: '重排模型',
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 布局预览。
|
|
310
|
+
*
|
|
311
|
+
* 它不只是「显示路径」,它同时回答四个问题:这个根路径下会长出什么、哪一段不能
|
|
312
|
+
* 删、上一次迁移把数据搬到哪了、以及**家当有没有真的在 `resource/` 里**。
|
|
313
|
+
* 目录结构的调整必须把原理一起交代出来,否则用户看到的只是三个陌生的文件夹名。
|
|
314
|
+
*/
|
|
315
|
+
export function LayoutPreview({ layout, error }: { layout: AgentLayoutView | null; error: string | null }): ReactNode {
|
|
316
|
+
if (error) return <p className="tita-layoutError" role="alert"><AlertCircle size={13} />{error}</p>
|
|
317
|
+
if (!layout) return <p className="tita-layoutError">正在读取目录布局…</p>
|
|
318
|
+
const outside = layout.resourcesOutsideResource ?? []
|
|
319
|
+
return <div className="tita-layoutPreview">
|
|
320
|
+
<ul className="tita-layoutSegments">
|
|
321
|
+
{SEGMENTS.map(segment => <li key={segment.key}>
|
|
322
|
+
<code>{segment.name}</code>
|
|
323
|
+
<span>{segment.note}</span>
|
|
324
|
+
<em>{segment.keep}</em>
|
|
325
|
+
<small title={layout[segment.key]}>{layout[segment.key]}</small>
|
|
326
|
+
</li>)}
|
|
327
|
+
</ul>
|
|
328
|
+
{/* 设置分区注册失败是**服务端**的故障,但用户先看到的是「设置服务不可用」这个
|
|
329
|
+
症状。把原因放在这里,才知道它不是自己的配置有问题。 */}
|
|
330
|
+
{layout.settingsError && <p className={cn('tita-kbNotice', 'tita-kbNoticeError')} role="alert">
|
|
331
|
+
<AlertCircle size={13} />
|
|
332
|
+
<span>设置分区没能注册:<code>{layout.settingsError}</code>。知识库设置会显示「设置服务不可用」,改动也不会生效 —— 这是插件 schema 的问题,不是你填错了。</span>
|
|
333
|
+
</p>}
|
|
334
|
+
{[layout.migration, layout.resourceMigration].map((migration, index) => {
|
|
335
|
+
if (!migration || migration.status === 'skipped') return null
|
|
336
|
+
const what = index === 0 ? '旧布局的知识树' : '旧位置的家当(模型 / Python 环境 / 注册表)'
|
|
337
|
+
return <p key={index} className={cn('tita-layoutMigration', migration.status === 'failed' && 'tita-layoutMigrationBad')}>
|
|
338
|
+
{migration.status === 'moved'
|
|
339
|
+
? <>已把{what}从 <code>{migration.from}</code> 搬进 <code>{migration.to}</code>({migration.moved?.length ?? 0} 项)。</>
|
|
340
|
+
: <>{what}搬迁失败:{migration.reason}。两边都还在原地,没有丢。</>}
|
|
341
|
+
</p>
|
|
342
|
+
})}
|
|
343
|
+
{/* 家当不在 `resource/` 下 = 删掉旧目录或 HF 缓存就会丢语义检索。两种原因要分开说,
|
|
344
|
+
因为要做的事完全不同:一种等迁移,一种得自己放。 */}
|
|
345
|
+
{outside.length > 0 && <div className="tita-layoutAssets" role="status">
|
|
346
|
+
<p><AlertCircle size={12} />{outside.length} 项家当不在 <code>resource/</code> 下</p>
|
|
347
|
+
<ul>
|
|
348
|
+
{outside.map(item => <li key={item.key}>
|
|
349
|
+
<b>{ASSET_LABELS[item.key] ?? item.key}</b>
|
|
350
|
+
{item.kind === 'outside'
|
|
351
|
+
? <> 还在旧位置 <code>{item.value}</code> —— 下次重启会把家当搬进 <code>resource/</code>。</>
|
|
352
|
+
: <> 是 <code>{item.value}</code>,不是一个路径 —— 它从来不是家当,迁移搬不了它。要固定下来就放成 <code>resource/models/<目录名></code>,或在设置里显式填一个路径。</>}
|
|
353
|
+
</li>)}
|
|
354
|
+
</ul>
|
|
355
|
+
</div>}
|
|
356
|
+
</div>
|
|
357
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 小标题后面的说明入口:**图标常驻,说明悬停才出现**。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么把常驻说明改到这里
|
|
5
|
+
*
|
|
6
|
+
* 设置抽屉里每一节原来都在标题下面挂一段说明。六节连起来就是一屏解释文字,而用户打开
|
|
7
|
+
* 设置是来**改数值**的 —— 那些段落把真正要操作的控件挤到需要滚动才能看见。
|
|
8
|
+
*
|
|
9
|
+
* 说明本身有价值(「阈值是整条链路里唯一跨查询可比的相关性判据」这种话不该删),所以
|
|
10
|
+
* 不是删掉,而是挪到图标后面:要的时候悬停或聚焦就能看到,不要的时候不占地方。
|
|
11
|
+
*
|
|
12
|
+
* ## 两处刻意的决定
|
|
13
|
+
*
|
|
14
|
+
* 1. **触发体是 `<button>` 而不是 `<span>`**:`TooltipTrigger` 里放一个不可聚焦的元素,
|
|
15
|
+
* 等于这条说明对键盘用户不存在。按钮能 Tab 到,`aria-label` 让读屏直接念出全文。
|
|
16
|
+
* 2. **图标用共享 icon 模块里的 `CircleHelp`**,不自己画、也不在插件里再引一份 lucide ——
|
|
17
|
+
* 视觉真源是 `packages/ui`,多一套就会多一种描边粗细。
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { ReactNode } from 'react'
|
|
23
|
+
import { CircleHelp } from '@tita-data-agent/ui/icons'
|
|
24
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@tita-data-agent/ui/ui/tooltip'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param text - 要说明的内容。同时作为 `aria-label`,所以它必须是一句能独立读懂的话,
|
|
28
|
+
* 而不是碎片(读屏用户拿到的只有这一句)。
|
|
29
|
+
*/
|
|
30
|
+
export function InfoHint({ text }: { text: string }): ReactNode {
|
|
31
|
+
return <TooltipProvider>
|
|
32
|
+
<Tooltip>
|
|
33
|
+
<TooltipTrigger asChild>
|
|
34
|
+
<button type="button" className="tita-infoHint" aria-label={text}>
|
|
35
|
+
<CircleHelp size={12} />
|
|
36
|
+
</button>
|
|
37
|
+
</TooltipTrigger>
|
|
38
|
+
{/*
|
|
39
|
+
`side="top"` + `avoidCollisions={false}` 必须**成对**用,只留一个都不对。
|
|
40
|
+
|
|
41
|
+
触发器在设置抽屉的**可滚动正文**里,而 radix 默认会拿裁剪祖先算碰撞:这一节的
|
|
42
|
+
标题正好是正文的第一个元素,「上方」落在正文的裁剪矩形之外,于是它把说明翻到
|
|
43
|
+
下方 —— 用户看到的就是「tips 没在上方,反而跑到下面去了」。
|
|
44
|
+
|
|
45
|
+
关掉翻转是安全的:说明通过 Portal 挂在 `body` 上,本来就不会被抽屉的 `overflow`
|
|
46
|
+
裁掉。翻转在这里解决的是一件并不存在的问题。
|
|
47
|
+
|
|
48
|
+
版式(限宽 / 换行 / 左对齐)交给插件 CSS 的 `.tita-infoHintPanel`,**不是**用
|
|
49
|
+
工具类覆盖 —— 插件没有自己的 Tailwind 构建:`max-w-[260px]` 这类任意值工具类
|
|
50
|
+
在插件里根本没有对应规则,而 `cn` 的 twMerge 又会先把 UI 包自带的 `max-w-xs`
|
|
51
|
+
删掉,结果是限宽彻底失效(实测宽度从 320px 变成 996px)。能生效的那几个
|
|
52
|
+
(`block`、`leading-relaxed`、`z-[1002]`)只是**碰巧** UI 包自己也用了同名类。
|
|
53
|
+
|
|
54
|
+
限宽那条规则写在 `styles.css` 里,选择器双写同名类以取得 (0,2,0) 的特异性 ——
|
|
55
|
+
见那里的说明。
|
|
56
|
+
|
|
57
|
+
`z-[1002]` 不是随手加的:UI 包的 tooltip 自带 `z-50`,而抽屉正文是 `z-[1001]`
|
|
58
|
+
(见 `overlays.tsx` 顶部那段关于弹层层级测量的注释)—— 不改的话 tips 会**画在
|
|
59
|
+
抽屉底下**,用户看到的就是「悬停什么都没出」。`SelectContent` 当年踩过同一个坑。
|
|
60
|
+
`cn` 是 `twMerge`,所以这里给的 `z-[1002]` 会直接替换掉 `z-50`,不需要 !important。
|
|
61
|
+
*/}
|
|
62
|
+
<TooltipContent side="top" avoidCollisions={false} className="tita-infoHintPanel z-[1002]">{text}</TooltipContent>
|
|
63
|
+
</Tooltip>
|
|
64
|
+
</TooltipProvider>
|
|
65
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「意图的逻辑」tab —— 把意图模型**讲清楚**,而不是只给一堆表单。
|
|
3
|
+
*
|
|
4
|
+
* 内容分五块,对应产品上的五个问题:什么是意图 / 为什么要配置 / 意图的描述是什么 /
|
|
5
|
+
* schema 各字段什么含义 / 背后的论文与术语。
|
|
6
|
+
*
|
|
7
|
+
* 这一页是**只读说明**:它不读引擎状态,也不写配置,所以不依赖 `settings` scope,
|
|
8
|
+
* 可以在设置服务不可用时照常阅读。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ReactNode } from 'react'
|
|
12
|
+
import { CircleHelp, ClipboardCheck, Library, Lightbulb, ListTree } from '@tita-data-agent/ui/icons'
|
|
13
|
+
import { Card } from '@tita-data-agent/ui/components'
|
|
14
|
+
import { styles } from './styles.ts'
|
|
15
|
+
|
|
16
|
+
/** 一张"字段含义"表。 */
|
|
17
|
+
interface FieldDoc {
|
|
18
|
+
name: string
|
|
19
|
+
required: boolean
|
|
20
|
+
meaning: string
|
|
21
|
+
where: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const RECORD_FIELDS: FieldDoc[] = [
|
|
25
|
+
{ name: 'sessionKey', required: true, meaning: '这条意图属于哪个会话。记录按 session + turn 分组,TTL 到期即释放。', where: 'Host 从会话头取,模型不用传' },
|
|
26
|
+
{ name: 'turnKey / turnId', required: true, meaning: '「哪一轮」。同一轮重复记录会 upsert 成一条,不会刷出多条流水。', where: 'Host 在 turn/start 时分配' },
|
|
27
|
+
{ name: 'userInput', required: true, meaning: '本轮用户原文(截断到 2000 字)。它是审计的证据,不是分类的输入。', where: '模型从本轮消息里复制' },
|
|
28
|
+
{ name: 'act', required: true, meaning: '用户在**做什么**:问、让做、补充、确认、取消……与 category 正交。', where: '模型从 act 词表里挑一个' },
|
|
29
|
+
{ name: 'category', required: false, meaning: '这一轮属于**哪个业务类别**。两段式判定:先匹配预置,不命中才自创或落 unknown。', where: '模型判断,可在意图配置里预置' },
|
|
30
|
+
{ name: 'predicate', required: false, meaning: '这轮最终要调的**工具/动作**,例如 query_babel_data。用来把意图和真实执行对齐。', where: '模型按可用工具填' },
|
|
31
|
+
{ name: 'slots', required: false, meaning: '从用户话里抽出的关键参数(时间范围、对象、指标……),自由 JSON。', where: '模型抽取' },
|
|
32
|
+
{ name: 'confidence', required: false, meaning: '模型自评把握度 0–1。低于澄清阈值只**标注** lowConfidence,不拦截请求。', where: '模型自评' },
|
|
33
|
+
{ name: 'refs', required: false, meaning: '引用了哪些对象(表、文档、指标),形如 { id, kind, label }。', where: '模型抽取' },
|
|
34
|
+
{ name: 'relation', required: true, meaning: '这轮与已有任务的关系:new_task(开新任务)/ subtask_of(续已有任务)/ unknown。', where: '模型判断,Host 校验 task 是否存在' },
|
|
35
|
+
{ name: 'lineage', required: true, meaning: 'intentId / taskId / subtaskId / runId 四个 id 的链路,下游工具与产物靠它回溯。', where: 'Host 分配,模型可显式续接' },
|
|
36
|
+
{ name: 'source', required: true, meaning: 'model = 模型记录;fallback = 轮次结束模型没记,Host 补的一条 unknown。', where: 'Host 标记' },
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
interface Paper {
|
|
40
|
+
title: string
|
|
41
|
+
authors: string
|
|
42
|
+
venue: string
|
|
43
|
+
url: string
|
|
44
|
+
/** 它对应本设计的哪一点。 */
|
|
45
|
+
relates: string
|
|
46
|
+
note?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const PAPERS: Paper[] = [
|
|
50
|
+
{
|
|
51
|
+
title: 'An Evaluation Dataset for Intent Classification and Out-of-Scope Prediction',
|
|
52
|
+
authors: 'Stefan Larson, Anish Mahendran, Joseph J. Peper, Christopher Clarke, Andrew Lee, Parker Hill, Jonathan Kummerfeld, Kevin Leach, Michael A. Laurenzano, Lingjia Tang, Jason Mars',
|
|
53
|
+
venue: 'EMNLP 2019(CLINC150)',
|
|
54
|
+
url: 'https://aclanthology.org/D19-1131/',
|
|
55
|
+
relates: '「全不命中怎么办」的标准答案:真实流量里必然有落在词表之外的句子,把它硬塞进最近的一类,比多问一句代价大得多。对应本设计的第 2 段(自创 id 或落 unknown)与 lowConfidence 标注。',
|
|
56
|
+
note: 'CLINC150 给出了 150 类 + out-of-scope 的评测集;数据集的标签体系与本产品的 12 类无关。',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
title: 'A Survey of Joint Intent Detection and Slot Filling Models in Natural Language Understanding',
|
|
60
|
+
authors: 'H. Weld, X. Huang, S. Long, J. Poon, S. C. Han',
|
|
61
|
+
venue: 'ACM Computing Surveys, 2022',
|
|
62
|
+
url: 'https://dl.acm.org/doi/10.1145/3547138',
|
|
63
|
+
relates: '说明 intent 与 slot 为什么要一起看:`category`(意图)与 `slots`(参数)是同一个判断的两面。本设计把它们放在同一条记录里,就是为了不让"分类对了但参数丢了"成为常态。',
|
|
64
|
+
note: '这里只借它的问题定义与术语,没有训练任何联合模型。',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
title: 'Efficient Intent Detection with Dual Sentence Encoders',
|
|
68
|
+
authors: 'Iñigo Casanueva, Tadas Temčinas, Daniela Gerz, Matthew Henderson, Ivan Vulić',
|
|
69
|
+
venue: 'NLP4ConvAI @ ACL 2020',
|
|
70
|
+
url: 'https://aclanthology.org/2020.nlp4convai-1.5/',
|
|
71
|
+
relates: '小样本下"用一句自然语言描述一个类别"就能做分类:这正是本设计的做法——预置分类用 `description` + `examples` 描述,而不是训练一个分类头。',
|
|
72
|
+
note: '该论文用双句编码器 + 少量标注样例;本产品直接把描述喂给大模型,没有额外模型。',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
title: 'IntentGPT: Few-shot Intent Discovery with Large Language Models',
|
|
76
|
+
authors: 'Juan Rodriguez, Nicholas Botzer, David Vazquez, Christopher Pal, Marco Pedersoli, Issam H. Laradji',
|
|
77
|
+
venue: 'arXiv:2411.10670, 2024',
|
|
78
|
+
url: 'https://arxiv.org/abs/2411.10670',
|
|
79
|
+
relates: '对应第 2 段"不命中就自己起一个 id":未知意图不应当被丢掉,而应当被发现为新类别。本设计把新 id 写进记录,后续可以据此把它提升为正式分类。',
|
|
80
|
+
note: '该论文用 LLM 做零样本意图发现并迭代扩展标签集;本产品只记录新 id,聚类/提升仍是人工动作。',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
title: 'Recent Neural Methods on Dialogue State Tracking for Task-Oriented Dialogue Systems: A Survey',
|
|
84
|
+
authors: 'Vevake Balaraman, Seyedmostafa Sheikhalishahi, Bernhard Magnini',
|
|
85
|
+
venue: 'SIGDIAL 2021',
|
|
86
|
+
url: 'https://aclanthology.org/2021.sigdial-1.25/',
|
|
87
|
+
relates: '对应 `relation` 与任务链路:多轮对话里"这一轮是在续上一个任务还是开新任务"是对话状态的一部分,不是每轮独立判断。本设计把它显式建模为 new_task / subtask_of。',
|
|
88
|
+
note: '只借它的状态建模视角;本设计用规则 + 会话内已记录的任务集合来判定,没有跑 DST 模型。',
|
|
89
|
+
},
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
function Section({ icon: Icon, title, children }: { icon: typeof CircleHelp; title: string; children: ReactNode }): ReactNode {
|
|
93
|
+
return <Card className={styles.kbCard}>
|
|
94
|
+
<div className={styles.kbCardHead}>
|
|
95
|
+
<div><h3><Icon size={13} />{title}</h3></div>
|
|
96
|
+
</div>
|
|
97
|
+
{children}
|
|
98
|
+
</Card>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function IntentLogicTab(): ReactNode {
|
|
102
|
+
return <div className={styles.viewStack}>
|
|
103
|
+
<Section icon={CircleHelp} title="什么是意图?">
|
|
104
|
+
<p className={styles.kbHint}>
|
|
105
|
+
这里说的「意图」不是要控制流程的指令,而是一条**审计事实**:这一轮用户在做什么、属于哪类业务、
|
|
106
|
+
要调什么动作、引用了哪些对象、和之前哪个任务同属一件事。
|
|
107
|
+
</p>
|
|
108
|
+
<ul className={styles.intentDocList}>
|
|
109
|
+
<li><b>判断完全交给大模型。</b>`intent_record` 只把模型给出的结论落成一条记录,不做关键词匹配、不做状态机、不反问、不阻断当前请求,也不会因为分类错了就让请求失败。</li>
|
|
110
|
+
<li><b>记录不驱动控制流。</b>它服务三件事:事后回溯(这轮到底想干什么)、链路串联(下游工具与产物继承 taskId / subtaskId)、质量观测(哪些轮次低置信度、哪些落到了 unknown)。</li>
|
|
111
|
+
<li><b>模型漏了也不丢。</b>轮次结束时如果没有任何记录,Host 补一条 <code>source: fallback</code> 的 unknown,保证"每轮一条"这个不变式成立。</li>
|
|
112
|
+
</ul>
|
|
113
|
+
</Section>
|
|
114
|
+
|
|
115
|
+
<Section icon={Lightbulb} title="为什么要配置意图?">
|
|
116
|
+
<p className={styles.kbHint}>
|
|
117
|
+
如果什么都不配,模型每轮都要**从零发明一套分类**。同一句话今天叫 data_analysis、明天叫
|
|
118
|
+
sales_query,记录就失去可比性:统计不出趋势,下游也接不住。
|
|
119
|
+
</p>
|
|
120
|
+
<ul className={styles.intentDocList}>
|
|
121
|
+
<li><b>预置分类是"先验"。</b>判定被拆成两段:先拿用户的话逐条比对预置分类的定义与例如说法,命中就直接用该 id;全不命中才自行判断。这让常见意图的判定从"生成"变成"匹配",跨轮次稳定。</li>
|
|
122
|
+
<li><b>判定文案就是提示词。</b>每个分类的 `description` + `examples` 会拼进 `intent_record` 的工具描述,模型每次调用都看得到。写得越具体,越不需要临场发挥。</li>
|
|
123
|
+
<li><b>词表要能被改。</b>业务会变:今天做"测试评审",明天做"合规检查"。分类、act 都在「意图配置」里就地增删改,保存后下一轮生效,不需要重启。</li>
|
|
124
|
+
<li><b>不追求一次分对。</b>拿不准就标 lowConfidence 或落 unknown,事后能筛出来复核——比强行归类更诚实。</li>
|
|
125
|
+
</ul>
|
|
126
|
+
</Section>
|
|
127
|
+
|
|
128
|
+
<Section icon={ListTree} title="意图的描述是什么?">
|
|
129
|
+
<p className={styles.kbHint}>
|
|
130
|
+
一条意图记录把"这一轮"描述成四个维度,它们互相正交,缺一个都会让回溯变模糊:
|
|
131
|
+
</p>
|
|
132
|
+
<div className={styles.intentDocGrid}>
|
|
133
|
+
<div><strong>做什么(act)</strong><span>问 / 让做 / 补充 / 确认 / 取消 …… 共 16 类,可配置。</span></div>
|
|
134
|
+
<div><strong>哪一类(category)</strong><span>数据分析 / 知识构建 / 产物交付 …… 12 类出厂预置,可配置。</span></div>
|
|
135
|
+
<div><strong>要调什么(predicate)</strong><span>最终落到哪个工具,例如 query_babel_data。</span></div>
|
|
136
|
+
<div><strong>和谁一起(relation + lineage)</strong><span>新任务还是续任务,以及 intentId / taskId / subtaskId / runId 链路。</span></div>
|
|
137
|
+
</div>
|
|
138
|
+
<p className={styles.kbHint}>
|
|
139
|
+
另一个维度是**把握度**:`confidence` 低于澄清阈值时记录被标成 lowConfidence。它只影响展示与筛选,
|
|
140
|
+
不改变这一轮的执行。
|
|
141
|
+
</p>
|
|
142
|
+
</Section>
|
|
143
|
+
|
|
144
|
+
<Section icon={ClipboardCheck} title="schema 分别是什么含义?">
|
|
145
|
+
<div className={styles.intentDocTableWrap}>
|
|
146
|
+
<table className={styles.intentDocTable}>
|
|
147
|
+
<thead><tr><th>字段</th><th>必填</th><th>含义</th><th>谁来填</th></tr></thead>
|
|
148
|
+
<tbody>
|
|
149
|
+
{RECORD_FIELDS.map(field => <tr key={field.name}>
|
|
150
|
+
<td><code>{field.name}</code></td>
|
|
151
|
+
<td>{field.required ? '✓' : ''}</td>
|
|
152
|
+
<td>{field.meaning}</td>
|
|
153
|
+
<td>{field.where}</td>
|
|
154
|
+
</tr>)}
|
|
155
|
+
</tbody>
|
|
156
|
+
</table>
|
|
157
|
+
</div>
|
|
158
|
+
<p className={styles.kbHint}>
|
|
159
|
+
配置侧还有三个 schema:<code>IntentCategory</code>(id / label / route / description / examples / enabled)、
|
|
160
|
+
<code>IntentActDef</code>(id / label / description / enabled)、以及 predicate 字符串表。
|
|
161
|
+
它们只决定"提示词给模型什么选项",**不是校验闸门**:模型写了一个没配置的 act 或 category,
|
|
162
|
+
记录照样落库,只在 `warnings` 里说明。
|
|
163
|
+
</p>
|
|
164
|
+
</Section>
|
|
165
|
+
|
|
166
|
+
<Section icon={Library} title="引用了什么论文?">
|
|
167
|
+
<p className={styles.kbHint}>
|
|
168
|
+
诚实说明:**这套实现没有复现任何一篇论文的模型**。它是工程取舍的产物;下面是同一批问题的
|
|
169
|
+
公开研究,用来说明这些设计点不是凭空来的。每条都标了它对应哪一处设计。
|
|
170
|
+
</p>
|
|
171
|
+
<ul className={styles.intentPaperList}>
|
|
172
|
+
{PAPERS.map(paper => <li key={paper.url}>
|
|
173
|
+
<a href={paper.url} target="_blank" rel="noreferrer">{paper.title}</a>
|
|
174
|
+
<span className={styles.intentPaperMeta}>{paper.authors} · {paper.venue}</span>
|
|
175
|
+
<p>{paper.relates}</p>
|
|
176
|
+
{paper.note && <p className={styles.intentPaperNote}>注:{paper.note}</p>}
|
|
177
|
+
</li>)}
|
|
178
|
+
</ul>
|
|
179
|
+
<p className={styles.kbHint}>
|
|
180
|
+
另外两处术语来源不在论文里:<b>act / predicate</b> 的分法沿用本插件既有的意图契约(issue 0012),
|
|
181
|
+
<b>lineage</b> 是 DSH 自身的链路约定(session / turn / intent / task / subtask / run 六个 id)。
|
|
182
|
+
</p>
|
|
183
|
+
</Section>
|
|
184
|
+
</div>
|
|
185
|
+
}
|