mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
fix(app): polish the MCPs dialog (#58953)
Co-authored-by: vimtor <36263538+vimtor@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
221a254083
commit
280c8e078a
@@ -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()
|
||||
})
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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": "ፋይል ምረጥ",
|
||||
|
||||
@@ -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": "اختيار ملف",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Изберете файл",
|
||||
|
||||
@@ -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": "ফাইল নির্বাচন করুন",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "ފައިލް ހޮވާށެވެ",
|
||||
|
||||
@@ -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": "ཡིག་སྣོད་སེལ་འཐུ་འབད།",
|
||||
|
||||
@@ -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": "Επιλογή αρχείου",
|
||||
|
||||
@@ -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}}",
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "فایل را انتخاب کنید",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "בחר קובץ",
|
||||
|
||||
@@ -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": "फ़ाइल का चयन करें",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Ընտրել ֆայլը",
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
|
||||
@@ -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á",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "ファイルを選択",
|
||||
|
||||
@@ -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": "აირჩიეთ ფაილი",
|
||||
|
||||
@@ -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": "ជ្រើសរើសឯកសារ",
|
||||
|
||||
@@ -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": "파일 선택",
|
||||
|
||||
@@ -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": "ເລືອກໄຟລ໌",
|
||||
|
||||
@@ -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ą",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Изберете датотека",
|
||||
|
||||
@@ -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": "Файл сонгоно уу",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "ဖိုင်ကို ရွေးပါ။",
|
||||
|
||||
@@ -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": "फाइल चयन गर्नुहोस्",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
|
||||
@@ -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": "فائل چنو",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -237,7 +237,6 @@ export const dict = {
|
||||
"prompt.toast.promptSendFailed.title": "Не удалось отправить запрос",
|
||||
|
||||
"dialog.mcp.title": "MCP",
|
||||
"dialog.mcp.description": "{{enabled}} из {{total}} включено",
|
||||
"dialog.mcp.empty": "MCP не настроены",
|
||||
|
||||
|
||||
|
||||
@@ -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": "ගොනුව තෝරන්න",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Изаберите датотеку",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Файлро интихоб кунед",
|
||||
|
||||
@@ -246,7 +246,6 @@ export const dict = {
|
||||
"prompt.toast.promptSendFailed.title": "ไม่สามารถส่งพรอมต์",
|
||||
|
||||
"dialog.mcp.title": "MCPs",
|
||||
"dialog.mcp.description": "{{enabled}} จาก {{total}} ที่เปิดใช้งาน",
|
||||
"dialog.mcp.empty": "ไม่มี MCP ที่กำหนดค่า",
|
||||
|
||||
|
||||
|
||||
@@ -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ň",
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
|
||||
@@ -266,7 +266,6 @@ export const dict = {
|
||||
"prompt.toast.promptSendFailed.title": "Не вдалося надіслати запит",
|
||||
|
||||
"dialog.mcp.title": "MCP",
|
||||
"dialog.mcp.description": "{{enabled}} з {{total}} увімкнено",
|
||||
"dialog.mcp.empty": "MCP не налаштовано",
|
||||
|
||||
|
||||
|
||||
@@ -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": "فائل منتخب کریں۔",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -264,7 +264,6 @@ export const dict = {
|
||||
"prompt.toast.promptSendFailed.title": "发送提示失败",
|
||||
|
||||
"dialog.mcp.title": "MCP 服务器",
|
||||
"dialog.mcp.description": "已启用 {{enabled}} / {{total}}",
|
||||
"dialog.mcp.empty": "未配置 MCP 服务器",
|
||||
|
||||
|
||||
|
||||
@@ -248,7 +248,6 @@ export const dict = {
|
||||
"prompt.toast.promptSendFailed.title": "傳送提示失敗",
|
||||
|
||||
"dialog.mcp.title": "MCP",
|
||||
"dialog.mcp.description": "已啟用 {{enabled}} / {{total}}",
|
||||
"dialog.mcp.empty": "未設定 MCP",
|
||||
|
||||
|
||||
|
||||
@@ -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 })}
|
||||
|
||||
Reference in New Issue
Block a user