dsh-router-laya 2.1.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/LICENSE +176 -0
- package/NOTICE +19 -0
- package/README.md +108 -0
- package/bin/setup.mjs +297 -0
- package/client.js +436 -0
- package/cordis.patch.yml +9 -0
- package/index.js +870 -0
- package/install.mjs +38 -0
- package/package.json +49 -0
- package/service/finetuned_judge.py +324 -0
- package/service/intent_parser.py +1012 -0
- package/service/laya/__init__.py +51 -0
- package/service/laya/agent.py +447 -0
- package/service/laya/common.py +280 -0
- package/service/laya/email.py +90 -0
- package/service/laya/lang.py +324 -0
- package/service/laya/presets.py +187 -0
- package/service/laya/pyproject.toml +38 -0
- package/service/laya/router.py +447 -0
- package/service/laya_router.py +320 -0
- package/service/requirements.lock.txt +27 -0
- package/service/start_router.ps1 +102 -0
- package/service/start_router.sh +144 -0
- package/weights/fetch.mjs +219 -0
- package/weights/manifest.json +36 -0
package/client.js
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier chip — browser half of `dsh-router-laya` (spec: docs/frontend-auto-tier-spec.md).
|
|
3
|
+
*
|
|
4
|
+
* Packaged as the client module system's lazy-CJS bundle: executing this file only REGISTERS a factory,
|
|
5
|
+
* the module body runs at first materialization, and every side effect (including the stylesheet) lives
|
|
6
|
+
* inside the factory closure. `require("react")` resolves against the shell's frozen platform seed.
|
|
7
|
+
*
|
|
8
|
+
* All colours, sizes, copy and timings are the spec's, copied verbatim; the two places where the spec's
|
|
9
|
+
* literal value cannot be used as written are marked ADAPTED below with the reason.
|
|
10
|
+
*/
|
|
11
|
+
window.__ModuleLoader__.load({
|
|
12
|
+
id: "dsh-router-laya",
|
|
13
|
+
factory: (require) => {
|
|
14
|
+
var module = { exports: {} }
|
|
15
|
+
var exports = module.exports
|
|
16
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
|
|
17
|
+
|
|
18
|
+
const React = require("react")
|
|
19
|
+
|
|
20
|
+
// ── spec §2/§3/§4/§5 constants ────────────────────────────────────────────────────────────────
|
|
21
|
+
const STATE_URL = "http://127.0.0.1:8765/state"
|
|
22
|
+
// Same-origin, unlike /state: a static client bundle has no `host.call` and no `ctx.remote.settings`,
|
|
23
|
+
// so the host half serves this route to read and switch the mode.
|
|
24
|
+
const MODE_URL = "/router-laya/mode"
|
|
25
|
+
const POLL_MS = 3000
|
|
26
|
+
const POLL_FAILURES_BEFORE_OFFLINE = 2
|
|
27
|
+
const TOAST_MS = 2000
|
|
28
|
+
const TOAST_GAP_PX = 8
|
|
29
|
+
const HOVER_SHOW_MS = 300
|
|
30
|
+
const HOVER_HIDE_MS = 150
|
|
31
|
+
const POPOVER_LIMIT = 20
|
|
32
|
+
const TASK_CHARS = 30
|
|
33
|
+
|
|
34
|
+
const TIER_ORDER = { low: 0, high: 1, max: 2 }
|
|
35
|
+
const TIER_COLOR = { low: "#3fb950", high: "#d29922", max: "#f85149" }
|
|
36
|
+
const TIER_TEXT = { low: "AUTO · 低", high: "AUTO · 高", max: "AUTO · MAX" }
|
|
37
|
+
const TIER_SHORT = { low: "低", high: "高", max: "MAX" }
|
|
38
|
+
const OFFLINE_COLOR = "#8b949e"
|
|
39
|
+
const OFFLINE_TEXT = "AUTO · 离线"
|
|
40
|
+
const OFFLINE_TIP = "判定服务未启动 · 已落 low(powershell -File routing/start_router.ps1)"
|
|
41
|
+
// The spec's five states do not cover "the service is healthy but nothing has been judged yet", which
|
|
42
|
+
// is the real state on every fresh load: /state's log is in-memory and empty until this session's
|
|
43
|
+
// first turn is judged. Showing the offline copy there claims a failure that is not happening.
|
|
44
|
+
const PENDING_TEXT = "AUTO · 待判定"
|
|
45
|
+
const PENDING_TIP = "自动 · 等待本轮判定"
|
|
46
|
+
const MANUAL_TEXT = "手动"
|
|
47
|
+
|
|
48
|
+
const TOAST_TEXT = {
|
|
49
|
+
escalate_regenerate: "⤴ 升档 · 检测到重试",
|
|
50
|
+
intent_force: "⚡ 用户指定",
|
|
51
|
+
intent_inherit: "↻ 保持上轮",
|
|
52
|
+
intent_exclude: "按你的要求排除",
|
|
53
|
+
}
|
|
54
|
+
const REASON_TEXT = {
|
|
55
|
+
laya: "内容判断",
|
|
56
|
+
escalate_regenerate: "检测到重试·升档",
|
|
57
|
+
intent_force: "用户指定",
|
|
58
|
+
intent_inherit: "保持上轮",
|
|
59
|
+
intent_exclude: "用户排除",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const CSS = `
|
|
63
|
+
@keyframes routerLayaTierPop{0%{transform:scale(1)}40%{transform:scale(1.08)}100%{transform:scale(1)}}
|
|
64
|
+
@keyframes routerLayaBreathe{0%,100%{opacity:.85}50%{opacity:1}}
|
|
65
|
+
@keyframes routerLayaToastIn{0%{opacity:0;transform:translateY(4px)}100%{opacity:1;transform:translateY(0)}}
|
|
66
|
+
@keyframes routerLayaToastOut{0%{opacity:1}100%{opacity:0}}
|
|
67
|
+
.routerLayaChip{display:inline-flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;
|
|
68
|
+
user-select:none;box-sizing:border-box;background:transparent;
|
|
69
|
+
transition:background-color .16s ease-out,border-color .16s ease-out,color .3s ease}
|
|
70
|
+
.routerLayaChip:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
71
|
+
.routerLayaDot{width:6px;height:6px;border-radius:999px;flex:none;transition:background-color .3s ease}
|
|
72
|
+
.routerLayaPop{animation:routerLayaTierPop 300ms ease-out}
|
|
73
|
+
.routerLayaMax{animation:routerLayaBreathe 2.4s ease-in-out infinite}
|
|
74
|
+
.routerLayaToast{position:absolute;bottom:calc(100% + ${TOAST_GAP_PX}px);right:0;white-space:nowrap;
|
|
75
|
+
padding:4px 9px;border-radius:7px;font-size:11px;line-height:16px;pointer-events:none;
|
|
76
|
+
background:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-layer-3,#fff));
|
|
77
|
+
border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);
|
|
78
|
+
box-shadow:0 6px 18px rgba(0,0,0,.14);animation:routerLayaToastIn .18s ease-out}
|
|
79
|
+
.routerLayaToastOut{animation:routerLayaToastOut .3s ease-in forwards}
|
|
80
|
+
.routerLayaPopover{position:absolute;bottom:calc(100% + 8px);right:0;width:330px;max-height:320px;
|
|
81
|
+
overflow-y:auto;padding:6px;border-radius:9px;text-align:left;z-index:30;
|
|
82
|
+
background:var(--dsw-alias-bg-overlay,var(--dsw-alias-bg-layer-3,#fff));
|
|
83
|
+
border:1px solid var(--dsw-alias-border-l1);box-shadow:0 10px 28px rgba(0,0,0,.18)}
|
|
84
|
+
.routerLayaRow{display:flex;align-items:center;gap:7px;padding:3px 6px;border-radius:5px;font-size:11px;line-height:16px}
|
|
85
|
+
.routerLayaRow:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
86
|
+
.routerLayaTime{opacity:.55;font-variant-numeric:tabular-nums;flex:none}
|
|
87
|
+
.routerLayaTask{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary)}
|
|
88
|
+
.routerLayaWhy{opacity:.6;flex:none}
|
|
89
|
+
`
|
|
90
|
+
|
|
91
|
+
// ── shared poll state: server owns the truth (spec §6 constraint 2) ──────────────────────────
|
|
92
|
+
const store = { data: null, offline: false, failures: 0, mode: null, listeners: new Set() }
|
|
93
|
+
let poller = null
|
|
94
|
+
let styleEl = null
|
|
95
|
+
|
|
96
|
+
function notify() {
|
|
97
|
+
for (const fn of Array.from(store.listeners)) {
|
|
98
|
+
try { fn() } catch (error) { /* one bad subscriber must not stop the rest */ }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function sessionsList() {
|
|
103
|
+
const data = store.data
|
|
104
|
+
if (data === null || typeof data !== "object" || data === null) return null
|
|
105
|
+
const sessions = data.sessions
|
|
106
|
+
return sessions !== null && typeof sessions === "object" ? sessions : null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Session match (spec §2): by id when known, otherwise the newest ts across all sessions. */
|
|
110
|
+
function rowsFor(sessionId) {
|
|
111
|
+
const sessions = sessionsList()
|
|
112
|
+
if (sessions === null) return []
|
|
113
|
+
if (sessionId !== undefined && sessionId !== null && Array.isArray(sessions[sessionId])) {
|
|
114
|
+
return sessions[sessionId]
|
|
115
|
+
}
|
|
116
|
+
let best = null
|
|
117
|
+
let bestTs = -Infinity
|
|
118
|
+
for (const key of Object.keys(sessions)) {
|
|
119
|
+
const rows = sessions[key]
|
|
120
|
+
if (!Array.isArray(rows) || rows.length === 0) continue
|
|
121
|
+
const tail = rows[rows.length - 1]
|
|
122
|
+
const ts = tail !== undefined && typeof tail.ts === "number" ? tail.ts : -Infinity
|
|
123
|
+
if (ts > bestTs) { bestTs = ts; best = rows }
|
|
124
|
+
}
|
|
125
|
+
return best === null ? [] : best
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function pollOnce() {
|
|
129
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return
|
|
130
|
+
fetch(STATE_URL, { cache: "no-store" })
|
|
131
|
+
.then((res) => (res.ok ? res.json() : Promise.reject(new Error("http " + String(res.status)))))
|
|
132
|
+
.then((json) => {
|
|
133
|
+
store.data = json
|
|
134
|
+
store.failures = 0
|
|
135
|
+
if (store.offline) store.offline = false
|
|
136
|
+
notify()
|
|
137
|
+
})
|
|
138
|
+
.catch(() => {
|
|
139
|
+
store.failures += 1
|
|
140
|
+
if (store.failures >= POLL_FAILURES_BEFORE_OFFLINE && !store.offline) {
|
|
141
|
+
store.offline = true
|
|
142
|
+
notify()
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
fetch(MODE_URL, { cache: "no-store" })
|
|
146
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
147
|
+
.then((json) => {
|
|
148
|
+
if (json !== null && (json.mode === "auto" || json.mode === "manual") && json.mode !== store.mode) {
|
|
149
|
+
store.mode = json.mode
|
|
150
|
+
notify()
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
.catch(() => { /* the chip keeps its last known mode */ })
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Switch mode; the response is authoritative, so a refused write cannot desync the chip. */
|
|
157
|
+
function writeMode(next) {
|
|
158
|
+
return fetch(MODE_URL, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: { "Content-Type": "application/json" },
|
|
161
|
+
body: JSON.stringify({ mode: next }),
|
|
162
|
+
})
|
|
163
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
164
|
+
.then((json) => {
|
|
165
|
+
if (json !== null && (json.mode === "auto" || json.mode === "manual")) {
|
|
166
|
+
store.mode = json.mode
|
|
167
|
+
notify()
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
.catch(() => { /* leave the displayed mode as it was */ })
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function startPolling() {
|
|
174
|
+
if (poller !== null) return
|
|
175
|
+
pollOnce()
|
|
176
|
+
poller = setInterval(pollOnce, POLL_MS)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── spec §4 toast copy ───────────────────────────────────────────────────────────────────────
|
|
180
|
+
function toastText(triggeredBy, nextTier, prevTier) {
|
|
181
|
+
if (Object.prototype.hasOwnProperty.call(TOAST_TEXT, triggeredBy)) return TOAST_TEXT[triggeredBy]
|
|
182
|
+
if (triggeredBy === "laya") {
|
|
183
|
+
const next = TIER_ORDER[nextTier]
|
|
184
|
+
const prev = TIER_ORDER[prevTier]
|
|
185
|
+
if (next === undefined || prev === undefined) return null
|
|
186
|
+
if (next > prev) return "⤴ 升档 · 内容判断"
|
|
187
|
+
if (next < prev) return "↩ 降回 · 新任务"
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function reasonText(triggeredBy) {
|
|
194
|
+
return Object.prototype.hasOwnProperty.call(REASON_TEXT, triggeredBy)
|
|
195
|
+
? REASON_TEXT[triggeredBy]
|
|
196
|
+
: String(triggeredBy === undefined || triggeredBy === null ? "" : triggeredBy)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function clockOf(ts) {
|
|
200
|
+
if (typeof ts !== "number" || !isFinite(ts)) return "--:--"
|
|
201
|
+
const d = new Date(ts * 1000)
|
|
202
|
+
const hh = String(d.getHours()).padStart(2, "0")
|
|
203
|
+
const mm = String(d.getMinutes()).padStart(2, "0")
|
|
204
|
+
return hh + ":" + mm
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function clip(text) {
|
|
208
|
+
const s = typeof text === "string" ? text : ""
|
|
209
|
+
return s.length > TASK_CHARS ? s.slice(0, TASK_CHARS) + "…" : s
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── spec §3 geometry: sit beside the model selector, copying its own shape ───────────────────
|
|
213
|
+
//
|
|
214
|
+
// ADAPTED (1 of 2): the spec gives a literal `translateX(-8px)`. In this composer the chip's cell
|
|
215
|
+
// renders BEFORE the model selector, so a negative offset moves it further away, not closer, and the
|
|
216
|
+
// acceptance item it serves is "间距 8px" (spec §7.1). A fixed number cannot satisfy that because the
|
|
217
|
+
// row's gap is not fixed, so the offset is measured: the chip is nudged by exactly
|
|
218
|
+
// (current gap - 8px). The spec's intent -- an 8px gap -- is what is implemented.
|
|
219
|
+
function measureGap(chipEl) {
|
|
220
|
+
try {
|
|
221
|
+
const effort = document.querySelector('[class*="triggerEffort"]')
|
|
222
|
+
if (effort === null) return null
|
|
223
|
+
const trigger = effort.closest("button") || effort.parentElement
|
|
224
|
+
if (trigger === null) return null
|
|
225
|
+
const chip = chipEl.getBoundingClientRect()
|
|
226
|
+
const target = trigger.getBoundingClientRect()
|
|
227
|
+
if (chip.width === 0 || target.width === 0) return null
|
|
228
|
+
return { delta: target.left - chip.right - 8, trigger: trigger }
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return null
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** ADAPTED (2 of 2): "读它 computed style 复制" -- radius/height/font copied from the neighbour. */
|
|
235
|
+
function copyNeighbourShape(trigger) {
|
|
236
|
+
try {
|
|
237
|
+
const cs = getComputedStyle(trigger)
|
|
238
|
+
const shape = {}
|
|
239
|
+
if (cs.borderRadius && cs.borderRadius !== "0px") shape.borderRadius = cs.borderRadius
|
|
240
|
+
const h = parseFloat(cs.height)
|
|
241
|
+
if (isFinite(h) && h >= 16 && h <= 40) shape.height = Math.round(h) + "px"
|
|
242
|
+
const fs = parseFloat(cs.fontSize)
|
|
243
|
+
if (isFinite(fs) && fs >= 10 && fs <= 18) shape.fontSize = Math.round(fs) + "px"
|
|
244
|
+
return shape
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return {}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function useStore() {
|
|
251
|
+
const tick = React.useState(0)
|
|
252
|
+
React.useEffect(() => {
|
|
253
|
+
startPolling()
|
|
254
|
+
const fn = () => tick[1]((n) => n + 1)
|
|
255
|
+
store.listeners.add(fn)
|
|
256
|
+
return () => { store.listeners.delete(fn) }
|
|
257
|
+
}, [])
|
|
258
|
+
return store
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function Chip(props) {
|
|
262
|
+
const state = useStore()
|
|
263
|
+
const chipRef = React.useState(null)
|
|
264
|
+
const offset = React.useState(0)
|
|
265
|
+
const shape = React.useState(null)
|
|
266
|
+
const pop = React.useState(0)
|
|
267
|
+
const toast = React.useState(null)
|
|
268
|
+
const toastOut = React.useState(false)
|
|
269
|
+
const popover = React.useState(false)
|
|
270
|
+
const previous = React.useState(null)
|
|
271
|
+
|
|
272
|
+
const sessionId = props === undefined || props === null ? undefined : props.sessionId
|
|
273
|
+
const rows = rowsFor(sessionId)
|
|
274
|
+
const latest = rows.length === 0 ? null : rows[rows.length - 1]
|
|
275
|
+
const offline = state.offline
|
|
276
|
+
const tier = latest !== null && typeof latest.tier === "string" ? latest.tier : null
|
|
277
|
+
|
|
278
|
+
const manual = state.mode === "manual"
|
|
279
|
+
|
|
280
|
+
// spec §4.3: first render is silent; only an adjacent-poll change on the same session animates.
|
|
281
|
+
React.useEffect(() => {
|
|
282
|
+
if (latest === null) return
|
|
283
|
+
const prev = previous[0]
|
|
284
|
+
previous[1]({ tier: latest.tier, triggeredBy: latest.triggered_by, sessionId: sessionId })
|
|
285
|
+
if (prev === null) return
|
|
286
|
+
if (prev.sessionId !== sessionId) return
|
|
287
|
+
if (prev.tier === latest.tier) return
|
|
288
|
+
pop[1]((n) => n + 1)
|
|
289
|
+
const text = toastText(latest.triggered_by, latest.tier, prev.tier)
|
|
290
|
+
if (text !== null) {
|
|
291
|
+
toast[1](text)
|
|
292
|
+
toastOut[1](false)
|
|
293
|
+
}
|
|
294
|
+
}, [latest === null ? null : latest.ts, sessionId])
|
|
295
|
+
|
|
296
|
+
React.useEffect(() => {
|
|
297
|
+
if (toast[0] === null) return undefined
|
|
298
|
+
const hide = setTimeout(() => toastOut[1](true), TOAST_MS)
|
|
299
|
+
const clear = setTimeout(() => { toast[1](null); toastOut[1](false) }, TOAST_MS + 320)
|
|
300
|
+
return () => { clearTimeout(hide); clearTimeout(clear) }
|
|
301
|
+
}, [toast[0]])
|
|
302
|
+
|
|
303
|
+
// Geometry: measure once after layout settles, then keep the neighbour's shape.
|
|
304
|
+
React.useEffect(() => {
|
|
305
|
+
const node = chipRef[0]
|
|
306
|
+
if (node === null) return undefined
|
|
307
|
+
let raf = 0
|
|
308
|
+
const apply = () => {
|
|
309
|
+
const found = measureGap(node)
|
|
310
|
+
if (found === null) return
|
|
311
|
+
offset[1](found.delta)
|
|
312
|
+
const copied = copyNeighbourShape(found.trigger)
|
|
313
|
+
if (Object.keys(copied).length > 0) shape[1](copied)
|
|
314
|
+
}
|
|
315
|
+
raf = setTimeout(apply, 300)
|
|
316
|
+
window.addEventListener("resize", apply)
|
|
317
|
+
return () => { clearTimeout(raf); window.removeEventListener("resize", apply) }
|
|
318
|
+
}, [props === undefined ? null : props.sessionId])
|
|
319
|
+
|
|
320
|
+
// Hover timing per spec §5: 300ms to show, 150ms to hide.
|
|
321
|
+
const hoverTimer = React.useState(null)
|
|
322
|
+
const onEnter = () => {
|
|
323
|
+
if (hoverTimer[0] !== null) clearTimeout(hoverTimer[0])
|
|
324
|
+
hoverTimer[1](setTimeout(() => popover[1](true), HOVER_SHOW_MS))
|
|
325
|
+
}
|
|
326
|
+
const onLeave = () => {
|
|
327
|
+
if (hoverTimer[0] !== null) clearTimeout(hoverTimer[0])
|
|
328
|
+
hoverTimer[1](setTimeout(() => popover[1](false), HOVER_HIDE_MS))
|
|
329
|
+
}
|
|
330
|
+
React.useEffect(() => () => { if (hoverTimer[0] !== null) clearTimeout(hoverTimer[0]) }, [])
|
|
331
|
+
|
|
332
|
+
// spec §3: manual shows 手动 with the pinned tier when one is known; colour returns to the default
|
|
333
|
+
// foreground (no tier colour is painted in manual).
|
|
334
|
+
const manualText = tier === null ? MANUAL_TEXT : MANUAL_TEXT + " · " + TIER_SHORT[tier]
|
|
335
|
+
const nothingYet = !offline && tier === null
|
|
336
|
+
const color = offline ? OFFLINE_COLOR : (manual || nothingYet ? null : TIER_COLOR[tier])
|
|
337
|
+
const text = offline
|
|
338
|
+
? OFFLINE_TEXT
|
|
339
|
+
: (manual ? manualText : (nothingYet ? PENDING_TEXT : TIER_TEXT[tier]))
|
|
340
|
+
const title = offline
|
|
341
|
+
? OFFLINE_TIP
|
|
342
|
+
: (manual ? "手动 · 点击恢复 Auto" : (nothingYet ? PENDING_TIP : "自动 · 点击切到手动"))
|
|
343
|
+
|
|
344
|
+
const style = {
|
|
345
|
+
height: "22px",
|
|
346
|
+
padding: "0 9px",
|
|
347
|
+
borderRadius: "999px",
|
|
348
|
+
fontSize: "11px",
|
|
349
|
+
lineHeight: "1",
|
|
350
|
+
border: "1px solid var(--dsw-alias-border-l1)",
|
|
351
|
+
color: offline || tier === null
|
|
352
|
+
? OFFLINE_COLOR
|
|
353
|
+
: (manual ? "var(--dsw-alias-label-primary)" : color),
|
|
354
|
+
fontWeight: manual ? 400 : 600,
|
|
355
|
+
transform: "translateX(" + String(offset[0]) + "px)",
|
|
356
|
+
}
|
|
357
|
+
if (shape[0] !== null) {
|
|
358
|
+
if (shape[0].height !== undefined) style.height = shape[0].height
|
|
359
|
+
if (shape[0].borderRadius !== undefined) style.borderRadius = shape[0].borderRadius
|
|
360
|
+
if (shape[0].fontSize !== undefined) style.fontSize = shape[0].fontSize
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const dot = React.createElement("span", {
|
|
364
|
+
className: "routerLayaDot",
|
|
365
|
+
style: { background: color === null ? "var(--dsw-alias-label-dimmed)" : color },
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
const classes = ["routerLayaChip"]
|
|
369
|
+
if (pop[0] !== 0) classes.push("routerLayaPop")
|
|
370
|
+
if (!offline && !manual && tier === "max") classes.push("routerLayaMax")
|
|
371
|
+
|
|
372
|
+
const chip = React.createElement("div", {
|
|
373
|
+
ref: (node) => chipRef[1](node),
|
|
374
|
+
className: classes.join(" "),
|
|
375
|
+
style: style,
|
|
376
|
+
title: title,
|
|
377
|
+
onClick: () => { writeMode(manual ? "auto" : "manual") },
|
|
378
|
+
onMouseEnter: onEnter,
|
|
379
|
+
onMouseLeave: onLeave,
|
|
380
|
+
}, dot, React.createElement("span", null, text))
|
|
381
|
+
|
|
382
|
+
const toastNode = toast[0] === null
|
|
383
|
+
? null
|
|
384
|
+
: React.createElement("div", {
|
|
385
|
+
className: "routerLayaToast" + (toastOut[0] ? " routerLayaToastOut" : ""),
|
|
386
|
+
}, toast[0])
|
|
387
|
+
|
|
388
|
+
const popoverNode = popover[0] && rows.length > 0
|
|
389
|
+
? React.createElement("div", { className: "routerLayaPopover" },
|
|
390
|
+
rows.slice(-POPOVER_LIMIT).reverse().map((row, index) => React.createElement("div", {
|
|
391
|
+
key: String(row.ts) + ":" + String(index),
|
|
392
|
+
className: "routerLayaRow",
|
|
393
|
+
},
|
|
394
|
+
React.createElement("span", { className: "routerLayaTime" }, clockOf(row.ts)),
|
|
395
|
+
React.createElement("span", {
|
|
396
|
+
className: "routerLayaDot",
|
|
397
|
+
style: { background: TIER_COLOR[row.tier] || OFFLINE_COLOR },
|
|
398
|
+
}),
|
|
399
|
+
React.createElement("span", { className: "routerLayaTask" }, clip(row.task)),
|
|
400
|
+
React.createElement("span", { className: "routerLayaWhy" },
|
|
401
|
+
reasonText(row.triggered_by) + (row.regenerate === true ? " ↻" : "")),
|
|
402
|
+
)))
|
|
403
|
+
: null
|
|
404
|
+
|
|
405
|
+
return React.createElement("div", {
|
|
406
|
+
style: { position: "relative", display: "inline-flex", alignItems: "center" },
|
|
407
|
+
}, chip, toastNode, popoverNode)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function apply(ctx) {
|
|
411
|
+
if (styleEl === null) {
|
|
412
|
+
styleEl = document.createElement("style")
|
|
413
|
+
styleEl.setAttribute("data-router-laya", "tier-chip")
|
|
414
|
+
styleEl.textContent = CSS
|
|
415
|
+
document.head.appendChild(styleEl)
|
|
416
|
+
}
|
|
417
|
+
startPolling()
|
|
418
|
+
ctx.effect(() => () => {
|
|
419
|
+
if (poller !== null) { clearInterval(poller); poller = null }
|
|
420
|
+
if (styleEl !== null && styleEl.parentNode !== null) styleEl.parentNode.removeChild(styleEl)
|
|
421
|
+
styleEl = null
|
|
422
|
+
})
|
|
423
|
+
ctx.slots.inject("conversation.input.right", () => ctx.slots.register(
|
|
424
|
+
{ name: "conversation.input.right", id: "router-laya-tier", order: 5 },
|
|
425
|
+
(slotProps) => React.createElement(Chip, {
|
|
426
|
+
sessionId: slotProps === undefined || slotProps === null ? undefined : slotProps.sessionId,
|
|
427
|
+
}),
|
|
428
|
+
))
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
exports.name = "router-laya-client"
|
|
432
|
+
exports.inject = ["slots"]
|
|
433
|
+
exports.apply = apply
|
|
434
|
+
return module.exports
|
|
435
|
+
},
|
|
436
|
+
})
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# dsh bundle patch: inserts this plugin's row into a profile's layer stack.
|
|
2
|
+
#
|
|
3
|
+
# NOTE for the current dev install: the web profile's own cordis.patch.yml already inserts a row with
|
|
4
|
+
# this id (`router-laya`), because that is how the plugin was mounted before this package existed. Do NOT
|
|
5
|
+
# list this package in `dsh.profile.bundles` while that hand-written row is still there -- Cordis refuses
|
|
6
|
+
# a tree with a duplicate loader entry id, and the profile would not boot.
|
|
7
|
+
- insert:
|
|
8
|
+
- id: router-laya
|
|
9
|
+
name: 'dsh-router-laya'
|