cortico 0.1.0 → 0.1.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/README.md +4 -11
- package/package.json +1 -3
- package/src/bot.ts +1 -1
- package/src/core/README.md +0 -4
- package/src/core/types.ts +1 -1
- package/src/core/util.ts +2 -1
- package/src/deploy.ts +8 -6
- package/src/providers/README.md +4 -5
- package/src/providers/base.ts +1 -1
- package/src/providers/configuration.ts +2 -1
- package/src/providers/console/settings.ts +8 -10
- package/src/providers/llamacpp/console/runtime-panel.ts +73 -32
- package/src/providers/llamacpp/console/server.ts +1 -20
- package/src/providers/llamacpp/index.ts +0 -2
- package/src/providers/llamacpp/options.ts +0 -2
- package/src/providers/llamacpp/strings.ts +6 -6
- package/src/providers/openai-responses-compat/index.ts +0 -3
- package/src/web/README.md +1 -5
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +35 -41
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +4 -4
- package/src/web/client/console-pages/host.ts +2 -2
- package/src/web/client/features/core/strings.ts +2 -2
- package/src/web/client/features/feature.ts +1 -1
- package/src/web/client/features/live/index.ts +8 -1
- package/src/web/client/features/live/onboarding.ts +4 -1
- package/src/web/client/features/live/strings.ts +11 -11
- package/src/web/client/features/live/timeline.ts +2 -1
- package/src/web/client/features/settings/general.ts +1 -0
- package/src/web/client/features/settings/index.ts +1 -0
- package/src/web/client/features/worlds/index.ts +1 -0
- package/src/web/client/shell/index.ts +27 -47
- package/src/web/client/shell/strings.ts +0 -22
- package/src/web/client/ui/icons.ts +5 -0
- package/src/web/console-pages.ts +1 -1
- package/src/web/public/styles.css +21 -8
- package/src/web/server.ts +11 -3
- package/src/web/shared/client-panel.ts +1 -1
- package/src/web/shared/console-protocol.ts +2 -1
- package/src/worlds/minecraft/README.md +61 -11
- package/src/worlds/minecraft/cell-facts.ts +226 -0
- package/src/worlds/minecraft/chests.ts +5 -0
- package/src/worlds/minecraft/containers.ts +325 -0
- package/src/worlds/minecraft/entity-facts.ts +31 -5
- package/src/worlds/minecraft/executor.ts +283 -10348
- package/src/worlds/minecraft/inventory.ts +268 -0
- package/src/worlds/minecraft/melee.ts +419 -0
- package/src/worlds/minecraft/placed-ledger.ts +152 -0
- package/src/worlds/minecraft/placement.ts +1038 -0
- package/src/worlds/minecraft/receipt.ts +340 -0
- package/src/worlds/minecraft/skill-context.ts +358 -0
- package/src/worlds/minecraft/skills-build.ts +1169 -0
- package/src/worlds/minecraft/skills-container.ts +1343 -0
- package/src/worlds/minecraft/skills-craft.ts +333 -0
- package/src/worlds/minecraft/skills-dig.ts +624 -0
- package/src/worlds/minecraft/skills-gather.ts +1230 -0
- package/src/worlds/minecraft/skills-interact.ts +1559 -0
- package/src/worlds/minecraft/tools.ts +331 -0
- package/src/worlds/minecraft/travel.ts +763 -0
- package/src/worlds/minecraft/until.ts +75 -0
- package/src/worlds/terminal/world.ts +8 -5
- package/src/providers/console/config.ts +0 -21
- package/src/providers/llamacpp/config.ts +0 -36
- package/src/providers/openai-responses-compat/config.ts +0 -20
|
@@ -13,7 +13,6 @@ import type {
|
|
|
13
13
|
} from '../../../../shared/client-panel.ts';
|
|
14
14
|
import { pricingEditor, type ModelQuote } from './pricing-panel.ts';
|
|
15
15
|
import { panel } from './strings.ts';
|
|
16
|
-
import { configField, type ConfigGroupEntry } from '../../../features/config/view.ts';
|
|
17
16
|
|
|
18
17
|
/** 每个端点实例的模型与推理配置。 */
|
|
19
18
|
interface Spec {
|
|
@@ -46,7 +45,6 @@ interface Instance {
|
|
|
46
45
|
entry: Entry;
|
|
47
46
|
quotes: ModelQuote[];
|
|
48
47
|
secretConfigured: 'env' | 'file' | 'none';
|
|
49
|
-
config: ConfigGroupEntry[];
|
|
50
48
|
}
|
|
51
49
|
interface SettingsState {
|
|
52
50
|
active: string;
|
|
@@ -80,7 +78,7 @@ interface ProbeResult {
|
|
|
80
78
|
|
|
81
79
|
const DEFAULT_ENDPOINT_PATH = '/responses';
|
|
82
80
|
|
|
83
|
-
/**
|
|
81
|
+
/** 模块自有段落挂在这个插槽里,排在连接与模型档之间。 */
|
|
84
82
|
const MODULE_SLOT = 'instance';
|
|
85
83
|
|
|
86
84
|
/** 开放模式的 effort 格 ↔ spec 的 thinking/reasoningEffort。 */
|
|
@@ -159,7 +157,7 @@ export const llmSettingsPanel: ConsolePanel = {
|
|
|
159
157
|
let selected = '';
|
|
160
158
|
/** 模块段落的句柄,重画前先结束上一批。 */
|
|
161
159
|
let sections: Disposable | null = null;
|
|
162
|
-
/**
|
|
160
|
+
/** 重画代号:慢一步回来的那次 load 不再往 DOM 上贴。 */
|
|
163
161
|
let generation = 0;
|
|
164
162
|
const report = ui.msgline();
|
|
165
163
|
const body = ui.h('div');
|
|
@@ -257,37 +255,26 @@ export const llmSettingsPanel: ConsolePanel = {
|
|
|
257
255
|
const spec: Spec = structuredClone(entry.spec) ?? { model: '', thinking: open };
|
|
258
256
|
const saveSpec = () => void patch({ spec });
|
|
259
257
|
|
|
260
|
-
function configInput(suffix: string, label: string, changed?: (value: unknown) => void | Promise<void>): HTMLElement {
|
|
261
|
-
const path = `providers.${current!.name}.${suffix}`;
|
|
262
|
-
const { group, values } = current!.config.find(({ group }) => path in group.schema.properties!)!;
|
|
263
|
-
const field = configField(ui, group.schema.properties![path], values?.[path], () => {
|
|
264
|
-
void (async () => {
|
|
265
|
-
const raw = field.read!();
|
|
266
|
-
const value = typeof raw === 'string' ? raw.trim() : raw;
|
|
267
|
-
try {
|
|
268
|
-
await ctx.setConfig(group.id, { [path]: value });
|
|
269
|
-
report.textContent = S.saved;
|
|
270
|
-
await ctx.refresh();
|
|
271
|
-
await changed?.(value);
|
|
272
|
-
} catch (error) {
|
|
273
|
-
report.textContent = String(error);
|
|
274
|
-
}
|
|
275
|
-
})();
|
|
276
|
-
}, ctx.signal);
|
|
277
|
-
(field.node.querySelector('input, select, textarea') ?? field.node).setAttribute('aria-label', label);
|
|
278
|
-
return field.node;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
258
|
// ---- 连接 ----
|
|
282
259
|
const connection = ui.sheet({ title: S.connectionTitle, desc: S.connectionDescription });
|
|
283
260
|
const options = entry.options ?? {};
|
|
284
|
-
|
|
261
|
+
let endpointPath =
|
|
285
262
|
typeof options.endpointPath === 'string' ? options.endpointPath : DEFAULT_ENDPOINT_PATH;
|
|
286
263
|
const jsonText = (value: unknown) =>
|
|
287
264
|
value === undefined ? '' : JSON.stringify(value, null, 2);
|
|
288
|
-
const baseUrlInput =
|
|
289
|
-
|
|
290
|
-
|
|
265
|
+
const baseUrlInput = ui.input({
|
|
266
|
+
value: entry.baseUrl,
|
|
267
|
+
cls: 'mono',
|
|
268
|
+
onChange: (value) => void connectionChanged({ baseUrl: value.trim() }),
|
|
269
|
+
});
|
|
270
|
+
baseUrlInput.setAttribute('aria-label', S.baseUrl);
|
|
271
|
+
const secretNameInput = ui.input({
|
|
272
|
+
value: entry.secret ?? '',
|
|
273
|
+
placeholder: S.secretNamePlaceholder,
|
|
274
|
+
cls: 'mono',
|
|
275
|
+
onChange: (value) => void connectionChanged({ secret: value.trim() }),
|
|
276
|
+
});
|
|
277
|
+
secretNameInput.setAttribute('aria-label', S.secretName);
|
|
291
278
|
connection.body.append(
|
|
292
279
|
ui.field(S.baseUrl, baseUrlInput),
|
|
293
280
|
ui.field(S.secretName, secretNameInput),
|
|
@@ -307,18 +294,24 @@ export const llmSettingsPanel: ConsolePanel = {
|
|
|
307
294
|
connection.body.append(ui.field(S.secretStatus, row));
|
|
308
295
|
if (!entry.secret) connection.body.append(ui.msgline(S.secretNameFirst));
|
|
309
296
|
}
|
|
310
|
-
connection.body.append(
|
|
297
|
+
connection.body.append(
|
|
298
|
+
ui.checkbox(S.multimodal, {
|
|
299
|
+
checked: entry.multimodal === true,
|
|
300
|
+
onChange: (checked) => void patch({ multimodal: checked }),
|
|
301
|
+
}).el,
|
|
302
|
+
);
|
|
311
303
|
let extraHeaders: HTMLTextAreaElement | null = null;
|
|
312
304
|
let extraBody: HTMLTextAreaElement | null = null;
|
|
313
|
-
let path: HTMLInputElement | null = null;
|
|
314
305
|
if (open) {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
306
|
+
const path = ui.input({
|
|
307
|
+
value: endpointPath,
|
|
308
|
+
cls: 'mono',
|
|
309
|
+
onChange: (value) => {
|
|
310
|
+
endpointPath = value.trim();
|
|
311
|
+
saveOptions();
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
path.setAttribute('aria-label', S.endpointPath);
|
|
322
315
|
extraHeaders = ui.textarea({
|
|
323
316
|
rows: 3,
|
|
324
317
|
cls: 'mono',
|
|
@@ -335,7 +328,7 @@ export const llmSettingsPanel: ConsolePanel = {
|
|
|
335
328
|
extraBody.setAttribute('aria-label', S.extraBody);
|
|
336
329
|
connection.body.append(
|
|
337
330
|
ui.section(S.advancedProtocolTitle, S.advancedProtocolDescription),
|
|
338
|
-
|
|
331
|
+
ui.field(S.endpointPath, path),
|
|
339
332
|
ui.field(S.extraHeaders, extraHeaders),
|
|
340
333
|
ui.field(S.extraBody, extraBody),
|
|
341
334
|
);
|
|
@@ -353,7 +346,7 @@ export const llmSettingsPanel: ConsolePanel = {
|
|
|
353
346
|
return parsed;
|
|
354
347
|
};
|
|
355
348
|
const edited: Record<string, unknown> = {
|
|
356
|
-
endpointPath
|
|
349
|
+
endpointPath,
|
|
357
350
|
extraHeaders: object(extraHeaders!, S.extraHeaders),
|
|
358
351
|
extraBody: object(extraBody!, S.extraBody),
|
|
359
352
|
};
|
|
@@ -438,7 +431,8 @@ export const llmSettingsPanel: ConsolePanel = {
|
|
|
438
431
|
if (!ctx.signal.aborted) renderModelField();
|
|
439
432
|
}
|
|
440
433
|
/** 地址或密钥改过就重取目录:换了端点,上一份模型表不作数。 */
|
|
441
|
-
async function connectionChanged(): Promise<void> {
|
|
434
|
+
async function connectionChanged(fields: Record<string, unknown>): Promise<void> {
|
|
435
|
+
if (!(await patch(fields))) return;
|
|
442
436
|
catalog = [];
|
|
443
437
|
await fetchCatalog();
|
|
444
438
|
}
|
|
@@ -27,12 +27,12 @@ const zh = {
|
|
|
27
27
|
extraBody: '附加请求体(JSON 对象)',
|
|
28
28
|
jsonObjectRequired: (label: string) => `${label} 必须是 JSON 对象`,
|
|
29
29
|
advancedProtocolTitle: '协议与请求扩展',
|
|
30
|
-
advancedProtocolDescription: '',
|
|
30
|
+
advancedProtocolDescription: '针对 OpenAI Responses 兼容端点的高级配置(端点路径与附加 JSON 字段)。',
|
|
31
31
|
modelFieldLabel: '模型标识',
|
|
32
32
|
effortFieldLabel: '推理强度',
|
|
33
33
|
temperatureFieldLabel: '采样温度',
|
|
34
34
|
specTitle: '模型档',
|
|
35
|
-
specDescription: '
|
|
35
|
+
specDescription: '配置此实例调用的模型及推理、温度和 Token 限制等生成参数。',
|
|
36
36
|
modelName: '明确模型名',
|
|
37
37
|
fetchModels: '取模型列表',
|
|
38
38
|
modelsFetched: (count: number) => `取到 ${count} 个模型。`,
|
|
@@ -131,12 +131,12 @@ const en: typeof zh = {
|
|
|
131
131
|
extraBody: 'Extra body (JSON object)',
|
|
132
132
|
jsonObjectRequired: (label: string) => `${label} must be a JSON object`,
|
|
133
133
|
advancedProtocolTitle: 'Protocol & Request Overrides',
|
|
134
|
-
advancedProtocolDescription: '',
|
|
134
|
+
advancedProtocolDescription: 'Advanced options for OpenAI Responses compatible endpoints (custom path and extra JSON fields).',
|
|
135
135
|
modelFieldLabel: 'Model identifier',
|
|
136
136
|
effortFieldLabel: 'Reasoning effort',
|
|
137
137
|
temperatureFieldLabel: 'Sampling temperature',
|
|
138
138
|
specTitle: 'Model',
|
|
139
|
-
specDescription: '
|
|
139
|
+
specDescription: 'Configure model identifier, reasoning effort, temperature and token limits for this instance.',
|
|
140
140
|
modelName: 'Exact model name',
|
|
141
141
|
fetchModels: 'Fetch models',
|
|
142
142
|
modelsFetched: (count: number) => `Fetched ${count} models.`,
|
|
@@ -380,11 +380,11 @@ export class ConsolePageHost {
|
|
|
380
380
|
|
|
381
381
|
/** Persona 页才有工具表页签,且要部署挂着 /api/tool-schemas。 */
|
|
382
382
|
private toolsTabOf(page: ConsolePageManifest): boolean {
|
|
383
|
-
if (page.kind !== 'persona') return false;
|
|
383
|
+
if (page.kind !== 'persona' || page.availability !== 'active') return false;
|
|
384
384
|
return this.snapshot?.framework?.capabilities?.toolSchemas !== false;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
/**
|
|
387
|
+
/** 工具表是整份装配结果,不按页筛:模型看见的那一张表就是这一张。 */
|
|
388
388
|
private async showTools(pageId: string, gen: number): Promise<void> {
|
|
389
389
|
const { slot } = this.ensurePanes();
|
|
390
390
|
const lifecycle = new Lifecycle(this.deps.onError);
|
|
@@ -12,7 +12,7 @@ const zh = {
|
|
|
12
12
|
subData: '数据',
|
|
13
13
|
subConfig: '配置',
|
|
14
14
|
dataTitle: 'Core 的数据',
|
|
15
|
-
dataDesc: '',
|
|
15
|
+
dataDesc: '事件库、session、运行日志、用量流水这些 Core 自己攒下的存储。',
|
|
16
16
|
configTitle: 'Core 配置',
|
|
17
17
|
configDesc: '修改自动保存到 config.json。标有重启要求的配置在重启后生效,其余立即生效。',
|
|
18
18
|
configEmpty: 'Core 没有可调配置。',
|
|
@@ -89,7 +89,7 @@ const en: typeof zh = {
|
|
|
89
89
|
subData: 'Data',
|
|
90
90
|
subConfig: 'Config',
|
|
91
91
|
dataTitle: 'Core data',
|
|
92
|
-
dataDesc: '',
|
|
92
|
+
dataDesc: 'Storage Core accumulates on its own: the event store, the session, run logs and the usage ledger.',
|
|
93
93
|
configTitle: 'Core config',
|
|
94
94
|
configDesc: 'Changes save to config.json automatically. Settings marked for restart apply after restarting; the rest apply immediately.',
|
|
95
95
|
configEmpty: 'Core has no tunable settings.',
|
|
@@ -57,7 +57,7 @@ export type FrameworkFeature = {
|
|
|
57
57
|
readonly label: string;
|
|
58
58
|
/** 左栏图标。 World 那一页不走此字段。 */
|
|
59
59
|
readonly icon?: ConsoleIconName;
|
|
60
|
-
/**
|
|
60
|
+
/** 这一行那盏灯在灯表里的键。省略就不点灯。 */
|
|
61
61
|
readonly lampId?: string;
|
|
62
62
|
/** 列出的 capability 任一已挂载即显示导航项;省略或为空时始终显示。 */
|
|
63
63
|
readonly needsAny?: readonly string[];
|
|
@@ -174,6 +174,7 @@ function mountLive(ctx: FeatureContext, env: SocketEnv): Disposable | void {
|
|
|
174
174
|
view.append(timeline.el, composer.el);
|
|
175
175
|
timeline.rebuild([], { empty: S.emptyConnecting });
|
|
176
176
|
|
|
177
|
+
// 没有可用端点时说清楚要去哪儿,而不是让操作员发出一条得不到回复的消息。
|
|
177
178
|
let providerReady = false;
|
|
178
179
|
ctx.lifecycle.own(subscribeLamps(ctx.root.ownerDocument, (lamps) => {
|
|
179
180
|
const lamp = lamps[PROVIDERS_LAMP_ID]?.[0];
|
|
@@ -184,12 +185,17 @@ function mountLive(ctx: FeatureContext, env: SocketEnv): Disposable | void {
|
|
|
184
185
|
|
|
185
186
|
const resumeRun = (): Promise<unknown> => post('/api/run/resume', {}, { signal: ctx.signal });
|
|
186
187
|
|
|
188
|
+
/**
|
|
189
|
+
* 开场引导认部署目录里那个一次性标记(`.onboarding`,自建部署时写入)。session 与事件游标
|
|
190
|
+
* 都当不了判据:全新部署起来就有一条系统前缀和一条 session 开场事件,而上下文交接后 session
|
|
191
|
+
* 反倒是空的。
|
|
192
|
+
*/
|
|
187
193
|
let onboarding: OnboardingView | null = null;
|
|
188
194
|
/** 本次挂载里已经收过一次;销标记的请求在路上时状态帧还会报 pending。 */
|
|
189
195
|
let dismissed = false;
|
|
190
196
|
const dismissOnboarding = (): void => {
|
|
191
197
|
dismissed = true;
|
|
192
|
-
void post('/api/onboarding/dismiss', {}, { signal: ctx.signal }).catch(() => { /*
|
|
198
|
+
void post('/api/onboarding/dismiss', {}, { signal: ctx.signal }).catch(() => { /* 下次打开再收 */ });
|
|
193
199
|
syncOnboarding();
|
|
194
200
|
};
|
|
195
201
|
const syncOnboarding = (): void => {
|
|
@@ -212,6 +218,7 @@ function mountLive(ctx: FeatureContext, env: SocketEnv): Disposable | void {
|
|
|
212
218
|
});
|
|
213
219
|
onboarding.setProvider(providerReady);
|
|
214
220
|
timeline.setHeader(onboarding.el);
|
|
221
|
+
// 引导期间系统前缀那张卡先收起来:这一页此刻要说的是怎么把 bot 配起来。
|
|
215
222
|
timeline.setHideSystem(true);
|
|
216
223
|
timeline.rebuild(state.messages, { head: state.head });
|
|
217
224
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 新部署的开场引导:四条 Cortico 署名的气泡,用 assistant 直接输出那套气泡样式。
|
|
3
3
|
* 端点状态由终端页从 providers 灯推进来,已启用的 World 在挂载时读一次接口。
|
|
4
4
|
* 是否显示由终端页判定,这里只画。
|
|
5
5
|
*/
|
|
@@ -60,6 +60,7 @@ export function createOnboarding(deps: OnboardingDeps): OnboardingView {
|
|
|
60
60
|
const box = ui.h('div', 'monolog');
|
|
61
61
|
box.appendChild(ui.h('div', 'monolog-body', line));
|
|
62
62
|
const state = ui.h('div', 'ob-state hidden');
|
|
63
|
+
// 外观取子页签那颗按钮(`.seg`),`ob-btn` 只挂本页的微调。
|
|
63
64
|
const button = ui.h('button', action?.accent ? 'seg active ob-btn ob-go' : 'seg active ob-btn');
|
|
64
65
|
button.type = 'button';
|
|
65
66
|
if (action?.icon) button.appendChild(icon(doc, action.icon));
|
|
@@ -87,6 +88,7 @@ export function createOnboarding(deps: OnboardingDeps): OnboardingView {
|
|
|
87
88
|
bubble(S.obPrompts, { label: S.obGoEdit, onClick: () => deps.go(['prompts']) });
|
|
88
89
|
|
|
89
90
|
const startLabel = S.obStart;
|
|
91
|
+
// 最后那颗按的是「开始跑」,图标与左下角运行控制里的继续是同一个。
|
|
90
92
|
const ready = bubble(S.obReady, {
|
|
91
93
|
label: startLabel,
|
|
92
94
|
icon: 'play',
|
|
@@ -106,6 +108,7 @@ export function createOnboarding(deps: OnboardingDeps): OnboardingView {
|
|
|
106
108
|
el,
|
|
107
109
|
setProvider(available) {
|
|
108
110
|
provider.setState(available ? S.obProviderReady : S.obProviderNone, available ? 'ok' : 'bad');
|
|
111
|
+
// 最后一条只在还缺端点时说话:配好了就只剩那颗按钮。
|
|
109
112
|
ready.setState(available ? null : S.obProviderNone, 'bad');
|
|
110
113
|
ready.button.disabled = !available;
|
|
111
114
|
},
|
|
@@ -111,14 +111,14 @@ const zh = {
|
|
|
111
111
|
|
|
112
112
|
// onboarding.ts
|
|
113
113
|
obWho: 'Cortico',
|
|
114
|
-
obWelcome: '欢迎使用 Cortico。',
|
|
115
|
-
obProvider: '
|
|
114
|
+
obWelcome: '欢迎使用 Cortico!现在,让我们开始部署你的第一个 Cortico Bot。',
|
|
115
|
+
obProvider: '首先,请配置模型提供商:',
|
|
116
116
|
obProviderNone: '尚未配置可用的模型提供商',
|
|
117
117
|
obProviderReady: '模型提供商已配置',
|
|
118
|
-
obWorlds: '
|
|
119
|
-
obWorldsState: (labels: readonly string[]) =>
|
|
120
|
-
obPrompts: '
|
|
121
|
-
obReady: '
|
|
118
|
+
obWorlds: '接下来,请启用并配置你的 Bot 接入的外部环境模组(Cortico World)。也可以先仅启用终端对话。',
|
|
119
|
+
obWorldsState: (labels: readonly string[]) => `当前已经启用了 ${labels.length} 个外部环境:${labels.join('、')}`,
|
|
120
|
+
obPrompts: '最后,你可以在这里方便地编辑系统提示词,来提供人格描述、行为规范、语言风格等定制化内容!',
|
|
121
|
+
obReady: '准备就绪!',
|
|
122
122
|
obGoConfigure: '前往配置',
|
|
123
123
|
obGoEdit: '开始编辑',
|
|
124
124
|
obStart: '打个招呼?',
|
|
@@ -235,15 +235,15 @@ const en: typeof zh = {
|
|
|
235
235
|
|
|
236
236
|
// onboarding.ts
|
|
237
237
|
obWho: 'Cortico',
|
|
238
|
-
obWelcome: "Welcome to Cortico.",
|
|
239
|
-
obProvider: '
|
|
238
|
+
obWelcome: "Welcome to Cortico! Let's set up your first Cortico Bot.",
|
|
239
|
+
obProvider: 'First, configure a model provider:',
|
|
240
240
|
obProviderNone: 'No usable model provider yet',
|
|
241
241
|
obProviderReady: 'Model provider configured',
|
|
242
|
-
obWorlds: '
|
|
242
|
+
obWorlds: 'Next, enable and configure the external environments your bot reaches (Cortico Worlds). Terminal chat alone is a fine start.',
|
|
243
243
|
obWorldsState: (labels: readonly string[]) =>
|
|
244
244
|
`Currently ${labels.length} external environment${labels.length === 1 ? '' : 's'} enabled: ${labels.join(', ')}`,
|
|
245
|
-
obPrompts: '
|
|
246
|
-
obReady: '
|
|
245
|
+
obPrompts: 'Finally, the system prompt is edited here: who it is, how it behaves, how it talks.',
|
|
246
|
+
obReady: 'Ready to go!',
|
|
247
247
|
obGoConfigure: 'Configure',
|
|
248
248
|
obGoEdit: 'Edit',
|
|
249
249
|
obStart: 'Say hello?',
|
|
@@ -100,7 +100,7 @@ export interface TimelineView {
|
|
|
100
100
|
append(m: ContextRecord, index: number, live: boolean): void;
|
|
101
101
|
/** 说话人展示名。头像图片加载不到时,ASSISTANT 组左栏的占位圆里印它的首字;下一次画到组时生效。 */
|
|
102
102
|
setSpeaker(name: string): void;
|
|
103
|
-
/**
|
|
103
|
+
/** 开场引导期间把系统前缀那张卡收起来,下一次重画生效。 */
|
|
104
104
|
setHideSystem(hide: boolean): void;
|
|
105
105
|
/** 在滚动区顶部挂一块外部内容,随时间线一起滚;传 null 取下。重画不动它。 */
|
|
106
106
|
setHeader(node: HTMLElement | null): void;
|
|
@@ -131,6 +131,7 @@ export function createTimeline(deps: TimelineDeps): TimelineView {
|
|
|
131
131
|
let turn: Turn | null = null;
|
|
132
132
|
/** 头像占位圆里的字。 */
|
|
133
133
|
let speakerInitial = 'B';
|
|
134
|
+
/** 收起系统前缀卡。 */
|
|
134
135
|
let hideSystem = false;
|
|
135
136
|
/** 挂在滚动区顶部的外部内容(开场引导)。 */
|
|
136
137
|
let header: HTMLElement | null = null;
|
|
@@ -5,6 +5,7 @@ import { S } from './strings.ts';
|
|
|
5
5
|
export function mountGeneral(ctx: FeatureContext): void {
|
|
6
6
|
const { ui, root, signal } = ctx;
|
|
7
7
|
const win = root.ownerDocument.defaultView!;
|
|
8
|
+
// 同页别处都是档案卡:标题与说明的字体从卡片来,这里自己写 h3/p 会与它们对不齐。
|
|
8
9
|
const sheet = ui.sheet({ title: S.language, en: 'language', desc: S.languageDesc });
|
|
9
10
|
const group = ui.h('div', 'rowbar');
|
|
10
11
|
group.setAttribute('role', 'group');
|
|
@@ -179,6 +179,7 @@ export function mountWorlds(ctx: FeatureContext): void {
|
|
|
179
179
|
);
|
|
180
180
|
if (ctx.signal.aborted) return;
|
|
181
181
|
setMsg(out?.result || (wantEnabled ? S.activated : S.deactivated));
|
|
182
|
+
// 左栏重排失败只是导航旧了一拍,激活本身已经成功,不进上面那行的失败文案。
|
|
182
183
|
void ctx.refreshNav?.().catch(ctx.onError);
|
|
183
184
|
await load();
|
|
184
185
|
} catch (err) {
|
|
@@ -117,6 +117,7 @@ export function createShell(deps: ShellDeps): ConsoleShell {
|
|
|
117
117
|
const el = ui.h('div');
|
|
118
118
|
el.id = 'rail';
|
|
119
119
|
|
|
120
|
+
// 左上角只有框架字标。这个 bot 叫什么写在底栏头像旁边——那才是这一台的名字。
|
|
120
121
|
const brand = ui.h('div', 'brand');
|
|
121
122
|
brand.setAttribute('role', 'img');
|
|
122
123
|
brand.setAttribute('aria-label', FRAMEWORK_NAME);
|
|
@@ -127,8 +128,14 @@ export function createShell(deps: ShellDeps): ConsoleShell {
|
|
|
127
128
|
|
|
128
129
|
const foot = ui.h('div', 'railfoot');
|
|
129
130
|
const avatar = createAvatarControl({ doc, ui, signal, onError });
|
|
130
|
-
const botName = ui.h('button', 'rail-name'
|
|
131
|
+
const botName = ui.h('button', 'rail-name');
|
|
131
132
|
botName.type = 'button';
|
|
133
|
+
// 名字自己是按钮(点了就地改名);铅笔只在悬停时露出来,不占额外宽度。
|
|
134
|
+
const botNameText = ui.h('span', 'rail-name-text', DEFAULT_BRAND);
|
|
135
|
+
const botNameEdit = ui.h('span', 'rail-name-edit');
|
|
136
|
+
botNameEdit.setAttribute('aria-hidden', 'true');
|
|
137
|
+
botNameEdit.appendChild(icon(doc, 'pencil'));
|
|
138
|
+
botName.append(botNameText, botNameEdit);
|
|
132
139
|
const who = ui.h('div', 'rail-who');
|
|
133
140
|
who.append(avatar.el, botName);
|
|
134
141
|
const footActions = ui.h('div', 'rail-actions');
|
|
@@ -137,16 +144,12 @@ export function createShell(deps: ShellDeps): ConsoleShell {
|
|
|
137
144
|
const shutdownButton = ui.h('button', 'rail-action rail-shutdown');
|
|
138
145
|
shutdownButton.type = 'button';
|
|
139
146
|
shutdownButton.appendChild(icon(doc, 'power'));
|
|
140
|
-
// 重启 = 同一套收尾 + 退出前落重启标志,启动器循环把进程拉起来。
|
|
141
|
-
const restartButton = ui.h('button', 'rail-action rail-restart');
|
|
142
|
-
restartButton.type = 'button';
|
|
143
|
-
restartButton.appendChild(icon(doc, 'refresh'));
|
|
144
147
|
const settingsButton = ui.h('button', 'rail-action');
|
|
145
148
|
settingsButton.type = 'button';
|
|
146
149
|
settingsButton.setAttribute('aria-label', S.settingsAria);
|
|
147
150
|
settingsButton.title = S.settingsTitle;
|
|
148
151
|
settingsButton.appendChild(icon(doc, 'settings'));
|
|
149
|
-
footActions.append(runButton, settingsButton,
|
|
152
|
+
footActions.append(runButton, settingsButton, shutdownButton);
|
|
150
153
|
foot.append(who, footActions);
|
|
151
154
|
|
|
152
155
|
let paused = false;
|
|
@@ -161,7 +164,7 @@ export function createShell(deps: ShellDeps): ConsoleShell {
|
|
|
161
164
|
};
|
|
162
165
|
renderRun();
|
|
163
166
|
|
|
164
|
-
/**
|
|
167
|
+
/** 关机请求的锁,等待请求完成后释放。 */
|
|
165
168
|
let shuttingDown = false;
|
|
166
169
|
const renderPower = (): void => {
|
|
167
170
|
const canShutdown = capabilities.shutdown === true;
|
|
@@ -169,45 +172,23 @@ export function createShell(deps: ShellDeps): ConsoleShell {
|
|
|
169
172
|
shutdownButton.disabled = !canShutdown || shuttingDown;
|
|
170
173
|
shutdownButton.setAttribute('aria-label', S.shutdownAria);
|
|
171
174
|
shutdownButton.title = shuttingDown ? S.finishing : S.shutdownTitle;
|
|
172
|
-
const canRestart = capabilities.restart === true;
|
|
173
|
-
restartButton.hidden = !canRestart;
|
|
174
|
-
restartButton.disabled = !canRestart || shuttingDown;
|
|
175
|
-
restartButton.setAttribute('aria-label', S.restartAria);
|
|
176
|
-
restartButton.title = shuttingDown
|
|
177
|
-
? S.finishing
|
|
178
|
-
: capabilities.supervised === true
|
|
179
|
-
? S.restartTitleSupervised
|
|
180
|
-
: S.restartTitleUnsupervised;
|
|
181
175
|
};
|
|
182
176
|
renderPower();
|
|
183
177
|
|
|
184
|
-
const powerAction = async (
|
|
178
|
+
const powerAction = async (): Promise<void> => {
|
|
185
179
|
if (shuttingDown) return;
|
|
186
|
-
if (
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
danger: true,
|
|
193
|
-
}
|
|
194
|
-
: {
|
|
195
|
-
title: S.confirmRestartTitle,
|
|
196
|
-
body: supervised ? S.restartSupervisedNote : S.restartUnsupervisedNote,
|
|
197
|
-
danger: true,
|
|
198
|
-
});
|
|
180
|
+
if (capabilities.shutdown !== true) return;
|
|
181
|
+
const first = await ui.confirm({
|
|
182
|
+
title: S.confirmShutdownTitle,
|
|
183
|
+
body: S.shutdownBody,
|
|
184
|
+
danger: true,
|
|
185
|
+
});
|
|
199
186
|
if (!first || signal.aborted) return;
|
|
200
|
-
const second = await ui.confirm(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
: {
|
|
207
|
-
title: S.confirmRestartAgainTitle,
|
|
208
|
-
body: supervised ? S.confirmRestartAgainSupervised : S.confirmRestartAgainUnsupervised,
|
|
209
|
-
danger: true,
|
|
210
|
-
});
|
|
187
|
+
const second = await ui.confirm({
|
|
188
|
+
title: S.confirmShutdownAgainTitle,
|
|
189
|
+
body: S.confirmShutdownAgainBody,
|
|
190
|
+
danger: true,
|
|
191
|
+
});
|
|
211
192
|
if (!second || signal.aborted) return;
|
|
212
193
|
shuttingDown = true;
|
|
213
194
|
renderPower();
|
|
@@ -217,7 +198,7 @@ export function createShell(deps: ShellDeps): ConsoleShell {
|
|
|
217
198
|
ok?: boolean; localComplete?: boolean; result?: string; error?: string;
|
|
218
199
|
steps?: Array<{ label: string; ok: boolean; ms: number; detail?: string }>;
|
|
219
200
|
externalChecks?: Array<{ status: 'verified-ended' | 'still-live' | 'unknown' }>;
|
|
220
|
-
}>(
|
|
201
|
+
}>('/api/run/shutdown', undefined, { signal });
|
|
221
202
|
if (out?.error) throw new Error(out.error);
|
|
222
203
|
const steps = out?.steps ?? [];
|
|
223
204
|
const localComplete = out?.localComplete ?? steps.every((step) => step.ok);
|
|
@@ -225,13 +206,12 @@ export function createShell(deps: ShellDeps): ConsoleShell {
|
|
|
225
206
|
.some((check) => check.status !== 'verified-ended');
|
|
226
207
|
const lines = steps.map((s) =>
|
|
227
208
|
`${s.ok ? '✓' : '✗'} ${s.label} · ${(s.ms / 1000).toFixed(1)}s${s.ok ? '' : ` — ${s.detail ?? S.stepIncomplete}`}`);
|
|
228
|
-
const done = kind === 'shutdown' ? S.doneShutdown : supervised ? S.doneRestartSupervised : S.doneRestart;
|
|
229
209
|
void ui.confirm({
|
|
230
210
|
title: !localComplete
|
|
231
211
|
? S.resultLocalIncomplete
|
|
232
212
|
: externalUnverified
|
|
233
213
|
? S.resultExternalUnverified
|
|
234
|
-
: out?.ok === false ? S.resultUnverified :
|
|
214
|
+
: out?.ok === false ? S.resultUnverified : S.doneShutdown,
|
|
235
215
|
body: [out?.result ?? S.resultDefault, '', ...lines].join('\n'),
|
|
236
216
|
});
|
|
237
217
|
} catch (err) {
|
|
@@ -246,8 +226,7 @@ export function createShell(deps: ShellDeps): ConsoleShell {
|
|
|
246
226
|
renderPower();
|
|
247
227
|
}
|
|
248
228
|
};
|
|
249
|
-
shutdownButton.addEventListener('click', () => { void powerAction(
|
|
250
|
-
restartButton.addEventListener('click', () => { void powerAction('restart'); }, { signal });
|
|
229
|
+
shutdownButton.addEventListener('click', () => { void powerAction(); }, { signal });
|
|
251
230
|
|
|
252
231
|
runButton.addEventListener('click', () => {
|
|
253
232
|
if (runPending || capabilities.run !== true) return;
|
|
@@ -456,7 +435,8 @@ export function createShell(deps: ShellDeps): ConsoleShell {
|
|
|
456
435
|
const setBrand = (name: string | null | undefined): void => {
|
|
457
436
|
const shown = typeof name === 'string' && name.trim() !== '' ? name.trim() : DEFAULT_BRAND;
|
|
458
437
|
shownName = shown;
|
|
459
|
-
|
|
438
|
+
botNameText.textContent = shown;
|
|
439
|
+
// 悬停说的是这一下能做什么;全名在改名框里看得到。
|
|
460
440
|
botName.title = S.renameTitle;
|
|
461
441
|
avatar.setLabel(shown);
|
|
462
442
|
doc.title = S.docTitle(shown);
|
|
@@ -13,24 +13,13 @@ const zh = {
|
|
|
13
13
|
settingsTitle: '设置',
|
|
14
14
|
finishing: '正在关闭…',
|
|
15
15
|
shutdownTitle: '关机',
|
|
16
|
-
restartAria: '重启',
|
|
17
|
-
restartTitleSupervised: '重启',
|
|
18
|
-
restartTitleUnsupervised: '退出后需手动启动',
|
|
19
16
|
shutdownBody: '进程将退出,控制台连接将断开。',
|
|
20
17
|
confirmShutdownTitle: '⚠ 关机',
|
|
21
|
-
confirmRestartTitle: '⚠ 重启',
|
|
22
|
-
restartSupervisedNote: '进程将重启。',
|
|
23
|
-
restartUnsupervisedNote: '进程将退出,需要手动重新启动。',
|
|
24
18
|
confirmShutdownAgainTitle: '⚠⚠ 确认关机?',
|
|
25
19
|
confirmShutdownAgainBody: '停止运行并退出进程?',
|
|
26
|
-
confirmRestartAgainTitle: '⚠⚠ 确认重启?',
|
|
27
|
-
confirmRestartAgainSupervised: '退出并重新启动进程?',
|
|
28
|
-
confirmRestartAgainUnsupervised: '退出进程?退出后需手动启动。',
|
|
29
20
|
finishingToast: '正在关闭…',
|
|
30
21
|
stepIncomplete: '未完成',
|
|
31
22
|
doneShutdown: '已关机',
|
|
32
|
-
doneRestartSupervised: '已退出,等待重新启动',
|
|
33
|
-
doneRestart: '已退出',
|
|
34
23
|
resultLocalIncomplete: '本地关闭步骤未全部完成',
|
|
35
24
|
resultExternalUnverified: '本地已关机,外部状态未确认',
|
|
36
25
|
resultUnverified: '关闭步骤已执行,状态未确认',
|
|
@@ -56,24 +45,13 @@ const en: typeof zh = {
|
|
|
56
45
|
settingsTitle: 'Settings',
|
|
57
46
|
finishing: 'Shutting down…',
|
|
58
47
|
shutdownTitle: 'Shut down',
|
|
59
|
-
restartAria: 'Restart',
|
|
60
|
-
restartTitleSupervised: 'Restart',
|
|
61
|
-
restartTitleUnsupervised: 'Manual startup required after exit',
|
|
62
48
|
shutdownBody: 'The process will exit and disconnect the console.',
|
|
63
49
|
confirmShutdownTitle: '⚠ Shut down',
|
|
64
|
-
confirmRestartTitle: '⚠ Restart',
|
|
65
|
-
restartSupervisedNote: 'The process will restart.',
|
|
66
|
-
restartUnsupervisedNote: 'The process will exit and must be restarted manually.',
|
|
67
50
|
confirmShutdownAgainTitle: '⚠⚠ Confirm shutdown?',
|
|
68
51
|
confirmShutdownAgainBody: 'Stop running and exit the process?',
|
|
69
|
-
confirmRestartAgainTitle: '⚠⚠ Confirm restart?',
|
|
70
|
-
confirmRestartAgainSupervised: 'Exit and restart the process?',
|
|
71
|
-
confirmRestartAgainUnsupervised: 'Exit the process? You must start it manually afterwards.',
|
|
72
52
|
finishingToast: 'Shutting down…',
|
|
73
53
|
stepIncomplete: 'incomplete',
|
|
74
54
|
doneShutdown: 'Shut down',
|
|
75
|
-
doneRestartSupervised: 'Exited; waiting to restart',
|
|
76
|
-
doneRestart: 'Exited',
|
|
77
55
|
resultLocalIncomplete: 'Local shutdown steps incomplete',
|
|
78
56
|
resultExternalUnverified: 'Shut down locally; external state unverified',
|
|
79
57
|
resultUnverified: 'Shutdown steps executed; state unverified',
|
|
@@ -17,6 +17,7 @@ export type ConsoleIconName =
|
|
|
17
17
|
| 'text'
|
|
18
18
|
| 'eye'
|
|
19
19
|
| 'eye-off'
|
|
20
|
+
| 'pencil'
|
|
20
21
|
| 'refresh';
|
|
21
22
|
|
|
22
23
|
type Shape = readonly [tag: 'path' | 'circle' | 'rect' | 'line', attrs: Readonly<Record<string, string>>];
|
|
@@ -93,6 +94,10 @@ const SHAPES: Readonly<Record<ConsoleIconName, readonly Shape[]>> = {
|
|
|
93
94
|
['path', { d: 'M6.6 6.6C3.8 8.5 2 12 2 12s3.5 7 10 7c1.6 0 3-.4 4.3-1' }],
|
|
94
95
|
['path', { d: 'M9.9 9.9a3 3 0 0 0 4.2 4.2' }],
|
|
95
96
|
],
|
|
97
|
+
pencil: [
|
|
98
|
+
['path', { d: 'M4 20h4.2L19.4 8.8a2.3 2.3 0 0 0-3.2-3.2L5 16.8V20Z' }],
|
|
99
|
+
['path', { d: 'm12.6 6.5 4.9 4.9' }],
|
|
100
|
+
],
|
|
96
101
|
refresh: [
|
|
97
102
|
['path', { d: 'M21 12a9 9 0 1 1-2.6-6.4' }],
|
|
98
103
|
['path', { d: 'M21 3v6h-6' }],
|
package/src/web/console-pages.ts
CHANGED
|
@@ -318,7 +318,7 @@ export class ConsolePageRegistry {
|
|
|
318
318
|
): Promise<{ ok: true; value: unknown } | { ok: false; failure: InvokeFailure }> {
|
|
319
319
|
const found = await this.resolvePanel(pageId, panelId, language);
|
|
320
320
|
if (!found.ok) return found;
|
|
321
|
-
// GET
|
|
321
|
+
// GET 不过写请求的同源闸门,任何站点凭 <img src> 就能发出;只放行面板点名的方法。
|
|
322
322
|
if (transport === 'get' && !(found.panel.getMethods ?? []).includes(method)) {
|
|
323
323
|
return {
|
|
324
324
|
ok: false,
|