@morlay/dsh-session-mode 0.0.1-alpha.4 → 0.0.1-alpha.5

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.
@@ -1,9 +1,13 @@
1
1
  /**
2
- * 新会话屏幕上的模式 chip:**切换列表**就在这里(点开是 coding / chat 两项)。
2
+ * composer 里的模式 chip:**本包在会话里唯一的面**(点开是 coding / chat 两项)。
3
3
  *
4
- * 为什么住在新会话屏幕而不是输入框上方:选择只在会话开始**之前**有效——一旦跑过 turn,那段历史是在某个
5
- * 模式的工具与提示词下产生的,host 会拒绝换(与上游 `agentPresets.select` 同一条判据)。一个大半辈子都
6
- * 该禁用的控件,放在它仍然可用的那块屏幕上更诚实。
4
+ * 选择只在会话开始**之前**有效——一旦跑过 turn,那段历史是在某个模式的工具与提示词下产生的。所以 chip 在
5
+ * 会话里有两个形态:空白期是选择器,开过 turn 之后**只读**(只写当前模式,点不动)。判据是 host 的投影
6
+ * `sessionModeEditable`(`turn/start` 一落库就为 `false`),与服务端拒绝切换读的是同一份事实。
7
+ *
8
+ * 它挂在 composer 上:新会话屏也是一个 blank session 的 composer,选择正好发生在那里;会话头部则没有它的
9
+ * 位置——头部只在"跑起来之后"值得看,而那时的 chip 已经把当前模式写在脸上,再挂一个只读标签就是同一句话的
10
+ * 复读。
7
11
  */
8
12
 
9
13
  import { useState } from "react";
@@ -15,46 +19,61 @@ import {
15
19
  import type { PropsLocale, PropsRuntime } from "@deepseek-ai/dsh-client-ui-slots";
16
20
  // Type-only:拉入本包 host 半的 Context / 投影声明(`sessionMode`),让 `projectionValues` 有类型。
17
21
  import type {} from "../index.ts";
18
- // Type-only:拉入 ui-conversation 的 SlotMap 合并(hero 的座位)。
22
+ // Type-only:拉入 ui-conversation 的 SlotMap 合并(composer 工具行的座位)。
19
23
  import type {} from "@morlay/dsh-client-ui-conversation/client";
20
24
  import { selectMode } from "./api.ts";
21
25
  import { useRoster } from "./use-roster.ts";
22
26
  import css from "./SessionModeSeat.module.css";
23
27
 
24
- /** 完整 props:hero 槽位的运行时 props + 本包的字典。 */
25
- export type SessionModeSeatProps = PropsRuntime<"conversation.hero.agentPreset"> &
28
+ /** 完整 props:composer 工具行左侧槽位的运行时 props + 本包的字典。 */
29
+ export type SessionModeSeatProps = PropsRuntime<"conversation.input.left"> &
26
30
  PropsLocale<"session-mode">;
27
31
 
28
32
  /**
29
- * 渲染新会话的模式 chip。
33
+ * 渲染 composer 里的模式切换 chip。
34
+ *
35
+ * 为什么在 composer(`conversation.input.left`):**新会话屏也是一个 blank session 的 composer**,选择正好
36
+ * 发生在那里;会话头部槽位在新会话屏根本不存在,而会话跑起来之后头部也不该重复说一遍 chip 已经写着的事。
37
+ * 官方 roster 那个座位(`conversation.hero.agentPreset`)是单注册槽位、已被官方占了,所以用自己的 list 槽位
38
+ * 并存。
30
39
  * @param props - 槽位合成的 props。
31
- * @returns chip;清单没读到、或不是主视图的会话时返回 null。
40
+ * @returns chip;清单没读到、或会话未知时返回 null。
32
41
  */
33
- export function SessionModeSeat({
34
- sessionId,
35
- useSessions,
36
- useSessionRetainInfo,
37
- t,
38
- }: SessionModeSeatProps) {
42
+ export function SessionModeSeat({ sessionId, useSessions, t }: SessionModeSeatProps) {
39
43
  const roster = useRoster();
40
44
  const selected = useSessions((state) => {
41
45
  const value =
42
46
  sessionId === undefined ? undefined : state.byId[sessionId]?.projectionValues?.sessionMode;
43
47
  return typeof value === "string" ? value : undefined;
44
48
  });
45
- const main = useSessionRetainInfo(
46
- (info) => sessionId === undefined || (info?.retainedBy.mainView ?? 0) > 0,
47
- );
49
+ const editable = useSessions((state) => {
50
+ const value =
51
+ sessionId === undefined
52
+ ? undefined
53
+ : state.byId[sessionId]?.projectionValues?.sessionModeEditable;
54
+ return typeof value === "boolean" ? value : undefined;
55
+ });
48
56
  const [open, setOpen] = useState(false);
49
57
  const [busy, setBusy] = useState(false);
50
58
  const [error, setError] = useState<string | null>(null);
51
59
 
52
- if (!main || roster === undefined) return null;
60
+ if (roster === undefined) return null;
53
61
 
54
62
  const current = selected ?? roster.default;
55
63
  const chosen = roster.modes.find((mode) => mode.id === current);
56
64
  const label = chosen?.name ?? current;
57
65
 
66
+ // 开过 turn 的会话**只读**:chip 只写当前模式,点不动也不给清单。判据与 host 拒绝切换读的是同一个投影
67
+ // (`sessionModeEditable`),所以不会出现"看起来能选、点了报错"。
68
+ if (editable === false) {
69
+ return (
70
+ <button type="button" className={css.seat} disabled title={t("lockedHint")}>
71
+ <IconAgentPresetOutlineRegular className={css.seatIcon} />
72
+ <span className={css.seatLabel}>{label}</span>
73
+ </button>
74
+ );
75
+ }
76
+
58
77
  return (
59
78
  <Menu
60
79
  open={open}
@@ -1,40 +1,35 @@
1
1
  /**
2
- * client 半:会话里的两个面(模式 chip 与头部标签)+ 设置页里那张「会话模式」卡片。
2
+ * client 半:会话里那一个面(模式 chip)+ 设置页里那张「会话模式」卡片。
3
3
  *
4
- * | 槽位 | 呈现 |
5
- * | ---------------------------------------- | --------------------------------------------- |
6
- * | `conversation.hero.agentPreset` | 新会话屏幕的顶部占位——chip 点开就是切换列表 |
7
- * | `conversation.session.header.actions` | 会话头部的只读模式标签(`order: -10`,贴标题) |
8
- * | `plugins.row.config`(key 见下) | 设置页里**本行**的配置入口:各模式的默认模型(`config.models`) |
4
+ * | 槽位 | 呈现 |
5
+ * | ------------------------- | --------------------------------------------------- |
6
+ * | `conversation.input.left` | chip——点开就是切换列表(新会话屏也是一个 composer) |
9
7
  *
10
- * 官方的 `@deepseek-ai/dsh-client-ui-agent-preset` 在装配里被禁用(它同时带来设置页那块 roster 面板),
11
- * 所以这两个面归我们。设置页那张卡片编辑的是**各模式的默认模型**——配置事实(`config.models`),不是模式
12
- * 清单;模式清单仍然是装配配置,改它不需要页面。
8
+ * 官方 `@deepseek-ai/dsh-client-ui-agent-preset` 保留(它提供 preset 的选择面与设置页 roster):官方管"挂哪套
9
+ * 行",我们管"会话级扩展",两套入口并存、互不遮挡。
13
10
  *
14
- * 卡片的数据面是 host 行的 settings namespace `session-mode`(namespace 名就是行 id):host 行被描述出来
15
- * 之后(`configForms.whileServed`)条目才注册,行不在时条目也一起消失。注册到 `plugins.row.config` 而不是
16
- * 官方分组 `plugins.item`:后者按上游契约由官方那几张设置卡占用(一个 host 命名空间一个伴生包),行自己的
17
- * 配置就该挂在自己的行上——key 由 `SESSION_MODE_ROW_CONFIG_KEY` 给出。
11
+ * 头部的只读标签**没有**:chip 本来就把当前模式写在脸上(会话与新会话屏都有 composer),右上角再写一遍是
12
+ * 同一句话的复读。
13
+ *
14
+ * 本行的配置页(各模式的默认模型 `config.models`)不再由本包画:那个字段是 volatile 的,页面由
15
+ * `@morlay/dsh-client-ui-primitives` 按 schema 自动生成(它注册到本行的配置入口 `plugins.row.config`,
16
+ * key = `<bundle 包名>#<行 id>`)。本包只管会话里那一个面。
18
17
  */
19
18
 
20
19
  import type { Context } from "@deepseek-ai/cordis";
20
+ // Type-only:`ctx.remote` 的合并面(选模型的候选来自 LLM 目录)。
21
21
  import type {} from "@deepseek-ai/dsh-api-remotes/client";
22
22
  import type {} from "@deepseek-ai/dsh-client-locale/client";
23
- import type {} from "@deepseek-ai/dsh-client-ui-plugin-manager/client";
24
- import type {} from "@deepseek-ai/dsh-client-ui-settings/client";
25
23
  import type {} from "@deepseek-ai/dsh-client-ui-slots";
24
+
26
25
  // Type-only:槽位声明与 standard props(session / session-maybe / global)。
27
26
  import type {} from "@morlay/dsh-client-ui-conversation/client";
28
- import { ModelDefaultsCard } from "./ModelDefaultsCard.tsx";
29
- import { SESSION_MODE_NS, ModelDefaultsCardController } from "./model-defaults-card-controller.ts";
30
- import { SessionModeLabel } from "./SessionModeLabel.tsx";
31
27
  import { SessionModeSeat } from "./SessionModeSeat.tsx";
32
- import { fetchRoster } from "./api.ts";
33
28
  import { en, zh, type SessionModeLocaleKey } from "./locales.ts";
34
29
 
35
30
  declare module "@deepseek-ai/dsh-client-ui-slots" {
36
31
  interface LocaleNamespaceMap {
37
- /** 模式选择面与设置卡片的文案。 */
32
+ /** 会话里两个面的文案(配置页文案在通用 schema 表单的字典里)。 */
38
33
  "session-mode": SessionModeLocaleKey;
39
34
  }
40
35
  }
@@ -42,101 +37,157 @@ declare module "@deepseek-ai/dsh-client-ui-slots" {
42
37
  /** 浏览器半插件的字典命名空间。 */
43
38
  const NS = "session-mode";
44
39
 
45
- /**
46
- * 本行的配置入口 key:`<bundle 包名>#<行 id>`。
47
- *
48
- * 页面侧由 `ui-plugin-manager` 的 `rowConfigKey(bundle, rowId)` 生成同一个串——bundle 是本包
49
- * (`dsh.profile.bundles` 里那一项),行 id 是本包 patch 里插的 `session-mode`。
50
- */
51
- export const SESSION_MODE_ROW_CONFIG_KEY = "@morlay/dsh-session-mode#session-mode";
40
+ /** 这一行里需要中文文案的字段(都在 `modes.<模式>.defaultModel` 里,所以按模板路径注册一次)。 */
41
+ const FIELDS = ["provider", "model", "reasoningEffort"] as const;
42
+
43
+ /** 动态键的占位段(与通用表单的字段树同一约定)。 */
44
+ const DYNAMIC = "*";
45
+
46
+ /** 一个可配置 provider 的候选信息:显示名 + 它的配置在哪(模型清单从那份配置里读)。 */
47
+ interface ProviderEntry {
48
+ value: string;
49
+ label: string;
50
+ settingsNs: string;
51
+ settingsPath: readonly string[];
52
+ }
52
53
 
53
- export type { ModelDefaultsCardProps } from "./ModelDefaultsCard.tsx";
54
- export type {
55
- ModelDefaultsCardFace,
56
- ModelDefaultsCardState,
57
- ModelDefaultsRow,
58
- ModelDirectory,
59
- ModelDefaultsSources,
60
- SessionModeSettings,
61
- } from "./model-defaults-card-controller.ts";
62
- export { ModelDefaultsCardController, SESSION_MODE_NS } from "./model-defaults-card-controller.ts";
63
- export type { SessionModeLabelProps } from "./SessionModeLabel.tsx";
64
54
  export type { SessionModeSeatProps } from "./SessionModeSeat.tsx";
65
55
  export type { SessionModeLocaleKey } from "./locales.ts";
66
56
 
67
57
  /** 需要的服务:槽位与字典(会话列表经槽位的标准 props 到达组件,不必自己 inject)。 */
68
58
  export const inject = ["slots", "locale"];
69
59
 
60
+ /** 本包 host 行 id:行配置页读的就是这个命名空间。 */
61
+ export const SESSION_MODE_NS = "session-mode";
62
+
63
+ /** 按路径读一段配置里的值(本包只读 provider 档案里的模型清单)。 */
64
+ function readAt(root: unknown, path: readonly string[]): unknown {
65
+ return path.reduce<unknown>((node, segment) => {
66
+ if (Array.isArray(node)) return node[Number(segment)];
67
+ if (typeof node !== "object" || node === null) return undefined;
68
+ return Reflect.get(node, segment);
69
+ }, root);
70
+ }
71
+
70
72
  /**
71
- * 装上三个面。
73
+ * 装上会话里的两个面,以及本行配置页的字段文案。
74
+ *
72
75
  * @param ctx - 浏览器插件上下文。
73
76
  */
74
77
  export function apply(ctx: Context): void {
78
+ const t = ctx.locale.bind(NS);
75
79
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "session-mode: dictionaries");
76
80
 
77
- // 会话里的两个面都要求会话流的上下文(hero 的座位与头部动作行挂在 conversation 上)。
81
+ // 本行的配置页由通用 schema 表单按 volatile 字段生成;这里给 `modes.<模式>.defaultModel` 里的三个字段补
82
+ // 中文标签与说明。
83
+ ctx.inject(["schemaFormHints"], (scope) =>
84
+ scope.effect(() => {
85
+ const hints = scope.schemaFormHints;
86
+ const offs = FIELDS.map((key) =>
87
+ hints.describe(SESSION_MODE_NS, ["modes", DYNAMIC, "defaultModel", key], () => ({
88
+ label: t(key),
89
+ hint: t(`${key}Hint` as "providerHint"),
90
+ })),
91
+ );
92
+ return () => {
93
+ for (const off of offs) off();
94
+ };
95
+ }, "session-mode: field wording"),
96
+ );
97
+
98
+ // 选模型的候选不在本行的 schema 里:provider 是部署里的 LLM 目录(活着的路由 + 可配置声明),模型清单读那份声明
99
+ // 指向的配置(`settingsNs` / `settingsPath`)。目录异步取到后注册——注册本身就是一次变更通知。
100
+ ctx.inject(["schemaFormHints"], (scope) =>
101
+ scope.effect(() => {
102
+ const hints = scope.schemaFormHints;
103
+ const remote = scope.get("remote");
104
+ const forms = scope.get("configForms");
105
+ if (remote === undefined || forms === undefined) return () => {};
106
+ let providers: ProviderEntry[] = [];
107
+ const load = async (): Promise<void> => {
108
+ const [routes, directory] = await Promise.all([
109
+ remote.llm.listProviders(),
110
+ remote.llm.listConfigurableProviders(),
111
+ ]);
112
+ if (!routes.ok || !directory.ok) return;
113
+ const merged = new Map<string, ProviderEntry>();
114
+ for (const entry of directory.value) {
115
+ merged.set(entry.provider, {
116
+ value: entry.provider,
117
+ label: entry.displayName,
118
+ settingsNs: entry.settingsNs,
119
+ settingsPath: [...entry.settingsPath],
120
+ });
121
+ }
122
+ for (const route of routes.value) {
123
+ if (!merged.has(route.id)) {
124
+ merged.set(route.id, {
125
+ value: route.id,
126
+ label: route.name,
127
+ settingsNs: "",
128
+ settingsPath: [],
129
+ });
130
+ }
131
+ }
132
+ providers = [...merged.values()];
133
+ hints.refresh();
134
+ };
135
+ const modelsOf = (provider: unknown): readonly { value: string }[] => {
136
+ if (typeof provider !== "string") return [];
137
+ const entry = providers.find((candidate) => candidate.value === provider);
138
+ if (entry === undefined || entry.settingsNs === "") return [];
139
+ const profile = readAt(forms.get(entry.settingsNs).getSnapshot().value, entry.settingsPath);
140
+ const models =
141
+ typeof profile === "object" && profile !== null
142
+ ? Reflect.get(profile, "models")
143
+ : undefined;
144
+ if (!Array.isArray(models)) return [];
145
+ return models.flatMap((model) => {
146
+ const id =
147
+ typeof model === "object" && model !== null ? Reflect.get(model, "id") : undefined;
148
+ return typeof id === "string" ? [{ value: id }] : [];
149
+ });
150
+ };
151
+ const offs = [
152
+ // 两个具名源:schema 上 `role('select', { source })` 认领它们,本包因此不必知道行 id 与字段路径。
153
+ hints.source("llm-providers", {
154
+ options: () => providers.map((entry) => ({ value: entry.value, label: entry.label })),
155
+ }),
156
+ // 换服务商就换模型清单:依赖声明让表单在投影时按当前 provider 重算候选。
157
+ hints.source("llm-models", {
158
+ dependsOn: [["provider"]],
159
+ options: (read) => modelsOf(read(["provider"])),
160
+ }),
161
+ remote.$on("llm/adapters-updated", () => {
162
+ void load();
163
+ }),
164
+ remote.$on("settings/document-updated", () => {
165
+ hints.refresh();
166
+ }),
167
+ ];
168
+ void load();
169
+ return () => {
170
+ for (const off of offs) off();
171
+ };
172
+ }, "session-mode: model candidates"),
173
+ );
174
+
175
+ // chip 挂 **composer 工具行左侧**(`conversation.input.left`,list + session scope):新会话屏也是一个
176
+ // blank session 的 composer,所以那里也能选模式——头部槽位在新会话屏不存在。
78
177
  ctx.inject(["slots", "conversation"], (scope) => {
79
178
  scope.effect(() => {
80
179
  const seat = scope.slots.register(
81
- { name: "conversation.hero.agentPreset", locale: NS },
82
- SessionModeSeat,
83
- );
84
- const label = scope.slots.register(
85
180
  {
86
- name: "conversation.session.header.actions",
181
+ name: "conversation.input.left",
87
182
  id: "session-mode",
88
- // 静态会话上下文占头部前导的负序位(与官方那一行同位)。
89
- order: -10,
183
+ order: 0,
90
184
  locale: NS,
91
185
  },
92
- SessionModeLabel,
186
+ SessionModeSeat,
93
187
  );
94
188
  return () => {
95
189
  seat();
96
- label();
97
- };
98
- }, "session-mode: hero chip and header label");
99
- });
100
-
101
- // 设置页那张卡片要 `configForms`(共享配置表单)、`remote.session`(模型目录)与 `plugins.item`
102
- // (Plugins 页的槽)。它们都可能比本行晚到、或在这个部署里根本没有,所以走 `ctx.inject` 与
103
- // `slots.inject` 等它们就位——本行的基础 `inject` 不点它们,缺了也不影响上面两个面。
104
- ctx.inject(["slots", "locale", "configForms", "remote", "remote.session"], (forms) => {
105
- const t = forms.locale.bind(NS);
106
- forms.effect(() => {
107
- const controller = new ModelDefaultsCardController(
108
- forms.configForms.get(SESSION_MODE_NS),
109
- {
110
- roster: fetchRoster,
111
- // 目录只在打开卡片时读一次:它决定选择器的选项,页面生命周期里不会变。
112
- directory: async () => {
113
- const response = await forms.remote.session.modelCatalog();
114
- if (!response.ok) throw new Error("model catalog is unavailable");
115
- return {
116
- groups: response.value.groups,
117
- failures: response.value.failures.map((failure) => failure.name),
118
- };
119
- },
120
- },
121
- );
122
- const face = controller.inject();
123
- const page = forms.configForms.whileServed([SESSION_MODE_NS], () =>
124
- forms.slots.inject("plugins.row.config", () =>
125
- forms.slots.register(
126
- {
127
- name: "plugins.row.config",
128
- key: SESSION_MODE_ROW_CONFIG_KEY,
129
- // 文案经我们自己的注入面传:keyed 槽的注册项不带 `locale` 声明,不赌页面一定注入字典。
130
- inject: () => ({ ...face, t }),
131
- },
132
- ModelDefaultsCard,
133
- ),
134
- ),
135
- );
136
- return () => {
137
- page();
138
- controller.dispose();
139
190
  };
140
- }, "session-mode: settings card");
191
+ }, "session-mode: composer chip");
141
192
  });
142
193
  }
@@ -1,88 +1,34 @@
1
1
  /**
2
2
  * client 半的文案:命名空间 `session-mode`(与 locale 注册时用的 key 一致)。
3
3
  *
4
- * 两个面共用这一份字典:会话里的两个槽位(模式 chip 与头部标签)与设置页那张卡片。模式的名字与说明由
5
- * host 的清单给(那是数据,不是文案)。
4
+ * 只有会话里那个模式 chip 用这份字典:模式的名字与说明由 host 的清单给(那是数据,不是文案)。本行的配置页
5
+ * (各模式的默认模型)由通用 schema 表单渲染,文案在它自己的字典里。
6
6
  */
7
7
 
8
- import type { SettingsFormLabels } from "@morlay/dsh-client-ui-primitives/client";
9
-
10
8
  export const zh = {
11
9
  seatHint: "选择这个会话的模式",
12
- headerHint: "这个会话运行的模式",
10
+ lockedHint: "这个会话已经开始:换模式请新开一个会话",
13
11
  noDescription: "(无说明)",
14
- switchRefused: "切换失败:{reason}",
15
- loadFailed: "读不到模式清单",
16
- cardTitle: "会话模式",
17
- cardDescription: "给每个会话模式指定默认模型;不指定就跟全局默认模型。",
18
- defaultsTitle: "各模式的默认模型",
19
- defaultsHint:
20
- "只在会话还没有模型事实(没选过模型、也还没跑过请求)时生效;留空就是跟全局默认模型。改完按下面的保存写进设置。",
21
- providerLabel: "服务商",
22
- modelLabel: "模型",
23
- effortLabel: "思考档位",
24
- inheritOption: "跟全局默认",
25
- chooseModel: "选择模型…",
26
- effortDefaultOption: "跟模型默认",
27
- staleOption: "{value}(不在目录里)",
28
- overridden: "已覆盖",
29
- reset: "恢复默认",
30
- modelRequired: "还没选模型",
31
- readOnly: "本部署的设置为只读。",
32
- unavailable: "本插件当前未加载,暂时无法配置。",
33
- save: "保存",
34
- saving: "保存中…",
35
- saveFailed: "本部署没有接受这些值,已保留供你修改。",
36
- rosterFailed: "读不到会话模式清单,暂时列不出模式。",
37
- directoryFailed: "读不到模型目录,暂时只能看当前值或恢复默认。",
38
- directoryPartial: "这些服务商的目录没读到:{names}",
12
+ provider: "服务商",
13
+ providerHint:
14
+ "服务商 id:部署里注册的任意路由——不论哪个适配器插件注册的(官方的、我们自己的都算)。",
15
+ model: "模型",
16
+ modelHint: "模型 id。",
17
+ reasoningEffort: "思考档位",
18
+ reasoningEffortHint: "省略就跟服务商自己的默认。",
39
19
  };
40
20
 
41
21
  export const en: Record<keyof typeof zh, string> = {
42
22
  seatHint: "Pick the mode for this session",
43
- headerHint: "The mode this session runs",
23
+ lockedHint: "This session has started: start a new one to pick a mode",
44
24
  noDescription: "(no description)",
45
- switchRefused: "Switch refused: {reason}",
46
- loadFailed: "Could not load the mode roster",
47
- cardTitle: "Session mode",
48
- cardDescription: "Set the default model of each session mode; unset follows the global default.",
49
- defaultsTitle: "Default model per mode",
50
- defaultsHint:
51
- "Applies only while a session has no model fact yet (no model picked, no request made); unset follows the global default model. Save writes it into the settings document.",
52
- providerLabel: "Provider",
53
- modelLabel: "Model",
54
- effortLabel: "Reasoning effort",
55
- inheritOption: "Same as global default",
56
- chooseModel: "Choose a model…",
57
- effortDefaultOption: "Model default",
58
- staleOption: "{value} (not in the directory)",
59
- overridden: "Overridden",
60
- reset: "Reset to default",
61
- modelRequired: "No model picked yet",
62
- readOnly: "This deployment stores settings read-only.",
63
- unavailable: "This plugin is not loaded, so it cannot be configured right now.",
64
- save: "Save",
65
- saving: "Saving…",
66
- saveFailed: "The deployment did not accept these values; they were left for you to correct.",
67
- rosterFailed: "Could not load the session mode roster, so no mode can be listed.",
68
- directoryFailed:
69
- "Could not read the model directory, so only the current value or a reset is available.",
70
- directoryPartial: "These providers could not be read: {names}",
25
+ provider: "Provider",
26
+ providerHint:
27
+ "Provider id: any route registered in this deployment, whichever adapter plugin supplies it.",
28
+ model: "Model",
29
+ modelHint: "Model id.",
30
+ reasoningEffort: "Reasoning effort",
31
+ reasoningEffortHint: "Unset keeps the provider's own default.",
71
32
  };
72
33
 
73
34
  export type SessionModeLocaleKey = keyof typeof zh;
74
-
75
- /**
76
- * 共享设置表单外框的文案,从本页字典里读。
77
- * @param t - 本页的字典读取器。
78
- * @returns `SettingsForm` 渲染用的标签。
79
- */
80
- export function formLabels(t: (key: SessionModeLocaleKey) => string): SettingsFormLabels {
81
- return {
82
- unavailable: t("unavailable"),
83
- readOnly: t("readOnly"),
84
- saveFailed: t("saveFailed"),
85
- save: t("save"),
86
- saving: t("saving"),
87
- };
88
- }