dsh-auto-sight 0.4.7 → 0.4.9
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 -21
- package/README.md +81 -81
- package/README.zh-CN.md +81 -81
- package/client.js +349 -349
- package/cordis.patch.yml +6 -6
- package/icon.svg +6 -6
- package/index.js +672 -627
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +60 -60
- package/test/auto-vision.test.mjs +550 -480
- package/test/client-render.mjs +158 -158
package/test/client-render.mjs
CHANGED
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
|
|
3
|
-
let definition
|
|
4
|
-
globalThis.window = { __ModuleLoader__: { load(value) { definition = value } } }
|
|
5
|
-
|
|
6
|
-
let hookIndex = 0
|
|
7
|
-
let hooks = []
|
|
8
|
-
let callback
|
|
9
|
-
let effectQueued = false
|
|
10
|
-
let renderCurrent
|
|
11
|
-
let registered
|
|
12
|
-
let mutation
|
|
13
|
-
|
|
14
|
-
function rerender() {
|
|
15
|
-
if (renderCurrent) renderCurrent()
|
|
16
|
-
}
|
|
17
|
-
function setAt(index, next) {
|
|
18
|
-
hooks[index] = typeof next === 'function' ? next(hooks[index]) : next
|
|
19
|
-
rerender()
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const React = {
|
|
23
|
-
createElement(type, props, ...children) {
|
|
24
|
-
return { type, props: { ...(props || {}), children: children.flat(Infinity) } }
|
|
25
|
-
},
|
|
26
|
-
useState(initial) {
|
|
27
|
-
const index = hookIndex++
|
|
28
|
-
if (!(index in hooks)) hooks[index] = typeof initial === 'function' ? initial() : initial
|
|
29
|
-
return [hooks[index], (next) => setAt(index, next)]
|
|
30
|
-
},
|
|
31
|
-
useCallback(fn) {
|
|
32
|
-
callback ??= fn
|
|
33
|
-
return callback
|
|
34
|
-
},
|
|
35
|
-
useEffect(fn) {
|
|
36
|
-
if (effectQueued) return
|
|
37
|
-
effectQueued = true
|
|
38
|
-
queueMicrotask(fn)
|
|
39
|
-
},
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const dict = {
|
|
43
|
-
nav: 'Auto Vision', title: 'Auto Vision', intro: '', target: 'Vision model', auto: 'Auto',
|
|
44
|
-
hint: '', refresh: 'Refresh', loading: 'Loading', saving: 'Saving', saved: 'Saved',
|
|
45
|
-
loadFailed: 'Load failed', saveFailed: 'Save failed', noModels: 'None', provider: 'API',
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const settingsView = {
|
|
49
|
-
ns: 'auto-vision',
|
|
50
|
-
revision: 3,
|
|
51
|
-
value: { visionProvider: 'p', visionModel: 'vision' },
|
|
52
|
-
}
|
|
53
|
-
const providerViews = [
|
|
54
|
-
{ ns: 'llm-p', revision: 1, value: { providers: { p: { models: [
|
|
55
|
-
{ id: 'vision', name: 'Vision', input: ['text', 'image'] },
|
|
56
|
-
{ id: 'text', name: 'Text', input: ['text'] },
|
|
57
|
-
{ id: 'DeepSeek-V4-Flash-Vision-Exp', name: 'DeepSeek-V4-Flash-Vision-Exp' },
|
|
58
|
-
] } } } },
|
|
59
|
-
{ ns: 'llm-q', revision: 2, value: { providers: { q: { models: [
|
|
60
|
-
{ id: 'vision2', name: 'Vision 2', input: ['image'] },
|
|
61
|
-
] } } } },
|
|
62
|
-
]
|
|
63
|
-
const ctx = {
|
|
64
|
-
effect(fn) { fn() },
|
|
65
|
-
locale: {
|
|
66
|
-
register() { return () => {} },
|
|
67
|
-
bind() { return (key) => dict[key] ?? key },
|
|
68
|
-
},
|
|
69
|
-
slots: {
|
|
70
|
-
inject(name, fn) { fn() },
|
|
71
|
-
register(reg, component) { registered = { reg, component } },
|
|
72
|
-
},
|
|
73
|
-
remote: {
|
|
74
|
-
settings: {
|
|
75
|
-
async describe() {
|
|
76
|
-
return { ok: true, value: { writable: true, namespaces: [settingsView, ...providerViews] } }
|
|
77
|
-
},
|
|
78
|
-
async mutate(ns, ops, revision) {
|
|
79
|
-
mutation = { ns, ops, revision }
|
|
80
|
-
return { ok: true, value: { ns, revision: revision + 1, value: { visionProvider: 'q', visionModel: 'vision2' } } }
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
llm: {
|
|
84
|
-
async listConfigurableProviders() {
|
|
85
|
-
return { ok: true, value: [
|
|
86
|
-
{ provider: 'p', displayName: 'Provider P', settingsNs: 'llm-p', settingsPath: ['providers', 'p'] },
|
|
87
|
-
{ provider: 'q', displayName: 'Provider Q', settingsNs: 'llm-q', settingsPath: ['providers', 'q'] },
|
|
88
|
-
] }
|
|
89
|
-
},
|
|
90
|
-
async listProviders() {
|
|
91
|
-
return { ok: true, value: [{ id: 'p' }, { id: 'q' }] }
|
|
92
|
-
},
|
|
93
|
-
async discoverModels() {
|
|
94
|
-
// Deliberately omit q's declared vision2: the official endpoint may
|
|
95
|
-
// not echo every configured alias, and declared models must still show.
|
|
96
|
-
return { ok: true, value: [
|
|
97
|
-
{ id: 'vision', name: 'Vision', inputModalities: ['text', 'image'] },
|
|
98
|
-
{ id: 'text', name: 'Text', inputModalities: ['text'] },
|
|
99
|
-
{ id: 'GLM-4.6V', name: 'GLM-4.6V', inputModalities: ['text', 'image'] },
|
|
100
|
-
{ id: 'GLM-5.3', name: 'GLM-5.3', inputModalities: ['text'] },
|
|
101
|
-
] }
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
await import('../client.js')
|
|
108
|
-
const factory = definition.factory
|
|
109
|
-
const plugin = factory((id) => {
|
|
110
|
-
if (id === 'react') return React
|
|
111
|
-
throw new Error(`unexpected require: ${id}`)
|
|
112
|
-
})
|
|
113
|
-
plugin.apply(ctx)
|
|
114
|
-
assert.equal(registered.reg.name, 'settings.section')
|
|
115
|
-
assert.equal(registered.reg.id, 'auto-vision')
|
|
116
|
-
assert.equal(typeof registered.reg.label(), 'string')
|
|
117
|
-
|
|
118
|
-
function render() {
|
|
119
|
-
hookIndex = 0
|
|
120
|
-
return registered.component({})
|
|
121
|
-
}
|
|
122
|
-
renderCurrent = () => { render() }
|
|
123
|
-
render()
|
|
124
|
-
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
125
|
-
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
126
|
-
|
|
127
|
-
function findAll(node, type, out = []) {
|
|
128
|
-
if (!node || typeof node !== 'object') return out
|
|
129
|
-
if (node.type === type) out.push(node)
|
|
130
|
-
for (const child of node.props?.children || []) findAll(child, type, out)
|
|
131
|
-
return out
|
|
132
|
-
}
|
|
133
|
-
const tree = render()
|
|
134
|
-
const select = findAll(tree, 'select')[0]
|
|
135
|
-
assert.ok(select, 'select rendered')
|
|
136
|
-
assert.equal(select.props.value, 'p|vision')
|
|
137
|
-
const optionValues = findAll(tree, 'option').map((option) => option.props.value)
|
|
138
|
-
assert.deepEqual(optionValues, [
|
|
139
|
-
'', 'p|vision', 'q|vision2', 'p|DeepSeek-V4-Flash-Vision-Exp',
|
|
140
|
-
])
|
|
141
|
-
const checkbox = findAll(tree, 'input').find((node) => node.props.type === 'checkbox')
|
|
142
|
-
assert.ok(checkbox, 'candidate toggle rendered')
|
|
143
|
-
checkbox.props.onChange({ target: { checked: true } })
|
|
144
|
-
assert.ok(findAll(render(), 'option').some((option) => option.props.value === 'p|GLM-4.6V'))
|
|
145
|
-
|
|
146
|
-
await select.props.onChange({ target: { value: 'q|vision2' } })
|
|
147
|
-
assert.deepEqual(mutation, {
|
|
148
|
-
ns: 'auto-vision',
|
|
149
|
-
ops: [
|
|
150
|
-
{ op: 'set', path: ['visionProvider'], value: 'q' },
|
|
151
|
-
{ op: 'set', path: ['visionModel'], value: 'vision2' },
|
|
152
|
-
],
|
|
153
|
-
revision: 3,
|
|
154
|
-
})
|
|
155
|
-
const after = render()
|
|
156
|
-
assert.equal(findAll(after, 'select')[0].props.value, 'q|vision2')
|
|
157
|
-
assert.ok(findAll(after, 'span').some((node) => node.props.children.flat().join('') === 'Saved'))
|
|
158
|
-
console.log('client smoke ok')
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
|
|
3
|
+
let definition
|
|
4
|
+
globalThis.window = { __ModuleLoader__: { load(value) { definition = value } } }
|
|
5
|
+
|
|
6
|
+
let hookIndex = 0
|
|
7
|
+
let hooks = []
|
|
8
|
+
let callback
|
|
9
|
+
let effectQueued = false
|
|
10
|
+
let renderCurrent
|
|
11
|
+
let registered
|
|
12
|
+
let mutation
|
|
13
|
+
|
|
14
|
+
function rerender() {
|
|
15
|
+
if (renderCurrent) renderCurrent()
|
|
16
|
+
}
|
|
17
|
+
function setAt(index, next) {
|
|
18
|
+
hooks[index] = typeof next === 'function' ? next(hooks[index]) : next
|
|
19
|
+
rerender()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const React = {
|
|
23
|
+
createElement(type, props, ...children) {
|
|
24
|
+
return { type, props: { ...(props || {}), children: children.flat(Infinity) } }
|
|
25
|
+
},
|
|
26
|
+
useState(initial) {
|
|
27
|
+
const index = hookIndex++
|
|
28
|
+
if (!(index in hooks)) hooks[index] = typeof initial === 'function' ? initial() : initial
|
|
29
|
+
return [hooks[index], (next) => setAt(index, next)]
|
|
30
|
+
},
|
|
31
|
+
useCallback(fn) {
|
|
32
|
+
callback ??= fn
|
|
33
|
+
return callback
|
|
34
|
+
},
|
|
35
|
+
useEffect(fn) {
|
|
36
|
+
if (effectQueued) return
|
|
37
|
+
effectQueued = true
|
|
38
|
+
queueMicrotask(fn)
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dict = {
|
|
43
|
+
nav: 'Auto Vision', title: 'Auto Vision', intro: '', target: 'Vision model', auto: 'Auto',
|
|
44
|
+
hint: '', refresh: 'Refresh', loading: 'Loading', saving: 'Saving', saved: 'Saved',
|
|
45
|
+
loadFailed: 'Load failed', saveFailed: 'Save failed', noModels: 'None', provider: 'API',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const settingsView = {
|
|
49
|
+
ns: 'auto-vision',
|
|
50
|
+
revision: 3,
|
|
51
|
+
value: { visionProvider: 'p', visionModel: 'vision' },
|
|
52
|
+
}
|
|
53
|
+
const providerViews = [
|
|
54
|
+
{ ns: 'llm-p', revision: 1, value: { providers: { p: { models: [
|
|
55
|
+
{ id: 'vision', name: 'Vision', input: ['text', 'image'] },
|
|
56
|
+
{ id: 'text', name: 'Text', input: ['text'] },
|
|
57
|
+
{ id: 'DeepSeek-V4-Flash-Vision-Exp', name: 'DeepSeek-V4-Flash-Vision-Exp' },
|
|
58
|
+
] } } } },
|
|
59
|
+
{ ns: 'llm-q', revision: 2, value: { providers: { q: { models: [
|
|
60
|
+
{ id: 'vision2', name: 'Vision 2', input: ['image'] },
|
|
61
|
+
] } } } },
|
|
62
|
+
]
|
|
63
|
+
const ctx = {
|
|
64
|
+
effect(fn) { fn() },
|
|
65
|
+
locale: {
|
|
66
|
+
register() { return () => {} },
|
|
67
|
+
bind() { return (key) => dict[key] ?? key },
|
|
68
|
+
},
|
|
69
|
+
slots: {
|
|
70
|
+
inject(name, fn) { fn() },
|
|
71
|
+
register(reg, component) { registered = { reg, component } },
|
|
72
|
+
},
|
|
73
|
+
remote: {
|
|
74
|
+
settings: {
|
|
75
|
+
async describe() {
|
|
76
|
+
return { ok: true, value: { writable: true, namespaces: [settingsView, ...providerViews] } }
|
|
77
|
+
},
|
|
78
|
+
async mutate(ns, ops, revision) {
|
|
79
|
+
mutation = { ns, ops, revision }
|
|
80
|
+
return { ok: true, value: { ns, revision: revision + 1, value: { visionProvider: 'q', visionModel: 'vision2' } } }
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
llm: {
|
|
84
|
+
async listConfigurableProviders() {
|
|
85
|
+
return { ok: true, value: [
|
|
86
|
+
{ provider: 'p', displayName: 'Provider P', settingsNs: 'llm-p', settingsPath: ['providers', 'p'] },
|
|
87
|
+
{ provider: 'q', displayName: 'Provider Q', settingsNs: 'llm-q', settingsPath: ['providers', 'q'] },
|
|
88
|
+
] }
|
|
89
|
+
},
|
|
90
|
+
async listProviders() {
|
|
91
|
+
return { ok: true, value: [{ id: 'p' }, { id: 'q' }] }
|
|
92
|
+
},
|
|
93
|
+
async discoverModels() {
|
|
94
|
+
// Deliberately omit q's declared vision2: the official endpoint may
|
|
95
|
+
// not echo every configured alias, and declared models must still show.
|
|
96
|
+
return { ok: true, value: [
|
|
97
|
+
{ id: 'vision', name: 'Vision', inputModalities: ['text', 'image'] },
|
|
98
|
+
{ id: 'text', name: 'Text', inputModalities: ['text'] },
|
|
99
|
+
{ id: 'GLM-4.6V', name: 'GLM-4.6V', inputModalities: ['text', 'image'] },
|
|
100
|
+
{ id: 'GLM-5.3', name: 'GLM-5.3', inputModalities: ['text'] },
|
|
101
|
+
] }
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await import('../client.js')
|
|
108
|
+
const factory = definition.factory
|
|
109
|
+
const plugin = factory((id) => {
|
|
110
|
+
if (id === 'react') return React
|
|
111
|
+
throw new Error(`unexpected require: ${id}`)
|
|
112
|
+
})
|
|
113
|
+
plugin.apply(ctx)
|
|
114
|
+
assert.equal(registered.reg.name, 'settings.section')
|
|
115
|
+
assert.equal(registered.reg.id, 'auto-vision')
|
|
116
|
+
assert.equal(typeof registered.reg.label(), 'string')
|
|
117
|
+
|
|
118
|
+
function render() {
|
|
119
|
+
hookIndex = 0
|
|
120
|
+
return registered.component({})
|
|
121
|
+
}
|
|
122
|
+
renderCurrent = () => { render() }
|
|
123
|
+
render()
|
|
124
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
125
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
126
|
+
|
|
127
|
+
function findAll(node, type, out = []) {
|
|
128
|
+
if (!node || typeof node !== 'object') return out
|
|
129
|
+
if (node.type === type) out.push(node)
|
|
130
|
+
for (const child of node.props?.children || []) findAll(child, type, out)
|
|
131
|
+
return out
|
|
132
|
+
}
|
|
133
|
+
const tree = render()
|
|
134
|
+
const select = findAll(tree, 'select')[0]
|
|
135
|
+
assert.ok(select, 'select rendered')
|
|
136
|
+
assert.equal(select.props.value, 'p|vision')
|
|
137
|
+
const optionValues = findAll(tree, 'option').map((option) => option.props.value)
|
|
138
|
+
assert.deepEqual(optionValues, [
|
|
139
|
+
'', 'p|vision', 'q|vision2', 'p|DeepSeek-V4-Flash-Vision-Exp',
|
|
140
|
+
])
|
|
141
|
+
const checkbox = findAll(tree, 'input').find((node) => node.props.type === 'checkbox')
|
|
142
|
+
assert.ok(checkbox, 'candidate toggle rendered')
|
|
143
|
+
checkbox.props.onChange({ target: { checked: true } })
|
|
144
|
+
assert.ok(findAll(render(), 'option').some((option) => option.props.value === 'p|GLM-4.6V'))
|
|
145
|
+
|
|
146
|
+
await select.props.onChange({ target: { value: 'q|vision2' } })
|
|
147
|
+
assert.deepEqual(mutation, {
|
|
148
|
+
ns: 'auto-vision',
|
|
149
|
+
ops: [
|
|
150
|
+
{ op: 'set', path: ['visionProvider'], value: 'q' },
|
|
151
|
+
{ op: 'set', path: ['visionModel'], value: 'vision2' },
|
|
152
|
+
],
|
|
153
|
+
revision: 3,
|
|
154
|
+
})
|
|
155
|
+
const after = render()
|
|
156
|
+
assert.equal(findAll(after, 'select')[0].props.value, 'q|vision2')
|
|
157
|
+
assert.ok(findAll(after, 'span').some((node) => node.props.children.flat().join('') === 'Saved'))
|
|
158
|
+
console.log('client smoke ok')
|