dsh-model-arena 0.2.0 → 0.2.1
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/arena.web.js +81 -29
- package/lib/arena.web.js.map +3 -3
- package/lib/client/index.d.ts +21 -10
- package/lib/client/index.js +13 -54
- package/lib/client/locales.d.ts +27 -0
- package/lib/client/locales.js +39 -0
- package/lib/client/view.d.ts +26 -1
- package/lib/client/view.js +56 -8
- package/package.json +4 -3
package/lib/arena.web.js
CHANGED
|
@@ -26,65 +26,117 @@ __export(index_exports, {
|
|
|
26
26
|
inject: () => inject
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
|
-
var import_react = require("react");
|
|
30
29
|
|
|
31
|
-
// src/client/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// src/client/locales.ts
|
|
31
|
+
var NS = "modelArena";
|
|
32
|
+
var zh = {
|
|
33
|
+
"nav": "\u6A21\u578B\u7ADE\u6280\u573A",
|
|
34
|
+
"title": "\u6A21\u578B\u7ADE\u6280\u573A",
|
|
35
|
+
"run": "\u8FD0\u884C",
|
|
36
|
+
"models": "\u6A21\u578B\u6570",
|
|
37
|
+
"date": "\u65F6\u95F4",
|
|
38
|
+
"prompt": "\u63D0\u793A\u8BCD",
|
|
39
|
+
"empty": "\u8FD8\u6CA1\u6709\u8FD0\u884C\u8BB0\u5F55\u3002\u7528\u540C\u4E00\u4E2A\u63D0\u793A\u8BCD\u8DD1\u591A\u4E2A\u6A21\u578B\u540E\uFF0C\u7ED3\u679C\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002",
|
|
40
|
+
"refresh": "\u5237\u65B0",
|
|
41
|
+
"loading": "\u6B63\u5728\u52A0\u8F7D\u2026",
|
|
42
|
+
"failed": "\u52A0\u8F7D\u5931\u8D25",
|
|
43
|
+
"retry": "\u91CD\u8BD5"
|
|
44
|
+
};
|
|
45
|
+
var en = {
|
|
46
|
+
"nav": "Model Arena",
|
|
47
|
+
"title": "Model Arena",
|
|
48
|
+
"run": "Run",
|
|
49
|
+
"models": "Models",
|
|
50
|
+
"date": "Date",
|
|
51
|
+
"prompt": "Prompt",
|
|
52
|
+
"empty": "No runs yet. Run one prompt through several models and the comparison appears here.",
|
|
53
|
+
"refresh": "Refresh",
|
|
54
|
+
"loading": "Loading\u2026",
|
|
55
|
+
"failed": "Failed to load",
|
|
56
|
+
"retry": "Retry"
|
|
57
|
+
};
|
|
39
58
|
|
|
40
|
-
// src/client/
|
|
59
|
+
// src/client/view.tsx
|
|
60
|
+
var import_react = require("react");
|
|
41
61
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
42
62
|
var PANEL_PATH = "/api/arena.panel";
|
|
63
|
+
var wrap = { display: "flex", flexDirection: "column", gap: 12, maxWidth: 760, fontFamily: "inherit" };
|
|
64
|
+
var head = { display: "flex", alignItems: "baseline", gap: 12, flexWrap: "wrap" };
|
|
65
|
+
var muted = { fontSize: 12, opacity: 0.75 };
|
|
66
|
+
var table = { borderCollapse: "collapse", width: "100%" };
|
|
67
|
+
var th = { textAlign: "left", padding: "4px 10px 4px 0", fontWeight: 600, fontSize: 12, opacity: 0.8, borderBottom: "0.5px solid rgba(128,128,128,0.4)" };
|
|
68
|
+
var td = { padding: "6px 10px 6px 0", fontSize: 13, borderBottom: "0.5px solid rgba(128,128,128,0.18)" };
|
|
43
69
|
function usePanel() {
|
|
44
|
-
const [
|
|
45
|
-
const [error, setError] = (0, import_react.useState)(null);
|
|
70
|
+
const [state, setState] = (0, import_react.useState)({ payload: null, error: null });
|
|
46
71
|
const [tick, setTick] = (0, import_react.useState)(0);
|
|
47
|
-
const reload = (0, import_react.useCallback)(() => setTick((
|
|
72
|
+
const reload = (0, import_react.useCallback)(() => setTick((value) => value + 1), []);
|
|
48
73
|
(0, import_react.useEffect)(() => {
|
|
49
74
|
const controller = new AbortController();
|
|
50
|
-
|
|
75
|
+
setState((previous) => ({ ...previous, error: null }));
|
|
51
76
|
fetch(PANEL_PATH, { signal: controller.signal }).then(async (response) => {
|
|
52
|
-
if (!response.ok) throw new Error(
|
|
77
|
+
if (!response.ok) throw new Error(String(response.status));
|
|
53
78
|
return response.json();
|
|
54
79
|
}).then((payload) => {
|
|
55
|
-
if (!controller.signal.aborted)
|
|
80
|
+
if (!controller.signal.aborted) setState({ payload, error: null });
|
|
56
81
|
}).catch((cause) => {
|
|
57
82
|
if (controller.signal.aborted) return;
|
|
58
|
-
|
|
83
|
+
setState({ payload: null, error: cause instanceof Error ? cause.message : String(cause) });
|
|
59
84
|
});
|
|
60
85
|
return () => controller.abort();
|
|
61
86
|
}, [tick]);
|
|
62
|
-
return {
|
|
87
|
+
return { ...state, reload };
|
|
63
88
|
}
|
|
64
|
-
function
|
|
65
|
-
const {
|
|
89
|
+
function ArenaPanel({ t }) {
|
|
90
|
+
const { payload, error, reload } = usePanel();
|
|
91
|
+
const header = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { style: head, children: [
|
|
92
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 13 }, children: t("title") }),
|
|
93
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
|
|
94
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: reload, style: { fontSize: 12 }, children: t("refresh") })
|
|
95
|
+
] });
|
|
66
96
|
if (error !== null) {
|
|
67
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: wrap, children: [
|
|
98
|
+
header,
|
|
99
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "alert", style: { margin: 0, fontSize: 13 }, children: [
|
|
100
|
+
t("failed"),
|
|
101
|
+
": ",
|
|
70
102
|
error
|
|
71
103
|
] }),
|
|
72
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: reload, children: "
|
|
104
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: reload, style: { alignSelf: "flex-start", fontSize: 12 }, children: t("retry") })
|
|
73
105
|
] });
|
|
74
106
|
}
|
|
75
|
-
if (
|
|
76
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.
|
|
107
|
+
if (payload === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: muted, "aria-live": "polite", children: t("loading") });
|
|
108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: wrap, children: [
|
|
109
|
+
header,
|
|
110
|
+
payload.recentRuns.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontSize: 13, opacity: 0.8 }, children: t("empty") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: table, children: [
|
|
111
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("run") }),
|
|
113
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("models") }),
|
|
114
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("date") }),
|
|
115
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("prompt") })
|
|
116
|
+
] }) }),
|
|
117
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: payload.recentRuns.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
|
|
118
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: { fontSize: 11 }, children: row.id }) }),
|
|
119
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: row.modelCount }),
|
|
120
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: new Date(row.timestamp).toLocaleString() }),
|
|
121
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: row.prompt.length > 60 ? `${row.prompt.slice(0, 60)}\u2026` : row.prompt })
|
|
122
|
+
] }, row.id)) })
|
|
123
|
+
] })
|
|
124
|
+
] });
|
|
77
125
|
}
|
|
78
|
-
|
|
126
|
+
|
|
127
|
+
// src/client/index.tsx
|
|
128
|
+
var inject = ["slots", "locale"];
|
|
79
129
|
function apply(ctx) {
|
|
130
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-model-arena: dictionaries");
|
|
80
131
|
ctx.slots.inject("settings.section", () => ctx.slots.register(
|
|
81
132
|
{
|
|
82
133
|
name: "settings.section",
|
|
83
134
|
id: "model-arena",
|
|
84
135
|
order: 42,
|
|
85
|
-
label: () => "
|
|
136
|
+
label: () => ctx.locale.bind(NS)("nav"),
|
|
137
|
+
locale: NS
|
|
86
138
|
},
|
|
87
|
-
|
|
139
|
+
ArenaPanel
|
|
88
140
|
));
|
|
89
141
|
}
|
|
90
142
|
return module.exports; } });
|
package/lib/arena.web.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/client/index.tsx", "../src/client/view.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * Browser half of dsh-model-arena:
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
3
|
+
"sources": ["../src/client/index.tsx", "../src/client/locales.ts", "../src/client/view.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * Browser half of dsh-model-arena: its page inside Settings.\n *\n * Two services are injected by their runtime identity, not by the packages that provide\n * them: the settings shell is `slots`, the dictionary registry is `locale`. The label and\n * every string inside the component are translated through this plugin's own namespace,\n * which is why the page follows the UI language instead of the plugin author's.\n *\n * @module client\n */\n\nimport type { ComponentType } from 'react'\n\nimport { NS, en, zh } from './locales.js'\nimport { ArenaPanel } from './view.js'\n\nexport const inject = ['slots', 'locale']\n\ninterface SlotsService {\n inject(name: string, register: () => void): void\n register(\n options: { name: string; id: string; order: number; label: () => string; locale: string },\n component: ComponentType<{ t: (key: string, params?: Record<string, unknown>) => string }>,\n ): unknown\n}\n\ninterface ClientContext {\n slots: SlotsService\n locale: {\n register(ns: string, dicts: { zh: unknown; en: unknown }): () => void\n bind(ns: string): (key: string, params?: Record<string, unknown>) => string\n }\n effect(callback: () => unknown, label?: string): unknown\n}\n\nexport function apply(ctx: ClientContext): void {\n // `zh` is the key-set source of truth, matching the official client plugins.\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-model-arena: dictionaries')\n\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n {\n name: 'settings.section',\n id: 'model-arena',\n order: 42,\n label: () => ctx.locale.bind(NS)('nav'),\n locale: NS,\n },\n ArenaPanel,\n ))\n}\n", "/**\n * Dictionaries for the Model Arena page.\n *\n * `zh` is the key-set source of truth, as in the official client plugins, and `en` is\n * typed against it: a key translated in one language but not the other fails the build\n * instead of silently rendering the raw key.\n *\n * @module client/locales\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'modelArena'\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'nav': '\u6A21\u578B\u7ADE\u6280\u573A',\n 'title': '\u6A21\u578B\u7ADE\u6280\u573A',\n 'run': '\u8FD0\u884C',\n 'models': '\u6A21\u578B\u6570',\n 'date': '\u65F6\u95F4',\n 'prompt': '\u63D0\u793A\u8BCD',\n 'empty': '\u8FD8\u6CA1\u6709\u8FD0\u884C\u8BB0\u5F55\u3002\u7528\u540C\u4E00\u4E2A\u63D0\u793A\u8BCD\u8DD1\u591A\u4E2A\u6A21\u578B\u540E\uFF0C\u7ED3\u679C\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002',\n 'refresh': '\u5237\u65B0',\n 'loading': '\u6B63\u5728\u52A0\u8F7D\u2026',\n 'failed': '\u52A0\u8F7D\u5931\u8D25',\n 'retry': '\u91CD\u8BD5',\n}\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en: typeof zh = {\n 'nav': 'Model Arena',\n 'title': 'Model Arena',\n 'run': 'Run',\n 'models': 'Models',\n 'date': 'Date',\n 'prompt': 'Prompt',\n 'empty': 'No runs yet. Run one prompt through several models and the comparison appears here.',\n 'refresh': 'Refresh',\n 'loading': 'Loading\u2026',\n 'failed': 'Failed to load',\n 'retry': 'Retry',\n}\n", "/**\n * Pure rendering half of the modelArena page.\n *\n * Separate from `index.tsx` so a static render can assert in Node what the page draws \u2014\n * the shipped bundle is a loader factory only a browser can run. Every user-visible\n * string comes from the `t` seat the renderer binds from this plugin's namespace, so the\n * page follows the UI language; no copy is hardcoded here.\n *\n * @module client/view\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { CSSProperties } from 'react'\n\nimport type { PanelPayload } from '../types.js'\n\n/** The translate seat the renderer binds from this plugin's locale namespace. */\nexport type Translate = (key: string, params?: Record<string, unknown>) => string\n\nexport interface PanelProps {\n /** Bound translate function for this plugin's namespace. */\n t: Translate\n}\n\n/** Panel route registered by the host half on the web connection. */\nconst PANEL_PATH = \"/api/arena.panel\"\n\nconst wrap: CSSProperties = { display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 760, fontFamily: 'inherit' }\nconst head: CSSProperties = { display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap' }\nconst muted: CSSProperties = { fontSize: 12, opacity: 0.75 }\nconst table: CSSProperties = { borderCollapse: 'collapse', width: '100%' }\nconst th: CSSProperties = { textAlign: 'left', padding: '4px 10px 4px 0', fontWeight: 600, fontSize: 12, opacity: 0.8, borderBottom: '0.5px solid rgba(128,128,128,0.4)' }\nconst td: CSSProperties = { padding: '6px 10px 6px 0', fontSize: 13, borderBottom: '0.5px solid rgba(128,128,128,0.18)' }\n\ninterface PanelState {\n payload: PanelPayload | null\n error: string | null\n}\n\n/** Fetch the host panel payload; `reload` re-runs the request. */\nexport function usePanel(): PanelState & { reload: () => void } {\n const [state, setState] = useState<PanelState>({ payload: null, error: null })\n const [tick, setTick] = useState(0)\n const reload = useCallback(() => setTick((value) => value + 1), [])\n\n useEffect(() => {\n const controller = new AbortController()\n setState((previous) => ({ ...previous, error: null }))\n fetch(PANEL_PATH, { signal: controller.signal })\n .then(async (response) => {\n if (!response.ok) throw new Error(String(response.status))\n return response.json() as Promise<PanelPayload>\n })\n .then((payload) => {\n if (!controller.signal.aborted) setState({ payload, error: null })\n })\n .catch((cause: unknown) => {\n if (controller.signal.aborted) return\n setState({ payload: null, error: cause instanceof Error ? cause.message : String(cause) })\n })\n return () => controller.abort()\n }, [tick])\n\n return { ...state, reload }\n}\n\nexport function ArenaPanel({ t }: PanelProps) {\n const { payload, error, reload } = usePanel()\n const header = (\n <header style={head}>\n <strong style={{ fontSize: 13 }}>{t('title')}</strong>\n <span style={{ flex: 1 }} />\n <button type=\"button\" onClick={reload} style={{ fontSize: 12 }}>{t('refresh')}</button>\n </header>\n )\n if (error !== null) {\n return (\n <div style={wrap}>\n {header}\n <p role=\"alert\" style={{ margin: 0, fontSize: 13 }}>{t('failed')}: {error}</p>\n <button type=\"button\" onClick={reload} style={{ alignSelf: 'flex-start', fontSize: 12 }}>{t('retry')}</button>\n </div>\n )\n }\n if (payload === null) return <p style={muted} aria-live=\"polite\">{t('loading')}</p>\n return (\n <div style={wrap}>\n {header}\n {payload.recentRuns.length === 0 ? (\n <p style={{ margin: 0, fontSize: 13, opacity: 0.8 }}>{t('empty')}</p>\n ) : (\n <table style={table}>\n <thead>\n <tr><th style={th}>{t('run')}</th><th style={th}>{t('models')}</th><th style={th}>{t('date')}</th><th style={th}>{t('prompt')}</th></tr>\n </thead>\n <tbody>\n {payload.recentRuns.map((row) => (\n <tr key={row.id}>\n <td style={td}><code style={{ fontSize: 11 }}>{row.id}</code></td>\n <td style={td}>{row.modelCount}</td>\n <td style={td}>{new Date(row.timestamp).toLocaleString()}</td>\n <td style={td}>{row.prompt.length > 60 ? `${row.prompt.slice(0, 60)}\u2026` : row.prompt}</td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/lib/client/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser half of dsh-model-arena:
|
|
2
|
+
* Browser half of dsh-model-arena: its page inside Settings.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* the settings shell is
|
|
6
|
-
*
|
|
7
|
-
* the
|
|
4
|
+
* Two services are injected by their runtime identity, not by the packages that provide
|
|
5
|
+
* them: the settings shell is `slots`, the dictionary registry is `locale`. The label and
|
|
6
|
+
* every string inside the component are translated through this plugin's own namespace,
|
|
7
|
+
* which is why the page follows the UI language instead of the plugin author's.
|
|
8
8
|
*
|
|
9
9
|
* @module client
|
|
10
10
|
*/
|
|
11
11
|
import type { ComponentType } from 'react';
|
|
12
|
+
export declare const inject: string[];
|
|
12
13
|
interface SlotsService {
|
|
13
14
|
inject(name: string, register: () => void): void;
|
|
14
15
|
register(options: {
|
|
@@ -16,11 +17,21 @@ interface SlotsService {
|
|
|
16
17
|
id: string;
|
|
17
18
|
order: number;
|
|
18
19
|
label: () => string;
|
|
19
|
-
|
|
20
|
+
locale: string;
|
|
21
|
+
}, component: ComponentType<{
|
|
22
|
+
t: (key: string, params?: Record<string, unknown>) => string;
|
|
23
|
+
}>): unknown;
|
|
20
24
|
}
|
|
21
|
-
|
|
22
|
-
export declare const inject: string[];
|
|
23
|
-
export declare function apply(ctx: {
|
|
25
|
+
interface ClientContext {
|
|
24
26
|
slots: SlotsService;
|
|
25
|
-
|
|
27
|
+
locale: {
|
|
28
|
+
register(ns: string, dicts: {
|
|
29
|
+
zh: unknown;
|
|
30
|
+
en: unknown;
|
|
31
|
+
}): () => void;
|
|
32
|
+
bind(ns: string): (key: string, params?: Record<string, unknown>) => string;
|
|
33
|
+
};
|
|
34
|
+
effect(callback: () => unknown, label?: string): unknown;
|
|
35
|
+
}
|
|
36
|
+
export declare function apply(ctx: ClientContext): void;
|
|
26
37
|
export {};
|
package/lib/client/index.js
CHANGED
|
@@ -1,65 +1,24 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
1
|
/**
|
|
3
|
-
* Browser half of dsh-model-arena:
|
|
2
|
+
* Browser half of dsh-model-arena: its page inside Settings.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
* the settings shell is
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
4
|
+
* Two services are injected by their runtime identity, not by the packages that provide
|
|
5
|
+
* them: the settings shell is `slots`, the dictionary registry is `locale`. The label and
|
|
6
|
+
* every string inside the component are translated through this plugin's own namespace,
|
|
7
|
+
* which is why the page follows the UI language instead of the plugin author's.
|
|
9
8
|
*
|
|
10
9
|
* @module client
|
|
11
10
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
const PANEL_PATH = '/api/arena.panel';
|
|
16
|
-
/** Fetch the panel payload; `reload` re-runs the request. */
|
|
17
|
-
function usePanel() {
|
|
18
|
-
const [html, setHtml] = useState(null);
|
|
19
|
-
const [error, setError] = useState(null);
|
|
20
|
-
const [tick, setTick] = useState(0);
|
|
21
|
-
const reload = useCallback(() => setTick((t) => t + 1), []);
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
const controller = new AbortController();
|
|
24
|
-
setError(null);
|
|
25
|
-
fetch(PANEL_PATH, { signal: controller.signal })
|
|
26
|
-
.then(async (response) => {
|
|
27
|
-
if (!response.ok)
|
|
28
|
-
throw new Error(`panel request failed with ${response.status}`);
|
|
29
|
-
return response.json();
|
|
30
|
-
})
|
|
31
|
-
.then((payload) => {
|
|
32
|
-
if (!controller.signal.aborted)
|
|
33
|
-
setHtml(renderPanel(payload));
|
|
34
|
-
})
|
|
35
|
-
.catch((cause) => {
|
|
36
|
-
if (controller.signal.aborted)
|
|
37
|
-
return;
|
|
38
|
-
// Say what failed rather than rendering an empty panel, which would read as
|
|
39
|
-
// "nothing recorded" — a different and wrong answer.
|
|
40
|
-
setError(cause instanceof Error ? cause.message : String(cause));
|
|
41
|
-
});
|
|
42
|
-
return () => controller.abort();
|
|
43
|
-
}, [tick]);
|
|
44
|
-
return { html, error, reload };
|
|
45
|
-
}
|
|
46
|
-
function ModelArenaPage() {
|
|
47
|
-
const { html, error, reload } = usePanel();
|
|
48
|
-
if (error !== null) {
|
|
49
|
-
return (_jsxs("section", { children: [_jsxs("p", { role: "alert", children: ["Model Arena panel failed to load: ", error] }), _jsx("button", { type: "button", onClick: reload, children: "Retry" })] }));
|
|
50
|
-
}
|
|
51
|
-
if (html === null)
|
|
52
|
-
return _jsx("p", { "aria-live": "polite", children: "Loading the Model Arena panel\u2026" });
|
|
53
|
-
// The markup is produced by `renderPanel`, which escapes every interpolated value.
|
|
54
|
-
return _jsx("div", { dangerouslySetInnerHTML: { __html: html } });
|
|
55
|
-
}
|
|
56
|
-
/** The slots service is what the settings shell exposes. */
|
|
57
|
-
export const inject = ['slots'];
|
|
11
|
+
import { NS, en, zh } from './locales.js';
|
|
12
|
+
import { ArenaPanel } from './view.js';
|
|
13
|
+
export const inject = ['slots', 'locale'];
|
|
58
14
|
export function apply(ctx) {
|
|
15
|
+
// `zh` is the key-set source of truth, matching the official client plugins.
|
|
16
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-model-arena: dictionaries');
|
|
59
17
|
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
60
18
|
name: 'settings.section',
|
|
61
19
|
id: 'model-arena',
|
|
62
20
|
order: 42,
|
|
63
|
-
label: () => '
|
|
64
|
-
|
|
21
|
+
label: () => ctx.locale.bind(NS)('nav'),
|
|
22
|
+
locale: NS,
|
|
23
|
+
}, ArenaPanel));
|
|
65
24
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dictionaries for the Model Arena page.
|
|
3
|
+
*
|
|
4
|
+
* `zh` is the key-set source of truth, as in the official client plugins, and `en` is
|
|
5
|
+
* typed against it: a key translated in one language but not the other fails the build
|
|
6
|
+
* instead of silently rendering the raw key.
|
|
7
|
+
*
|
|
8
|
+
* @module client/locales
|
|
9
|
+
*/
|
|
10
|
+
/** Dictionary namespace owned by this plugin. */
|
|
11
|
+
export declare const NS = "modelArena";
|
|
12
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
13
|
+
export declare const zh: {
|
|
14
|
+
nav: string;
|
|
15
|
+
title: string;
|
|
16
|
+
run: string;
|
|
17
|
+
models: string;
|
|
18
|
+
date: string;
|
|
19
|
+
prompt: string;
|
|
20
|
+
empty: string;
|
|
21
|
+
refresh: string;
|
|
22
|
+
loading: string;
|
|
23
|
+
failed: string;
|
|
24
|
+
retry: string;
|
|
25
|
+
};
|
|
26
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
27
|
+
export declare const en: typeof zh;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dictionaries for the Model Arena page.
|
|
3
|
+
*
|
|
4
|
+
* `zh` is the key-set source of truth, as in the official client plugins, and `en` is
|
|
5
|
+
* typed against it: a key translated in one language but not the other fails the build
|
|
6
|
+
* instead of silently rendering the raw key.
|
|
7
|
+
*
|
|
8
|
+
* @module client/locales
|
|
9
|
+
*/
|
|
10
|
+
/** Dictionary namespace owned by this plugin. */
|
|
11
|
+
export const NS = 'modelArena';
|
|
12
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
13
|
+
export const zh = {
|
|
14
|
+
'nav': '模型竞技场',
|
|
15
|
+
'title': '模型竞技场',
|
|
16
|
+
'run': '运行',
|
|
17
|
+
'models': '模型数',
|
|
18
|
+
'date': '时间',
|
|
19
|
+
'prompt': '提示词',
|
|
20
|
+
'empty': '还没有运行记录。用同一个提示词跑多个模型后,结果会显示在这里。',
|
|
21
|
+
'refresh': '刷新',
|
|
22
|
+
'loading': '正在加载…',
|
|
23
|
+
'failed': '加载失败',
|
|
24
|
+
'retry': '重试',
|
|
25
|
+
};
|
|
26
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
27
|
+
export const en = {
|
|
28
|
+
'nav': 'Model Arena',
|
|
29
|
+
'title': 'Model Arena',
|
|
30
|
+
'run': 'Run',
|
|
31
|
+
'models': 'Models',
|
|
32
|
+
'date': 'Date',
|
|
33
|
+
'prompt': 'Prompt',
|
|
34
|
+
'empty': 'No runs yet. Run one prompt through several models and the comparison appears here.',
|
|
35
|
+
'refresh': 'Refresh',
|
|
36
|
+
'loading': 'Loading…',
|
|
37
|
+
'failed': 'Failed to load',
|
|
38
|
+
'retry': 'Retry',
|
|
39
|
+
};
|
package/lib/client/view.d.ts
CHANGED
|
@@ -1,2 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure rendering half of the modelArena page.
|
|
3
|
+
*
|
|
4
|
+
* Separate from `index.tsx` so a static render can assert in Node what the page draws —
|
|
5
|
+
* the shipped bundle is a loader factory only a browser can run. Every user-visible
|
|
6
|
+
* string comes from the `t` seat the renderer binds from this plugin's namespace, so the
|
|
7
|
+
* page follows the UI language; no copy is hardcoded here.
|
|
8
|
+
*
|
|
9
|
+
* @module client/view
|
|
10
|
+
*/
|
|
1
11
|
import type { PanelPayload } from '../types.js';
|
|
2
|
-
|
|
12
|
+
/** The translate seat the renderer binds from this plugin's locale namespace. */
|
|
13
|
+
export type Translate = (key: string, params?: Record<string, unknown>) => string;
|
|
14
|
+
export interface PanelProps {
|
|
15
|
+
/** Bound translate function for this plugin's namespace. */
|
|
16
|
+
t: Translate;
|
|
17
|
+
}
|
|
18
|
+
interface PanelState {
|
|
19
|
+
payload: PanelPayload | null;
|
|
20
|
+
error: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** Fetch the host panel payload; `reload` re-runs the request. */
|
|
23
|
+
export declare function usePanel(): PanelState & {
|
|
24
|
+
reload: () => void;
|
|
25
|
+
};
|
|
26
|
+
export declare function ArenaPanel({ t }: PanelProps): import("react").JSX.Element;
|
|
27
|
+
export {};
|
package/lib/client/view.js
CHANGED
|
@@ -1,9 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Pure rendering half of the modelArena page.
|
|
4
|
+
*
|
|
5
|
+
* Separate from `index.tsx` so a static render can assert in Node what the page draws —
|
|
6
|
+
* the shipped bundle is a loader factory only a browser can run. Every user-visible
|
|
7
|
+
* string comes from the `t` seat the renderer binds from this plugin's namespace, so the
|
|
8
|
+
* page follows the UI language; no copy is hardcoded here.
|
|
9
|
+
*
|
|
10
|
+
* @module client/view
|
|
11
|
+
*/
|
|
12
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
13
|
+
/** Panel route registered by the host half on the web connection. */
|
|
14
|
+
const PANEL_PATH = "/api/arena.panel";
|
|
15
|
+
const wrap = { display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 760, fontFamily: 'inherit' };
|
|
16
|
+
const head = { display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap' };
|
|
17
|
+
const muted = { fontSize: 12, opacity: 0.75 };
|
|
18
|
+
const table = { borderCollapse: 'collapse', width: '100%' };
|
|
19
|
+
const th = { textAlign: 'left', padding: '4px 10px 4px 0', fontWeight: 600, fontSize: 12, opacity: 0.8, borderBottom: '0.5px solid rgba(128,128,128,0.4)' };
|
|
20
|
+
const td = { padding: '6px 10px 6px 0', fontSize: 13, borderBottom: '0.5px solid rgba(128,128,128,0.18)' };
|
|
21
|
+
/** Fetch the host panel payload; `reload` re-runs the request. */
|
|
22
|
+
export function usePanel() {
|
|
23
|
+
const [state, setState] = useState({ payload: null, error: null });
|
|
24
|
+
const [tick, setTick] = useState(0);
|
|
25
|
+
const reload = useCallback(() => setTick((value) => value + 1), []);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
setState((previous) => ({ ...previous, error: null }));
|
|
29
|
+
fetch(PANEL_PATH, { signal: controller.signal })
|
|
30
|
+
.then(async (response) => {
|
|
31
|
+
if (!response.ok)
|
|
32
|
+
throw new Error(String(response.status));
|
|
33
|
+
return response.json();
|
|
34
|
+
})
|
|
35
|
+
.then((payload) => {
|
|
36
|
+
if (!controller.signal.aborted)
|
|
37
|
+
setState({ payload, error: null });
|
|
38
|
+
})
|
|
39
|
+
.catch((cause) => {
|
|
40
|
+
if (controller.signal.aborted)
|
|
41
|
+
return;
|
|
42
|
+
setState({ payload: null, error: cause instanceof Error ? cause.message : String(cause) });
|
|
43
|
+
});
|
|
44
|
+
return () => controller.abort();
|
|
45
|
+
}, [tick]);
|
|
46
|
+
return { ...state, reload };
|
|
47
|
+
}
|
|
48
|
+
export function ArenaPanel({ t }) {
|
|
49
|
+
const { payload, error, reload } = usePanel();
|
|
50
|
+
const header = (_jsxs("header", { style: head, children: [_jsx("strong", { style: { fontSize: 13 }, children: t('title') }), _jsx("span", { style: { flex: 1 } }), _jsx("button", { type: "button", onClick: reload, style: { fontSize: 12 }, children: t('refresh') })] }));
|
|
51
|
+
if (error !== null) {
|
|
52
|
+
return (_jsxs("div", { style: wrap, children: [header, _jsxs("p", { role: "alert", style: { margin: 0, fontSize: 13 }, children: [t('failed'), ": ", error] }), _jsx("button", { type: "button", onClick: reload, style: { alignSelf: 'flex-start', fontSize: 12 }, children: t('retry') })] }));
|
|
53
|
+
}
|
|
54
|
+
if (payload === null)
|
|
55
|
+
return _jsx("p", { style: muted, "aria-live": "polite", children: t('loading') });
|
|
56
|
+
return (_jsxs("div", { style: wrap, children: [header, payload.recentRuns.length === 0 ? (_jsx("p", { style: { margin: 0, fontSize: 13, opacity: 0.8 }, children: t('empty') })) : (_jsxs("table", { style: table, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { style: th, children: t('run') }), _jsx("th", { style: th, children: t('models') }), _jsx("th", { style: th, children: t('date') }), _jsx("th", { style: th, children: t('prompt') })] }) }), _jsx("tbody", { children: payload.recentRuns.map((row) => (_jsxs("tr", { children: [_jsx("td", { style: td, children: _jsx("code", { style: { fontSize: 11 }, children: row.id }) }), _jsx("td", { style: td, children: row.modelCount }), _jsx("td", { style: td, children: new Date(row.timestamp).toLocaleString() }), _jsx("td", { style: td, children: row.prompt.length > 60 ? `${row.prompt.slice(0, 60)}…` : row.prompt })] }, row.id))) })] }))] }));
|
|
9
57
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-arena",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Run the same prompt through every model you have, and see the difference without squinting.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -41,8 +41,9 @@
|
|
|
41
41
|
"client": {
|
|
42
42
|
"platform": "web",
|
|
43
43
|
"inject": [
|
|
44
|
-
"
|
|
45
|
-
"
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
45
|
+
"@deepseek-ai/dsh-client-locale",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
46
47
|
]
|
|
47
48
|
}
|
|
48
49
|
},
|