@dsh-plus/web-search-services 0.1.20 → 0.2.0
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/lib/client.js +1203 -178
- package/lib/index.d.ts +10 -83
- package/lib/index.js +16 -649
- package/package.json +15 -15
- package/src/client/card.tsx +130 -72
- package/src/client/client.ts +19 -7
- package/src/client/draft.ts +29 -89
- package/src/client/i18n.ts +109 -52
- package/src/client/styles.ts +10 -7
- package/src/config.ts +24 -74
- package/src/index.ts +29 -14
- package/scripts/README.md +0 -34
- package/scripts/context7.py +0 -259
- package/scripts/env_loader.py +0 -46
- package/scripts/exa.py +0 -251
- package/scripts/fetch.py +0 -938
- package/scripts/fetch_playwright.py +0 -525
- package/scripts/openai_chat.py +0 -570
- package/scripts/search.py +0 -962
- package/scripts/tavily.py +0 -609
- package/src/args.ts +0 -30
- package/src/env-file.ts +0 -49
- package/src/normalize.ts +0 -173
- package/src/provider.ts +0 -188
- package/src/python.ts +0 -193
- package/src/runner.ts +0 -140
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/web-search-services",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "dsh-plus service plugin: web_search
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "dsh-plus service plugin: web_search 多后端聚合——经官方 ctx.web provider 扩展点接入 Tavily/Exa/OpenAI 兼容/Context7/DeepSeek Search(顺序尝试 + 失败自动回退 + 官方 credentials 密钥),官方 web_search 工具与输出格式零修改",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"lib",
|
|
21
|
-
"src"
|
|
22
|
-
"scripts"
|
|
21
|
+
"src"
|
|
23
22
|
],
|
|
24
23
|
"dsh": {
|
|
25
24
|
"client": {
|
|
@@ -32,21 +31,22 @@
|
|
|
32
31
|
}
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|
|
35
|
-
"@dsh-plus/shared": "0.1.
|
|
34
|
+
"@dsh-plus/shared": "0.1.43",
|
|
35
|
+
"@dsh-plus/web-providers-core": "0.1.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@deepseek-ai/cordis": "^4.0.5
|
|
39
|
-
"@deepseek-ai/dsh-home-paths": "^0.2.1-
|
|
40
|
-
"@deepseek-ai/dsh-settings": "^0.2.1-
|
|
41
|
-
"@deepseek-ai/dsh-web": "^0.2.1-
|
|
42
|
-
"@deepseek-ai/schemastery": "^3.18.5
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.5",
|
|
39
|
+
"@deepseek-ai/dsh-home-paths": "^0.2.1-rc.2",
|
|
40
|
+
"@deepseek-ai/dsh-settings": "^0.2.1-rc.2",
|
|
41
|
+
"@deepseek-ai/dsh-web": "^0.2.1-rc.2",
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.5"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@deepseek-ai/cordis": "4.0.5
|
|
46
|
-
"@deepseek-ai/dsh-home-paths": "0.2.1-
|
|
47
|
-
"@deepseek-ai/dsh-settings": "0.2.1-
|
|
48
|
-
"@deepseek-ai/dsh-web": "0.2.1-
|
|
49
|
-
"@deepseek-ai/schemastery": "3.18.5
|
|
45
|
+
"@deepseek-ai/cordis": "4.0.5",
|
|
46
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-settings": "0.2.1-rc.2",
|
|
48
|
+
"@deepseek-ai/dsh-web": "0.2.1-rc.2",
|
|
49
|
+
"@deepseek-ai/schemastery": "3.18.5",
|
|
50
50
|
"@types/react": "~18.3.31",
|
|
51
51
|
"react": "^18.2.0"
|
|
52
52
|
},
|
package/src/client/card.tsx
CHANGED
|
@@ -1,56 +1,131 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 「搜索后端聚合」配置卡片:经 injectPluginConfigCard 注册(插件页
|
|
3
3
|
* plugins.row.config / plugins.bundle.config,view 分发 summary/page)。
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* useNamespaceDraft);纯校验逻辑在同目录 draft.ts(node --test 可直测)。
|
|
4
|
+
*
|
|
5
|
+
* 结构:总开关 / 暴露条目 / 代理 → 后端链(条目块)→ 密钥(官方 credentials)。
|
|
6
|
+
* 字段与默认值来自 `@dsh-plus/web-providers-core/spec`,校验逻辑来自其 `card` 工具箱,
|
|
7
|
+
* 本文件只负责布局与文案。
|
|
9
8
|
* @module web-search-services/client/card
|
|
10
9
|
*/
|
|
11
|
-
|
|
12
10
|
import {
|
|
13
11
|
CardChrome,
|
|
14
12
|
CardLoading,
|
|
13
|
+
type CardStatusState,
|
|
14
|
+
CheckRow,
|
|
15
|
+
type CredentialsRemoteFace,
|
|
16
|
+
CredentialsSection,
|
|
15
17
|
type NamespaceSettingsApi,
|
|
16
18
|
type PluginConfigViewProps,
|
|
17
19
|
type Scope,
|
|
18
20
|
TextField,
|
|
19
21
|
useNamespaceDraft,
|
|
20
22
|
} from '@dsh-plus/shared/client'
|
|
21
|
-
import type {
|
|
23
|
+
import type { IssueCode } from '@dsh-plus/web-providers-core/card'
|
|
24
|
+
import { EntryEditor, type EntryEditorLabels } from '@dsh-plus/web-providers-core/card-ui'
|
|
25
|
+
import { type ReactElement, useMemo } from 'react'
|
|
22
26
|
|
|
23
27
|
import {
|
|
24
28
|
type ConfigValue,
|
|
25
29
|
type Draft,
|
|
26
30
|
draftFromValue,
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
draftValid,
|
|
32
|
+
refsOf,
|
|
33
|
+
SEARCH_KIT,
|
|
29
34
|
toPatch,
|
|
30
35
|
} from './draft.ts'
|
|
31
|
-
import type { Translate } from './i18n.ts'
|
|
36
|
+
import type { DictKey, Translate } from './i18n.ts'
|
|
32
37
|
|
|
33
38
|
export interface CardProps extends PluginConfigViewProps {
|
|
34
39
|
t: Translate
|
|
35
40
|
scope: Scope
|
|
36
41
|
api: NamespaceSettingsApi
|
|
42
|
+
credentials: CredentialsRemoteFace
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
export function WebSearchServicesCard(props: CardProps): ReactElement | string | null {
|
|
40
|
-
const { t, scope, api } = props
|
|
46
|
+
const { t, scope, api, credentials } = props
|
|
41
47
|
const { draft, dirty, saving, status, disabled, edit, reset, save } = useNamespaceDraft<
|
|
42
48
|
ConfigValue,
|
|
43
49
|
Draft
|
|
44
50
|
>({ scope, api, from: draftFromValue, patch: toPatch, saveFailedLabel: t('saveFailed') })
|
|
45
|
-
// 插件页 page 视图为表单落地页,默认展开;旧槽位无 view,保持折叠。
|
|
46
51
|
|
|
47
52
|
// 插件页 summary 视图只出一行简介(hooks 已全部落定,可安全提前返回)。
|
|
48
53
|
if (props.view === 'summary') return t('summaryLine')
|
|
49
|
-
|
|
50
54
|
if (draft === null) return <CardLoading prefix="wsv" text={t('loading')} />
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
return (
|
|
57
|
+
<CardBody
|
|
58
|
+
t={t}
|
|
59
|
+
draft={draft}
|
|
60
|
+
status={status}
|
|
61
|
+
disabled={disabled}
|
|
62
|
+
dirty={dirty}
|
|
63
|
+
saving={saving}
|
|
64
|
+
credentials={credentials}
|
|
65
|
+
onEdit={edit}
|
|
66
|
+
onReset={reset}
|
|
67
|
+
onSave={save}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 校验代号 → 文案键。 */
|
|
73
|
+
const ISSUE_KEYS: Record<IssueCode, DictKey> = {
|
|
74
|
+
'id-empty': 'issueIdEmpty',
|
|
75
|
+
required: 'issueRequired',
|
|
76
|
+
'id-duplicate': 'issueIdDuplicate',
|
|
77
|
+
'type-unknown': 'issueTypeUnknown',
|
|
78
|
+
int: 'issueInt',
|
|
79
|
+
'below-min': 'issueBelowMin',
|
|
80
|
+
'above-max': 'issueAboveMax',
|
|
81
|
+
option: 'issueOption',
|
|
82
|
+
json: 'issueJson',
|
|
83
|
+
'json-array': 'issueJsonArray',
|
|
84
|
+
'json-object': 'issueJsonObject',
|
|
85
|
+
'json-strings': 'issueJsonStrings',
|
|
86
|
+
'library-shape': 'issueLibraryShape',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 条目编辑器的已翻译文案(字段标签用 spec 的技术名,不做翻译)。 */
|
|
90
|
+
function editorLabels(t: Translate): EntryEditorLabels {
|
|
91
|
+
return {
|
|
92
|
+
title: t('entriesTitle'),
|
|
93
|
+
description: t('entriesHint'),
|
|
94
|
+
addEntry: t('addEntry'),
|
|
95
|
+
empty: t('entriesEmpty'),
|
|
96
|
+
unknownType: t('unknownType'),
|
|
97
|
+
entryId: t('entryId'),
|
|
98
|
+
entryIdHint: t('entryIdHint'),
|
|
99
|
+
entryEnabled: t('entryEnabled'),
|
|
100
|
+
moveUp: t('moveUp'),
|
|
101
|
+
moveDown: t('moveDown'),
|
|
102
|
+
remove: t('remove'),
|
|
103
|
+
unsetOption: t('unsetOption'),
|
|
104
|
+
csvHint: t('csvHint'),
|
|
105
|
+
librariesHint: t('librariesHint'),
|
|
106
|
+
headersHint: t('headersHint'),
|
|
107
|
+
issue: (code) => t(ISSUE_KEYS[code]),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface CardBodyProps {
|
|
112
|
+
t: Translate
|
|
113
|
+
draft: Draft
|
|
114
|
+
status: CardStatusState
|
|
115
|
+
disabled: boolean
|
|
116
|
+
dirty: boolean
|
|
117
|
+
saving: boolean
|
|
118
|
+
credentials: CredentialsRemoteFace
|
|
119
|
+
onEdit(patch: Partial<Draft>): void
|
|
120
|
+
onReset(): void
|
|
121
|
+
onSave(): void
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function CardBody(props: CardBodyProps): ReactElement {
|
|
125
|
+
const { t, draft, status, disabled, dirty, saving, credentials } = props
|
|
126
|
+
const refs = useMemo(() => refsOf(draft), [draft])
|
|
127
|
+
const valid = draftValid(draft)
|
|
128
|
+
|
|
54
129
|
return (
|
|
55
130
|
<CardChrome
|
|
56
131
|
prefix="wsv"
|
|
@@ -61,83 +136,66 @@ export function WebSearchServicesCard(props: CardProps): ReactElement | string |
|
|
|
61
136
|
readOnlyNotice={disabled ? t('readOnly') : undefined}
|
|
62
137
|
status={status}
|
|
63
138
|
actions={[
|
|
64
|
-
{
|
|
65
|
-
key: 'discard',
|
|
66
|
-
label: t('discard'),
|
|
67
|
-
disabled: !dirty || saving,
|
|
68
|
-
onClick: reset,
|
|
69
|
-
},
|
|
139
|
+
{ key: 'discard', label: t('discard'), disabled: !dirty || saving, onClick: props.onReset },
|
|
70
140
|
{
|
|
71
141
|
key: 'save',
|
|
72
142
|
label: t(saving ? 'saving' : 'save'),
|
|
73
143
|
variant: 'primary',
|
|
74
|
-
disabled: !dirty ||
|
|
75
|
-
onClick:
|
|
144
|
+
disabled: !dirty || !valid || saving || disabled,
|
|
145
|
+
onClick: props.onSave,
|
|
76
146
|
},
|
|
77
147
|
]}
|
|
78
148
|
>
|
|
79
|
-
<
|
|
149
|
+
<CheckRow
|
|
80
150
|
prefix="wsv"
|
|
81
|
-
id="wsv-
|
|
82
|
-
label={t('
|
|
83
|
-
|
|
84
|
-
value={draft.scriptPath}
|
|
151
|
+
id="wsv-enabled"
|
|
152
|
+
label={t('enabled')}
|
|
153
|
+
checked={draft.enabled}
|
|
85
154
|
disabled={disabled}
|
|
86
|
-
onEdit={(
|
|
155
|
+
onEdit={(checked) => props.onEdit({ enabled: checked })}
|
|
87
156
|
/>
|
|
88
|
-
<
|
|
157
|
+
<CheckRow
|
|
89
158
|
prefix="wsv"
|
|
90
|
-
id="wsv-
|
|
91
|
-
label={t('
|
|
92
|
-
|
|
93
|
-
value={draft.envFile}
|
|
159
|
+
id="wsv-expose"
|
|
160
|
+
label={t('exposeEntries')}
|
|
161
|
+
checked={draft.exposeEntries}
|
|
94
162
|
disabled={disabled}
|
|
95
|
-
onEdit={(
|
|
163
|
+
onEdit={(checked) => props.onEdit({ exposeEntries: checked })}
|
|
96
164
|
/>
|
|
165
|
+
<p className="wsv-hint">{t('exposeEntriesHint')}</p>
|
|
97
166
|
<TextField
|
|
98
167
|
prefix="wsv"
|
|
99
|
-
id="wsv-
|
|
100
|
-
label={t('
|
|
101
|
-
hint={t('
|
|
102
|
-
value={draft.
|
|
168
|
+
id="wsv-proxy"
|
|
169
|
+
label={t('proxy')}
|
|
170
|
+
hint={t('proxyHint')}
|
|
171
|
+
value={draft.proxy}
|
|
103
172
|
disabled={disabled}
|
|
104
|
-
onEdit={(
|
|
173
|
+
onEdit={(value) => props.onEdit({ proxy: value })}
|
|
105
174
|
/>
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
<label className="wsv-label" htmlFor="wsv-priority">
|
|
109
|
-
{t('priority')}
|
|
110
|
-
</label>
|
|
111
|
-
</div>
|
|
112
|
-
<textarea
|
|
113
|
-
id="wsv-priority"
|
|
114
|
-
className="wsv-priority"
|
|
115
|
-
rows={3}
|
|
116
|
-
value={draft.priorityText}
|
|
117
|
-
disabled={disabled}
|
|
118
|
-
onChange={(event) => edit({ priorityText: event.target.value })}
|
|
119
|
-
/>
|
|
120
|
-
<p className={priorityInvalid ? 'wsv-invalid' : 'wsv-hint'}>
|
|
121
|
-
{priorityInvalid ? t('invalidPriority') : t('priorityHint')}
|
|
122
|
-
</p>
|
|
123
|
-
</div>
|
|
124
|
-
<TextField
|
|
175
|
+
<p className="wsv-hint">{t('enabledHint')}</p>
|
|
176
|
+
<EntryEditor
|
|
125
177
|
prefix="wsv"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
value={draft.timeoutMsText}
|
|
130
|
-
numeric
|
|
178
|
+
kit={SEARCH_KIT}
|
|
179
|
+
labels={editorLabels(t)}
|
|
180
|
+
draft={draft}
|
|
131
181
|
disabled={disabled}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
182
|
+
onChange={(next) => props.onEdit(next)}
|
|
183
|
+
/>
|
|
184
|
+
<CredentialsSection
|
|
185
|
+
prefix="wsv"
|
|
186
|
+
title={t('secretsTitle')}
|
|
187
|
+
description={t('secretsBody')}
|
|
188
|
+
rows={refs.map((ref) => ({ ref, label: ref }))}
|
|
189
|
+
face={credentials}
|
|
190
|
+
labels={{
|
|
191
|
+
save: t('save'),
|
|
192
|
+
clear: t('secretClear'),
|
|
193
|
+
configured: t('secretConfigured'),
|
|
194
|
+
missing: t('secretMissing'),
|
|
195
|
+
shadowed: t('secretShadowed'),
|
|
196
|
+
failed: t('secretFailed'),
|
|
197
|
+
}}
|
|
135
198
|
/>
|
|
136
|
-
<div className="wsv-keys">
|
|
137
|
-
<p className="wsv-keysTitle">{t('keysGroup')}</p>
|
|
138
|
-
<p className="wsv-hint">{t('keysHint')}</p>
|
|
139
|
-
<p className="wsv-hint">{t('keysUserLayerHint')}</p>
|
|
140
|
-
</div>
|
|
141
199
|
<p className="wsv-hint">{t('reloadHint')}</p>
|
|
142
200
|
</CardChrome>
|
|
143
201
|
)
|
package/src/client/client.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 浏览器半入口:注册 locale 字典 + 注册「搜索后端聚合」卡片
|
|
3
|
-
* (injectPluginConfigCard 两槽位:插件页 plugins.row.config /
|
|
4
|
-
* plugins.bundle.config)。
|
|
3
|
+
* (injectPluginConfigCard 两槽位:插件页 plugins.row.config / plugins.bundle.config)。
|
|
5
4
|
* 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
|
|
6
5
|
* (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
|
|
7
|
-
* 配置读写经 ctx.remote.settings
|
|
8
|
-
*
|
|
6
|
+
* 配置读写经 ctx.remote.settings、密钥读写经 ctx.remote.credentials(均来自
|
|
7
|
+
* @dsh-plus/shared/client 的窄面)。
|
|
9
8
|
* @module web-search-services/client
|
|
10
9
|
*/
|
|
11
10
|
import type { Context } from '@deepseek-ai/cordis'
|
|
12
11
|
import {
|
|
13
12
|
createNamespaceApi,
|
|
14
13
|
createSettingsScope,
|
|
14
|
+
credentialsFace,
|
|
15
15
|
injectPluginConfigCard,
|
|
16
16
|
type PluginClientContext,
|
|
17
|
+
type RemoteWithCredentials,
|
|
17
18
|
} from '@dsh-plus/shared/client'
|
|
18
19
|
|
|
19
20
|
import { SETTINGS_NS } from '../ns.ts'
|
|
@@ -23,8 +24,18 @@ import { injectStyle } from './styles.ts'
|
|
|
23
24
|
|
|
24
25
|
export const name = 'dsh-plus-web-search-services'
|
|
25
26
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject
|
|
29
|
+
* 管包加载顺序)。`remote.credentials` 为硬依赖:卡片是密钥的唯一配置面,
|
|
30
|
+
* 命名空间缺席时整张卡片都不该挂(官方设置页同样如此声明)。
|
|
31
|
+
*/
|
|
32
|
+
export const inject = [
|
|
33
|
+
'slots',
|
|
34
|
+
'locale',
|
|
35
|
+
'remote',
|
|
36
|
+
'remote.settings',
|
|
37
|
+
'remote.credentials',
|
|
38
|
+
] as const
|
|
28
39
|
|
|
29
40
|
/** 宿主窄面收编在 @dsh-plus/shared/client;TKey 传入本包 DictKey 使键名受检。 */
|
|
30
41
|
type ClientContext = PluginClientContext<DictKey>
|
|
@@ -41,11 +52,12 @@ export function apply(ctx: Context): void {
|
|
|
41
52
|
c.effect(() => c.locale.register(NS, { zh, en }), 'web-search-services: locale')
|
|
42
53
|
const scope = createSettingsScope(c, SETTINGS_NS, 'web-search-services: settings scope')
|
|
43
54
|
const api = createNamespaceApi(c.get('remote').settings, SETTINGS_NS)
|
|
55
|
+
const credentials = credentialsFace(c.get('remote') as RemoteWithCredentials)
|
|
44
56
|
injectPluginConfigCard(c.slots, {
|
|
45
57
|
ns: SETTINGS_NS,
|
|
46
58
|
rowId: 'dsh-plus-web-search-services',
|
|
47
59
|
pkg: '@dsh-plus/web-search-services',
|
|
48
60
|
component: WebSearchServicesCard,
|
|
49
|
-
inject: () => ({ t: c.locale.bind(NS), scope, api }),
|
|
61
|
+
inject: () => ({ t: c.locale.bind(NS), scope, api, credentials }),
|
|
50
62
|
})
|
|
51
63
|
}
|
package/src/client/draft.ts
CHANGED
|
@@ -1,92 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* 搜索插件的卡片逻辑绑定:字段清单/默认值/校验全部来自
|
|
3
|
+
* `@dsh-plus/web-providers-core`(与后端 schema 同源),本文件只把搜索那份 spec 绑上。
|
|
4
4
|
* @module web-search-services/client/draft
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function draftFromValue(value: ConfigValue): Draft {
|
|
36
|
-
return {
|
|
37
|
-
scriptPath: value.scriptPath,
|
|
38
|
-
envFile: value.envFile,
|
|
39
|
-
python: value.python,
|
|
40
|
-
priorityText: value.priority.join('\n'),
|
|
41
|
-
timeoutMsText: String(value.timeoutMs),
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** 优先级文本 → 后端序列;顺带返回无法识别的行(供 UI 提示)。 */
|
|
46
|
-
export function parsePriority(text: string): {
|
|
47
|
-
backends: SearchBackend[]
|
|
48
|
-
unknown: string[]
|
|
49
|
-
duplicated: string[]
|
|
50
|
-
} {
|
|
51
|
-
const backends: SearchBackend[] = []
|
|
52
|
-
const unknown: string[] = []
|
|
53
|
-
const duplicated: string[] = []
|
|
54
|
-
for (const raw of text.split('\n')) {
|
|
55
|
-
const line = raw.trim()
|
|
56
|
-
if (line === '') continue
|
|
57
|
-
const match = SEARCH_BACKENDS.find((backend) => backend === line)
|
|
58
|
-
if (match === undefined) {
|
|
59
|
-
unknown.push(line)
|
|
60
|
-
continue
|
|
61
|
-
}
|
|
62
|
-
if (backends.includes(match)) {
|
|
63
|
-
duplicated.push(line)
|
|
64
|
-
continue
|
|
65
|
-
}
|
|
66
|
-
backends.push(match)
|
|
67
|
-
}
|
|
68
|
-
return { backends, unknown, duplicated }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** 优先级是否可用:至少一个后端、无未知项、无重复项。 */
|
|
72
|
-
export function priorityTextOk(text: string): boolean {
|
|
73
|
-
const { backends, unknown, duplicated } = parsePriority(text)
|
|
74
|
-
return backends.length > 0 && unknown.length === 0 && duplicated.length === 0
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** 超时是否可用:≥1000 的整数(对齐 schema 的 .natural().min(1000))。 */
|
|
78
|
-
export function timeoutTextOk(text: string): boolean {
|
|
79
|
-
if (!/^\d+$/.test(text.trim())) return false
|
|
80
|
-
return Number(text) >= TIMEOUT_MIN_MS
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** 草稿 → 提交形状(调用方保证已通过校验)。 */
|
|
84
|
-
export function toPatch(draft: Draft): Record<string, unknown> {
|
|
85
|
-
return {
|
|
86
|
-
scriptPath: draft.scriptPath,
|
|
87
|
-
envFile: draft.envFile,
|
|
88
|
-
python: draft.python,
|
|
89
|
-
priority: parsePriority(draft.priorityText).backends,
|
|
90
|
-
timeoutMs: Number(draft.timeoutMsText),
|
|
91
|
-
}
|
|
92
|
-
}
|
|
6
|
+
import {
|
|
7
|
+
type ConfigValue,
|
|
8
|
+
createCardKit,
|
|
9
|
+
type Draft,
|
|
10
|
+
type EntryDraft,
|
|
11
|
+
type FieldValue,
|
|
12
|
+
type IssueCode,
|
|
13
|
+
} from '@dsh-plus/web-providers-core/card'
|
|
14
|
+
import { SEARCH_ENTRY_SPECS } from '@dsh-plus/web-providers-core/spec'
|
|
15
|
+
|
|
16
|
+
export type { ConfigValue, Draft, EntryDraft, FieldValue, IssueCode }
|
|
17
|
+
|
|
18
|
+
export const SEARCH_KIT = createCardKit(SEARCH_ENTRY_SPECS)
|
|
19
|
+
|
|
20
|
+
const kit = SEARCH_KIT
|
|
21
|
+
|
|
22
|
+
export const entryTypes = kit.entryTypes
|
|
23
|
+
export const specOf = kit.specOf
|
|
24
|
+
export const draftFromValue = kit.draftFromValue
|
|
25
|
+
export const addEntry = kit.addEntry
|
|
26
|
+
export const removeEntry = kit.removeEntry
|
|
27
|
+
export const moveEntry = kit.moveEntry
|
|
28
|
+
export const entryIssues = kit.entryIssues
|
|
29
|
+
export const issuesOf = kit.issuesOf
|
|
30
|
+
export const draftValid = kit.draftValid
|
|
31
|
+
export const toPatch = kit.toPatch
|
|
32
|
+
export const refsOf = kit.refsOf
|