diff --git a/ui/src/i18n/locales/de.ts b/ui/src/i18n/locales/de.ts index f45ffc3f4c0..7fd638766e7 100644 --- a/ui/src/i18n/locales/de.ts +++ b/ui/src/i18n/locales/de.ts @@ -5,6 +5,7 @@ export const de: TranslationMap = { version: "Version", health: "Status", ok: "OK", + online: "Online", offline: "Offline", connect: "Verbinden", refresh: "Aktualisieren", diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index df80f2d7c78..370fec9c660 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -4,6 +4,7 @@ export const en: TranslationMap = { common: { health: "Health", ok: "OK", + online: "Online", offline: "Offline", connect: "Connect", refresh: "Refresh", diff --git a/ui/src/i18n/locales/es.ts b/ui/src/i18n/locales/es.ts index a96ee7ad2d7..091cd2ca937 100644 --- a/ui/src/i18n/locales/es.ts +++ b/ui/src/i18n/locales/es.ts @@ -5,6 +5,7 @@ export const es: TranslationMap = { version: "Versión", health: "Estado", ok: "Correcto", + online: "En línea", offline: "Desconectado", connect: "Conectar", refresh: "Actualizar", diff --git a/ui/src/i18n/locales/pt-BR.ts b/ui/src/i18n/locales/pt-BR.ts index aaaa26c253e..cb9ba1ba283 100644 --- a/ui/src/i18n/locales/pt-BR.ts +++ b/ui/src/i18n/locales/pt-BR.ts @@ -4,6 +4,7 @@ export const pt_BR: TranslationMap = { common: { health: "Saúde", ok: "OK", + online: "Online", offline: "Offline", connect: "Conectar", refresh: "Atualizar", diff --git a/ui/src/i18n/locales/zh-CN.ts b/ui/src/i18n/locales/zh-CN.ts index ac321857253..b039be16f41 100644 --- a/ui/src/i18n/locales/zh-CN.ts +++ b/ui/src/i18n/locales/zh-CN.ts @@ -4,6 +4,7 @@ export const zh_CN: TranslationMap = { common: { health: "健康状况", ok: "正常", + online: "在线", offline: "离线", connect: "连接", refresh: "刷新", diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts index 56a80c61d92..a6a616209e7 100644 --- a/ui/src/i18n/locales/zh-TW.ts +++ b/ui/src/i18n/locales/zh-TW.ts @@ -4,6 +4,7 @@ export const zh_TW: TranslationMap = { common: { health: "健康狀況", ok: "正常", + online: "在線", offline: "離線", connect: "連接", refresh: "刷新", diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css index f05a2665ff6..f40a88be86c 100644 --- a/ui/src/styles/chat/layout.css +++ b/ui/src/styles/chat/layout.css @@ -65,7 +65,7 @@ background: transparent; } -.chat-thread-inner> :first-child { +.chat-thread-inner > :first-child { margin-top: 0 !important; } @@ -291,7 +291,7 @@ } /* Hide the "Message" label - keep textarea only */ -.chat-compose__field>span { +.chat-compose__field > span { display: none; } @@ -362,7 +362,7 @@ } } -.agent-chat__input>textarea { +.agent-chat__input > textarea { width: 100%; min-height: 40px; max-height: 150px; @@ -378,7 +378,7 @@ box-sizing: border-box; } -.agent-chat__input>textarea::placeholder { +.agent-chat__input > textarea::placeholder { color: var(--muted); } @@ -520,7 +520,7 @@ scrollbar-width: thin; } -.slash-menu-group+.slash-menu-group { +.slash-menu-group + .slash-menu-group { margin-top: 4px; padding-top: 4px; border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent); diff --git a/ui/src/styles/chat/sidebar.css b/ui/src/styles/chat/sidebar.css index 934e285d95b..de6010f3ed7 100644 --- a/ui/src/styles/chat/sidebar.css +++ b/ui/src/styles/chat/sidebar.css @@ -82,6 +82,18 @@ line-height: 1.5; } +.sidebar-markdown .markdown-inline-image { + display: block; + max-width: 100%; + max-height: 420px; + width: auto; + height: auto; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: color-mix(in srgb, var(--secondary) 70%, transparent); + object-fit: contain; +} + .sidebar-markdown pre { background: rgba(0, 0, 0, 0.12); border-radius: 4px; diff --git a/ui/src/styles/chat/text.css b/ui/src/styles/chat/text.css index 56224fabf9e..dd76434e041 100644 --- a/ui/src/styles/chat/text.css +++ b/ui/src/styles/chat/text.css @@ -56,6 +56,19 @@ font-size: 0.9em; } +.chat-text :where(.markdown-inline-image) { + display: block; + max-width: min(100%, 420px); + max-height: 320px; + width: auto; + height: auto; + margin-top: 0.75em; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: color-mix(in srgb, var(--secondary) 70%, transparent); + object-fit: contain; +} + .chat-text :where(:not(pre) > code) { background: rgba(0, 0, 0, 0.15); padding: 0.15em 0.4em; diff --git a/ui/src/styles/components.css b/ui/src/styles/components.css index b2806f3208f..0bd72d9e972 100644 --- a/ui/src/styles/components.css +++ b/ui/src/styles/components.css @@ -2416,6 +2416,19 @@ font-size: 0.9em; } +.chat-text :where(.markdown-inline-image) { + display: block; + max-width: min(100%, 420px); + max-height: 320px; + width: auto; + height: auto; + margin-top: 0.75em; + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: color-mix(in srgb, var(--secondary) 70%, transparent); + object-fit: contain; +} + .chat-text :where(:not(pre) > code) { padding: 0.15em 0.35em; border-radius: var(--radius-sm); diff --git a/ui/src/styles/layout.css b/ui/src/styles/layout.css index c9bfb8e6951..6e19806bb32 100644 --- a/ui/src/styles/layout.css +++ b/ui/src/styles/layout.css @@ -5,7 +5,7 @@ .shell { --shell-pad: 16px; --shell-gap: 16px; - --shell-nav-width: 288px; + --shell-nav-width: 258px; --shell-nav-rail-width: 78px; --shell-topbar-height: 52px; --shell-focus-duration: 200ms; @@ -70,7 +70,7 @@ padding-top: 0; } -.shell--chat-focus .content>*+* { +.shell--chat-focus .content > * + * { margin-top: 0; } @@ -340,7 +340,7 @@ flex-direction: column; min-height: 0; flex: 1; - padding: 14px 14px 12px; + padding: 14px 10px 12px; border: none; border-radius: 0; background: transparent; @@ -503,7 +503,7 @@ justify-content: space-between; gap: 8px; width: 100%; - padding: 0 12px; + padding: 0 10px; min-height: 28px; background: transparent; border: none; @@ -522,9 +522,9 @@ } .nav-section__label-text { - font-size: 11px; + font-size: 12px; font-weight: 700; - letter-spacing: 0.08em; + letter-spacing: 0.06em; text-transform: uppercase; } @@ -555,9 +555,9 @@ display: flex; align-items: center; justify-content: flex-start; - gap: 10px; - min-height: 38px; - padding: 0 12px; + gap: 8px; + min-height: 40px; + padding: 0 9px; border-radius: 12px; border: 1px solid transparent; background: transparent; @@ -595,8 +595,8 @@ } .nav-item__text { - font-size: 13px; - font-weight: 550; + font-size: 14px; + font-weight: 600; white-space: nowrap; } @@ -688,9 +688,11 @@ .sidebar--collapsed .nav-item.active, .sidebar--collapsed .nav-item--active { - background: linear-gradient(180deg, + background: linear-gradient( + 180deg, color-mix(in srgb, #0b2f34 84%, var(--bg-elevated) 16%) 0%, - color-mix(in srgb, #081f25 90%, var(--bg) 10%) 100%); + color-mix(in srgb, #081f25 90%, var(--bg) 10%) 100% + ); border-color: color-mix(in srgb, #1ed2c2 18%, var(--border) 82%); box-shadow: inset 0 1px 0 color-mix(in srgb, white 8%, transparent), @@ -761,6 +763,24 @@ margin: 0 auto; } +.sidebar-version__status { + width: 8px; + height: 8px; + border-radius: var(--radius-full); + flex-shrink: 0; + margin-left: auto; +} + +.sidebar-version__status.sidebar-connection-status--online { + background: var(--ok); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--ok) 14%, transparent); +} + +.sidebar-version__status.sidebar-connection-status--offline { + background: var(--danger); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--danger) 14%, transparent); +} + .sidebar--collapsed .sidebar-shell__footer { padding: 8px 0 2px; } @@ -778,6 +798,10 @@ border-radius: 16px; } +.sidebar--collapsed .sidebar-version__status { + margin-left: 0; +} + .shell--nav-collapsed .shell-nav { width: var(--shell-nav-rail-width); min-width: var(--shell-nav-rail-width); @@ -831,7 +855,7 @@ overflow-x: hidden; } -.content>*+* { +.content > * + * { margin-top: 20px; } @@ -847,7 +871,7 @@ padding-bottom: 0; } -.content--chat>*+* { +.content--chat > * + * { margin-top: 0; } @@ -906,7 +930,7 @@ padding-bottom: 0; } -.content--chat .content-header>div:first-child { +.content--chat .content-header > div:first-child { text-align: left; } diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index 0ebafc22d4d..eaf94616032 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -743,6 +743,23 @@ export function renderTopbarThemeModeToggle(state: AppViewState) { `; } +export function renderSidebarConnectionStatus(state: AppViewState) { + const label = state.connected ? t("common.online") : t("common.offline"); + const toneClass = state.connected + ? "sidebar-connection-status--online" + : "sidebar-connection-status--offline"; + + return html` + + `; +} + export function renderThemeToggle(state: AppViewState) { const setOpen = (orb: HTMLElement, nextOpen: boolean) => { orb.classList.toggle("theme-orb--open", nextOpen); diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index b1ddf9e323c..0e9e522b743 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -10,6 +10,7 @@ import { renderChatControls, renderChatSessionSelect, renderTab, + renderSidebarConnectionStatus, renderTopbarThemeModeToggle, } from "./app-render.helpers.ts"; import type { AppViewState } from "./app-view-state.ts"; @@ -437,9 +438,7 @@ export function renderApp(state: AppViewState) { ⌘K -
+ @@ -543,9 +542,10 @@ export function renderApp(state: AppViewState) { ? html` + ${renderSidebarConnectionStatus(state)} ` : html` - + ${renderSidebarConnectionStatus(state)} ` } @@ -915,6 +915,7 @@ export function renderApp(state: AppViewState) { }, onRefresh: async () => { await loadAgents(state); + await loadConfig(state); const agentIds = state.agentsList?.agents?.map((entry) => entry.id) ?? []; if (agentIds.length > 0) { void loadAgentIdentities(state, agentIds); @@ -924,9 +925,24 @@ export function renderApp(state: AppViewState) { state.agentsList?.defaultId ?? state.agentsList?.agents?.[0]?.id ?? null; + if (refreshedAgentId) { + void loadAgentIdentity(state, refreshedAgentId); + } + if (state.agentsPanel === "files" && refreshedAgentId) { + void loadAgentFiles(state, refreshedAgentId); + } + if (state.agentsPanel === "skills" && refreshedAgentId) { + void loadAgentSkills(state, refreshedAgentId); + } if (state.agentsPanel === "tools" && refreshedAgentId) { void loadToolsCatalog(state, refreshedAgentId); } + if (state.agentsPanel === "channels") { + void loadChannels(state, false); + } + if (state.agentsPanel === "cron") { + void state.loadCron(); + } }, onSelectAgent: (agentId) => { if (state.agentsSelectedId === agentId) { diff --git a/ui/src/ui/markdown.test.ts b/ui/src/ui/markdown.test.ts index 279cb2b53fb..90bce3b65f5 100644 --- a/ui/src/ui/markdown.test.ts +++ b/ui/src/ui/markdown.test.ts @@ -39,6 +39,7 @@ describe("toSanitizedMarkdownHtml", () => { it("preserves base64 data URI images (#15437)", () => { const html = toSanitizedMarkdownHtml(""); expect(html).toContain("