@neurocode-ai/tui 1.18.8
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/bunfig.toml +4 -0
- package/package.json +72 -0
- package/src/app.tsx +1134 -0
- package/src/attention.ts +260 -0
- package/src/audio.d.ts +9 -0
- package/src/audio.ts +53 -0
- package/src/clipboard.ts +124 -0
- package/src/component/bg-pulse-render.ts +436 -0
- package/src/component/bg-pulse.tsx +99 -0
- package/src/component/command-palette.tsx +79 -0
- package/src/component/dialog-agent.tsx +31 -0
- package/src/component/dialog-console-org.tsx +135 -0
- package/src/component/dialog-debug.tsx +90 -0
- package/src/component/dialog-mcp.tsx +85 -0
- package/src/component/dialog-model.tsx +197 -0
- package/src/component/dialog-move-session.tsx +353 -0
- package/src/component/dialog-provider.tsx +469 -0
- package/src/component/dialog-retry-action.tsx +160 -0
- package/src/component/dialog-session-delete-failed.tsx +99 -0
- package/src/component/dialog-session-list.tsx +364 -0
- package/src/component/dialog-session-rename.tsx +31 -0
- package/src/component/dialog-skill.tsx +70 -0
- package/src/component/dialog-stash.tsx +87 -0
- package/src/component/dialog-status.tsx +168 -0
- package/src/component/dialog-tag.tsx +47 -0
- package/src/component/dialog-theme-list.tsx +50 -0
- package/src/component/dialog-variant.tsx +39 -0
- package/src/component/dialog-workspace-create.tsx +308 -0
- package/src/component/dialog-workspace-file-changes.tsx +144 -0
- package/src/component/dialog-workspace-list.tsx +112 -0
- package/src/component/dialog-workspace-unavailable.tsx +69 -0
- package/src/component/error-component.tsx +240 -0
- package/src/component/logo.tsx +61 -0
- package/src/component/plugin-route-missing.tsx +14 -0
- package/src/component/prompt/autocomplete.tsx +781 -0
- package/src/component/prompt/cwd.ts +0 -0
- package/src/component/prompt/frecency.tsx +1 -0
- package/src/component/prompt/history.tsx +1 -0
- package/src/component/prompt/index.tsx +1713 -0
- package/src/component/prompt/local-attachment.ts +48 -0
- package/src/component/prompt/move.tsx +205 -0
- package/src/component/prompt/stash.tsx +1 -0
- package/src/component/prompt/workspace.tsx +137 -0
- package/src/component/register-spinner.ts +6 -0
- package/src/component/spinner.tsx +26 -0
- package/src/component/startup-loading.tsx +63 -0
- package/src/component/todo-item.tsx +32 -0
- package/src/component/use-connected.tsx +12 -0
- package/src/component/workspace-label.tsx +19 -0
- package/src/config/index.tsx +129 -0
- package/src/config/keybind.ts +471 -0
- package/src/context/args.tsx +16 -0
- package/src/context/clipboard.tsx +18 -0
- package/src/context/data.tsx +569 -0
- package/src/context/directory.ts +17 -0
- package/src/context/editor.ts +408 -0
- package/src/context/epilogue.tsx +6 -0
- package/src/context/event.ts +36 -0
- package/src/context/exit.tsx +8 -0
- package/src/context/helper.tsx +26 -0
- package/src/context/kv.tsx +66 -0
- package/src/context/local.tsx +542 -0
- package/src/context/location.tsx +14 -0
- package/src/context/path-format.tsx +24 -0
- package/src/context/permission.tsx +26 -0
- package/src/context/project.tsx +115 -0
- package/src/context/prompt.tsx +18 -0
- package/src/context/route.tsx +60 -0
- package/src/context/runtime.tsx +62 -0
- package/src/context/sdk.tsx +151 -0
- package/src/context/sync.tsx +666 -0
- package/src/context/theme.tsx +332 -0
- package/src/context/thinking.ts +67 -0
- package/src/editor-zed.ts +286 -0
- package/src/editor.ts +101 -0
- package/src/feature-plugins/builtins.ts +36 -0
- package/src/feature-plugins/home/footer.tsx +100 -0
- package/src/feature-plugins/home/tips-view.tsx +287 -0
- package/src/feature-plugins/home/tips.tsx +59 -0
- package/src/feature-plugins/sidebar/context.tsx +65 -0
- package/src/feature-plugins/sidebar/files.tsx +70 -0
- package/src/feature-plugins/sidebar/footer.tsx +98 -0
- package/src/feature-plugins/sidebar/lsp.tsx +65 -0
- package/src/feature-plugins/sidebar/mcp.tsx +97 -0
- package/src/feature-plugins/sidebar/todo.tsx +49 -0
- package/src/feature-plugins/system/diff-viewer-file-tree-utils.ts +232 -0
- package/src/feature-plugins/system/diff-viewer-file-tree.tsx +162 -0
- package/src/feature-plugins/system/diff-viewer-ui.tsx +103 -0
- package/src/feature-plugins/system/diff-viewer.tsx +1077 -0
- package/src/feature-plugins/system/notifications.ts +94 -0
- package/src/feature-plugins/system/plugins.tsx +269 -0
- package/src/feature-plugins/system/which-key.tsx +608 -0
- package/src/index.tsx +1 -0
- package/src/keymap.tsx +290 -0
- package/src/logo.ts +11 -0
- package/src/parsers-config.ts +386 -0
- package/src/plugin/adapters.tsx +355 -0
- package/src/plugin/api.ts +52 -0
- package/src/plugin/command-shim.ts +109 -0
- package/src/plugin/runtime.tsx +81 -0
- package/src/plugin/slots.tsx +65 -0
- package/src/prompt/display.ts +48 -0
- package/src/prompt/frecency.tsx +80 -0
- package/src/prompt/history.tsx +111 -0
- package/src/prompt/part.ts +29 -0
- package/src/prompt/stash.tsx +89 -0
- package/src/prompt/traits.ts +29 -0
- package/src/routes/home/session-destination.tsx +41 -0
- package/src/routes/home.tsx +95 -0
- package/src/routes/session/dialog-fork-from-timeline.tsx +76 -0
- package/src/routes/session/dialog-message.tsx +109 -0
- package/src/routes/session/dialog-subagent.tsx +26 -0
- package/src/routes/session/dialog-timeline.tsx +47 -0
- package/src/routes/session/footer.tsx +91 -0
- package/src/routes/session/index.tsx +2710 -0
- package/src/routes/session/permission.tsx +718 -0
- package/src/routes/session/question.tsx +514 -0
- package/src/routes/session/sidebar.tsx +103 -0
- package/src/routes/session/subagent-footer.tsx +132 -0
- package/src/runtime.tsx +9 -0
- package/src/terminal-win32.ts +130 -0
- package/src/theme/assets/aura.json +69 -0
- package/src/theme/assets/ayu.json +80 -0
- package/src/theme/assets/carbonfox.json +248 -0
- package/src/theme/assets/catppuccin-frappe.json +230 -0
- package/src/theme/assets/catppuccin-macchiato.json +230 -0
- package/src/theme/assets/catppuccin.json +112 -0
- package/src/theme/assets/cobalt2.json +225 -0
- package/src/theme/assets/cursor.json +249 -0
- package/src/theme/assets/dracula.json +219 -0
- package/src/theme/assets/everforest.json +241 -0
- package/src/theme/assets/flexoki.json +237 -0
- package/src/theme/assets/github.json +233 -0
- package/src/theme/assets/gruvbox.json +242 -0
- package/src/theme/assets/kanagawa.json +77 -0
- package/src/theme/assets/lucent-orng.json +234 -0
- package/src/theme/assets/material.json +235 -0
- package/src/theme/assets/matrix.json +77 -0
- package/src/theme/assets/mercury.json +252 -0
- package/src/theme/assets/monokai.json +221 -0
- package/src/theme/assets/nightowl.json +221 -0
- package/src/theme/assets/nord.json +223 -0
- package/src/theme/assets/one-dark.json +84 -0
- package/src/theme/assets/opencode.json +245 -0
- package/src/theme/assets/orng.json +249 -0
- package/src/theme/assets/osaka-jade.json +93 -0
- package/src/theme/assets/palenight.json +222 -0
- package/src/theme/assets/rosepine.json +234 -0
- package/src/theme/assets/solarized.json +223 -0
- package/src/theme/assets/synthwave84.json +226 -0
- package/src/theme/assets/tokyonight.json +243 -0
- package/src/theme/assets/vercel.json +245 -0
- package/src/theme/assets/vesper.json +218 -0
- package/src/theme/assets/zenburn.json +223 -0
- package/src/theme/index.ts +1089 -0
- package/src/ui/border.ts +21 -0
- package/src/ui/dialog-alert.tsx +66 -0
- package/src/ui/dialog-confirm.tsx +108 -0
- package/src/ui/dialog-export-options.tsx +217 -0
- package/src/ui/dialog-help.tsx +40 -0
- package/src/ui/dialog-prompt.tsx +126 -0
- package/src/ui/dialog-select.tsx +790 -0
- package/src/ui/dialog.tsx +231 -0
- package/src/ui/link.tsx +34 -0
- package/src/ui/spinner.ts +368 -0
- package/src/ui/toast.tsx +102 -0
- package/src/util/collapse-tool-output.ts +19 -0
- package/src/util/error.ts +182 -0
- package/src/util/filetype.ts +130 -0
- package/src/util/format.ts +20 -0
- package/src/util/layout.ts +25 -0
- package/src/util/locale.ts +86 -0
- package/src/util/model.ts +28 -0
- package/src/util/path.ts +12 -0
- package/src/util/persistence.ts +33 -0
- package/src/util/presentation.ts +38 -0
- package/src/util/provider-origin.ts +7 -0
- package/src/util/record.ts +3 -0
- package/src/util/renderer.ts +7 -0
- package/src/util/revert-diff.ts +18 -0
- package/src/util/scroll.ts +27 -0
- package/src/util/selection.ts +79 -0
- package/src/util/session.ts +3 -0
- package/src/util/signal.ts +51 -0
- package/src/util/system.ts +20 -0
- package/src/util/tool-display.ts +13 -0
- package/src/util/transcript.ts +112 -0
- package/sst-env.d.ts +10 -0
- package/test/app-lifecycle.test.tsx +128 -0
- package/test/cli/cmd/tui/dialog-workspace-create.test.ts +28 -0
- package/test/cli/cmd/tui/model-options.test.ts +32 -0
- package/test/cli/cmd/tui/notifications.test.ts +267 -0
- package/test/cli/cmd/tui/provider-options.test.ts +41 -0
- package/test/cli/cmd/tui/sync-fixture.tsx +70 -0
- package/test/cli/cmd/tui/sync-live-hydration.test.tsx +262 -0
- package/test/cli/cmd/tui/sync-undefined-messages.test.tsx +43 -0
- package/test/cli/cmd/tui/sync.test.tsx +65 -0
- package/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap +92 -0
- package/test/cli/tui/data.test.tsx +486 -0
- package/test/cli/tui/dialog-prompt.test.tsx +147 -0
- package/test/cli/tui/diff-viewer-file-tree.test.tsx +200 -0
- package/test/cli/tui/diff-viewer.test.tsx +268 -0
- package/test/cli/tui/inline-tool-wrap-snapshot.test.tsx +351 -0
- package/test/cli/tui/prompt-submit-race.test.ts +98 -0
- package/test/cli/tui/thinking.test.ts +36 -0
- package/test/cli/tui/use-event.test.tsx +148 -0
- package/test/clipboard.test.ts +19 -0
- package/test/component/dialog-session-list.test.ts +46 -0
- package/test/config.test.tsx +127 -0
- package/test/context/local.test.ts +22 -0
- package/test/editor.test.ts +32 -0
- package/test/feature-plugins/diff-viewer-file-tree-utils.test.ts +323 -0
- package/test/fixture/fixture.ts +13 -0
- package/test/fixture/tui-environment.tsx +32 -0
- package/test/fixture/tui-plugin.ts +36 -0
- package/test/fixture/tui-runtime.ts +12 -0
- package/test/fixture/tui-sdk.ts +109 -0
- package/test/index.test.tsx +6 -0
- package/test/keymap.test.tsx +141 -0
- package/test/plugin/runtime.test.ts +50 -0
- package/test/plugin/slots.test.tsx +38 -0
- package/test/prompt/display.test.ts +33 -0
- package/test/prompt/history.test.ts +39 -0
- package/test/prompt/jsonl.test.ts +24 -0
- package/test/prompt/local-attachment.test.ts +43 -0
- package/test/prompt/part.test.ts +53 -0
- package/test/prompt/persistence.test.ts +23 -0
- package/test/prompt/traits.test.ts +25 -0
- package/test/runtime.test.tsx +37 -0
- package/test/theme.test.ts +81 -0
- package/test/util/error.test.ts +49 -0
- package/test/util/filetype.test.ts +16 -0
- package/test/util/format.test.ts +59 -0
- package/test/util/model.test.ts +9 -0
- package/test/util/presentation.test.ts +8 -0
- package/test/util/renderer.test.ts +30 -0
- package/test/util/revert-diff.test.ts +35 -0
- package/test/util/session.test.ts +10 -0
- package/test/util/tool-display.test.ts +40 -0
- package/test/util/transcript.test.ts +421 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { batch } from "solid-js"
|
|
2
|
+
import type { Path, Workspace } from "@neurocode-ai/sdk/v2"
|
|
3
|
+
import { createStore, reconcile } from "solid-js/store"
|
|
4
|
+
import { createSimpleContext } from "./helper"
|
|
5
|
+
import { useSDK } from "./sdk"
|
|
6
|
+
|
|
7
|
+
type WorkspaceStatus = "connected" | "connecting" | "disconnected" | "error"
|
|
8
|
+
|
|
9
|
+
export const { use: useProject, provider: ProjectProvider } = createSimpleContext({
|
|
10
|
+
name: "Project",
|
|
11
|
+
init: () => {
|
|
12
|
+
const sdk = useSDK()
|
|
13
|
+
|
|
14
|
+
const defaultPath = {
|
|
15
|
+
home: "",
|
|
16
|
+
state: "",
|
|
17
|
+
config: "",
|
|
18
|
+
worktree: "",
|
|
19
|
+
directory: sdk.directory ?? "",
|
|
20
|
+
} satisfies Path
|
|
21
|
+
|
|
22
|
+
const [store, setStore] = createStore({
|
|
23
|
+
project: {
|
|
24
|
+
id: undefined as string | undefined,
|
|
25
|
+
worktree: undefined as string | undefined,
|
|
26
|
+
mainDir: undefined as string | undefined,
|
|
27
|
+
},
|
|
28
|
+
instance: {
|
|
29
|
+
path: defaultPath,
|
|
30
|
+
},
|
|
31
|
+
workspace: {
|
|
32
|
+
current: undefined as string | undefined,
|
|
33
|
+
list: [] as Workspace[],
|
|
34
|
+
status: {} as Record<string, WorkspaceStatus>,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
async function sync() {
|
|
39
|
+
const workspace = store.workspace.current
|
|
40
|
+
const [instancePath, project] = await Promise.all([
|
|
41
|
+
sdk.client.path.get({ workspace }),
|
|
42
|
+
sdk.client.project.current({ workspace }),
|
|
43
|
+
])
|
|
44
|
+
const directories = project.data?.id
|
|
45
|
+
? await sdk.client.project.directories({ projectID: project.data.id, workspace })
|
|
46
|
+
: undefined
|
|
47
|
+
batch(() => {
|
|
48
|
+
setStore("instance", "path", reconcile(instancePath.data || defaultPath))
|
|
49
|
+
setStore("project", "id", project.data?.id)
|
|
50
|
+
setStore("project", "worktree", project.data?.worktree)
|
|
51
|
+
setStore("project", "mainDir", directories?.data?.findLast((item) => item.strategy === undefined)?.directory)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function syncWorkspace() {
|
|
56
|
+
const listed = await sdk.client.experimental.workspace.list().catch(() => undefined)
|
|
57
|
+
if (!listed?.data) return
|
|
58
|
+
const status = await sdk.client.experimental.workspace.status().catch(() => undefined)
|
|
59
|
+
const next = Object.fromEntries((status?.data ?? []).map((item) => [item.workspaceID, item.status]))
|
|
60
|
+
|
|
61
|
+
batch(() => {
|
|
62
|
+
setStore("workspace", "list", reconcile(listed.data))
|
|
63
|
+
setStore("workspace", "status", reconcile(next))
|
|
64
|
+
if (!listed.data.some((item) => item.id === store.workspace.current)) {
|
|
65
|
+
setStore("workspace", "current", undefined)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
sdk.event.on("event", (event) => {
|
|
71
|
+
if (event.payload.type === "workspace.status") {
|
|
72
|
+
setStore("workspace", "status", event.payload.properties.workspaceID, event.payload.properties.status)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
data: store,
|
|
78
|
+
project() {
|
|
79
|
+
return store.project.id
|
|
80
|
+
},
|
|
81
|
+
instance: {
|
|
82
|
+
path() {
|
|
83
|
+
return store.instance.path
|
|
84
|
+
},
|
|
85
|
+
directory() {
|
|
86
|
+
return store.instance.path.directory
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
workspace: {
|
|
90
|
+
current() {
|
|
91
|
+
return store.workspace.current
|
|
92
|
+
},
|
|
93
|
+
set(next?: string | null) {
|
|
94
|
+
const workspace = next ?? undefined
|
|
95
|
+
if (store.workspace.current === workspace) return
|
|
96
|
+
setStore("workspace", "current", workspace)
|
|
97
|
+
},
|
|
98
|
+
list() {
|
|
99
|
+
return store.workspace.list
|
|
100
|
+
},
|
|
101
|
+
get(workspaceID: string) {
|
|
102
|
+
return store.workspace.list.find((item) => item.id === workspaceID)
|
|
103
|
+
},
|
|
104
|
+
status(workspaceID: string) {
|
|
105
|
+
return store.workspace.status[workspaceID]
|
|
106
|
+
},
|
|
107
|
+
statuses() {
|
|
108
|
+
return store.workspace.status
|
|
109
|
+
},
|
|
110
|
+
sync: syncWorkspace,
|
|
111
|
+
},
|
|
112
|
+
sync,
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createSimpleContext } from "./helper"
|
|
2
|
+
import type { PromptRef } from "../component/prompt"
|
|
3
|
+
|
|
4
|
+
export const { use: usePromptRef, provider: PromptRefProvider } = createSimpleContext({
|
|
5
|
+
name: "PromptRef",
|
|
6
|
+
init: () => {
|
|
7
|
+
let current: PromptRef | undefined
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
get current() {
|
|
11
|
+
return current
|
|
12
|
+
},
|
|
13
|
+
set(ref: PromptRef | undefined) {
|
|
14
|
+
current = ref
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createStore, reconcile } from "solid-js/store"
|
|
2
|
+
import { createSimpleContext } from "./helper"
|
|
3
|
+
import type { PromptInfo } from "../prompt/history"
|
|
4
|
+
import { useTuiStartup } from "./runtime"
|
|
5
|
+
|
|
6
|
+
export type HomeRoute = {
|
|
7
|
+
type: "home"
|
|
8
|
+
prompt?: PromptInfo
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type SessionRoute = {
|
|
12
|
+
type: "session"
|
|
13
|
+
sessionID: string
|
|
14
|
+
prompt?: PromptInfo
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type PluginRoute = {
|
|
18
|
+
type: "plugin"
|
|
19
|
+
id: string
|
|
20
|
+
data?: Record<string, unknown>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Route = HomeRoute | SessionRoute | PluginRoute
|
|
24
|
+
|
|
25
|
+
export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
|
|
26
|
+
name: "Route",
|
|
27
|
+
init: (props: { initialRoute?: Route }) => {
|
|
28
|
+
const startup = useTuiStartup()
|
|
29
|
+
const [store, setStore] = createStore<Route>(
|
|
30
|
+
props.initialRoute ?? initialRoute(startup.initialRoute) ?? { type: "home" },
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
get data() {
|
|
35
|
+
return store
|
|
36
|
+
},
|
|
37
|
+
navigate(route: Route) {
|
|
38
|
+
setStore(reconcile(route))
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
function initialRoute(value: unknown): Route | undefined {
|
|
45
|
+
if (!value || typeof value !== "object" || !("type" in value)) return
|
|
46
|
+
if (value.type === "home") return { type: "home" }
|
|
47
|
+
if (value.type === "session" && "sessionID" in value && typeof value.sessionID === "string") {
|
|
48
|
+
return { type: "session", sessionID: value.sessionID }
|
|
49
|
+
}
|
|
50
|
+
if (value.type === "plugin" && "id" in value && typeof value.id === "string") {
|
|
51
|
+
return { type: "plugin", id: value.id }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type RouteContext = ReturnType<typeof useRoute>
|
|
56
|
+
|
|
57
|
+
export function useRouteData<T extends Route["type"]>(type: T) {
|
|
58
|
+
const route = useRoute()
|
|
59
|
+
return route.data as Extract<Route, { type: typeof type }>
|
|
60
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createComponent, createContext, type JSX, useContext } from "solid-js"
|
|
2
|
+
|
|
3
|
+
export type TuiPaths = Readonly<{
|
|
4
|
+
cwd: string
|
|
5
|
+
home: string
|
|
6
|
+
state: string
|
|
7
|
+
worktree: string
|
|
8
|
+
}>
|
|
9
|
+
|
|
10
|
+
export type TuiTerminalEnvironment = Readonly<{
|
|
11
|
+
platform: string
|
|
12
|
+
multiplexer?: "tmux" | "screen"
|
|
13
|
+
displayServer?: "wayland" | "x11"
|
|
14
|
+
}>
|
|
15
|
+
|
|
16
|
+
export type TuiStartup = Readonly<{
|
|
17
|
+
initialRoute?: unknown
|
|
18
|
+
skipInitialLoading: boolean
|
|
19
|
+
}>
|
|
20
|
+
|
|
21
|
+
const PathsContext = createContext<TuiPaths>()
|
|
22
|
+
const TerminalEnvironmentContext = createContext<TuiTerminalEnvironment>()
|
|
23
|
+
const StartupContext = createContext<TuiStartup>()
|
|
24
|
+
|
|
25
|
+
function provider<T>(context: ReturnType<typeof createContext<T>>, value: T, children: () => JSX.Element) {
|
|
26
|
+
return createComponent(context.Provider, {
|
|
27
|
+
value: Object.freeze({ ...value }),
|
|
28
|
+
get children() {
|
|
29
|
+
return children()
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function TuiPathsProvider(props: { value: TuiPaths; children: JSX.Element }) {
|
|
35
|
+
return provider(PathsContext, props.value, () => props.children)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function TuiTerminalEnvironmentProvider(props: { value: TuiTerminalEnvironment; children: JSX.Element }) {
|
|
39
|
+
return provider(TerminalEnvironmentContext, props.value, () => props.children)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function TuiStartupProvider(props: { value: TuiStartup; children: JSX.Element }) {
|
|
43
|
+
return provider(StartupContext, props.value, () => props.children)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function required<T>(context: ReturnType<typeof createContext<T>>, name: string) {
|
|
47
|
+
const value = useContext(context)
|
|
48
|
+
if (!value) throw new Error(`${name} is missing`)
|
|
49
|
+
return value
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useTuiPaths() {
|
|
53
|
+
return required(PathsContext, "TuiPathsProvider")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useTuiTerminalEnvironment() {
|
|
57
|
+
return required(TerminalEnvironmentContext, "TuiTerminalEnvironmentProvider")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useTuiStartup() {
|
|
61
|
+
return required(StartupContext, "TuiStartupProvider")
|
|
62
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createNeurocodeClient } from "@neurocode-ai/sdk/v2"
|
|
2
|
+
import type { GlobalEvent } from "@neurocode-ai/sdk/v2"
|
|
3
|
+
import { Flag } from "@neurocode-ai/core/flag/flag"
|
|
4
|
+
import { createSimpleContext } from "./helper"
|
|
5
|
+
import { batch, onCleanup, onMount } from "solid-js"
|
|
6
|
+
|
|
7
|
+
export type EventSource = {
|
|
8
|
+
subscribe: (handler: (event: GlobalEvent) => void) => Promise<() => void>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|
12
|
+
name: "SDK",
|
|
13
|
+
init: (props: {
|
|
14
|
+
url: string
|
|
15
|
+
directory?: string
|
|
16
|
+
fetch?: typeof fetch
|
|
17
|
+
headers?: RequestInit["headers"]
|
|
18
|
+
events?: EventSource
|
|
19
|
+
}) => {
|
|
20
|
+
const abort = new AbortController()
|
|
21
|
+
let sse: AbortController | undefined
|
|
22
|
+
|
|
23
|
+
function createSDK() {
|
|
24
|
+
return createNeurocodeClient({
|
|
25
|
+
baseUrl: props.url,
|
|
26
|
+
signal: abort.signal,
|
|
27
|
+
directory: props.directory,
|
|
28
|
+
fetch: props.fetch,
|
|
29
|
+
headers: props.headers,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let sdk = createSDK()
|
|
34
|
+
|
|
35
|
+
const handlers = new Set<(event: GlobalEvent) => void>()
|
|
36
|
+
const emitter = {
|
|
37
|
+
emit(_type: "event", event: GlobalEvent) {
|
|
38
|
+
for (const handler of handlers) handler(event)
|
|
39
|
+
},
|
|
40
|
+
on(_type: "event", handler: (event: GlobalEvent) => void) {
|
|
41
|
+
handlers.add(handler)
|
|
42
|
+
return () => {
|
|
43
|
+
handlers.delete(handler)
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let queue: GlobalEvent[] = []
|
|
49
|
+
let timer: Timer | undefined
|
|
50
|
+
let last = 0
|
|
51
|
+
const retryDelay = 1000
|
|
52
|
+
const maxRetryDelay = 30000
|
|
53
|
+
|
|
54
|
+
const flush = () => {
|
|
55
|
+
if (queue.length === 0) return
|
|
56
|
+
const events = queue
|
|
57
|
+
queue = []
|
|
58
|
+
timer = undefined
|
|
59
|
+
last = Date.now()
|
|
60
|
+
// Batch all event emissions so all store updates result in a single render
|
|
61
|
+
batch(() => {
|
|
62
|
+
for (const event of events) {
|
|
63
|
+
emitter.emit("event", event)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const handleEvent = (event: GlobalEvent) => {
|
|
69
|
+
queue.push(event)
|
|
70
|
+
const elapsed = Date.now() - last
|
|
71
|
+
|
|
72
|
+
if (timer) return
|
|
73
|
+
// If we just flushed recently (within 16ms), batch this with future events
|
|
74
|
+
// Otherwise, process immediately to avoid latency
|
|
75
|
+
if (elapsed < 16) {
|
|
76
|
+
timer = setTimeout(flush, 16)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
flush()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function startSSE() {
|
|
83
|
+
sse?.abort()
|
|
84
|
+
const ctrl = new AbortController()
|
|
85
|
+
sse = ctrl
|
|
86
|
+
;(async () => {
|
|
87
|
+
let attempt = 0
|
|
88
|
+
while (true) {
|
|
89
|
+
if (abort.signal.aborted || ctrl.signal.aborted) break
|
|
90
|
+
|
|
91
|
+
const events = await sdk.global.event({
|
|
92
|
+
signal: ctrl.signal,
|
|
93
|
+
sseMaxRetryAttempts: 0,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
if (Flag.NEUROCODE_EXPERIMENTAL_WORKSPACES) {
|
|
97
|
+
// Start syncing workspaces, it's important to do this after
|
|
98
|
+
// we've started listening to events
|
|
99
|
+
await sdk.sync.start().catch(() => {})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for await (const event of events.stream) {
|
|
103
|
+
if (ctrl.signal.aborted) break
|
|
104
|
+
handleEvent(event)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (timer) clearTimeout(timer)
|
|
108
|
+
if (queue.length > 0) flush()
|
|
109
|
+
attempt += 1
|
|
110
|
+
if (abort.signal.aborted || ctrl.signal.aborted) break
|
|
111
|
+
|
|
112
|
+
// Exponential backoff
|
|
113
|
+
const backoff = Math.min(retryDelay * 2 ** (attempt - 1), maxRetryDelay)
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, backoff))
|
|
115
|
+
}
|
|
116
|
+
})().catch(() => {})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
onMount(async () => {
|
|
120
|
+
if (props.events) {
|
|
121
|
+
const unsub = await props.events.subscribe(handleEvent)
|
|
122
|
+
onCleanup(unsub)
|
|
123
|
+
|
|
124
|
+
if (Flag.NEUROCODE_EXPERIMENTAL_WORKSPACES) {
|
|
125
|
+
// Start syncing workspaces, it's important to do this after
|
|
126
|
+
// we've started listening to events
|
|
127
|
+
await sdk.sync.start().catch(() => {})
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
startSSE()
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
onCleanup(() => {
|
|
135
|
+
abort.abort()
|
|
136
|
+
sse?.abort()
|
|
137
|
+
if (timer) clearTimeout(timer)
|
|
138
|
+
handlers.clear()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
get client() {
|
|
143
|
+
return sdk
|
|
144
|
+
},
|
|
145
|
+
directory: props.directory,
|
|
146
|
+
event: emitter,
|
|
147
|
+
fetch: props.fetch ?? fetch,
|
|
148
|
+
url: props.url,
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
})
|