mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-09 05:19:32 +08:00
fix(web ui): agent model selection
This commit is contained in:
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo.
|
||||
- Web UI: fix agent model selection saves for default/non-default agents and wrap long workspace paths. Thanks @Takhoffman.
|
||||
- Web UI: resolve header logo path when `gateway.controlUi.basePath` is set. (#7178) Thanks @Yeom-JinHo.
|
||||
- Web UI: apply button styling to the new-messages indicator.
|
||||
- Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin.
|
||||
|
||||
@@ -1659,6 +1659,13 @@
|
||||
.agent-kv {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-kv > div {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.agent-kv-sub {
|
||||
|
||||
@@ -107,6 +107,27 @@ export function renderApp(state: AppViewState) {
|
||||
state.agentsList?.defaultId ??
|
||||
state.agentsList?.agents?.[0]?.id ??
|
||||
null;
|
||||
const ensureAgentListEntry = (agentId: string) => {
|
||||
const snapshot = (state.configForm ??
|
||||
(state.configSnapshot?.config as Record<string, unknown> | null)) as {
|
||||
agents?: { list?: unknown[] };
|
||||
} | null;
|
||||
const listRaw = snapshot?.agents?.list;
|
||||
const list = Array.isArray(listRaw) ? listRaw : [];
|
||||
let index = list.findIndex(
|
||||
(entry) =>
|
||||
entry &&
|
||||
typeof entry === "object" &&
|
||||
"id" in entry &&
|
||||
(entry as { id?: string }).id === agentId,
|
||||
);
|
||||
if (index < 0) {
|
||||
const nextList = [...list, { id: agentId }];
|
||||
updateConfigFormValue(state as unknown as ConfigState, ["agents", "list"], nextList);
|
||||
index = nextList.length - 1;
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="shell ${isChat ? "shell--chat" : ""} ${chatFocus ? "shell--chat-focus" : ""} ${state.settings.navCollapsed ? "shell--nav-collapsed" : ""} ${state.onboarding ? "shell--onboarding" : ""}">
|
||||
@@ -637,26 +658,48 @@ export function renderApp(state: AppViewState) {
|
||||
if (!configValue) {
|
||||
return;
|
||||
}
|
||||
const list = (configValue as { agents?: { list?: unknown[] } }).agents?.list;
|
||||
if (!Array.isArray(list)) {
|
||||
return;
|
||||
}
|
||||
const index = list.findIndex(
|
||||
(entry) =>
|
||||
entry &&
|
||||
typeof entry === "object" &&
|
||||
"id" in entry &&
|
||||
(entry as { id?: string }).id === agentId,
|
||||
);
|
||||
if (index < 0) {
|
||||
const defaultId = state.agentsList?.defaultId ?? null;
|
||||
if (defaultId && agentId === defaultId) {
|
||||
const basePath = ["agents", "defaults", "model"];
|
||||
const defaults =
|
||||
(configValue as { agents?: { defaults?: { model?: unknown } } }).agents
|
||||
?.defaults ?? {};
|
||||
const existing = defaults.model;
|
||||
if (!modelId) {
|
||||
removeConfigFormValue(state as unknown as ConfigState, basePath);
|
||||
return;
|
||||
}
|
||||
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
||||
const fallbacks = (existing as { fallbacks?: unknown }).fallbacks;
|
||||
const next = {
|
||||
primary: modelId,
|
||||
...(Array.isArray(fallbacks) ? { fallbacks } : {}),
|
||||
};
|
||||
updateConfigFormValue(state as unknown as ConfigState, basePath, next);
|
||||
} else {
|
||||
updateConfigFormValue(state as unknown as ConfigState, basePath, {
|
||||
primary: modelId,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const index = ensureAgentListEntry(agentId);
|
||||
const basePath = ["agents", "list", index, "model"];
|
||||
if (!modelId) {
|
||||
removeConfigFormValue(state as unknown as ConfigState, basePath);
|
||||
return;
|
||||
}
|
||||
const entry = list[index] as { model?: unknown };
|
||||
const list = (
|
||||
(state.configForm ??
|
||||
(state.configSnapshot?.config as Record<string, unknown> | null)) as {
|
||||
agents?: { list?: unknown[] };
|
||||
}
|
||||
)?.agents?.list;
|
||||
const entry =
|
||||
Array.isArray(list) && list[index]
|
||||
? (list[index] as { model?: unknown })
|
||||
: null;
|
||||
const existing = entry?.model;
|
||||
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
||||
const fallbacks = (existing as { fallbacks?: unknown }).fallbacks;
|
||||
@@ -673,23 +716,57 @@ export function renderApp(state: AppViewState) {
|
||||
if (!configValue) {
|
||||
return;
|
||||
}
|
||||
const list = (configValue as { agents?: { list?: unknown[] } }).agents?.list;
|
||||
if (!Array.isArray(list)) {
|
||||
return;
|
||||
}
|
||||
const index = list.findIndex(
|
||||
(entry) =>
|
||||
entry &&
|
||||
typeof entry === "object" &&
|
||||
"id" in entry &&
|
||||
(entry as { id?: string }).id === agentId,
|
||||
);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
const basePath = ["agents", "list", index, "model"];
|
||||
const entry = list[index] as { model?: unknown };
|
||||
const normalized = fallbacks.map((name) => name.trim()).filter(Boolean);
|
||||
const defaultId = state.agentsList?.defaultId ?? null;
|
||||
if (defaultId && agentId === defaultId) {
|
||||
const basePath = ["agents", "defaults", "model"];
|
||||
const defaults =
|
||||
(configValue as { agents?: { defaults?: { model?: unknown } } }).agents
|
||||
?.defaults ?? {};
|
||||
const existing = defaults.model;
|
||||
const resolvePrimary = () => {
|
||||
if (typeof existing === "string") {
|
||||
return existing.trim() || null;
|
||||
}
|
||||
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
||||
const primary = (existing as { primary?: unknown }).primary;
|
||||
if (typeof primary === "string") {
|
||||
const trimmed = primary.trim();
|
||||
return trimmed || null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const primary = resolvePrimary();
|
||||
if (normalized.length === 0) {
|
||||
if (primary) {
|
||||
updateConfigFormValue(state as unknown as ConfigState, basePath, {
|
||||
primary,
|
||||
});
|
||||
} else {
|
||||
removeConfigFormValue(state as unknown as ConfigState, basePath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const next = primary
|
||||
? { primary, fallbacks: normalized }
|
||||
: { fallbacks: normalized };
|
||||
updateConfigFormValue(state as unknown as ConfigState, basePath, next);
|
||||
return;
|
||||
}
|
||||
|
||||
const index = ensureAgentListEntry(agentId);
|
||||
const basePath = ["agents", "list", index, "model"];
|
||||
const list = (
|
||||
(state.configForm ??
|
||||
(state.configSnapshot?.config as Record<string, unknown> | null)) as {
|
||||
agents?: { list?: unknown[] };
|
||||
}
|
||||
)?.agents?.list;
|
||||
const entry =
|
||||
Array.isArray(list) && list[index]
|
||||
? (list[index] as { model?: unknown })
|
||||
: null;
|
||||
const existing = entry.model;
|
||||
const resolvePrimary = () => {
|
||||
if (typeof existing === "string") {
|
||||
|
||||
@@ -875,16 +875,24 @@ function renderAgentOverview(params: {
|
||||
<div class="label">Model Selection</div>
|
||||
<div class="row" style="gap: 12px; flex-wrap: wrap;">
|
||||
<label class="field" style="min-width: 260px; flex: 1;">
|
||||
<span>Primary model</span>
|
||||
<span>Primary model${isDefault ? " (default)" : ""}</span>
|
||||
<select
|
||||
.value=${effectivePrimary ?? ""}
|
||||
?disabled=${!configForm || configLoading || configSaving}
|
||||
@change=${(e: Event) =>
|
||||
onModelChange(agent.id, (e.target as HTMLSelectElement).value || null)}
|
||||
>
|
||||
<option value="">
|
||||
${defaultPrimary ? `Inherit default (${defaultPrimary})` : "Inherit default"}
|
||||
</option>
|
||||
${
|
||||
isDefault
|
||||
? nothing
|
||||
: html`
|
||||
<option value="">
|
||||
${
|
||||
defaultPrimary ? `Inherit default (${defaultPrimary})` : "Inherit default"
|
||||
}
|
||||
</option>
|
||||
`
|
||||
}
|
||||
${buildModelOptions(configForm, effectivePrimary ?? undefined)}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user