fix(app): polish the MCPs dialog (#58953)

Co-authored-by: vimtor <36263538+vimtor@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-10-25 12:00:03 +00:00
committed by GitHub
Unverified
parent 221a254083
commit 280c8e078a
69 changed files with 258 additions and 269 deletions
@@ -156,7 +156,7 @@ for (const surface of ["dialog"] as const) {
// The dialog opens with its search focused and the first server highlighted.
if (surface === "dialog") {
await expect(view.panel.getByRole("searchbox", { name: "Search", exact: true })).toBeFocused()
await expect(view.panel.getByRole("combobox", { name: "Search MCPs", exact: true })).toBeFocused()
await expect(view.panel.getByRole("option")).toHaveAttribute("aria-selected", "true")
}
@@ -176,13 +176,13 @@ for (const surface of ["dialog"] as const) {
])
expect(view.requests.every((request) => request.directory === workspace)).toBe(true)
// Status is separate from the shared v2 button that opens the error.
const failed = view.panel.getByRole("button", { name: "Details for figma-desktop", exact: true })
// The red dot is the status and the switch stays on; a ghost action beside it opens the error.
const failed = view.panel.getByRole("button", { name: "View error for figma-desktop", exact: true })
const details = page.getByRole("dialog", { name: "figma-desktop", exact: true })
await expect(view.toggle).toHaveCount(0)
await expect(view.panel.getByText("Failed", { exact: true })).toHaveCount(1)
await expect(failed).toHaveText("Details")
await expect(failed).toHaveAttribute("data-component", "button-v2")
await expect(view.toggle).toBeChecked()
await expect(view.toggle).toHaveAccessibleDescription("Failed")
await expect(failed).toHaveText("View error")
await expect(failed).toHaveAttribute("data-variant", "ghost-muted")
await failed.click()
await expect(details).toHaveAccessibleDescription("Connection failed")
await expect(details.getByText(error, { exact: true })).toBeVisible()
@@ -220,6 +220,34 @@ for (const surface of ["dialog"] as const) {
})
}
test("toggling an MCP from the dialog keeps it highlighted", async ({ page }) => {
const connected = new Set<string>()
const names = ["alpha-mcp", "figma-desktop", "zeta-mcp"]
const view = await open(page, {
surface: "dialog",
mcp: () => names.map((name) => ({ name, status: { status: connected.has(name) ? "connected" : "disabled" } })),
onMcpAction: (input) =>
void (input.action === "connect" ? connected.add(input.server) : connected.delete(input.server)),
})
const row = view.panel.getByRole("option").filter({ hasText: "figma-desktop" })
const toggle = row.getByRole("switch", { name: "figma-desktop", exact: true })
// From the keyboard, Enter toggles the highlighted server and the highlight stays on it.
await page.keyboard.press("ArrowDown")
await expect(row).toHaveAttribute("aria-selected", "true")
await page.keyboard.press("Enter")
await expect(toggle).toBeChecked()
await expect(row).toHaveAttribute("aria-selected", "true")
// With the pointer, the switch toggles the row under it and the highlight stays there too.
await row.locator('[data-slot="switch-control"]').click()
await expect(toggle).not.toBeChecked()
await expect(toggle).toBeEnabled()
await expect(row).toHaveAttribute("aria-selected", "true")
})
for (const surface of ["popover", "dialog"] as const) {
test(`signs in to an auth-gated MCP from the ${surface}`, async ({ page, context }) => {
const state = { signedIn: false }
@@ -254,6 +282,7 @@ for (const surface of ["popover", "dialog"] as const) {
})
await expect(signIn).toHaveAttribute("data-component", "button-v2")
await expect(signIn).toHaveAttribute("data-variant", "ghost-muted")
await expect(signIn.locator("svg")).toHaveCount(0)
await signIn.click()
await expect(signIn).toHaveText("Signing in…")
@@ -312,9 +341,9 @@ for (const surface of ["popover", "dialog"] as const) {
.locator(surface === "dialog" ? '[role="option"]' : ".session-mcp-row")
.filter({ hasText: name })
// The popover's dot is the status, so the control beside the name must stay whole.
// The dot is the status, so the control beside the name must stay whole.
const status =
surface === "dialog" || index === 3
index === 3
? row.getByText(["Connected", "Disabled", "Failed", "Sign in"][index], { exact: true })
: row.locator('[data-slot="switch-control"]')
@@ -342,7 +371,7 @@ for (const surface of ["popover", "dialog"] as const) {
await page.setViewportSize({ width: 390, height: 844 })
await expect(name).toHaveCSS("white-space", "normal")
await expect.poll(() => name.evaluate((el) => el.scrollWidth <= el.clientWidth)).toBe(true)
await view.panel.getByRole("button", { name: `Details for ${names[2]}`, exact: true }).scrollIntoViewIfNeeded()
await expect(view.panel.getByRole("button", { name: `Details for ${names[2]}`, exact: true })).toBeInViewport()
await view.panel.getByRole("button", { name: `View error for ${names[2]}`, exact: true }).scrollIntoViewIfNeeded()
await expect(view.panel.getByRole("button", { name: `View error for ${names[2]}`, exact: true })).toBeInViewport()
})
}
@@ -603,7 +603,7 @@ test("MCP rows toggle once per action and keep sign-in, errors, and management a
expect(writes).toHaveLength(6)
await submenu.getByRole("button", { name: "Manage MCP servers", exact: true }).click()
await expect(page.getByRole("dialog").getByText("MCPs", { exact: true })).toBeVisible()
await expect(page.getByRole("dialog", { name: "MCPs", exact: true })).toBeVisible()
await expect(submenu).toBeHidden()
})
+119 -183
View File
@@ -1,19 +1,20 @@
import { Component, createEffect, createMemo, For, on, Show } from "solid-js"
import { Component, createEffect, createMemo, on, Show } from "solid-js"
import { createStore } from "solid-js/store"
import fuzzysort from "fuzzysort"
import { useData } from "@/runtime/server/current"
import { useWorkspaceLocation } from "@/workspaces/location"
import { Button } from "@opencode/ui/button"
import { Dialog, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode/ui/dialog"
import { useFilteredList } from "@opencode/ui/hooks"
import { Dialog, DialogBody, DialogHeader, DialogTitle, DialogTitleGroup } from "@opencode/ui/dialog"
import { Icon } from "@opencode/ui/icon"
import { ErrorDetails } from "@opencode/ui/error-details"
import { SearchList } from "@opencode/ui/search-list"
import { Switch } from "@opencode/ui/switch"
import { TextInput } from "@opencode/ui/text-input"
import { TruncatedText } from "@opencode/ui/truncated-text"
import { useLanguage } from "@/runtime/i18n/language"
import { useMcpToggle } from "@/providers/connect/mcp"
import { usePlatform } from "@/runtime/platform/platform"
import { showToast } from "@/shell/notifications/toast"
import "@/shell/commands/dialog.css"
import "@/shell/search-list.css"
const statusLabels = {
@@ -37,7 +38,13 @@ export const DialogMcp: Component<{ directory: string; server?: string }> = (pro
const platform = usePlatform()
const directory = () => props.directory
const toggle = useMcpToggle(directory)
const [store, setStore] = createStore<{ detail?: string; copied: boolean }>({ detail: props.server, copied: false })
const [store, setStore] = createStore<{ detail?: string; copied: boolean; query: string }>({
detail: props.server,
copied: false,
query: "",
})
const loaded = () => data.location.mcp.server.list({ directory: directory() })
const servers = createMemo(() => loaded() ?? [])
@@ -49,7 +56,16 @@ export const DialogMcp: Component<{ directory: string; server?: string }> = (pro
.sort((a, b) => a.name.localeCompare(b.name)),
)
const enabledCount = createMemo(() => items().filter((i) => i.status === "connected").length)
const results = createMemo(() => {
const query = store.query.trim()
if (!query) return items()
return fuzzysort
.go(query, items(), { keys: ["name", "status"] })
.map((result) => result.obj)
.sort((a, b) => a.name.localeCompare(b.name))
})
// Like the TUI, a failed server's row opens its error; retrying and disabling happen there.
const failure = createMemo(() => {
@@ -71,15 +87,10 @@ export const DialogMcp: Component<{ directory: string; server?: string }> = (pro
requestAnimationFrame(() => backRef?.focus())
}
// The list stays mounted behind the error, so going back keeps the search, the highlight and the scroll.
const back = () => {
setStore("detail", undefined)
requestAnimationFrame(() => {
searchRef?.focus()
const active = list.active()
if (active)
resultsRef?.querySelector(`[data-option-key="${CSS.escape(active)}"]`)?.scrollIntoView({ block: "nearest" })
})
requestAnimationFrame(() => listRef?.querySelector<HTMLInputElement>('[role="combobox"]')?.focus())
}
const onDetailKeyDown = (event: KeyboardEvent) => {
@@ -110,182 +121,18 @@ export const DialogMcp: Component<{ directory: string; server?: string }> = (pro
)
}
// A toggle changes the server's status, which makes the list reset its highlight to the first row; keep the user's place.
let kept = ""
const list = useFilteredList({
items,
key: (x) => x.name,
filterKeys: ["name", "status"],
sortBy: (a, b) => a.name.localeCompare(b.name),
onSelect: (x) => {
if (!x) return
kept = x.name
select(x.name)
},
})
createEffect(
on(
list.flat,
(items) => {
if (kept && items.some((item) => item.name === kept)) list.setActive(kept)
},
{ defer: true },
),
)
let resultsRef: HTMLDivElement | undefined
let searchRef: HTMLInputElement | undefined
let listRef: HTMLDivElement | undefined
let backRef: HTMLButtonElement | undefined
const onKeyDown = (event: KeyboardEvent) => {
list.onKeyDown(event)
const active = list.active()
if (!active) return
if (kept) kept = active
resultsRef?.querySelector(`[data-option-key="${CSS.escape(active)}"]`)?.scrollIntoView({ block: "nearest" })
}
return (
<Dialog class="search-list-dialog mcp-dialog">
// Like the model picker and the TUI, the list has no visible header; the search field leads.
<Dialog class="command-palette search-list-dialog mcp-dialog" size="large">
<Show
when={failure()}
fallback={
<>
<DialogHeader>
<DialogTitleGroup
title={language.t("dialog.mcp.title")}
description={language.t("dialog.mcp.description", { enabled: enabledCount(), total: items().length })}
/>
</DialogHeader>
<DialogBody class="search-list-body">
<div class="search-list-search">
<TextInput
type="search"
appearance="base"
class="!w-full"
leadingIcon={<Icon name="magnifying-glass" size="small" />}
value={list.filter()}
onInput={(event) => {
kept = ""
list.onInput(event.currentTarget.value)
}}
ref={searchRef}
onKeyDown={onKeyDown}
placeholder={language.t("common.search.placeholder")}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
autofocus
aria-label={language.t("common.search.placeholder")}
showClearButton={!!list.filter()}
clearIcon="circle-xmark"
clearLabel={language.t("common.clear")}
onClearClick={() => list.clear()}
/>
</div>
<div class="search-list-scroll" data-scrollable ref={(el) => (resultsRef = el)}>
<Show
when={list.flat().length > 0}
fallback={
<div class="search-list-empty">
{language.t(items().length > 0 ? "dialog.mcp.search.empty" : "dialog.mcp.empty")}
</div>
}
>
<div role="listbox" class="search-list-results">
<For each={list.flat()}>
{(item) => {
const server = () => servers().find((entry) => entry.name === item.name)
const status = () => server()?.status
// The switch would start sign-in anyway, so an auth-gated server shows that action instead.
const signIn = () => status()?.status === "needs_auth" && !!server()?.integrationID
const busy = () => toggle.isPending && toggle.variables === item.name
const detail = () => {
const current = status()
if (!current || current.status === "pending" || current.status === "failed" || signIn())
return
return language.t(statusLabels[current.status])
}
return (
<div
role="option"
aria-selected={list.active() === item.name}
data-option-key={item.name}
data-active={list.active() === item.name ? "" : undefined}
class="search-list-row"
onMouseMove={() => {
kept = item.name
list.setActive(item.name)
}}
onMouseLeave={() => {
kept = ""
list.setActive("")
}}
onMouseDown={(event) => event.preventDefault()}
onClick={() => select(item.name)}
>
<div class="search-list-row-text">
<TruncatedText text={item.name} class="search-list-title" />
</div>
<div class="search-list-trailing mcp-trailing" onClick={(event) => event.stopPropagation()}>
<Show
when={status()?.status === "failed"}
fallback={
<Show
when={signIn()}
fallback={
<Switch
checked={status()?.status === "connected"}
disabled={status()?.status === "pending" || busy()}
onChange={() => toggleServer(item.name)}
>
<span class="sr-only">{item.name}</span>
<span aria-hidden="true">{detail()}</span>
</Switch>
}
>
<Button
size="small"
variant="neutral"
disabled={busy()}
aria-busy={busy()}
aria-label={language.t("dialog.mcp.signIn.open", { name: item.name })}
onClick={() => toggleServer(item.name)}
>
{language.t(busy() ? "dialog.mcp.signingIn" : "dialog.mcp.signIn")}
</Button>
</Show>
}
>
<span class="text-v2-state-fg-danger">{language.t("mcp.status.failed")}</span>
<Button
size="small"
variant="neutral"
aria-label={language.t("dialog.mcp.error.actionLabel", { name: item.name })}
onClick={() => select(item.name)}
>
{language.t("dialog.mcp.error.action")}
</Button>
</Show>
</div>
</div>
)
}}
</For>
</div>
</Show>
</div>
</DialogBody>
</>
<div class="sr-only">
<DialogTitle>{language.t("dialog.mcp.title")}</DialogTitle>
</div>
}
>
{(current) => (
@@ -338,6 +185,95 @@ export const DialogMcp: Component<{ directory: string; server?: string }> = (pro
</div>
)}
</Show>
<div class="contents" ref={listRef} style={{ display: failure() ? "none" : undefined }}>
<DialogBody class="command-palette-body">
<SearchList
query={store.query}
onQuery={(query) => setStore("query", query)}
placeholder={language.t("dialog.mcp.search.placeholder")}
items={results()}
key={(item) => item.name}
empty={language.t(items().length > 0 ? "dialog.mcp.search.empty" : "dialog.mcp.empty")}
onSelect={(item) => select(item.name)}
>
{(item, row) => {
const server = () => servers().find((entry) => entry.name === item().name)
const status = () => server()?.status
const error = () => {
const current = status()
return current?.status === "failed" ? current.error : undefined
}
// The switch would start sign-in anyway, so an auth-gated server shows that action instead.
const signIn = () => status()?.status === "needs_auth" && !!server()?.integrationID
const busy = () => {
const target = toggle.isPending ? toggle.variables : undefined
return (typeof target === "string" ? target : target?.name) === item().name
}
// The dot shows the status, as in the session's MCP list; the words stay for assistive technology.
const label = () => {
const current = status()?.status
return current && current !== "pending" ? language.t(statusLabels[current]) : undefined
}
return (
<div {...row} data-option-key={item().name} data-status={status()?.status}>
<span class="mcp-dialog-lead">
<span class="mcp-dialog-dot" data-status={status()?.status} aria-hidden="true" />
<TruncatedText text={item().name} class="mcp-dialog-name" />
</span>
<div class="mcp-trailing" onClick={(event) => event.stopPropagation()}>
<Show when={error()}>
<Button
size="small"
variant="ghost-muted"
class="mcp-status-action"
aria-label={language.t("dialog.mcp.error.viewLabel", { name: item().name })}
onClick={() => select(item().name)}
>
{language.t("dialog.mcp.error.view")}
</Button>
</Show>
<Show
when={signIn()}
fallback={
<Switch
checked={!!status() && status()?.status !== "disabled"}
disabled={status()?.status === "pending" || busy()}
description={label()}
class="[&_[data-slot=switch-description]]:sr-only"
onChange={(enabled) => {
if (toggle.isPending) return
toggle.mutate({ name: item().name, enabled })
}}
>
<span class="sr-only">{item().name}</span>
</Switch>
}
>
<Button
size="small"
variant="ghost-muted"
class="mcp-status-action"
disabled={busy()}
aria-busy={busy()}
aria-label={language.t("dialog.mcp.signIn.open", { name: item().name })}
onClick={() => toggleServer(item().name)}
>
{language.t(busy() ? "dialog.mcp.signingIn" : "dialog.mcp.signIn")}
</Button>
</Show>
</div>
</div>
)
}}
</SearchList>
</DialogBody>
</div>
</Dialog>
)
}
-1
View File
@@ -216,7 +216,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "ትዕዛዝ መላክ አልተሳካም",
"prompt.toast.promptSendFailed.title": "ፕሮምፕት መላክ አልተሳካም",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} ከ{{total}} ነቅቷል",
"dialog.mcp.empty": "ምንም MCPs አልተዋቀረም",
"dialog.fork.empty": "አዲስ ቅርንጫፍ ለመፍጠር መልዕክት የለም",
"dialog.directory.action.selectFile": "ፋይል ምረጥ",
-1
View File
@@ -229,7 +229,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "فشل إرسال الأمر",
"prompt.toast.promptSendFailed.title": "فشل إرسال الموجه",
"dialog.mcp.title": "خوادم MCP",
"dialog.mcp.description": "تم تفعيل {{enabled}} من أصل {{total}}",
"dialog.mcp.empty": "لم يتم تكوين أي خوادم MCP",
"dialog.fork.empty": "لا توجد رسائل للتفرع منها",
"dialog.directory.action.selectFile": "اختيار ملف",
-1
View File
@@ -218,7 +218,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Əmr göndərilə bilmədi",
"prompt.toast.promptSendFailed.title": "Prompt göndərilə bilmədi",
"dialog.mcp.title": "MCP-lər",
"dialog.mcp.description": "{{total}}-dən {{enabled}} aktiv",
"dialog.mcp.empty": "MCP konfiqurasiya edilməyib",
"dialog.fork.empty": "Yeni sessiyaya ayırmaq üçün mesaj yoxdur",
"dialog.directory.action.selectFile": "Fayl seçin",
-1
View File
@@ -217,7 +217,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Неуспешно изпращане на команда",
"prompt.toast.promptSendFailed.title": "Неуспешно изпращане на подкана",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} от {{total}} активирани",
"dialog.mcp.empty": "Няма конфигурирани MCP",
"dialog.fork.empty": "Няма съобщения за разклонение",
"dialog.directory.action.selectFile": "Изберете файл",
-1
View File
@@ -221,7 +221,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "কমান্ড পাঠাতে ব্যর্থ হয়েছে",
"prompt.toast.promptSendFailed.title": "প্রম্পট পাঠাতে ব্যর্থ হয়েছে৷",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{total}} এর {{enabled}} সক্ষম",
"dialog.mcp.empty": "কোনো MCPs কনফিগার করা নেই",
"dialog.fork.empty": "থেকে কাঁটাচামচ কোন বার্তা",
"dialog.directory.action.selectFile": "ফাইল নির্বাচন করুন",
-1
View File
@@ -433,7 +433,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Falha ao enviar comando",
"prompt.toast.promptSendFailed.title": "Falha ao enviar prompt",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} de {{total}} ativados",
"dialog.mcp.empty": "Nenhum MCP configurado",
"dialog.fork.empty": "Nenhuma mensagem para bifurcar",
"dialog.directory.action.selectFile": "Selecionar arquivo",
-1
View File
@@ -238,7 +238,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "Neuspješno slanje upita",
"dialog.mcp.title": "MCP-ovi",
"dialog.mcp.description": "{{enabled}} od {{total}} omogućeno",
"dialog.mcp.empty": "Nema konfigurisanih MCP-ova",
-1
View File
@@ -216,7 +216,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "No s'ha pogut enviar l'ordre",
"prompt.toast.promptSendFailed.title": "No s'ha pogut enviar la sol·licitud",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} de {{total}} activat",
"dialog.mcp.empty": "No s'ha configurat cap MCP",
"dialog.fork.empty": "No hi ha missatges per bifurcar",
"dialog.directory.action.selectFile": "Seleccioneu el fitxer",
-1
View File
@@ -216,7 +216,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Odeslání příkazu se nezdařilo",
"prompt.toast.promptSendFailed.title": "Odeslání výzvy se nezdařilo",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} z {{total}} povoleno",
"dialog.mcp.empty": "Nejsou nakonfigurovány žádné MCP",
"dialog.fork.empty": "Žádné zprávy k rozvětvení",
"dialog.directory.action.selectFile": "Vyberte soubor",
-1
View File
@@ -367,7 +367,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "Kunne ikke sende forespørgsel",
"dialog.mcp.title": "MCP'er",
"dialog.mcp.description": "{{enabled}} af {{total}} aktiveret",
"dialog.mcp.empty": "Ingen MCP'er konfigureret",
-1
View File
@@ -366,7 +366,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Befehl konnte nicht gesendet werden",
"prompt.toast.promptSendFailed.title": "Eingabe konnte nicht gesendet werden",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} von {{total}} aktiviert",
"dialog.mcp.empty": "Keine MCPs konfiguriert",
"dialog.fork.empty": "Keine Nachrichten zum Abzweigen vorhanden",
"dialog.directory.action.selectFile": "Datei auswählen",
-1
View File
@@ -221,7 +221,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "ކޮމާންޑް ފޮނުވަން ނާކާމިޔާބުވިއެވެ",
"prompt.toast.promptSendFailed.title": "ޕްރޮމްޕްޓް ފޮނުވަން ނާކާމިޔާބުވިއެވެ",
"dialog.mcp.title": "MCPs އެވެ",
"dialog.mcp.description": "\u2068{{enabled}}\u2069 ގެ \u2068{{total}}\u2069 އެނެބަލްކޮށްފައިވެއެވެ",
"dialog.mcp.empty": "އެއްވެސް MCPs ކޮންފިގްރޭޓް ކުރެވިފައެއް ނުވެއެވެ",
"dialog.fork.empty": "ފޯކް ކުރާނެ މެސެޖެއް ނެތެވެ",
"dialog.directory.action.selectFile": "ފައިލް ހޮވާށެވެ",
-1
View File
@@ -228,7 +228,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "བརྡ་བཀོད་གཏང་ནི་ལུ་འཐུས་ཤོར་འབྱུང་ཡོདཔ།",
"prompt.toast.promptSendFailed.title": "བརྡ་སྟོན་གཏང་ནི་ལུ་འཐུས་ཤོར་འབྱུང་ཡོདཔ།",
"dialog.mcp.title": "MCPs།",
"dialog.mcp.description": "{{enabled}} གི་ {{total}} ལྕོགས་ཅན་བཟོ་ཡོདཔ།",
"dialog.mcp.empty": "MCPsརིམ་སྒྲིག་མ་འབད་བས།",
"dialog.fork.empty": "ལས་ཕོརཀ་འབད་ནི་ལུ་འཕྲིན་དོན་མེད།",
"dialog.directory.action.selectFile": "ཡིག་སྣོད་སེལ་འཐུ་འབད།",
-1
View File
@@ -218,7 +218,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Αποτυχία αποστολής εντολής",
"prompt.toast.promptSendFailed.title": "Αποτυχία αποστολής προτροπής",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} από {{total}} ενεργοποιημένα",
"dialog.mcp.empty": "Δεν έχει διαμορφωθεί το MCPs",
"dialog.fork.empty": "Δεν υπάρχουν μηνύματα για διακλάδωση",
"dialog.directory.action.selectFile": "Επιλογή αρχείου",
+3 -1
View File
@@ -357,8 +357,8 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "Failed to send prompt",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} of {{total}} enabled",
"dialog.mcp.empty": "No MCPs configured",
"dialog.mcp.search.placeholder": "Search MCPs",
"dialog.mcp.search.empty": "No MCPs found",
"dialog.mcp.error.title": "MCP server: {{name}}",
"dialog.mcp.error.copy": "Copy details",
@@ -368,6 +368,8 @@ export const dict = {
"dialog.mcp.error.status": "Connection failed",
"dialog.mcp.signingIn": "Signing in…",
"dialog.mcp.error.actionLabel": "Details for {{name}}",
"dialog.mcp.error.view": "View error",
"dialog.mcp.error.viewLabel": "View error for {{name}}",
"dialog.mcp.error.disable": "Disable",
"dialog.mcp.signIn": "Sign in",
"dialog.mcp.signIn.open": "Sign in to {{name}}",
-1
View File
@@ -453,7 +453,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "Fallo al enviar prompt",
"dialog.mcp.title": "Servidores MCP",
"dialog.mcp.description": "{{enabled}} de {{total}} habilitados",
"dialog.mcp.empty": "No hay servidores MCP configurados",
-1
View File
@@ -216,7 +216,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Käsu saatmine ebaõnnestus",
"prompt.toast.promptSendFailed.title": "Viipa saatmine ebaõnnestus",
"dialog.mcp.title": "MCP-d",
"dialog.mcp.description": "{{enabled}} / {{total}} on lubatud",
"dialog.mcp.empty": "MCP-sid pole konfigureeritud",
"dialog.fork.empty": "Ühtegi sõnumit pole",
"dialog.directory.action.selectFile": "Valige fail",
-1
View File
@@ -226,7 +226,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "فرمان ارسال نشد",
"prompt.toast.promptSendFailed.title": "درخواست ارسال نشد",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} از {{total}} فعال است",
"dialog.mcp.empty": "MCPs پیکربندی نشده است",
"dialog.fork.empty": "هیچ پیامی برای فورک وجود ندارد",
"dialog.directory.action.selectFile": "فایل را انتخاب کنید",
-1
View File
@@ -349,7 +349,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Komennon lähettäminen epäonnistui",
"prompt.toast.promptSendFailed.title": "Kehotteen lähettäminen epäonnistui",
"dialog.mcp.title": "MCP:t",
"dialog.mcp.description": "{{enabled}}/{{total}} käytössä",
"dialog.mcp.empty": "MCP:tä ei ole määritetty",
"dialog.fork.empty": "Ei viestejä, joista voisi haarauttaa",
"dialog.directory.action.selectFile": "Valitse tiedosto",
-1
View File
@@ -216,7 +216,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Tað eydnaðist ikki at senda skipan",
"prompt.toast.promptSendFailed.title": "Tað eydnaðist ikki at senda boð",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} av {{total}} virkið",
"dialog.mcp.empty": "Ongin MCP uppsettur",
"dialog.fork.empty": "Eingi boð at greina frá",
"dialog.directory.action.selectFile": "Vel fílu",
-1
View File
@@ -437,7 +437,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Échec de l'envoi de la commande",
"prompt.toast.promptSendFailed.title": "Échec de l'envoi de l'invite",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "MCP actifs : {{enabled}} sur {{total}}",
"dialog.mcp.empty": "Aucun MCP configuré",
"dialog.fork.empty": "Aucun message à partir duquel bifurquer",
"dialog.directory.action.selectFile": "Sélectionner le fichier",
-1
View File
@@ -225,7 +225,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "שליחת הפקודה נכשלה",
"prompt.toast.promptSendFailed.title": "שליחת הבקשה נכשלה",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} מתוך {{total}} מופעלים",
"dialog.mcp.empty": "לא הוגדרו MCPs",
"dialog.fork.empty": "אין הודעות שמהן אפשר ליצור הפעלה",
"dialog.directory.action.selectFile": "בחר קובץ",
-1
View File
@@ -222,7 +222,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "कमांड भेजने में विफल",
"prompt.toast.promptSendFailed.title": "प्रॉम्प्ट भेजने में विफल",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{total}} में से {{enabled}} सक्षम",
"dialog.mcp.empty": "कोई MCP कॉन्फ़िगर नहीं किया गया",
"dialog.fork.empty": "फ़ोर्क करने के लिए कोई संदेश नहीं",
"dialog.directory.action.selectFile": "फ़ाइल का चयन करें",
-1
View File
@@ -229,7 +229,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Slanje naredbe nije uspjelo",
"prompt.toast.promptSendFailed.title": "Slanje upita nije uspjelo",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} od {{total}} omogućen",
"dialog.mcp.empty": "Nema konfiguriranih MCP-ova",
"dialog.fork.empty": "Nema poruka za račvanje",
"dialog.directory.action.selectFile": "Odaberite datoteku",
-1
View File
@@ -229,7 +229,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Nem sikerült elküldeni a parancsot",
"prompt.toast.promptSendFailed.title": "Nem sikerült elküldeni a felszólítást",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}}/{{total}} engedélyezve",
"dialog.mcp.empty": "Nincs konfigurálva MCP",
"dialog.fork.empty": "Nincsenek üzenetek, ahonnan elágazhatna",
"dialog.directory.action.selectFile": "Válassza ki a fájlt",
-1
View File
@@ -217,7 +217,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Չհաջողվեց ուղարկել հրամանը",
"prompt.toast.promptSendFailed.title": "Չհաջողվեց ուղարկել հուշումը",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}}-ը {{total}}-ից միացված է",
"dialog.mcp.empty": "Կազմաձևված չէ MCPs",
"dialog.fork.empty": "Չկան պատառաքաղված հաղորդագրություններ",
"dialog.directory.action.selectFile": "Ընտրել ֆայլը",
-1
View File
@@ -249,7 +249,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "Gagal mengirim prompt",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} dari {{total}} diaktifkan",
"dialog.mcp.empty": "Tidak ada MCP yang dikonfigurasi",
-1
View File
@@ -229,7 +229,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Mistókst að senda skipun",
"prompt.toast.promptSendFailed.title": "Mistókst að senda tilkynningu",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} af {{total}} virkt",
"dialog.mcp.empty": "Engar MCPs stilltar",
"dialog.fork.empty": "Engin skilaboð til að punga frá",
"dialog.directory.action.selectFile": "Veldu skrá",
-1
View File
@@ -370,7 +370,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Impossibile inviare il comando",
"prompt.toast.promptSendFailed.title": "Impossibile inviare il prompt",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} di {{total}} abilitati",
"dialog.mcp.empty": "Nessun MCP configurato",
"dialog.fork.empty": "Nessun messaggio da cui effettuare il fork",
"dialog.directory.action.selectFile": "Seleziona file",
-1
View File
@@ -228,7 +228,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "コマンドの送信に失敗しました",
"prompt.toast.promptSendFailed.title": "プロンプトの送信に失敗しました",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{total}}個中{{enabled}}個が有効",
"dialog.mcp.empty": "MCPが設定されていません",
"dialog.fork.empty": "フォーク元のメッセージがありません",
"dialog.directory.action.selectFile": "ファイルを選択",
-1
View File
@@ -216,7 +216,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "ბრძანების გაგზავნა ვერ მოხერხდა",
"prompt.toast.promptSendFailed.title": "მოთხოვნის გაგზავნა ვერ მოხერხდა",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} / {{total}}-დან ჩართულია",
"dialog.mcp.empty": "არა MCPs კონფიგურირებული",
"dialog.fork.empty": "განშტოებისთვის შეტყობინებები არ არის",
"dialog.directory.action.selectFile": "აირჩიეთ ფაილი",
-1
View File
@@ -224,7 +224,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "បរាជ័យក្នុងការផ្ញើពាក្យបញ្ជា",
"prompt.toast.promptSendFailed.title": "បរាជ័យក្នុងការផ្ញើប្រអប់បញ្ចូល",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} នៃ {{total}} ត្រូវបានបើក",
"dialog.mcp.empty": "មិនបានកំណត់រចនាសម្ព័ន្ធ MCPs ទេ។",
"dialog.fork.empty": "គ្មានសារដែលត្រូវបំបែកពី",
"dialog.directory.action.selectFile": "ជ្រើសរើសឯកសារ",
-1
View File
@@ -152,7 +152,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "명령 전송 실패",
"prompt.toast.promptSendFailed.title": "프롬프트 전송 실패",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{total}}개 중 {{enabled}}개 활성화됨",
"dialog.mcp.empty": "구성된 MCP 없음",
"dialog.fork.empty": "분기할 메시지 없음",
"dialog.directory.action.selectFile": "파일 선택",
-1
View File
@@ -224,7 +224,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "ລົ້ມເຫລວໃນການສົ່ງຄໍາສັ່ງ",
"prompt.toast.promptSendFailed.title": "ສົ່ງ prompt ບໍ່ສຳເລັດ",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} ຂອງ {{total}} ຖືກເປີດໃຊ້",
"dialog.mcp.empty": "ບໍ່ມີ MCPs ທີ່ຖືກຕັ້ງຄ່າ",
"dialog.fork.empty": "ບໍ່ມີຂໍ້ຄວາມທີ່ຈະແຍກອອກຈາກ",
"dialog.directory.action.selectFile": "ເລືອກໄຟລ໌",
-1
View File
@@ -229,7 +229,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Nepavyko išsiųsti komandos",
"prompt.toast.promptSendFailed.title": "Nepavyko išsiųsti raginimo",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "Įjungtas {{enabled}} arba {{total}}",
"dialog.mcp.empty": "MCP nesukonfigūruota",
"dialog.fork.empty": "Nėra pranešimų, iš kurių būtų galima išsišakoti",
"dialog.directory.action.selectFile": "Pasirinkite failą",
-1
View File
@@ -216,7 +216,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Neizdevās nosūtīt komandu",
"prompt.toast.promptSendFailed.title": "Neizdevās nosūtīt pieprasījumu",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} no {{total}} ieslēgti",
"dialog.mcp.empty": "Nav konfigurētu MCP",
"dialog.fork.empty": "Nav ziņu, no kurām atzarot",
"dialog.directory.action.selectFile": "Izvēlieties failu",
-1
View File
@@ -217,7 +217,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Не успеа да се испрати командата",
"prompt.toast.promptSendFailed.title": "Не успеа да се испрати потсетник",
"dialog.mcp.title": "MCPс",
"dialog.mcp.description": "{{enabled}} од {{total}} е овозможено",
"dialog.mcp.empty": "Не се конфигурирани MCPs",
"dialog.fork.empty": "Нема пораки од кои може да се префрлите",
"dialog.directory.action.selectFile": "Изберете датотека",
-1
View File
@@ -217,7 +217,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Командыг илгээж чадсангүй",
"prompt.toast.promptSendFailed.title": "Промпт илгээж чадсангүй",
"dialog.mcp.title": "MCPсек",
"dialog.mcp.description": "{{total}}-ийн {{enabled}}-г идэвхжүүлсэн",
"dialog.mcp.empty": "Тохируулсан MCPс байхгүй",
"dialog.fork.empty": "Салаалах зурвас алга",
"dialog.directory.action.selectFile": "Файл сонгоно уу",
-1
View File
@@ -225,7 +225,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Gagal menghantar arahan",
"prompt.toast.promptSendFailed.title": "Gagal menghantar prompt",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} daripada {{total}} diaktifkan",
"dialog.mcp.empty": "Tiada MCP dikonfigurasi",
"dialog.fork.empty": "Tiada mesej untuk dicabang",
"dialog.directory.action.selectFile": "Pilih fail",
-1
View File
@@ -225,7 +225,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "အမိန့်ပေးပို့ရန် မအောင်မြင်ပါ။",
"prompt.toast.promptSendFailed.title": "Prompt ပေးပို့ရန် မအောင်မြင်ပါ။",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} ၏ {{total}} ဖွင့်ထားသည်။",
"dialog.mcp.empty": "MCP များကို ပြင်ဆင်သတ်မှတ်ထားခြင်းမရှိပါ။",
"dialog.fork.empty": "လမ်းခွဲရန် မက်ဆေ့ချ်များ မရှိပါ။",
"dialog.directory.action.selectFile": "ဖိုင်ကို ရွေးပါ။",
-1
View File
@@ -221,7 +221,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "आदेश पठाउन असफल भयो",
"prompt.toast.promptSendFailed.title": "प्रम्प्ट पठाउन असफल भयो",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} को {{total}} सक्षम गरियो",
"dialog.mcp.empty": "कुनै पनि MCPs कन्फिगर गरिएको छैन",
"dialog.fork.empty": "बाट फोर्क गर्न कुनै सन्देश छैन",
"dialog.directory.action.selectFile": "फाइल चयन गर्नुहोस्",
-1
View File
@@ -421,7 +421,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Kan opdracht niet verzenden",
"prompt.toast.promptSendFailed.title": "Kan de prompt niet verzenden",
"dialog.mcp.title": "MCP's",
"dialog.mcp.description": "{{enabled}} of {{total}} ingeschakeld",
"dialog.mcp.empty": "Geen MCP's geconfigureerd",
"dialog.fork.empty": "Geen berichten om van af te splitsen",
"dialog.directory.action.selectFile": "Selecteer bestand",
-1
View File
@@ -427,7 +427,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "Kunne ikke sende forespørsel",
"dialog.mcp.title": "MCP-er",
"dialog.mcp.description": "{{enabled}} av {{total}} aktivert",
"dialog.mcp.empty": "Ingen MCP-er konfigurert",
-1
View File
@@ -224,7 +224,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "کمانڈ بھیجن چ ناکام رہیا",
"prompt.toast.promptSendFailed.title": "پرامپٹ بھیجن چ ناکام رہیا",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "\u2068{{total}}\u2069 وچوں \u2068{{enabled}}\u2069 چالو",
"dialog.mcp.empty": "کوئی MCP سیٹ نئیں کیتا گیا",
"dialog.fork.empty": "فورک کرن لئی کوئی پیغام نئیں",
"dialog.directory.action.selectFile": "فائل چنو",
-1
View File
@@ -220,7 +220,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Nie udało się wysłać polecenia",
"prompt.toast.promptSendFailed.title": "Nie udało się wysłać zapytania",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "Włączone: {{enabled}} z {{total}}",
"dialog.mcp.empty": "Brak skonfigurowanych MCP",
"dialog.fork.empty": "Brak wiadomości do rozwidlenia",
"dialog.directory.action.selectFile": "Wybierz plik",
-1
View File
@@ -216,7 +216,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Nu s-a putut trimite comanda",
"prompt.toast.promptSendFailed.title": "Nu s-a putut trimite promptul",
"dialog.mcp.title": "MCP-uri",
"dialog.mcp.description": "{{enabled}} din {{total}} activate",
"dialog.mcp.empty": "Niciun MCP configurat",
"dialog.fork.empty": "Niciun mesaj de bifurcat",
"dialog.directory.action.selectFile": "Selectează fișier",
-1
View File
@@ -237,7 +237,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "Не удалось отправить запрос",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} из {{total}} включено",
"dialog.mcp.empty": "MCP не настроены",
-1
View File
@@ -220,7 +220,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "විධානය යැවීමට අසමත් විය",
"prompt.toast.promptSendFailed.title": "ප්‍රොම්ප්ට් එක යැවීමට අසමත් විය",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{total}} න් {{enabled}} සබලයි",
"dialog.mcp.empty": "MCPs වින්‍යාස කර නැත",
"dialog.fork.empty": "ෆෝක් කිරීමට පණිවිඩ නැත",
"dialog.directory.action.selectFile": "ගොනුව තෝරන්න",
-1
View File
@@ -242,7 +242,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Nepodarilo sa odoslať príkaz",
"prompt.toast.promptSendFailed.title": "Nepodarilo sa odoslať výzvu",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} z {{total}} povolených",
"dialog.mcp.empty": "Nie sú nastavené žiadne MCP",
"dialog.fork.empty": "Žiadne správy na vytvorenie vetvy",
"dialog.directory.action.selectFile": "Vybrať súbor",
-1
View File
@@ -242,7 +242,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Ukaza ni bilo mogoče poslati",
"prompt.toast.promptSendFailed.title": "Poziva ni bilo mogoče poslati",
"dialog.mcp.title": "MCP-ji",
"dialog.mcp.description": "{{enabled}} od {{total}} omogočen",
"dialog.mcp.empty": "MCP-ji niso konfigurirani",
"dialog.fork.empty": "Ni sporočil za odcepitev",
"dialog.directory.action.selectFile": "Izberite datoteko",
-1
View File
@@ -217,7 +217,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Dërgimi i komandës dështoi",
"prompt.toast.promptSendFailed.title": "Dërgimi i kërkesës dështoi",
"dialog.mcp.title": "MCP-të",
"dialog.mcp.description": "{{enabled}} nga {{total}} është aktivizuar",
"dialog.mcp.empty": "Asnjë MCP nuk është konfiguruar",
"dialog.fork.empty": "Nuk ka mesazhe për të marrë nga",
"dialog.directory.action.selectFile": "Zgjidhni skedarin",
-1
View File
@@ -230,7 +230,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Слање команде није успело",
"prompt.toast.promptSendFailed.title": "Слање упита није успело",
"dialog.mcp.title": "MCPс",
"dialog.mcp.description": "{{enabled}} од {{total}} омогућено",
"dialog.mcp.empty": "Нема конфигурисаних MCP",
"dialog.fork.empty": "Нема порука од којих треба да се рачвате",
"dialog.directory.action.selectFile": "Изаберите датотеку",
-1
View File
@@ -415,7 +415,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Det gick inte att skicka kommandot",
"prompt.toast.promptSendFailed.title": "Det gick inte att skicka meddelandet",
"dialog.mcp.title": "MCP:er",
"dialog.mcp.description": "{{enabled}} av {{total}} aktiverade",
"dialog.mcp.empty": "Inga MCP:er konfigurerade",
"dialog.fork.empty": "Inga meddelanden att förgrena från",
"dialog.directory.action.selectFile": "Välj fil",
-1
View File
@@ -217,7 +217,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Фармон фиристода нашуд",
"prompt.toast.promptSendFailed.title": "Ирсоли промпт муяссар нашуд",
"dialog.mcp.title": "MCPс",
"dialog.mcp.description": "{{enabled}} аз {{total}} фаъол",
"dialog.mcp.empty": "Ягон MCP танзим карда нашудааст",
"dialog.fork.empty": "Паёме барои сохтани шоха нест",
"dialog.directory.action.selectFile": "Файлро интихоб кунед",
-1
View File
@@ -246,7 +246,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "ไม่สามารถส่งพรอมต์",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} จาก {{total}} ที่เปิดใช้งาน",
"dialog.mcp.empty": "ไม่มี MCP ที่กำหนดค่า",
-1
View File
@@ -217,7 +217,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Buýruk iberilmedi",
"prompt.toast.promptSendFailed.title": "Prompt iberilmedi",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{total}}-iň {{enabled}} açyk",
"dialog.mcp.empty": "Hiç hili MCP düzülmedi",
"dialog.fork.empty": "Şahalandyrmaly habar ýok",
"dialog.directory.action.selectFile": "Faýly saýlaň",
-1
View File
@@ -250,7 +250,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "İstem gönderilemedi",
"dialog.mcp.title": "MCP'ler",
"dialog.mcp.description": "Toplam {{total}} öğeden {{enabled}} tanesi etkin",
"dialog.mcp.empty": "Yapılandırılmış MCP yok",
-1
View File
@@ -266,7 +266,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "Не вдалося надіслати запит",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} з {{total}} увімкнено",
"dialog.mcp.empty": "MCP не налаштовано",
-1
View File
@@ -225,7 +225,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "کمانڈ بھیجنے میں ناکام",
"prompt.toast.promptSendFailed.title": "پرامپٹ بھیجنے میں ناکام",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "\u2068{{total}}\u2069 میں سے \u2068{{enabled}}\u2069 فعال",
"dialog.mcp.empty": "کوئی MCPs کنفیگر نہیں ہے۔",
"dialog.fork.empty": "کوئی پیغامات نہیں ہیں جن سے فورک کیا جائے۔",
"dialog.directory.action.selectFile": "فائل منتخب کریں۔",
-1
View File
@@ -217,7 +217,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Buyruq yuborilmadi",
"prompt.toast.promptSendFailed.title": "Soʻrov yuborilmadi",
"dialog.mcp.title": "MCPlar",
"dialog.mcp.description": "{{enabled}}, {{total}} yoqilgan",
"dialog.mcp.empty": "Hech qanday MCP sozlanmagan",
"dialog.fork.empty": "Hech qanday xabar yo'q",
"dialog.directory.action.selectFile": "Faylni tanlang",
-1
View File
@@ -230,7 +230,6 @@ export const dict = {
"prompt.toast.commandSendFailed.title": "Không gửi được lệnh",
"prompt.toast.promptSendFailed.title": "Không gửi được lời nhắc",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "{{enabled}} trong số {{total}} đã bật",
"dialog.mcp.empty": "Không có MCP nào được định cấu hình",
"dialog.fork.empty": "Không có tin nhắn nào để phân nhánh",
"dialog.directory.action.selectFile": "Chọn tệp",
-1
View File
@@ -264,7 +264,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "发送提示失败",
"dialog.mcp.title": "MCP 服务器",
"dialog.mcp.description": "已启用 {{enabled}} / {{total}}",
"dialog.mcp.empty": "未配置 MCP 服务器",
-1
View File
@@ -248,7 +248,6 @@ export const dict = {
"prompt.toast.promptSendFailed.title": "傳送提示失敗",
"dialog.mcp.title": "MCP",
"dialog.mcp.description": "已啟用 {{enabled}} / {{total}}",
"dialog.mcp.empty": "未設定 MCP",
+82 -10
View File
@@ -115,24 +115,101 @@
letter-spacing: -0.04px;
}
.mcp-dialog .search-list-row {
[data-component="dialog-v2"]:has(.mcp-dialog) [data-slot="dialog-container"] {
width: min(calc(100vw - 24px), 480px);
}
.mcp-dialog [data-slot="search-list-row"] {
gap: 16px;
}
.mcp-dialog .search-list-row-text {
.mcp-dialog-lead {
display: flex;
min-width: 0;
flex: 1;
align-items: center;
gap: 10px;
}
.mcp-dialog .search-list-title {
flex: 1;
.mcp-dialog-name {
color: var(--v2-text-text-base);
font-size: 13px;
font-weight: 530;
line-height: var(--line-height-compact);
letter-spacing: -0.04px;
}
.mcp-dialog [data-status="disabled"] .mcp-dialog-name {
color: var(--v2-text-text-muted);
}
/* The session MCP list's status dot: filled while on, a ring when disabled. */
.mcp-dialog-dot {
width: 6px;
height: 6px;
flex-shrink: 0;
border-radius: 50%;
background: currentColor;
color: var(--v2-icon-icon-faint);
}
.mcp-dialog-dot[data-status="connected"] {
color: var(--icon-success-base);
}
.mcp-dialog-dot[data-status="failed"] {
color: var(--icon-critical-base);
}
.mcp-dialog-dot[data-status="needs_auth"] {
color: var(--icon-warning-base);
}
.mcp-dialog-dot[data-status="disabled"] {
background: transparent;
box-shadow: inset 0 0 0 1px currentColor;
}
.mcp-dialog-dot[data-status="pending"] {
animation: mcp-dialog-pulse 1s ease-in-out infinite;
}
@keyframes mcp-dialog-pulse {
50% {
opacity: 0.35;
}
}
.mcp-trailing {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: flex-end;
gap: 8px;
white-space: nowrap;
}
/* A quiet ghost action in the switch's place, small enough to weigh about as much as a switch. */
.mcp-status-action[data-component="button-v2"][data-size="small"] {
height: 20px;
padding-inline: 5px;
font-size: 11px;
font-weight: 440;
line-height: 16px;
letter-spacing: 0.05px;
}
/* At the row's end, the label lines up with the switches above and below it. */
.mcp-status-action[data-component="button-v2"]:last-child {
margin-inline-end: -5px;
}
@media (prefers-reduced-motion: reduce) {
.mcp-dialog-dot[data-status="pending"] {
animation: none;
}
}
.mcp-error-heading {
display: grid;
min-width: 0;
@@ -160,14 +237,9 @@
}
@media (max-width: 639px), (hover: none) {
.mcp-dialog .search-list-row {
.mcp-dialog [data-slot="search-list-row"] {
height: auto;
min-height: 44px;
padding-block: 8px;
}
.mcp-dialog .search-list-title {
white-space: normal;
overflow-wrap: anywhere;
}
}
@@ -409,6 +409,17 @@ button.session-service-dot {
white-space: nowrap;
}
/* A quiet ghost action in the switch's place, as in the MCPs dialog. */
.session-mcp-sign-in[data-component="button-v2"][data-size="small"] {
height: 20px;
margin-inline-end: -5px;
padding-inline: 5px;
font-size: 11px;
font-weight: 440;
line-height: 16px;
letter-spacing: 0.05px;
}
div.session-mcp-row {
cursor: pointer;
}
@@ -655,7 +655,8 @@ function useMcp(props: ServiceProps, close: () => void): ServiceSource {
>
<Button
size="small"
variant="neutral"
variant="ghost-muted"
class="session-mcp-sign-in"
disabled={pending()}
aria-busy={busy()}
aria-label={ctx.t("mcp.signIn.open", { name: server().name })}