feat(host): 导出 schemastery Config -> DSH 设置页出现悬浮窗配置卡片;实测 profile config 经 apply(ctx,config) 下发到浮窗;README 补设置入口说明
This commit is contained in:
@@ -170,6 +170,18 @@ curl http://127.0.0.1:8099/dsh-float-window/state
|
|||||||
|
|
||||||
日志:`%DSH_HOME%\float-window\float-window.log`(每次启动都会记,排障先看它)。
|
日志:`%DSH_HOME%\float-window\float-window.log`(每次启动都会记,排障先看它)。
|
||||||
|
|
||||||
|
## 设置在哪里改
|
||||||
|
|
||||||
|
| 入口 | 适用版本 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| 浮窗内 ⚙ 齿轮 | 全部 | 不透明度 / 界面缩放 / 背景效果(不透明·毛玻璃·云母·模糊·半透明)/ 三个开关,即时生效,记在 `window.json` |
|
||||||
|
| **DSH 设置 → 插件 → dsh-float-window** | dsh 0.1.7+ | 插件导出 schemastery `Config`,设置页把它渲染成一张卡片(`topmost / posLock / sizeLock / exclude / opacity / scale / backdrop`)。改动写进 profile 的 cordis patch,插件启动时读到并通过 SSE 下发给浮窗(首次连接时应用一次,之后以浮窗自己的开关为准,避免来回打架) |
|
||||||
|
| `ctx.settings.register('dsh-float-window', schema)` | dsh 0.1.0 | 老版本的设置服务路径,自动降级 |
|
||||||
|
|
||||||
|
> link: 安装的插件自身目录没有 `node_modules`,`import '@deepseek-ai/schemastery'` 会失败;
|
||||||
|
> 插件用 `createRequire(process.argv[1] ?? <profile>/package.json)` 去宿主/profile 那边解析,
|
||||||
|
> 解析不到就干脆不导出 `Config`(不再报错)。
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
+49
-1
@@ -14,6 +14,7 @@
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import os from 'node:os'
|
import os from 'node:os'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
import { randomUUID } from 'node:crypto'
|
import { randomUUID } from 'node:crypto'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
@@ -28,6 +29,46 @@ function dshHome() {
|
|||||||
return process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
return process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 配置 schema
|
||||||
|
// 0.1.7 的设置模型是"编辑插件自身的 Config(profile 条目配置)",所以这里导出一个
|
||||||
|
// schemastery schema,DSH 的「设置 → 插件」就能渲染出悬浮窗的配置卡片。
|
||||||
|
// 难点:插件用 link: 安装时自身目录没有 node_modules,裸 import 会失败 ——
|
||||||
|
// 用 createRequire 以"宿主入口 / profile 目录"为基准去解析 schemastery。
|
||||||
|
function loadSchemastery() {
|
||||||
|
const tries = [
|
||||||
|
() => process.argv[1] && createRequire(process.argv[1]),
|
||||||
|
() => createRequire(path.join(dshHome(), 'profiles', 'desktop', 'package.json')),
|
||||||
|
() => createRequire(path.join(dshHome(), 'profiles', 'web', 'package.json')),
|
||||||
|
]
|
||||||
|
for (const make of tries) {
|
||||||
|
try {
|
||||||
|
const req = make()
|
||||||
|
if (!req) continue
|
||||||
|
const m = req('@deepseek-ai/schemastery')
|
||||||
|
const z = m?.default || m
|
||||||
|
if (z?.object) return z
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const z = loadSchemastery()
|
||||||
|
const floatSchema = z
|
||||||
|
? z.object({
|
||||||
|
topmost: z.boolean().default(true).description('始终置顶'),
|
||||||
|
posLock: z.boolean().default(false).description('固定位置(图钉)'),
|
||||||
|
sizeLock: z.boolean().default(false).description('锁定尺寸(小锁)'),
|
||||||
|
exclude: z.boolean().default(true).description('抓屏时隐藏本窗'),
|
||||||
|
opacity: z.number().min(0.3).max(1).default(0.92).description('不透明度(毛玻璃时是霜化浓度)'),
|
||||||
|
scale: z.number().min(0.8).max(1.5).default(1).description('界面缩放'),
|
||||||
|
backdrop: z.union(['opaque', 'translucent', 'acrylic', 'mica', 'blur']).default('opaque').description('背景效果'),
|
||||||
|
})
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
/** 浮窗设置项(DSH 设置页写进 profile 条目 config,这里读取并下发给浮窗) */
|
||||||
|
export const Config = floatSchema
|
||||||
|
|
||||||
|
|
||||||
/** 从 content 数组/字符串里取纯文本 */
|
/** 从 content 数组/字符串里取纯文本 */
|
||||||
function textOf(content) {
|
function textOf(content) {
|
||||||
if (typeof content === 'string') return content
|
if (typeof content === 'string') return content
|
||||||
@@ -103,7 +144,13 @@ function sanitize(v, depth = 0) {
|
|||||||
|
|
||||||
const SAMPLE_TYPES = new Set(['user/message', 'assistant/chunk', 'assistant/message', 'tool/call', 'tool/result', 'session/title', 'request/header', 'request/context'])
|
const SAMPLE_TYPES = new Set(['user/message', 'assistant/chunk', 'assistant/message', 'tool/call', 'tool/result', 'session/title', 'request/header', 'request/context'])
|
||||||
|
|
||||||
export function apply(ctx) {
|
export function apply(ctx, config) {
|
||||||
|
// 浮窗设置:DSH 设置页写进 profile 条目 config 的值 + 默认值
|
||||||
|
const floatCfg = Object.assign(
|
||||||
|
{ topmost: true, posLock: false, sizeLock: false, exclude: true, opacity: 0.92, scale: 1, backdrop: 'opaque' },
|
||||||
|
(config && typeof config === 'object') ? config : {},
|
||||||
|
)
|
||||||
|
|
||||||
// 运行状态与自检日志落盘:插件跑在 DSH 内部,控制台不一定看得到,
|
// 运行状态与自检日志落盘:插件跑在 DSH 内部,控制台不一定看得到,
|
||||||
// 有了这个文件(和 /health)才能在用户机器上远程排障。
|
// 有了这个文件(和 /health)才能在用户机器上远程排障。
|
||||||
const notes = []
|
const notes = []
|
||||||
@@ -297,6 +344,7 @@ export function apply(ctx) {
|
|||||||
title: (id && titles.get(id)) || session?.meta?.title || session?.title || null,
|
title: (id && titles.get(id)) || session?.meta?.title || session?.title || null,
|
||||||
messages: (id && rows.get(id)) || [],
|
messages: (id && rows.get(id)) || [],
|
||||||
metrics: session ? metricsFor(session) : { turns: null, steps: null, tps: null, tokens: null, cacheHit: null, contextUsed: null },
|
metrics: session ? metricsFor(session) : { turns: null, steps: null, tps: null, tokens: null, cacheHit: null, contextUsed: null },
|
||||||
|
settings: floatCfg,
|
||||||
sessions: sessions.slice(-10).map((s) => ({ id: s?.id, title: s?.meta?.title || s?.title || null })),
|
sessions: sessions.slice(-10).map((s) => ({ id: s?.id, title: s?.meta?.title || s?.title || null })),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -654,9 +654,25 @@
|
|||||||
if (c === 'show') document.body.style.visibility = 'visible'
|
if (c === 'show') document.body.style.visibility = 'visible'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let settingsApplied = false
|
||||||
|
function applyDshSettings(s) {
|
||||||
|
if (!s) return
|
||||||
|
// DSH 设置页里的值 → 只应用一次,之后以浮窗自己的开关为准,避免来回打架
|
||||||
|
const want = {}
|
||||||
|
if (s.opacity != null && Math.abs(s.opacity - S.opacity) > 0.01) { S.opacity = s.opacity; cmd('opacity', { value: s.opacity }) }
|
||||||
|
if (s.scale != null && Math.abs(s.scale - S.scale) > 0.01) { S.scale = s.scale; cmd('scale', { value: s.scale }) }
|
||||||
|
if (s.backdrop && s.backdrop !== S.backdrop) { S.backdrop = s.backdrop; cmd('backdrop', { value: s.backdrop }) }
|
||||||
|
if (s.topmost != null && !!s.topmost !== !!S.topmost) cmd('topmost')
|
||||||
|
if (s.posLock != null && !!s.posLock !== !!S.posLock) cmd('posLock')
|
||||||
|
if (s.sizeLock != null && !!s.sizeLock !== !!S.sizeLock) cmd('sizeLock')
|
||||||
|
if (s.exclude != null && !!s.exclude !== !!S.exclude) cmd('exclude')
|
||||||
|
paint()
|
||||||
|
}
|
||||||
|
|
||||||
function render(msg) {
|
function render(msg) {
|
||||||
if (!msg) return
|
if (!msg) return
|
||||||
if (msg.type === 'cmd') return handleCmd(msg.cmd)
|
if (msg.type === 'cmd') return handleCmd(msg.cmd)
|
||||||
|
if (msg.settings && !settingsApplied) { settingsApplied = true; applyDshSettings(msg.settings) }
|
||||||
if (msg.metrics) metrics(msg.metrics)
|
if (msg.metrics) metrics(msg.metrics)
|
||||||
if (msg.messages) messages(msg.messages)
|
if (msg.messages) messages(msg.messages)
|
||||||
if (msg.title) {
|
if (msg.title) {
|
||||||
|
|||||||
Reference in New Issue
Block a user