diff --git a/chat/src/client.ts b/chat/src/client.ts index d0e6b50..8d25364 100644 --- a/chat/src/client.ts +++ b/chat/src/client.ts @@ -16,7 +16,7 @@ import { import {conversationHash, conversationIdFromHash} from "../lib/conversation-hash"; import {conversationTitlePrompt, normalizeGeneratedConversationTitle, untitledConversationLabel} from "../lib/conversation-title"; import type {Conversation, ConversationSummary, MessageCompletion, ResponseMetadata, StoredChatMessage, WorkingItem} from "../lib/conversation-types"; -import {defaultGenerationSettings, type GenerationSettings} from "../lib/generation-settings"; +import {defaultGenerationSettings, type GenerationSettings, type ReasoningLevel} from "../lib/generation-settings"; import { activateOfflineProfile, cachedLastFetchAt, @@ -115,7 +115,8 @@ const state = { importing: false, importStatus: "", importTitleTemplate: window.localStorage.getItem("xiteng-chat-import-title-template") || "{title}", - composerFullscreen: false + composerFullscreen: false, + settingsOpen: false }; const titleGenerationConversationIds = new Set(); @@ -749,23 +750,40 @@ async function updateAvatar() { } } +type ModelChoice = {provider: ChatProvider; model: ProviderModel; key: string}; + +function availableModelChoices(): ModelChoice[] { + return state.config?.providers.flatMap((item) => item.models.map((model) => ({provider: item, model, key: `${item.id}/${model.id}`}))) || []; +} + +function renderEffortControl(name: string) { + const options: Array<[ReasoningLevel, string]> = [["auto", "自动"], ["none", "关闭"], ["low", "低"], ["medium", "中"], ["high", "高"]]; + return `
Effort控制模型的思考强度
${options.map(([value, label]) => ``).join("")}
`; +} + +function renderModelOption(choice: ModelChoice) { + const active = choice.provider.id === state.providerId && choice.model.id === state.model; + return ``; +} + +function quickModelChoices(choices: ModelChoice[]) { + const activeKey = `${state.providerId}/${state.model}`; + const preferredKeys = [activeKey, ...state.recentModelKeys, ...(state.config?.providers.map((item) => `${item.id}/${item.defaultModel}`) || [])]; + const selected: ModelChoice[] = []; + for (const key of preferredKeys) { + const choice = choices.find((item) => item.key === key); + if (choice && !selected.includes(choice)) selected.push(choice); + } + for (const choice of choices) if (selected.length < 6 && !selected.includes(choice)) selected.push(choice); + return selected.slice(0, 6); +} + function renderModelPicker() { const active = provider(); if (!active || !state.config) return ""; const activeModelName = compactModelName(active.models.find((model) => model.id === state.model)?.name || state.model); - const settings = state.generationSettings; - const query = state.modelQuery.trim().toLowerCase(); - const choices = state.config.providers.flatMap((item) => item.models.map((model) => ({provider: item, model, key: `${item.id}/${model.id}`}))); - const matches = choices.filter((choice) => !query || choice.key.toLowerCase().includes(query) || choice.model.name.toLowerCase().includes(query)); - const recent = state.recentModelKeys.map((key) => choices.find((choice) => choice.key === key)).filter((choice): choice is NonNullable => Boolean(choice)).filter((choice) => matches.includes(choice)); - const option = (choice: typeof choices[number]) => ``; - const groups = state.config.providers.map((item) => { - const items = matches.filter((choice) => choice.provider.id === item.id); - return items.length ? `

${escapeHtml(item.name)}

${items.map(option).join("")}
` : ""; - }).join(""); - const providerSettings = renderProviderSettings(); - const generationSettings = `
生成参数随当前会话保存
界面仅保存在当前浏览器
显示“提交后让 AI 回答”和编辑助手回答。
`; - return `
${escapeHtml(activeModelName)}${icons.down}
${recent.length ? `

${icons.clock}最近使用

${recent.map(option).join("")}
` : ""}${groups || '

没有匹配的模型

'}${providerSettings}
${generationSettings}
`; + const choices = quickModelChoices(availableModelChoices()); + return `
${escapeHtml(activeModelName)}${icons.down}
模型当前与最近使用
${choices.map(renderModelOption).join("")}
${renderEffortControl("quick-effort")}
`; } function renderProviderSettings() { @@ -776,6 +794,26 @@ function renderProviderSettings() { }).join("")}`; } +function renderGenerationSettings() { + const settings = state.generationSettings; + return `${renderEffortControl("settings-effort")}
`; +} + +function renderSettingsPage() { + if (!state.settingsOpen || !state.config) return ""; + const query = state.modelQuery.trim().toLowerCase(); + const choices = availableModelChoices(); + const matches = choices.filter((choice) => !query || choice.key.toLowerCase().includes(query) || choice.model.name.toLowerCase().includes(query) || choice.provider.name.toLowerCase().includes(query)); + const groups = state.config.providers.map((item) => { + const items = matches.filter((choice) => choice.provider.id === item.id); + return items.length ? `

${escapeHtml(item.name)}

${items.map(renderModelOption).join("")}
` : ""; + }).join(""); + const providerSettings = renderProviderSettings(); + const accountHref = state.authenticated ? "https://xiteng.site/account" : appUrl("/api/login"); + const accountLabel = state.authenticated ? "管理 Backend Provider 与凭据" : "登录并配置 Backend Provider"; + return ``; +} + function historyItem(item: ConversationSummary, label = item.name, depth = 0) { const displayLabel = String(label || "").trim() || untitledConversationLabel; return `
`; @@ -909,7 +947,7 @@ function renderWorkingPanel() { const unfinished = state.workingItems.filter((item) => item.kind === "assistant-stream" && item.status !== "streaming"); const requestAssistantReply = activeDraft()?.requestAssistantReply ?? true; const assistantReplyToggle = state.advancedActions - ? `` + ? `` : ""; const draftRows = drafts.map((item) => { const text = workingItemText(item).trim().replace(/\s+/g, " "); @@ -919,7 +957,7 @@ function renderWorkingPanel() { const text = workingItemText(item).trim().replace(/\s+/g, " "); return `
未完成回答${escapeHtml(text.slice(0, 64) || "尚未输出正文")} · ${new Date(item.updatedAt).toLocaleString()}
`; }).join(""); - return `
${assistantReplyToggle}${unfinishedRows ? `
未完成 ${unfinished.length}
${unfinishedRows}
` : ""}
草稿 ${drafts.length}
${draftRows}
`; + return `
${assistantReplyToggle}${renderComposerControls(activeDraft())}${unfinishedRows ? `
未完成 ${unfinished.length}
${unfinishedRows}
` : ""}
草稿 ${drafts.length}
${draftRows}
`; } function replyTargetLabel(message: StoredChatMessage) { @@ -968,7 +1006,7 @@ function renderThread() { const placeholder = fullscreen ? editing ? "编辑消息;Ctrl/⌘ + Enter 提交" : "输入消息;Ctrl/⌘ + Enter 提交" : editing ? "编辑消息,提交后从此处继续" : "输入消息,Enter 发送,Shift + Enter 换行"; - return `
${renderBranchPreviewNotice()}
${renderMessagesMarkup()}
${fullscreenHeader}${renderWorkingPanel()}${editing ? `
正在编辑${editRole};提交后当前会话将从这里继续
` : ""}${renderComposerControls(draft)}
${state.streaming ? `` : ""}
${renderModelPicker()}

${note}

`; + return `
${renderBranchPreviewNotice()}
${renderMessagesMarkup()}
${fullscreenHeader}${renderWorkingPanel()}${editing ? `
正在编辑${editRole};提交后当前会话将从这里继续
` : ""}
${state.streaming ? `` : ""}
${renderModelPicker()}

${note}

`; } function setComposerFullscreen(fullscreen: boolean) { @@ -1042,7 +1080,7 @@ function renderApp() { return; } const profile = state.config.profile; - root.innerHTML = `
${renderIdentitySyncControl(profile)}
${renderHistory()}${renderThread()}${renderImportPanel()}
`; + root.innerHTML = `
${renderIdentitySyncControl(profile)}
${renderHistory()}${renderThread()}${renderImportPanel()}${renderSettingsPage()}
`; scheduleMathTypesetting(root); void updateAvatar(); } @@ -2336,6 +2374,12 @@ root.addEventListener("submit", (event) => { }); root.addEventListener("keydown", (event) => { + if (event.key === "Escape" && state.settingsOpen) { + state.settingsOpen = false; + state.modelQuery = ""; + renderApp(); + return; + } if (event.key === "Escape" && state.importPanelOpen && !state.importing) { state.importPanelOpen = false; renderApp(); @@ -2405,6 +2449,12 @@ root.addEventListener("change", (event) => { renderApp(); return; } + if (target.dataset.action === "history-tree-setting" && target instanceof HTMLInputElement) { + state.historyTree = target.checked; + window.localStorage.setItem("xiteng-chat-history-tree", target.checked ? "1" : "0"); + renderApp(); + return; + } if (target.dataset.action === "session-file" && target instanceof HTMLInputElement) { const files = [...(target.files || [])]; if (files.length) void expandImportFiles(files).then((items) => importSessionFiles(items, `${files.length} 个所选文件`)).catch(showError).finally(() => { target.value = ""; }); @@ -2451,6 +2501,8 @@ root.addEventListener("click", (event) => { const button = (event.target as Element).closest("[data-action]"); if (!button) return; const action = button.dataset.action; + if (action === "open-settings") { state.settingsOpen = true; state.modelQuery = ""; renderApp(); } + if (action === "close-settings") { state.settingsOpen = false; state.modelQuery = ""; renderApp(); } if (action === "toggle-history") { state.historyOpen = !state.historyOpen; renderApp(); } if (action === "close-history") { state.historyOpen = false; renderApp(); } if (action === "toggle-history-tree") { diff --git a/chat/src/styles.css b/chat/src/styles.css index 4785cae..d287b34 100644 --- a/chat/src/styles.css +++ b/chat/src/styles.css @@ -42,17 +42,17 @@ button, a { -webkit-tap-highlight-color: transparent; } .identity-sync-control.offline .identity-sync-status { background: var(--danger); box-shadow: 0 0 0 1px rgba(163,56,45,.25); } .identity-sync-control.error .identity-sync-status { background: #d3932f; box-shadow: 0 0 0 1px rgba(211,147,47,.3); } .identity-sync-control.fetching .identity-sync-status { border: 1px solid var(--muted); border-top-color: transparent; background: var(--panel); box-shadow: none; animation: spin .75s linear infinite; } -.model-provider-settings { display: grid; gap: 3px; margin-top: 8px; border-top: 1px solid var(--line); padding: 12px 0 4px; } -.model-provider-settings > .settings-section-heading { padding: 0 8px 4px; } -.local-key-entry { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: 8px; border-radius: 9px; padding: 7px; } -.local-key-entry:hover { background: var(--soft); } +.model-provider-settings { display: grid; gap: 6px; } +.model-provider-settings > .settings-section-heading { padding: 0 2px 4px; } +.local-key-entry { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: 12px; border: 1px solid var(--line); border-radius: 11px; padding: 10px 11px; background: color-mix(in srgb, var(--panel) 70%, transparent); } +.local-key-entry:hover { background: var(--panel); } .local-key-entry > span { min-width: 0; text-align: left; } .local-key-entry > span strong, .local-key-entry > span small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.local-key-entry > span small { margin-top: 2px; color: var(--muted); font-size: 9px; } +.local-key-entry > span small { margin-top: 3px; color: var(--muted); font-size: 10px; } .local-key-entry > span .local-key-error { max-width: 190px; color: var(--danger); white-space: normal; } .local-key-entry > div { display: flex; flex: 0 0 auto; gap: 2px; } -.model-provider-settings button { border: 0; border-radius: 8px; padding: 8px 9px; background: transparent; color: var(--text); text-align: left; cursor: pointer; } -.model-provider-settings button:hover { background: var(--panel); } +.model-provider-settings button { border: 0; border-radius: 8px; padding: 8px 9px; background: var(--soft); color: var(--text); text-align: left; cursor: pointer; } +.model-provider-settings button:hover { background: color-mix(in srgb, var(--soft) 76%, var(--text)); } .model-provider-settings button.dangerous { color: #b42318; } .model-picker { position: relative; } .model-picker > summary { display: flex; min-width: 190px; max-width: 290px; height: 36px; align-items: center; justify-content: space-between; gap: 8px; border: 1px solid var(--line); border-radius: 11px; padding: 0 10px; background: rgba(255,255,255,.72); color: var(--text); cursor: pointer; list-style: none; } @@ -63,32 +63,80 @@ button, a { -webkit-tap-highlight-color: transparent; } .model-picker > summary::-webkit-details-marker { display: none; } .model-picker[open] > summary { border-color: rgba(23,23,23,.24); background: #fff; } .model-picker[open] > summary .picker-chevron { transform: rotate(180deg); } -.model-menu { position: fixed; z-index: 30; top: 56px; right: 22px; display: grid; width: min(680px, calc(100vw - 24px)); max-height: min(670px, calc(100dvh - 82px)); grid-template-columns: minmax(300px, 1fr) 240px; overflow: hidden; border: 1px solid var(--line); border-radius: 14px; padding: 0; background: var(--panel); box-shadow: 0 18px 48px rgba(0,0,0,.15); backdrop-filter: blur(18px); } -.model-browser { min-height: 0; overflow-y: auto; padding: 8px; } -.model-search { position: sticky; z-index: 1; top: -8px; display: flex; height: 40px; align-items: center; gap: 8px; margin: -1px -1px 5px; padding: 0 9px; border-bottom: 1px solid var(--line); background: var(--panel); color: var(--muted); } -.model-search input { width: 100%; border: 0; outline: 0; background: transparent; color: var(--text); } -.model-menu-group { display: grid; gap: 2px; padding: 5px 0 8px; } -.model-menu-group + .model-menu-group { border-top: 1px solid var(--line); } -.model-menu-group h3 { display: flex; align-items: center; gap: 5px; margin: 0; padding: 4px 8px; color: var(--muted); font-size: 10px; font-weight: 750; letter-spacing: .08em; text-transform: uppercase; } +.model-menu { position: fixed; z-index: 30; top: 56px; right: 22px; display: grid; width: min(360px, calc(100vw - 24px)); max-height: min(520px, calc(100dvh - 82px)); overflow-y: auto; border: 1px solid var(--line); border-radius: 15px; padding: 8px; background: var(--panel); box-shadow: 0 18px 48px rgba(0,0,0,.15); backdrop-filter: blur(18px); } +.quick-models { min-height: 0; } +.quick-config-heading, .effort-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 7px 8px; } +.quick-config-heading strong, .effort-heading strong { font-size: 11px; } +.quick-config-heading small, .effort-heading small { color: var(--muted); font-size: 9px; } +.quick-model-list { display: grid; max-height: 238px; overflow-y: auto; gap: 2px; } .model-option { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 12px; border: 0; border-radius: 8px; padding: 8px; background: transparent; color: var(--text); text-align: left; cursor: pointer; } .model-option:hover, .model-option.active { background: var(--soft); } .model-option > span { min-width: 0; } .model-option strong, .model-option small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .model-option strong { font-size: 12px; font-weight: 620; } .model-option small { color: var(--muted); font-size: 10px; } -.model-menu-empty { margin: 10px; color: var(--muted); text-align: center; } -.model-generation-settings { display: grid; align-content: start; gap: 10px; overflow-y: auto; border-left: 1px solid var(--line); padding: 14px; background: color-mix(in srgb, var(--panel) 84%, var(--soft)); } +.model-option.active::after { flex: 0 0 auto; color: var(--text); content: "✓"; font-size: 11px; font-weight: 800; } +.model-option.active > small { display: none; } +.effort-control { display: grid; gap: 3px; margin-top: 7px; border-top: 1px solid var(--line); padding-top: 5px; } +.effort-options { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 3px; padding: 0 3px 6px; } +.effort-options label { min-width: 0; cursor: pointer; } +.effort-options input { position: absolute; opacity: 0; pointer-events: none; } +.effort-options span { display: grid; height: 30px; place-items: center; border-radius: 8px; color: var(--muted); font-size: 10px; } +.effort-options input:checked + span { background: var(--soft); color: var(--text); font-weight: 700; } +.effort-options input:focus-visible + span { outline: 2px solid var(--text); outline-offset: 1px; } +.open-settings-button { display: grid; width: 100%; grid-template-columns: 28px minmax(0, 1fr); align-items: center; gap: 9px; margin-top: 3px; border: 0; border-top: 1px solid var(--line); padding: 11px 8px 6px; background: transparent; color: var(--text); text-align: left; cursor: pointer; } +.open-settings-button:hover { background: var(--soft); } +.open-settings-button .ui-icon { color: var(--muted); } +.open-settings-button strong, .open-settings-button small { display: block; } +.open-settings-button strong { font-size: 11px; } +.open-settings-button small { margin-top: 2px; color: var(--muted); font-size: 9px; } .settings-section-heading { display: grid; gap: 2px; margin-bottom: 2px; } .settings-section-heading strong { font-size: 12px; } .settings-section-heading small { color: var(--muted); font-size: 9px; } -.model-generation-settings label { display: grid; gap: 5px; color: var(--muted); font-size: 10px; } -.model-generation-settings select, .model-generation-settings input[type="number"] { min-width: 0; width: 100%; border: 1px solid var(--line); border-radius: 8px; padding: 8px; background: var(--bg); color: var(--text); } -.model-generation-settings .generation-check { display: flex; align-items: center; gap: 7px; color: var(--text); } -.settings-divider { border-top: 1px solid var(--line); margin: 2px 0; } -.settings-help { margin-top: -6px; color: var(--muted); font-size: 9px; line-height: 1.45; } -.model-generation-settings button { border: 0; border-radius: 8px; padding: 9px; background: var(--soft); color: var(--text); cursor: pointer; } -.state-card > .model-picker { display: inline-block; margin-bottom: 24px; text-align: left; } -.state-card > .model-picker > summary { min-width: 260px; } +.settings-page { position: fixed; z-index: 90; inset: 0; display: flex; flex-direction: column; background: var(--bg); } +.settings-page-header { display: flex; height: 64px; flex: 0 0 auto; align-items: center; gap: 12px; border-bottom: 1px solid var(--line); padding: 0 22px; background: var(--panel); } +.settings-page-header button { display: grid; width: 34px; height: 34px; place-items: center; border: 0; border-radius: 10px; background: transparent; color: var(--text); cursor: pointer; } +.settings-page-header button:hover { background: var(--soft); } +.settings-page-header > span strong, .settings-page-header > span small { display: block; } +.settings-page-header > span strong { font-size: 15px; } +.settings-page-header > span small { margin-top: 2px; color: var(--muted); font-size: 9px; } +.settings-layout { display: grid; min-height: 0; flex: 1; grid-template-columns: 176px minmax(0, 760px); justify-content: center; gap: 28px; overflow: hidden; padding: 28px 24px; } +.settings-nav { display: grid; align-content: start; gap: 3px; } +.settings-nav a { border-radius: 9px; padding: 9px 11px; color: var(--muted); font-size: 11px; font-weight: 650; text-decoration: none; } +.settings-nav a:hover { background: var(--soft); color: var(--text); } +.settings-content { display: grid; min-width: 0; align-content: start; overflow-y: auto; gap: 16px; padding: 0 8px 40px 0; scroll-behavior: smooth; } +.settings-card { border: 1px solid var(--line); border-radius: 16px; padding: 18px; background: var(--panel); scroll-margin-top: 12px; } +.settings-card > header { margin-bottom: 16px; } +.settings-card h2, .settings-card h3, .settings-card p { margin: 0; } +.settings-card h2 { font-size: 16px; letter-spacing: -.02em; } +.settings-card > header p { margin-top: 5px; color: var(--muted); font-size: 10px; line-height: 1.5; } +.settings-model-search { display: flex; height: 40px; align-items: center; gap: 8px; border: 1px solid var(--line); border-radius: 10px; padding: 0 11px; background: var(--bg); color: var(--muted); } +.settings-model-search input { min-width: 0; width: 100%; border: 0; outline: 0; background: transparent; color: var(--text); } +.settings-model-groups { display: grid; gap: 13px; margin-top: 13px; } +.settings-model-group { display: grid; gap: 5px; } +.settings-model-group h3 { padding: 0 8px; color: var(--muted); font-size: 10px; } +.settings-model-group > div { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3px; } +.settings-model-group .model-option { border: 1px solid transparent; } +.settings-model-group .model-option.active { border-color: var(--line); } +.settings-empty { color: var(--muted); font-size: 11px; text-align: center; } +.settings-card > .effort-control { margin: 0; border: 0; padding: 0; } +.settings-card .effort-heading { padding: 0 0 8px; } +.settings-card .effort-options { max-width: 440px; padding: 0; } +.settings-field-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(160px, .65fr) minmax(160px, .65fr); gap: 10px; margin-top: 18px; } +.settings-field-grid > label:not(.settings-check) { display: grid; align-content: start; gap: 6px; color: var(--muted); font-size: 10px; } +.settings-field-grid input[type="number"] { min-width: 0; width: 100%; border: 1px solid var(--line); border-radius: 9px; padding: 9px; background: var(--bg); color: var(--text); } +.settings-check { display: flex; min-width: 0; align-items: flex-start; gap: 9px; border-radius: 10px; padding: 9px; color: var(--text); cursor: pointer; } +.settings-check:hover { background: var(--soft); } +.settings-check input { margin-top: 2px; } +.settings-check span { min-width: 0; } +.settings-check strong, .settings-check small { display: block; } +.settings-check strong { font-size: 11px; } +.settings-check small { margin-top: 3px; color: var(--muted); font-size: 9px; line-height: 1.45; } +.settings-reset-button { margin-top: 14px; border: 0; border-radius: 9px; padding: 9px 11px; background: var(--soft); color: var(--text); cursor: pointer; } +.settings-account-link { display: flex; align-items: center; gap: 9px; margin-top: 12px; border: 1px solid var(--line); border-radius: 11px; padding: 11px; color: var(--text); font-size: 11px; font-weight: 650; text-decoration: none; } +.settings-account-link:hover { background: var(--soft); } +.settings-account-link .ui-icon { color: var(--muted); } +.settings-interface-options { display: grid; gap: 4px; } .history-toggle, .history-heading button, .history-delete { display: grid; place-items: center; border: 0; background: transparent; color: inherit; cursor: pointer; } .history-toggle { width: 32px; height: 32px; border-radius: 9px; } .history-toggle:hover, .history-heading button:hover, .history-delete:hover { background: var(--soft); } @@ -197,6 +245,7 @@ button, a { -webkit-tap-highlight-color: transparent; } .message-error { margin-top: 10px; border: 1px solid rgba(163,56,45,.2); border-radius: 10px; padding: 10px 12px; background: rgba(163,56,45,.07); color: var(--danger); font-size: 13px; } .partial-badge { display: inline-flex; max-width: min(240px, 40vw); flex: 0 0 auto; overflow: hidden; border: 1px solid rgba(163,56,45,.22); border-radius: 999px; padding: 3px 8px; background: rgba(163,56,45,.07); color: var(--danger); font-size: 10px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; } .thread-footer { position: sticky; bottom: 0; display: flex; width: min(760px, 100%); flex-direction: column; align-items: center; margin: auto auto 0; padding: 40px 0 16px; background: linear-gradient(to bottom, transparent, var(--bg) 38%); } +.thread-footer:has(.model-picker[open]) { z-index: 50; } .edit-context { display: flex; width: calc(100% - 24px); align-items: center; justify-content: space-between; gap: 12px; margin: 0 12px -1px; border: 1px solid var(--line); border-bottom: 0; border-radius: 12px 12px 0 0; padding: 8px 12px 9px; background: var(--soft); color: var(--muted); font-size: 11px; } .edit-context button { border: 0; background: transparent; color: var(--text); font: inherit; font-weight: 650; cursor: pointer; } .edit-context + .composer { border-top-left-radius: 10px; border-top-right-radius: 10px; } @@ -247,6 +296,8 @@ button, a { -webkit-tap-highlight-color: transparent; } .working-panel { display: flex; width: 100%; min-width: 0; align-items: center; justify-content: flex-end; gap: 6px; margin-bottom: 7px; } .working-panel .assistant-reply-toggle { display: flex; min-width: 0; align-items: center; gap: 5px; margin-right: auto; color: var(--text); font-size: 10px; white-space: nowrap; } .working-panel .assistant-reply-toggle input { margin: 0; } +.working-panel .composer-controls { width: auto; min-width: 0; flex: 1 1 auto; justify-content: flex-end; padding: 0; overflow: hidden; } +.working-panel .reply-context { max-width: 100%; } .working-panel details { position: relative; } .working-panel summary { display: flex; height: 28px; align-items: center; border: 1px solid var(--line); border-radius: 9px; padding: 0 9px; background: var(--panel); color: var(--muted); font-size: 10px; cursor: pointer; list-style: none; } .working-panel summary::-webkit-details-marker { display: none; } @@ -313,9 +364,7 @@ button, a { -webkit-tap-highlight-color: transparent; } .provider-control select { max-width: 105px; } .model-picker > summary { min-width: 132px; max-width: 170px; } .picker-icons > svg:first-child { display: none; } - .model-menu { position: fixed; top: 64px; right: 12px; left: 12px; width: auto; max-height: calc(100dvh - 76px); grid-template-columns: minmax(0, 1fr); overflow-y: auto; } - .model-browser { flex: none; overflow: visible; } - .model-generation-settings { overflow: visible; border-top: 1px solid var(--line); border-left: 0; } + .model-menu { position: fixed; top: 64px; right: 12px; left: 12px; width: auto; max-height: calc(100dvh - 76px); overflow-y: auto; } .history-sidebar { position: fixed; z-index: 40; top: 0; width: min(300px, 86vw); border-right: 1px solid var(--line); } .history-heading { height: 58px; } .history-close { display: grid !important; } @@ -324,6 +373,16 @@ button, a { -webkit-tap-highlight-color: transparent; } .session-location-help dl > div { grid-template-columns: 76px minmax(0, 1fr); } .session-title-template { grid-template-columns: 1fr; gap: 6px; } .session-import-actions { grid-template-columns: 1fr; } + .settings-page-header { height: 58px; padding: 0 12px; } + .settings-layout { display: flex; flex-direction: column; gap: 12px; padding: 12px 0 0; } + .settings-nav { display: flex; flex: 0 0 auto; gap: 2px; overflow-x: auto; padding: 0 12px; } + .settings-nav a { flex: 0 0 auto; padding: 8px 10px; } + .settings-content { gap: 12px; padding: 0 12px max(28px, env(safe-area-inset-bottom)); } + .settings-card { border-radius: 13px; padding: 14px; } + .settings-model-group > div { grid-template-columns: 1fr; } + .settings-field-grid { grid-template-columns: 1fr; } + .local-key-entry { align-items: flex-start; flex-direction: column; } + .local-key-entry > div { width: 100%; } .history-backdrop { position: fixed; z-index: 35; inset: 0; display: block; border: 0; background: rgba(0,0,0,.36); opacity: 0; pointer-events: none; transition: opacity .18s ease; } .history-backdrop.open { opacity: 1; pointer-events: auto; } .with-history.history-open .thread-root { padding-left: 0; } @@ -342,7 +401,7 @@ button, a { -webkit-tap-highlight-color: transparent; } .composer-controls select { max-width: 170px; } .composer > .model-picker > summary { width: auto; min-width: 0; max-width: 116px; } .composer > .model-picker > summary .picker-label { max-width: 86px; } - .composer > .model-picker .model-menu { top: auto; right: 12px; bottom: max(76px, calc(env(safe-area-inset-bottom) + 70px)); left: 12px; max-height: calc(100dvh - 100px); } + .composer > .model-picker .model-menu { top: auto; right: 12px; bottom: max(76px, calc(env(safe-area-inset-bottom) + 70px)); left: 12px; max-height: min(520px, calc(100dvh - 152px - env(safe-area-inset-bottom))); } .branch-preview-notice { flex-direction: column; align-items: stretch; gap: 8px; margin-bottom: 6px; } .branch-preview-notice > div { display: grid; width: 100%; grid-template-columns: auto minmax(0, 1fr); } .branch-preview-notice button { min-width: 0; }