Files
dsh-easy-desktop/plugins/dsh-desktop-vision/client.js
Tommy f8a3c2dc6f Add README, plugin citations, and multi-platform release CI.
Ship screenshots and third-party notices for the desktop shell, and
package Windows, macOS, deb, rpm, and Flatpak from GitHub Releases.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-15 22:02:13 +08:00

210 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
window.__ModuleLoader__.load({
id: 'dsh-desktop-vision',
factory: (require) => {
var module = { exports: {} }
var exports = module.exports
var React = require('react')
var jsx = require('react/jsx-runtime')
var css = [
'.dshdv{width:100%;max-width:640px;display:flex;flex-direction:column;gap:14px;color:var(--dsw-alias-label-primary)}',
'.dshdv h3{margin:0;font-size:15px;font-weight:600;line-height:22px}',
'.dshdv p{margin:0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}',
'.dshdv label{display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary)}',
'.dshdv .head{display:flex;align-items:center;justify-content:space-between;gap:12px}',
'.dshdv a{color:var(--dsw-alias-state-business-primary);text-decoration:none;font-size:12px;line-height:18px}',
'.dshdv a:hover{text-decoration:underline}',
'.dshdv input,.dshdv select{height:36px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font:inherit}',
'.dshdv input:focus,.dshdv select:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}',
'.dshdv button{align-self:flex-start;height:32px;padding:0 14px;border:0;border-radius:8px;background:var(--dsw-alias-state-business-primary);color:#fff;font:inherit;cursor:pointer}',
'.dshdv button:disabled{opacity:.55;cursor:default}',
'.dshdv .ok{color:var(--dsw-alias-state-success-primary)}',
'.dshdv .err{color:var(--dsw-alias-state-error-primary)}',
].join('')
function ensureCss() {
if (typeof document === 'undefined') return
if (document.querySelector('style[data-plugin-css="dsh-desktop-vision"]')) return
var tag = document.createElement('style')
tag.dataset.pluginCss = 'dsh-desktop-vision'
tag.textContent = css
document.head.appendChild(tag)
}
function remote(provider) {
var q = provider ? '?provider=' + encodeURIComponent(provider) : ''
return fetch('/dsh-desktop/modlens' + q).then(function (res) {
if (!res.ok) throw new Error('load failed ' + res.status)
return res.json()
})
}
function VisionSettings() {
ensureCss()
var state = React.useState({ status: 'loading' })
var snap = state[0]
var setSnap = state[1]
React.useEffect(function () {
var live = true
remote()
.then(function (form) {
if (live) setSnap({ status: 'ready', form: form, message: '' })
})
.catch(function (error) {
if (live) setSnap({ status: 'error', message: String(error.message || error) })
})
return function () {
live = false
}
}, [])
function patch(field, value) {
setSnap(function (cur) {
if (cur.status !== 'ready') return cur
return { status: 'ready', form: Object.assign({}, cur.form, { [field]: value }), message: '' }
})
}
function onProvider(id) {
remote(id)
.then(function (form) {
setSnap({ status: 'ready', form: form, message: '' })
})
.catch(function () {
patch('provider', id)
})
}
function save() {
if (snap.status !== 'ready' || snap.saving) return
setSnap(Object.assign({}, snap, { saving: true, message: '' }))
fetch('/dsh-desktop/modlens', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(snap.form),
})
.then(function (res) {
return res.json().then(function (body) {
if (!res.ok) throw new Error(body.error || 'save failed')
return body
})
})
.then(function (form) {
setSnap({ status: 'ready', form: form, message: '已保存', saving: false })
})
.catch(function (error) {
setSnap(Object.assign({}, snap, { saving: false, message: String(error.message || error) }))
})
}
if (snap.status === 'loading') {
return jsx.jsx('div', { className: 'dshdv', children: jsx.jsx('p', { children: '正在读取 ModLens 配置…' }) })
}
if (snap.status === 'error') {
return jsx.jsx('div', { className: 'dshdv', children: jsx.jsx('p', { className: 'err', children: snap.message }) })
}
var form = snap.form
var remoteEngine = form.provider === 'openai' || form.provider === 'gemini-api' || form.provider === 'anthropic'
var options = form.options || []
var current = options.filter(function (item) { return item.id === form.provider })[0]
var apiUrl = (current && current.apiUrl) || form.apiUrl || ''
var example = (current && current.example) || form.example || ''
var getApi = apiUrl
? jsx.jsx('a', { href: apiUrl, children: '获取 API' })
: null
return jsx.jsxs('div', {
className: 'dshdv',
children: [
jsx.jsx('h3', { children: 'ModLens 视觉模型' }),
jsx.jsx('p', {
children:
'纯文本对话模型读图时使用这里的引擎。已声明视觉能力的模型(如 Qwen不会走这条桥。',
}),
jsx.jsxs('label', {
children: [
'引擎',
jsx.jsx('select', {
value: form.provider,
onChange: function (event) {
onProvider(event.target.value)
},
children: options.map(function (item) {
return jsx.jsx('option', { value: item.id, children: item.label }, item.id)
}),
}),
],
}),
jsx.jsxs('label', {
children: [
'接口地址',
jsx.jsx('input', {
value: form.baseUrl || '',
disabled: !remoteEngine,
placeholder: form.officialBaseUrl || '',
onChange: function (event) {
patch('baseUrl', event.target.value)
},
}),
],
}),
jsx.jsxs('label', {
children: [
jsx.jsxs('span', { className: 'head', children: ['API 密钥', getApi] }),
jsx.jsx('input', {
type: 'password',
value: form.apiKey || '',
disabled: !remoteEngine,
onChange: function (event) {
patch('apiKey', event.target.value)
},
}),
],
}),
jsx.jsxs('label', {
children: [
'视觉模型',
jsx.jsx('input', {
value: form.model || '',
placeholder: example ? '例如 ' + example : '',
onChange: function (event) {
patch('model', event.target.value)
},
}),
],
}),
jsx.jsx('button', {
type: 'button',
disabled: !!snap.saving,
onClick: save,
children: snap.saving ? '保存中…' : '保存',
}),
snap.message
? jsx.jsx('p', {
className: /失败|failed|error/i.test(snap.message) ? 'err' : 'ok',
children: snap.message,
})
: null,
],
})
}
function apply(ctx) {
ctx.slots.inject('settings.section', function () {
return ctx.slots.register(
{
name: 'settings.section',
id: 'modlens-vision',
order: 12,
label: '视觉模型',
},
VisionSettings,
)
})
}
exports.apply = apply
exports.inject = ['slots']
return module.exports
},
})