reigncode-app 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +30 -0
- package/Dockerfile +21 -0
- package/README.md +51 -0
- package/bunfig.toml +3 -0
- package/create-effect-simplification-spec.md +515 -0
- package/e2e/AGENTS.md +226 -0
- package/e2e/actions.ts +1018 -0
- package/e2e/app/home.spec.ts +24 -0
- package/e2e/app/navigation.spec.ts +10 -0
- package/e2e/app/palette.spec.ts +20 -0
- package/e2e/app/server-default.spec.ts +58 -0
- package/e2e/app/session.spec.ts +16 -0
- package/e2e/app/titlebar-history.spec.ts +120 -0
- package/e2e/commands/input-focus.spec.ts +15 -0
- package/e2e/commands/panels.spec.ts +33 -0
- package/e2e/commands/tab-close.spec.ts +32 -0
- package/e2e/files/file-open.spec.ts +31 -0
- package/e2e/files/file-tree.spec.ts +56 -0
- package/e2e/files/file-viewer.spec.ts +156 -0
- package/e2e/fixtures.ts +154 -0
- package/e2e/models/model-picker.spec.ts +48 -0
- package/e2e/models/models-visibility.spec.ts +61 -0
- package/e2e/projects/project-edit.spec.ts +43 -0
- package/e2e/projects/projects-close.spec.ts +54 -0
- package/e2e/projects/projects-switch.spec.ts +116 -0
- package/e2e/projects/workspace-new-session.spec.ts +94 -0
- package/e2e/projects/workspaces.spec.ts +375 -0
- package/e2e/prompt/context.spec.ts +95 -0
- package/e2e/prompt/prompt-async.spec.ts +76 -0
- package/e2e/prompt/prompt-drop-file-uri.spec.ts +22 -0
- package/e2e/prompt/prompt-drop-file.spec.ts +30 -0
- package/e2e/prompt/prompt-history.spec.ts +184 -0
- package/e2e/prompt/prompt-mention.spec.ts +26 -0
- package/e2e/prompt/prompt-multiline.spec.ts +24 -0
- package/e2e/prompt/prompt-shell.spec.ts +62 -0
- package/e2e/prompt/prompt-slash-open.spec.ts +22 -0
- package/e2e/prompt/prompt-slash-share.spec.ts +64 -0
- package/e2e/prompt/prompt-slash-terminal.spec.ts +18 -0
- package/e2e/prompt/prompt.spec.ts +55 -0
- package/e2e/selectors.ts +75 -0
- package/e2e/session/session-child-navigation.spec.ts +37 -0
- package/e2e/session/session-composer-dock.spec.ts +530 -0
- package/e2e/session/session-model-persistence.spec.ts +359 -0
- package/e2e/session/session-review.spec.ts +426 -0
- package/e2e/session/session-undo-redo.spec.ts +233 -0
- package/e2e/session/session.spec.ts +174 -0
- package/e2e/settings/settings-keybinds.spec.ts +389 -0
- package/e2e/settings/settings-models.spec.ts +122 -0
- package/e2e/settings/settings-providers.spec.ts +136 -0
- package/e2e/settings/settings.spec.ts +519 -0
- package/e2e/sidebar/sidebar-popover-actions.spec.ts +118 -0
- package/e2e/sidebar/sidebar-session-links.spec.ts +30 -0
- package/e2e/sidebar/sidebar.spec.ts +40 -0
- package/e2e/status/status-popover.spec.ts +94 -0
- package/e2e/terminal/terminal-init.spec.ts +28 -0
- package/e2e/terminal/terminal-reconnect.spec.ts +46 -0
- package/e2e/terminal/terminal-tabs.spec.ts +168 -0
- package/e2e/terminal/terminal.spec.ts +18 -0
- package/e2e/thinking-level.spec.ts +25 -0
- package/e2e/tsconfig.json +9 -0
- package/e2e/utils.ts +63 -0
- package/happydom.ts +75 -0
- package/index.html +23 -0
- package/package.json +77 -0
- package/playwright.config.ts +45 -0
- package/public/_headers +17 -0
- package/public/oc-theme-preload.js +35 -0
- package/script/e2e-local.ts +180 -0
- package/src/addons/serialize.test.ts +319 -0
- package/src/addons/serialize.ts +634 -0
- package/src/app.tsx +308 -0
- package/src/components/debug-bar.tsx +443 -0
- package/src/components/dialog-connect-provider.tsx +617 -0
- package/src/components/dialog-custom-provider-form.ts +158 -0
- package/src/components/dialog-custom-provider.test.ts +80 -0
- package/src/components/dialog-custom-provider.tsx +329 -0
- package/src/components/dialog-edit-project.tsx +255 -0
- package/src/components/dialog-fork.tsx +108 -0
- package/src/components/dialog-manage-models.tsx +101 -0
- package/src/components/dialog-release-notes.tsx +144 -0
- package/src/components/dialog-select-directory.tsx +392 -0
- package/src/components/dialog-select-file.tsx +466 -0
- package/src/components/dialog-select-mcp.tsx +107 -0
- package/src/components/dialog-select-model-unpaid.tsx +137 -0
- package/src/components/dialog-select-model.tsx +220 -0
- package/src/components/dialog-select-provider.tsx +86 -0
- package/src/components/dialog-select-server.tsx +649 -0
- package/src/components/dialog-settings.tsx +73 -0
- package/src/components/file-tree.test.ts +78 -0
- package/src/components/file-tree.tsx +507 -0
- package/src/components/link.tsx +26 -0
- package/src/components/model-tooltip.tsx +91 -0
- package/src/components/prompt-input/attachments.test.ts +44 -0
- package/src/components/prompt-input/attachments.ts +201 -0
- package/src/components/prompt-input/build-request-parts.test.ts +312 -0
- package/src/components/prompt-input/build-request-parts.ts +175 -0
- package/src/components/prompt-input/context-items.tsx +88 -0
- package/src/components/prompt-input/drag-overlay.tsx +25 -0
- package/src/components/prompt-input/editor-dom.test.ts +99 -0
- package/src/components/prompt-input/editor-dom.ts +148 -0
- package/src/components/prompt-input/files.ts +66 -0
- package/src/components/prompt-input/history.test.ts +153 -0
- package/src/components/prompt-input/history.ts +256 -0
- package/src/components/prompt-input/image-attachments.tsx +58 -0
- package/src/components/prompt-input/paste.ts +24 -0
- package/src/components/prompt-input/placeholder.test.ts +48 -0
- package/src/components/prompt-input/placeholder.ts +15 -0
- package/src/components/prompt-input/slash-popover.tsx +141 -0
- package/src/components/prompt-input/submit.test.ts +346 -0
- package/src/components/prompt-input/submit.ts +579 -0
- package/src/components/prompt-input.tsx +1595 -0
- package/src/components/server/server-row.tsx +130 -0
- package/src/components/session/index.ts +5 -0
- package/src/components/session/session-context-breakdown.test.ts +61 -0
- package/src/components/session/session-context-breakdown.ts +132 -0
- package/src/components/session/session-context-format.ts +20 -0
- package/src/components/session/session-context-metrics.test.ts +101 -0
- package/src/components/session/session-context-metrics.ts +82 -0
- package/src/components/session/session-context-tab.tsx +339 -0
- package/src/components/session/session-header.tsx +486 -0
- package/src/components/session/session-new-view.tsx +91 -0
- package/src/components/session/session-sortable-tab.tsx +70 -0
- package/src/components/session/session-sortable-terminal-tab.tsx +193 -0
- package/src/components/session-context-usage.tsx +122 -0
- package/src/components/settings-general.tsx +585 -0
- package/src/components/settings-keybinds.tsx +453 -0
- package/src/components/settings-list.tsx +5 -0
- package/src/components/settings-models.tsx +137 -0
- package/src/components/settings-providers.tsx +251 -0
- package/src/components/status-popover.tsx +419 -0
- package/src/components/terminal.tsx +653 -0
- package/src/components/titlebar-history.test.ts +63 -0
- package/src/components/titlebar-history.ts +57 -0
- package/src/components/titlebar.tsx +312 -0
- package/src/constants/file-picker.ts +89 -0
- package/src/context/command-keybind.test.ts +69 -0
- package/src/context/command.test.ts +25 -0
- package/src/context/command.tsx +437 -0
- package/src/context/comments.test.ts +186 -0
- package/src/context/comments.tsx +243 -0
- package/src/context/file/content-cache.ts +88 -0
- package/src/context/file/path.test.ts +360 -0
- package/src/context/file/path.ts +151 -0
- package/src/context/file/tree-store.ts +170 -0
- package/src/context/file/types.ts +41 -0
- package/src/context/file/view-cache.ts +146 -0
- package/src/context/file/watcher.test.ts +149 -0
- package/src/context/file/watcher.ts +53 -0
- package/src/context/file-content-eviction-accounting.test.ts +65 -0
- package/src/context/file.tsx +280 -0
- package/src/context/global-sdk.tsx +232 -0
- package/src/context/global-sync/bootstrap.ts +206 -0
- package/src/context/global-sync/child-store.test.ts +38 -0
- package/src/context/global-sync/child-store.ts +281 -0
- package/src/context/global-sync/event-reducer.test.ts +552 -0
- package/src/context/global-sync/event-reducer.ts +359 -0
- package/src/context/global-sync/eviction.ts +28 -0
- package/src/context/global-sync/queue.ts +83 -0
- package/src/context/global-sync/session-cache.test.ts +102 -0
- package/src/context/global-sync/session-cache.ts +62 -0
- package/src/context/global-sync/session-load.ts +25 -0
- package/src/context/global-sync/session-prefetch.test.ts +96 -0
- package/src/context/global-sync/session-prefetch.ts +100 -0
- package/src/context/global-sync/session-trim.test.ts +59 -0
- package/src/context/global-sync/session-trim.ts +56 -0
- package/src/context/global-sync/types.ts +133 -0
- package/src/context/global-sync/utils.ts +25 -0
- package/src/context/global-sync.test.ts +122 -0
- package/src/context/global-sync.tsx +408 -0
- package/src/context/highlights.tsx +233 -0
- package/src/context/language.tsx +248 -0
- package/src/context/layout-scroll.test.ts +64 -0
- package/src/context/layout-scroll.ts +126 -0
- package/src/context/layout.test.ts +69 -0
- package/src/context/layout.tsx +937 -0
- package/src/context/local.tsx +422 -0
- package/src/context/model-variant.test.ts +86 -0
- package/src/context/model-variant.ts +52 -0
- package/src/context/models.tsx +163 -0
- package/src/context/notification.tsx +373 -0
- package/src/context/permission-auto-respond.test.ts +102 -0
- package/src/context/permission-auto-respond.ts +51 -0
- package/src/context/permission.tsx +277 -0
- package/src/context/platform.tsx +99 -0
- package/src/context/prompt.tsx +297 -0
- package/src/context/sdk.tsx +49 -0
- package/src/context/server.tsx +295 -0
- package/src/context/settings.tsx +241 -0
- package/src/context/sync-optimistic.test.ts +123 -0
- package/src/context/sync.tsx +618 -0
- package/src/context/terminal-title.ts +51 -0
- package/src/context/terminal.test.ts +82 -0
- package/src/context/terminal.tsx +437 -0
- package/src/entry.tsx +144 -0
- package/src/env.d.ts +18 -0
- package/src/hooks/use-providers.ts +44 -0
- package/src/i18n/ar.ts +855 -0
- package/src/i18n/br.ts +867 -0
- package/src/i18n/bs.ts +943 -0
- package/src/i18n/da.ts +937 -0
- package/src/i18n/de.ts +879 -0
- package/src/i18n/en.ts +948 -0
- package/src/i18n/es.ts +950 -0
- package/src/i18n/fr.ts +878 -0
- package/src/i18n/ja.ts +861 -0
- package/src/i18n/ko.ts +860 -0
- package/src/i18n/no.ts +944 -0
- package/src/i18n/parity.test.ts +32 -0
- package/src/i18n/pl.ts +865 -0
- package/src/i18n/ru.ts +946 -0
- package/src/i18n/th.ts +933 -0
- package/src/i18n/tr.ts +952 -0
- package/src/i18n/zh.ts +930 -0
- package/src/i18n/zht.ts +925 -0
- package/src/index.css +29 -0
- package/src/index.ts +6 -0
- package/src/pages/directory-layout.tsx +88 -0
- package/src/pages/error.tsx +327 -0
- package/src/pages/home.tsx +131 -0
- package/src/pages/layout/deep-links.ts +50 -0
- package/src/pages/layout/helpers.test.ts +211 -0
- package/src/pages/layout/helpers.ts +98 -0
- package/src/pages/layout/inline-editor.tsx +126 -0
- package/src/pages/layout/sidebar-items.tsx +437 -0
- package/src/pages/layout/sidebar-project.tsx +384 -0
- package/src/pages/layout/sidebar-shell.tsx +125 -0
- package/src/pages/layout/sidebar-workspace.tsx +504 -0
- package/src/pages/layout.tsx +2509 -0
- package/src/pages/session/composer/index.ts +2 -0
- package/src/pages/session/composer/session-composer-region.tsx +255 -0
- package/src/pages/session/composer/session-composer-state.test.ts +128 -0
- package/src/pages/session/composer/session-composer-state.ts +249 -0
- package/src/pages/session/composer/session-followup-dock.tsx +109 -0
- package/src/pages/session/composer/session-permission-dock.tsx +74 -0
- package/src/pages/session/composer/session-question-dock.tsx +449 -0
- package/src/pages/session/composer/session-request-tree.ts +52 -0
- package/src/pages/session/composer/session-revert-dock.tsx +99 -0
- package/src/pages/session/composer/session-todo-dock.tsx +330 -0
- package/src/pages/session/file-tab-scroll.test.ts +40 -0
- package/src/pages/session/file-tab-scroll.ts +67 -0
- package/src/pages/session/file-tabs.tsx +456 -0
- package/src/pages/session/handoff.ts +36 -0
- package/src/pages/session/helpers.test.ts +181 -0
- package/src/pages/session/helpers.ts +198 -0
- package/src/pages/session/message-gesture.test.ts +62 -0
- package/src/pages/session/message-gesture.ts +21 -0
- package/src/pages/session/message-id-from-hash.ts +6 -0
- package/src/pages/session/message-timeline.tsx +1013 -0
- package/src/pages/session/review-tab.tsx +170 -0
- package/src/pages/session/session-layout.ts +20 -0
- package/src/pages/session/session-model-helpers.test.ts +51 -0
- package/src/pages/session/session-model-helpers.ts +16 -0
- package/src/pages/session/session-side-panel.tsx +453 -0
- package/src/pages/session/terminal-label.ts +16 -0
- package/src/pages/session/terminal-panel.test.ts +25 -0
- package/src/pages/session/terminal-panel.tsx +326 -0
- package/src/pages/session/use-session-commands.tsx +495 -0
- package/src/pages/session/use-session-hash-scroll.test.ts +16 -0
- package/src/pages/session/use-session-hash-scroll.ts +197 -0
- package/src/pages/session.tsx +1841 -0
- package/src/sst-env.d.ts +12 -0
- package/src/testing/model-selection.ts +80 -0
- package/src/testing/prompt.ts +56 -0
- package/src/testing/session-composer.ts +84 -0
- package/src/testing/terminal.ts +118 -0
- package/src/theme-preload.test.ts +46 -0
- package/src/utils/agent.ts +23 -0
- package/src/utils/aim.ts +138 -0
- package/src/utils/base64.ts +10 -0
- package/src/utils/comment-note.ts +88 -0
- package/src/utils/id.ts +99 -0
- package/src/utils/notification-click.test.ts +27 -0
- package/src/utils/notification-click.ts +13 -0
- package/src/utils/persist.test.ts +115 -0
- package/src/utils/persist.ts +476 -0
- package/src/utils/prompt.test.ts +44 -0
- package/src/utils/prompt.ts +203 -0
- package/src/utils/runtime-adapters.test.ts +62 -0
- package/src/utils/runtime-adapters.ts +39 -0
- package/src/utils/same.ts +6 -0
- package/src/utils/scoped-cache.test.ts +69 -0
- package/src/utils/scoped-cache.ts +104 -0
- package/src/utils/server-errors.test.ts +131 -0
- package/src/utils/server-errors.ts +80 -0
- package/src/utils/server-health.test.ts +123 -0
- package/src/utils/server-health.ts +91 -0
- package/src/utils/server.ts +22 -0
- package/src/utils/solid-dnd.tsx +49 -0
- package/src/utils/sound.ts +117 -0
- package/src/utils/terminal-writer.test.ts +64 -0
- package/src/utils/terminal-writer.ts +65 -0
- package/src/utils/time.ts +22 -0
- package/src/utils/uuid.test.ts +78 -0
- package/src/utils/uuid.ts +12 -0
- package/src/utils/worktree.test.ts +46 -0
- package/src/utils/worktree.ts +73 -0
- package/sst-env.d.ts +10 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +15 -0
- package/vite.js +26 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { Button } from "@reign-labs/ui/button"
|
|
2
|
+
import { useDialog } from "@reign-labs/ui/context/dialog"
|
|
3
|
+
import { ProviderIcon } from "@reign-labs/ui/provider-icon"
|
|
4
|
+
import { Tag } from "@reign-labs/ui/tag"
|
|
5
|
+
import { showToast } from "@reign-labs/ui/toast"
|
|
6
|
+
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
|
7
|
+
import { createMemo, type Component, For, Show } from "solid-js"
|
|
8
|
+
import { useLanguage } from "@/context/language"
|
|
9
|
+
import { useGlobalSDK } from "@/context/global-sdk"
|
|
10
|
+
import { useGlobalSync } from "@/context/global-sync"
|
|
11
|
+
import { DialogConnectProvider } from "./dialog-connect-provider"
|
|
12
|
+
import { DialogSelectProvider } from "./dialog-select-provider"
|
|
13
|
+
import { DialogCustomProvider } from "./dialog-custom-provider"
|
|
14
|
+
import { SettingsList } from "./settings-list"
|
|
15
|
+
|
|
16
|
+
type ProviderSource = "env" | "api" | "config" | "custom"
|
|
17
|
+
type ProviderItem = ReturnType<ReturnType<typeof useProviders>["connected"]>[number]
|
|
18
|
+
|
|
19
|
+
const PROVIDER_NOTES = [
|
|
20
|
+
{ match: (id: string) => id === "opencode", key: "dialog.provider.opencode.note" },
|
|
21
|
+
{ match: (id: string) => id === "opencode-go", key: "dialog.provider.opencodeGo.tagline" },
|
|
22
|
+
{ match: (id: string) => id === "anthropic", key: "dialog.provider.anthropic.note" },
|
|
23
|
+
{ match: (id: string) => id.startsWith("github-copilot"), key: "dialog.provider.copilot.note" },
|
|
24
|
+
{ match: (id: string) => id === "openai", key: "dialog.provider.openai.note" },
|
|
25
|
+
{ match: (id: string) => id === "google", key: "dialog.provider.google.note" },
|
|
26
|
+
{ match: (id: string) => id === "openrouter", key: "dialog.provider.openrouter.note" },
|
|
27
|
+
{ match: (id: string) => id === "vercel", key: "dialog.provider.vercel.note" },
|
|
28
|
+
] as const
|
|
29
|
+
|
|
30
|
+
export const SettingsProviders: Component = () => {
|
|
31
|
+
const dialog = useDialog()
|
|
32
|
+
const language = useLanguage()
|
|
33
|
+
const globalSDK = useGlobalSDK()
|
|
34
|
+
const globalSync = useGlobalSync()
|
|
35
|
+
const providers = useProviders()
|
|
36
|
+
|
|
37
|
+
const connected = createMemo(() => {
|
|
38
|
+
return providers
|
|
39
|
+
.connected()
|
|
40
|
+
.filter((p) => p.id !== "opencode" || Object.values(p.models).find((m) => m.cost?.input))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const popular = createMemo(() => {
|
|
44
|
+
const connectedIDs = new Set(connected().map((p) => p.id))
|
|
45
|
+
const items = providers
|
|
46
|
+
.popular()
|
|
47
|
+
.filter((p) => !connectedIDs.has(p.id))
|
|
48
|
+
.slice()
|
|
49
|
+
items.sort((a, b) => popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id))
|
|
50
|
+
return items
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const source = (item: ProviderItem): ProviderSource | undefined => {
|
|
54
|
+
if (!("source" in item)) return
|
|
55
|
+
const value = item.source
|
|
56
|
+
if (value === "env" || value === "api" || value === "config" || value === "custom") return value
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const type = (item: ProviderItem) => {
|
|
61
|
+
const current = source(item)
|
|
62
|
+
if (current === "env") return language.t("settings.providers.tag.environment")
|
|
63
|
+
if (current === "api") return language.t("provider.connect.method.apiKey")
|
|
64
|
+
if (current === "config") {
|
|
65
|
+
if (isConfigCustom(item.id)) return language.t("settings.providers.tag.custom")
|
|
66
|
+
return language.t("settings.providers.tag.config")
|
|
67
|
+
}
|
|
68
|
+
if (current === "custom") return language.t("settings.providers.tag.custom")
|
|
69
|
+
return language.t("settings.providers.tag.other")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const canDisconnect = (item: ProviderItem) => source(item) !== "env"
|
|
73
|
+
|
|
74
|
+
const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key
|
|
75
|
+
|
|
76
|
+
const isConfigCustom = (providerID: string) => {
|
|
77
|
+
const provider = globalSync.data.config.provider?.[providerID]
|
|
78
|
+
if (!provider) return false
|
|
79
|
+
if (provider.npm !== "@ai-sdk/openai-compatible") return false
|
|
80
|
+
if (!provider.models || Object.keys(provider.models).length === 0) return false
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const disableProvider = async (providerID: string, name: string) => {
|
|
85
|
+
const before = globalSync.data.config.disabled_providers ?? []
|
|
86
|
+
const next = before.includes(providerID) ? before : [...before, providerID]
|
|
87
|
+
globalSync.set("config", "disabled_providers", next)
|
|
88
|
+
|
|
89
|
+
await globalSync
|
|
90
|
+
.updateConfig({ disabled_providers: next })
|
|
91
|
+
.then(() => {
|
|
92
|
+
showToast({
|
|
93
|
+
variant: "success",
|
|
94
|
+
icon: "circle-check",
|
|
95
|
+
title: language.t("provider.disconnect.toast.disconnected.title", { provider: name }),
|
|
96
|
+
description: language.t("provider.disconnect.toast.disconnected.description", { provider: name }),
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
.catch((err: unknown) => {
|
|
100
|
+
globalSync.set("config", "disabled_providers", before)
|
|
101
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
102
|
+
showToast({ title: language.t("common.requestFailed"), description: message })
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const disconnect = async (providerID: string, name: string) => {
|
|
107
|
+
if (isConfigCustom(providerID)) {
|
|
108
|
+
await globalSDK.client.auth.remove({ providerID }).catch(() => undefined)
|
|
109
|
+
await disableProvider(providerID, name)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
await globalSDK.client.auth
|
|
113
|
+
.remove({ providerID })
|
|
114
|
+
.then(async () => {
|
|
115
|
+
await globalSDK.client.global.dispose()
|
|
116
|
+
showToast({
|
|
117
|
+
variant: "success",
|
|
118
|
+
icon: "circle-check",
|
|
119
|
+
title: language.t("provider.disconnect.toast.disconnected.title", { provider: name }),
|
|
120
|
+
description: language.t("provider.disconnect.toast.disconnected.description", { provider: name }),
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
.catch((err: unknown) => {
|
|
124
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
125
|
+
showToast({ title: language.t("common.requestFailed"), description: message })
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
|
|
131
|
+
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
|
132
|
+
<div class="flex flex-col gap-1 pt-6 pb-8 max-w-[720px]">
|
|
133
|
+
<h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div class="flex flex-col gap-8 max-w-[720px]">
|
|
138
|
+
<div class="flex flex-col gap-1" data-component="connected-providers-section">
|
|
139
|
+
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.providers.section.connected")}</h3>
|
|
140
|
+
<SettingsList>
|
|
141
|
+
<Show
|
|
142
|
+
when={connected().length > 0}
|
|
143
|
+
fallback={
|
|
144
|
+
<div class="py-4 text-14-regular text-text-weak">
|
|
145
|
+
{language.t("settings.providers.connected.empty")}
|
|
146
|
+
</div>
|
|
147
|
+
}
|
|
148
|
+
>
|
|
149
|
+
<For each={connected()}>
|
|
150
|
+
{(item) => (
|
|
151
|
+
<div class="group flex flex-wrap items-center justify-between gap-4 min-h-16 py-3 border-b border-border-weak-base last:border-none">
|
|
152
|
+
<div class="flex items-center gap-3 min-w-0">
|
|
153
|
+
<ProviderIcon id={item.id} class="size-5 shrink-0 icon-strong-base" />
|
|
154
|
+
<span class="text-14-medium text-text-strong truncate">{item.name}</span>
|
|
155
|
+
<Tag>{type(item)}</Tag>
|
|
156
|
+
</div>
|
|
157
|
+
<Show
|
|
158
|
+
when={canDisconnect(item)}
|
|
159
|
+
fallback={
|
|
160
|
+
<span class="text-14-regular text-text-base opacity-0 group-hover:opacity-100 transition-opacity duration-200 pr-3 cursor-default">
|
|
161
|
+
{language.t("settings.providers.connected.environmentDescription")}
|
|
162
|
+
</span>
|
|
163
|
+
}
|
|
164
|
+
>
|
|
165
|
+
<Button size="large" variant="ghost" onClick={() => void disconnect(item.id, item.name)}>
|
|
166
|
+
{language.t("common.disconnect")}
|
|
167
|
+
</Button>
|
|
168
|
+
</Show>
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
</For>
|
|
172
|
+
</Show>
|
|
173
|
+
</SettingsList>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="flex flex-col gap-1">
|
|
177
|
+
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.providers.section.popular")}</h3>
|
|
178
|
+
<SettingsList>
|
|
179
|
+
<For each={popular()}>
|
|
180
|
+
{(item) => (
|
|
181
|
+
<div class="flex flex-wrap items-center justify-between gap-4 min-h-16 py-3 border-b border-border-weak-base last:border-none">
|
|
182
|
+
<div class="flex flex-col min-w-0">
|
|
183
|
+
<div class="flex items-center gap-x-3">
|
|
184
|
+
<ProviderIcon id={item.id} class="size-5 shrink-0 icon-strong-base" />
|
|
185
|
+
<span class="text-14-medium text-text-strong">{item.name}</span>
|
|
186
|
+
<Show when={item.id === "opencode"}>
|
|
187
|
+
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
|
188
|
+
</Show>
|
|
189
|
+
<Show when={item.id === "opencode-go"}>
|
|
190
|
+
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
|
191
|
+
</Show>
|
|
192
|
+
</div>
|
|
193
|
+
<Show when={note(item.id)}>
|
|
194
|
+
{(key) => <span class="text-12-regular text-text-weak pl-8">{language.t(key())}</span>}
|
|
195
|
+
</Show>
|
|
196
|
+
</div>
|
|
197
|
+
<Button
|
|
198
|
+
size="large"
|
|
199
|
+
variant="secondary"
|
|
200
|
+
icon="plus-small"
|
|
201
|
+
onClick={() => {
|
|
202
|
+
dialog.show(() => <DialogConnectProvider provider={item.id} />)
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
{language.t("common.connect")}
|
|
206
|
+
</Button>
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
</For>
|
|
210
|
+
|
|
211
|
+
<div
|
|
212
|
+
class="flex items-center justify-between gap-4 min-h-16 border-b border-border-weak-base last:border-none flex-wrap py-3"
|
|
213
|
+
data-component="custom-provider-section"
|
|
214
|
+
>
|
|
215
|
+
<div class="flex flex-col min-w-0">
|
|
216
|
+
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
|
|
217
|
+
<ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
|
|
218
|
+
<span class="text-14-medium text-text-strong">{language.t("provider.custom.title")}</span>
|
|
219
|
+
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
|
220
|
+
</div>
|
|
221
|
+
<span class="text-12-regular text-text-weak pl-8">
|
|
222
|
+
{language.t("settings.providers.custom.description")}
|
|
223
|
+
</span>
|
|
224
|
+
</div>
|
|
225
|
+
<Button
|
|
226
|
+
size="large"
|
|
227
|
+
variant="secondary"
|
|
228
|
+
icon="plus-small"
|
|
229
|
+
onClick={() => {
|
|
230
|
+
dialog.show(() => <DialogCustomProvider back="close" />)
|
|
231
|
+
}}
|
|
232
|
+
>
|
|
233
|
+
{language.t("common.connect")}
|
|
234
|
+
</Button>
|
|
235
|
+
</div>
|
|
236
|
+
</SettingsList>
|
|
237
|
+
|
|
238
|
+
<Button
|
|
239
|
+
variant="ghost"
|
|
240
|
+
class="px-0 py-0 mt-5 text-14-medium text-text-interactive-base text-left justify-start hover:bg-transparent active:bg-transparent"
|
|
241
|
+
onClick={() => {
|
|
242
|
+
dialog.show(() => <DialogSelectProvider />)
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
{language.t("dialog.provider.viewAll")}
|
|
246
|
+
</Button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
)
|
|
251
|
+
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { Button } from "@reign-labs/ui/button"
|
|
2
|
+
import { useDialog } from "@reign-labs/ui/context/dialog"
|
|
3
|
+
import { Icon } from "@reign-labs/ui/icon"
|
|
4
|
+
import { Popover } from "@reign-labs/ui/popover"
|
|
5
|
+
import { Switch } from "@reign-labs/ui/switch"
|
|
6
|
+
import { Tabs } from "@reign-labs/ui/tabs"
|
|
7
|
+
import { useMutation } from "@tanstack/solid-query"
|
|
8
|
+
import { showToast } from "@reign-labs/ui/toast"
|
|
9
|
+
import { useNavigate } from "@solidjs/router"
|
|
10
|
+
import { type Accessor, createEffect, createMemo, createSignal, For, type JSXElement, onCleanup, Show } from "solid-js"
|
|
11
|
+
import { createStore, reconcile } from "solid-js/store"
|
|
12
|
+
import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
|
|
13
|
+
import { useLanguage } from "@/context/language"
|
|
14
|
+
import { usePlatform } from "@/context/platform"
|
|
15
|
+
import { useSDK } from "@/context/sdk"
|
|
16
|
+
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
|
17
|
+
import { useSync } from "@/context/sync"
|
|
18
|
+
import { useCheckServerHealth, type ServerHealth } from "@/utils/server-health"
|
|
19
|
+
import { DialogSelectServer } from "./dialog-select-server"
|
|
20
|
+
|
|
21
|
+
const pollMs = 10_000
|
|
22
|
+
|
|
23
|
+
const pluginEmptyMessage = (value: string, file: string): JSXElement => {
|
|
24
|
+
const parts = value.split(file)
|
|
25
|
+
if (parts.length === 1) return value
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
{parts[0]}
|
|
29
|
+
<code class="bg-surface-raised-base px-1.5 py-0.5 rounded-sm text-text-base">{file}</code>
|
|
30
|
+
{parts.slice(1).join(file)}
|
|
31
|
+
</>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const listServersByHealth = (
|
|
36
|
+
list: ServerConnection.Any[],
|
|
37
|
+
active: ServerConnection.Key | undefined,
|
|
38
|
+
status: Record<ServerConnection.Key, ServerHealth | undefined>,
|
|
39
|
+
) => {
|
|
40
|
+
if (!list.length) return list
|
|
41
|
+
const order = new Map(list.map((url, index) => [url, index] as const))
|
|
42
|
+
const rank = (value?: ServerHealth) => {
|
|
43
|
+
if (value?.healthy === true) return 0
|
|
44
|
+
if (value?.healthy === false) return 2
|
|
45
|
+
return 1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return list.slice().sort((a, b) => {
|
|
49
|
+
if (ServerConnection.key(a) === active) return -1
|
|
50
|
+
if (ServerConnection.key(b) === active) return 1
|
|
51
|
+
const diff = rank(status[ServerConnection.key(a)]) - rank(status[ServerConnection.key(b)])
|
|
52
|
+
if (diff !== 0) return diff
|
|
53
|
+
return (order.get(a) ?? 0) - (order.get(b) ?? 0)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const useServerHealth = (servers: Accessor<ServerConnection.Any[]>) => {
|
|
58
|
+
const checkServerHealth = useCheckServerHealth()
|
|
59
|
+
const [status, setStatus] = createStore({} as Record<ServerConnection.Key, ServerHealth | undefined>)
|
|
60
|
+
|
|
61
|
+
createEffect(() => {
|
|
62
|
+
const list = servers()
|
|
63
|
+
let dead = false
|
|
64
|
+
|
|
65
|
+
const refresh = async () => {
|
|
66
|
+
const results: Record<string, ServerHealth> = {}
|
|
67
|
+
await Promise.all(
|
|
68
|
+
list.map(async (conn) => {
|
|
69
|
+
results[ServerConnection.key(conn)] = await checkServerHealth(conn.http)
|
|
70
|
+
}),
|
|
71
|
+
)
|
|
72
|
+
if (dead) return
|
|
73
|
+
setStatus(reconcile(results))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void refresh()
|
|
77
|
+
const id = setInterval(() => void refresh(), pollMs)
|
|
78
|
+
onCleanup(() => {
|
|
79
|
+
dead = true
|
|
80
|
+
clearInterval(id)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
return status
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const useDefaultServerKey = (
|
|
88
|
+
get: (() => string | Promise<string | null | undefined> | null | undefined) | undefined,
|
|
89
|
+
) => {
|
|
90
|
+
const [state, setState] = createStore({
|
|
91
|
+
url: undefined as string | undefined,
|
|
92
|
+
tick: 0,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
createEffect(() => {
|
|
96
|
+
state.tick
|
|
97
|
+
let dead = false
|
|
98
|
+
const result = get?.()
|
|
99
|
+
if (!result) {
|
|
100
|
+
setState("url", undefined)
|
|
101
|
+
onCleanup(() => {
|
|
102
|
+
dead = true
|
|
103
|
+
})
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (result instanceof Promise) {
|
|
108
|
+
void result.then((next) => {
|
|
109
|
+
if (dead) return
|
|
110
|
+
setState("url", next ? normalizeServerUrl(next) : undefined)
|
|
111
|
+
})
|
|
112
|
+
onCleanup(() => {
|
|
113
|
+
dead = true
|
|
114
|
+
})
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setState("url", normalizeServerUrl(result))
|
|
119
|
+
onCleanup(() => {
|
|
120
|
+
dead = true
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
key: () => {
|
|
126
|
+
const u = state.url
|
|
127
|
+
if (!u) return
|
|
128
|
+
return ServerConnection.key({ type: "http", http: { url: u } })
|
|
129
|
+
},
|
|
130
|
+
refresh: () => setState("tick", (value) => value + 1),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const useMcpToggleMutation = () => {
|
|
135
|
+
const sync = useSync()
|
|
136
|
+
const sdk = useSDK()
|
|
137
|
+
const language = useLanguage()
|
|
138
|
+
|
|
139
|
+
return useMutation(() => ({
|
|
140
|
+
mutationFn: async (name: string) => {
|
|
141
|
+
const status = sync.data.mcp[name]
|
|
142
|
+
await (status?.status === "connected" ? sdk.client.mcp.disconnect({ name }) : sdk.client.mcp.connect({ name }))
|
|
143
|
+
const result = await sdk.client.mcp.status()
|
|
144
|
+
if (result.data) sync.set("mcp", result.data)
|
|
145
|
+
},
|
|
146
|
+
onError: (err) => {
|
|
147
|
+
showToast({
|
|
148
|
+
variant: "error",
|
|
149
|
+
title: language.t("common.requestFailed"),
|
|
150
|
+
description: err instanceof Error ? err.message : String(err),
|
|
151
|
+
})
|
|
152
|
+
},
|
|
153
|
+
}))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function StatusPopover() {
|
|
157
|
+
const sync = useSync()
|
|
158
|
+
const server = useServer()
|
|
159
|
+
const platform = usePlatform()
|
|
160
|
+
const dialog = useDialog()
|
|
161
|
+
const language = useLanguage()
|
|
162
|
+
const navigate = useNavigate()
|
|
163
|
+
|
|
164
|
+
const [shown, setShown] = createSignal(false)
|
|
165
|
+
const servers = createMemo(() => {
|
|
166
|
+
const current = server.current
|
|
167
|
+
const list = server.list
|
|
168
|
+
if (!current) return list
|
|
169
|
+
if (list.every((item) => ServerConnection.key(item) !== ServerConnection.key(current))) return [current, ...list]
|
|
170
|
+
return [current, ...list.filter((item) => ServerConnection.key(item) !== ServerConnection.key(current))]
|
|
171
|
+
})
|
|
172
|
+
const health = useServerHealth(servers)
|
|
173
|
+
const sortedServers = createMemo(() => listServersByHealth(servers(), server.key, health))
|
|
174
|
+
const toggleMcp = useMcpToggleMutation()
|
|
175
|
+
const defaultServer = useDefaultServerKey(platform.getDefaultServer)
|
|
176
|
+
const mcpNames = createMemo(() => Object.keys(sync.data.mcp ?? {}).sort((a, b) => a.localeCompare(b)))
|
|
177
|
+
const mcpStatus = (name: string) => sync.data.mcp?.[name]?.status
|
|
178
|
+
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
|
|
179
|
+
const lspItems = createMemo(() => sync.data.lsp ?? [])
|
|
180
|
+
const lspCount = createMemo(() => lspItems().length)
|
|
181
|
+
const plugins = createMemo(() => sync.data.config.plugin ?? [])
|
|
182
|
+
const pluginCount = createMemo(() => plugins().length)
|
|
183
|
+
const pluginEmpty = createMemo(() => pluginEmptyMessage(language.t("dialog.plugins.empty"), "reigncode.json"))
|
|
184
|
+
const overallHealthy = createMemo(() => {
|
|
185
|
+
const serverHealthy = server.healthy() === true
|
|
186
|
+
const anyMcpIssue = mcpNames().some((name) => {
|
|
187
|
+
const status = mcpStatus(name)
|
|
188
|
+
return status !== "connected" && status !== "disabled"
|
|
189
|
+
})
|
|
190
|
+
return serverHealthy && !anyMcpIssue
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<Popover
|
|
195
|
+
open={shown()}
|
|
196
|
+
onOpenChange={setShown}
|
|
197
|
+
triggerAs={Button}
|
|
198
|
+
triggerProps={{
|
|
199
|
+
variant: "ghost",
|
|
200
|
+
class: "titlebar-icon w-8 h-6 p-0 box-border",
|
|
201
|
+
"aria-label": language.t("status.popover.trigger"),
|
|
202
|
+
style: { scale: 1 },
|
|
203
|
+
}}
|
|
204
|
+
trigger={
|
|
205
|
+
<div class="relative size-4">
|
|
206
|
+
<div class="badge-mask-tight size-4 flex items-center justify-center">
|
|
207
|
+
<Icon name={shown() ? "status-active" : "status"} size="small" />
|
|
208
|
+
</div>
|
|
209
|
+
<div
|
|
210
|
+
classList={{
|
|
211
|
+
"absolute -top-px -right-px size-1.5 rounded-full": true,
|
|
212
|
+
"bg-icon-success-base": overallHealthy(),
|
|
213
|
+
"bg-icon-critical-base": !overallHealthy() && server.healthy() !== undefined,
|
|
214
|
+
"bg-border-weak-base": server.healthy() === undefined,
|
|
215
|
+
}}
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
}
|
|
219
|
+
class="[&_[data-slot=popover-body]]:p-0 w-[360px] max-w-[calc(100vw-40px)] bg-transparent border-0 shadow-none rounded-xl"
|
|
220
|
+
gutter={4}
|
|
221
|
+
placement="bottom-end"
|
|
222
|
+
shift={-168}
|
|
223
|
+
>
|
|
224
|
+
<div class="flex items-center gap-1 w-[360px] rounded-xl shadow-[var(--shadow-lg-border-base)]">
|
|
225
|
+
<Tabs
|
|
226
|
+
aria-label={language.t("status.popover.ariaLabel")}
|
|
227
|
+
class="tabs bg-background-strong rounded-xl overflow-hidden"
|
|
228
|
+
data-component="tabs"
|
|
229
|
+
data-active="servers"
|
|
230
|
+
defaultValue="servers"
|
|
231
|
+
variant="alt"
|
|
232
|
+
>
|
|
233
|
+
<Tabs.List data-slot="tablist" class="bg-transparent border-b-0 px-4 pt-2 pb-0 gap-4 h-10">
|
|
234
|
+
<Tabs.Trigger value="servers" data-slot="tab" class="text-12-regular">
|
|
235
|
+
{sortedServers().length > 0 ? `${sortedServers().length} ` : ""}
|
|
236
|
+
{language.t("status.popover.tab.servers")}
|
|
237
|
+
</Tabs.Trigger>
|
|
238
|
+
<Tabs.Trigger value="mcp" data-slot="tab" class="text-12-regular">
|
|
239
|
+
{mcpConnected() > 0 ? `${mcpConnected()} ` : ""}
|
|
240
|
+
{language.t("status.popover.tab.mcp")}
|
|
241
|
+
</Tabs.Trigger>
|
|
242
|
+
<Tabs.Trigger value="lsp" data-slot="tab" class="text-12-regular">
|
|
243
|
+
{lspCount() > 0 ? `${lspCount()} ` : ""}
|
|
244
|
+
{language.t("status.popover.tab.lsp")}
|
|
245
|
+
</Tabs.Trigger>
|
|
246
|
+
<Tabs.Trigger value="plugins" data-slot="tab" class="text-12-regular">
|
|
247
|
+
{pluginCount() > 0 ? `${pluginCount()} ` : ""}
|
|
248
|
+
{language.t("status.popover.tab.plugins")}
|
|
249
|
+
</Tabs.Trigger>
|
|
250
|
+
</Tabs.List>
|
|
251
|
+
|
|
252
|
+
<Tabs.Content value="servers">
|
|
253
|
+
<div class="flex flex-col px-2 pb-2">
|
|
254
|
+
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
|
255
|
+
<For each={sortedServers()}>
|
|
256
|
+
{(s) => {
|
|
257
|
+
const key = ServerConnection.key(s)
|
|
258
|
+
const isBlocked = () => health[key]?.healthy === false
|
|
259
|
+
return (
|
|
260
|
+
<button
|
|
261
|
+
type="button"
|
|
262
|
+
class="flex items-center gap-2 w-full h-8 pl-3 pr-1.5 py-1.5 rounded-md transition-colors text-left"
|
|
263
|
+
classList={{
|
|
264
|
+
"hover:bg-surface-raised-base-hover": !isBlocked(),
|
|
265
|
+
"cursor-not-allowed": isBlocked(),
|
|
266
|
+
}}
|
|
267
|
+
aria-disabled={isBlocked()}
|
|
268
|
+
onClick={() => {
|
|
269
|
+
if (isBlocked()) return
|
|
270
|
+
navigate("/")
|
|
271
|
+
queueMicrotask(() => server.setActive(key))
|
|
272
|
+
}}
|
|
273
|
+
>
|
|
274
|
+
<ServerHealthIndicator health={health[key]} />
|
|
275
|
+
<ServerRow
|
|
276
|
+
conn={s}
|
|
277
|
+
dimmed={isBlocked()}
|
|
278
|
+
status={health[key]}
|
|
279
|
+
class="flex items-center gap-2 w-full min-w-0"
|
|
280
|
+
nameClass="text-14-regular text-text-base truncate"
|
|
281
|
+
versionClass="text-12-regular text-text-weak truncate"
|
|
282
|
+
badge={
|
|
283
|
+
<Show when={key === defaultServer.key()}>
|
|
284
|
+
<span class="text-11-regular text-text-base bg-surface-base px-1.5 py-0.5 rounded-md">
|
|
285
|
+
{language.t("common.default")}
|
|
286
|
+
</span>
|
|
287
|
+
</Show>
|
|
288
|
+
}
|
|
289
|
+
>
|
|
290
|
+
<div class="flex-1" />
|
|
291
|
+
<Show when={server.current && key === ServerConnection.key(server.current)}>
|
|
292
|
+
<Icon name="check" size="small" class="text-icon-weak shrink-0" />
|
|
293
|
+
</Show>
|
|
294
|
+
</ServerRow>
|
|
295
|
+
</button>
|
|
296
|
+
)
|
|
297
|
+
}}
|
|
298
|
+
</For>
|
|
299
|
+
|
|
300
|
+
<Button
|
|
301
|
+
variant="secondary"
|
|
302
|
+
class="mt-3 self-start h-8 px-3 py-1.5"
|
|
303
|
+
onClick={() => dialog.show(() => <DialogSelectServer />, defaultServer.refresh)}
|
|
304
|
+
>
|
|
305
|
+
{language.t("status.popover.action.manageServers")}
|
|
306
|
+
</Button>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</Tabs.Content>
|
|
310
|
+
|
|
311
|
+
<Tabs.Content value="mcp">
|
|
312
|
+
<div class="flex flex-col px-2 pb-2">
|
|
313
|
+
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
|
314
|
+
<Show
|
|
315
|
+
when={mcpNames().length > 0}
|
|
316
|
+
fallback={
|
|
317
|
+
<div class="text-14-regular text-text-base text-center my-auto">
|
|
318
|
+
{language.t("dialog.mcp.empty")}
|
|
319
|
+
</div>
|
|
320
|
+
}
|
|
321
|
+
>
|
|
322
|
+
<For each={mcpNames()}>
|
|
323
|
+
{(name) => {
|
|
324
|
+
const status = () => mcpStatus(name)
|
|
325
|
+
const enabled = () => status() === "connected"
|
|
326
|
+
return (
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
class="flex items-center gap-2 w-full h-8 pl-3 pr-2 py-1 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left"
|
|
330
|
+
onClick={() => {
|
|
331
|
+
if (toggleMcp.isPending) return
|
|
332
|
+
toggleMcp.mutate(name)
|
|
333
|
+
}}
|
|
334
|
+
disabled={toggleMcp.isPending && toggleMcp.variables === name}
|
|
335
|
+
>
|
|
336
|
+
<div
|
|
337
|
+
classList={{
|
|
338
|
+
"size-1.5 rounded-full shrink-0": true,
|
|
339
|
+
"bg-icon-success-base": status() === "connected",
|
|
340
|
+
"bg-icon-critical-base": status() === "failed",
|
|
341
|
+
"bg-border-weak-base": status() === "disabled",
|
|
342
|
+
"bg-icon-warning-base":
|
|
343
|
+
status() === "needs_auth" || status() === "needs_client_registration",
|
|
344
|
+
}}
|
|
345
|
+
/>
|
|
346
|
+
<span class="text-14-regular text-text-base truncate flex-1">{name}</span>
|
|
347
|
+
<div onClick={(event) => event.stopPropagation()}>
|
|
348
|
+
<Switch
|
|
349
|
+
checked={enabled()}
|
|
350
|
+
disabled={toggleMcp.isPending && toggleMcp.variables === name}
|
|
351
|
+
onChange={() => {
|
|
352
|
+
if (toggleMcp.isPending) return
|
|
353
|
+
toggleMcp.mutate(name)
|
|
354
|
+
}}
|
|
355
|
+
/>
|
|
356
|
+
</div>
|
|
357
|
+
</button>
|
|
358
|
+
)
|
|
359
|
+
}}
|
|
360
|
+
</For>
|
|
361
|
+
</Show>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
</Tabs.Content>
|
|
365
|
+
|
|
366
|
+
<Tabs.Content value="lsp">
|
|
367
|
+
<div class="flex flex-col px-2 pb-2">
|
|
368
|
+
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
|
369
|
+
<Show
|
|
370
|
+
when={lspItems().length > 0}
|
|
371
|
+
fallback={
|
|
372
|
+
<div class="text-14-regular text-text-base text-center my-auto">
|
|
373
|
+
{language.t("dialog.lsp.empty")}
|
|
374
|
+
</div>
|
|
375
|
+
}
|
|
376
|
+
>
|
|
377
|
+
<For each={lspItems()}>
|
|
378
|
+
{(item) => (
|
|
379
|
+
<div class="flex items-center gap-2 w-full px-2 py-1">
|
|
380
|
+
<div
|
|
381
|
+
classList={{
|
|
382
|
+
"size-1.5 rounded-full shrink-0": true,
|
|
383
|
+
"bg-icon-success-base": item.status === "connected",
|
|
384
|
+
"bg-icon-critical-base": item.status === "error",
|
|
385
|
+
}}
|
|
386
|
+
/>
|
|
387
|
+
<span class="text-14-regular text-text-base truncate">{item.name || item.id}</span>
|
|
388
|
+
</div>
|
|
389
|
+
)}
|
|
390
|
+
</For>
|
|
391
|
+
</Show>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
</Tabs.Content>
|
|
395
|
+
|
|
396
|
+
<Tabs.Content value="plugins">
|
|
397
|
+
<div class="flex flex-col px-2 pb-2">
|
|
398
|
+
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
|
399
|
+
<Show
|
|
400
|
+
when={plugins().length > 0}
|
|
401
|
+
fallback={<div class="text-14-regular text-text-base text-center my-auto">{pluginEmpty()}</div>}
|
|
402
|
+
>
|
|
403
|
+
<For each={plugins()}>
|
|
404
|
+
{(plugin) => (
|
|
405
|
+
<div class="flex items-center gap-2 w-full px-2 py-1">
|
|
406
|
+
<div class="size-1.5 rounded-full shrink-0 bg-icon-success-base" />
|
|
407
|
+
<span class="text-14-regular text-text-base truncate">{plugin}</span>
|
|
408
|
+
</div>
|
|
409
|
+
)}
|
|
410
|
+
</For>
|
|
411
|
+
</Show>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
</Tabs.Content>
|
|
415
|
+
</Tabs>
|
|
416
|
+
</div>
|
|
417
|
+
</Popover>
|
|
418
|
+
)
|
|
419
|
+
}
|