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