@schlessera/brain-ui-react 0.14.0 → 0.15.0
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/README.md +25 -0
- package/dist/components/chat/brain-markdown.js +2 -2
- package/dist/components/chat/brain-markdown.js.map +1 -1
- package/dist/components/files/file-viewer-binary.js +2 -2
- package/dist/components/files/file-viewer-binary.js.map +1 -1
- package/dist/components/files/file-viewer.js +2 -2
- package/dist/components/files/file-viewer.js.map +1 -1
- package/dist/components/graph/graph-page.js +3 -3
- package/dist/components/graph/graph-page.js.map +1 -1
- package/dist/components/images/mask-editor.js +2 -2
- package/dist/components/images/mask-editor.js.map +1 -1
- package/dist/config.d.ts +37 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +29 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/api-client.js +2 -2
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/backend.d.ts +3 -3
- package/dist/lib/backend.d.ts.map +1 -1
- package/dist/lib/backend.js +16 -15
- package/dist/lib/backend.js.map +1 -1
- package/dist/lib/share-intake.js +2 -2
- package/dist/lib/share-intake.js.map +1 -1
- package/dist/lib/share.js +2 -2
- package/dist/lib/share.js.map +1 -1
- package/dist/stores/chat-store.d.ts.map +1 -1
- package/dist/stores/chat-store.js +9 -5
- package/dist/stores/chat-store.js.map +1 -1
- package/dist/stores/file-store.js +5 -5
- package/dist/stores/file-store.js.map +1 -1
- package/dist/stores/graph-store.d.ts.map +1 -1
- package/dist/stores/graph-store.js +8 -7
- package/dist/stores/graph-store.js.map +1 -1
- package/package.json +2 -2
- package/src/components/chat/brain-markdown.tsx +2 -2
- package/src/components/files/file-viewer-binary.tsx +2 -2
- package/src/components/files/file-viewer.tsx +2 -2
- package/src/components/graph/graph-page.tsx +3 -3
- package/src/components/images/mask-editor.tsx +2 -2
- package/src/config.ts +63 -4
- package/src/index.ts +1 -1
- package/src/lib/api-client.ts +2 -2
- package/src/lib/backend.ts +16 -15
- package/src/lib/share-intake.ts +2 -2
- package/src/lib/share.ts +2 -2
- package/src/stores/chat-store.ts +8 -5
- package/src/stores/file-store.ts +5 -5
- package/src/stores/graph-store.ts +7 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { Eraser, RotateCcw, X } from "lucide-react";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { apiBase } from "../../lib/backend.js";
|
|
5
5
|
import { useMaskStore } from "../../stores/mask-store.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -43,7 +43,7 @@ export function MaskEditor({
|
|
|
43
43
|
const [error, setError] = useState<string | null>(null);
|
|
44
44
|
|
|
45
45
|
const rawUrl = request
|
|
46
|
-
? `${
|
|
46
|
+
? `${apiBase()}/files/content?path=${encodeURIComponent(request.imagePath)}&raw=1`
|
|
47
47
|
: null;
|
|
48
48
|
|
|
49
49
|
// Reset per request: a second mask on a different image must not inherit the
|
package/src/config.ts
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The package's single configuration chokepoint.
|
|
3
|
+
*
|
|
4
|
+
* `ui-server` resolves its configuration once, at the edge, in `createApp()`,
|
|
5
|
+
* and nothing deeper in that package touches the ambient environment. This is
|
|
6
|
+
* the browser-side mirror of that rule: the deployment shell calls
|
|
7
|
+
* `configureBrainUi()` once at boot, before the first render, and every module
|
|
8
|
+
* below reads the resolved values instead of reaching for build-tool globals.
|
|
9
|
+
*
|
|
10
|
+
* The rule exists because a library that reads `import.meta.env` pins its
|
|
11
|
+
* consumers to one bundler. `VITE_BACKEND_URL` used to be read here at module
|
|
12
|
+
* load, so a webpack or Next.js consumer had no way to point the client at a
|
|
13
|
+
* split-topology backend at all, and no way to discover that from the types.
|
|
14
|
+
* `scripts/check-env-access.ts` now refuses `import.meta.env` anywhere in a
|
|
15
|
+
* package's `src`, so the loophole cannot reopen.
|
|
16
|
+
*
|
|
17
|
+
* Values are a module-level singleton, matching the renderer/ASR registries.
|
|
18
|
+
* Runtime plugin-style reconfiguration is deliberately unsupported.
|
|
6
19
|
*/
|
|
7
20
|
export interface BrainUiConfig {
|
|
8
21
|
/** Product name shown on the login screen and connection status. */
|
|
@@ -13,6 +26,24 @@ export interface BrainUiConfig {
|
|
|
13
26
|
shareTitle: string;
|
|
14
27
|
/** Composer placeholder. */
|
|
15
28
|
composerPlaceholder: string;
|
|
29
|
+
/**
|
|
30
|
+
* Origin of the API/WebSocket backend, for a SPLIT topology (client and
|
|
31
|
+
* backend on different origins, e.g. a public frontend reaching its backend
|
|
32
|
+
* over a VPN). Empty — the default — means SAME-ORIGIN: API calls go to
|
|
33
|
+
* `/api` and the WebSocket derives its host from `window.location`.
|
|
34
|
+
*
|
|
35
|
+
* The shell resolves this however it likes (a Vite `VITE_*` define, a
|
|
36
|
+
* `<meta>` tag, a runtime fetch) and passes the result in. A trailing slash
|
|
37
|
+
* is stripped.
|
|
38
|
+
*/
|
|
39
|
+
backendUrl: string;
|
|
40
|
+
/**
|
|
41
|
+
* Install the `window.__chatStore` / `window.__graphStore` debug handles,
|
|
42
|
+
* which let browser automation inject fixture messages without a live agent
|
|
43
|
+
* session. The shell decides what "development" means — this package must
|
|
44
|
+
* not infer it from a bundler's DEV flag.
|
|
45
|
+
*/
|
|
46
|
+
devTools: boolean;
|
|
16
47
|
}
|
|
17
48
|
|
|
18
49
|
export const uiConfig: BrainUiConfig = {
|
|
@@ -20,8 +51,36 @@ export const uiConfig: BrainUiConfig = {
|
|
|
20
51
|
assistantName: "Brain",
|
|
21
52
|
shareTitle: "Shared from Brain",
|
|
22
53
|
composerPlaceholder: "Ask your brain anything...",
|
|
54
|
+
backendUrl: "",
|
|
55
|
+
devTools: false,
|
|
23
56
|
};
|
|
24
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Dev-handle installers, registered at module scope by the stores that own a
|
|
60
|
+
* handle. They cannot read `uiConfig.devTools` themselves: ES imports are
|
|
61
|
+
* hoisted, so a store's module body runs BEFORE the shell's
|
|
62
|
+
* `configureBrainUi()` call. Registering instead of reading lets the flag
|
|
63
|
+
* arrive late and still take effect.
|
|
64
|
+
*/
|
|
65
|
+
const devHandleInstallers: Array<() => void> = [];
|
|
66
|
+
let devHandlesInstalled = false;
|
|
67
|
+
|
|
68
|
+
function installDevHandles(): void {
|
|
69
|
+
if (devHandlesInstalled) return;
|
|
70
|
+
devHandlesInstalled = true;
|
|
71
|
+
for (const install of devHandleInstallers) install();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Register a debug handle to install if (and when) `devTools` is turned on. */
|
|
75
|
+
export function registerDevHandle(install: () => void): void {
|
|
76
|
+
devHandleInstallers.push(install);
|
|
77
|
+
if (uiConfig.devTools) install();
|
|
78
|
+
}
|
|
79
|
+
|
|
25
80
|
export function configureBrainUi(overrides: Partial<BrainUiConfig>): void {
|
|
26
81
|
Object.assign(uiConfig, overrides);
|
|
82
|
+
if (typeof overrides.backendUrl === "string") {
|
|
83
|
+
uiConfig.backendUrl = overrides.backendUrl.replace(/\/$/, "");
|
|
84
|
+
}
|
|
85
|
+
if (uiConfig.devTools) installDevHandles();
|
|
27
86
|
}
|
package/src/index.ts
CHANGED
|
@@ -72,4 +72,4 @@ export { ShareIntake } from "./components/chat/share-card.js";
|
|
|
72
72
|
|
|
73
73
|
// API surface (typed REST client + backend URL helpers).
|
|
74
74
|
export { api } from "./lib/api-client.js";
|
|
75
|
-
export {
|
|
75
|
+
export { apiBase, getWsUrl, getBackendUrl } from "./lib/backend.js";
|
package/src/lib/api-client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { apiBase } from "./backend.js";
|
|
2
2
|
import type {
|
|
3
3
|
VoiceKeytermsResponse,
|
|
4
4
|
VoiceTokenResponse,
|
|
@@ -46,7 +46,7 @@ export interface BackendInfo {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
|
|
49
|
-
const res = await fetch(`${
|
|
49
|
+
const res = await fetch(`${apiBase()}${path}`, {
|
|
50
50
|
...init,
|
|
51
51
|
headers: {
|
|
52
52
|
"Content-Type": "application/json",
|
package/src/lib/backend.ts
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Where the API and WebSocket live.
|
|
3
3
|
*
|
|
4
4
|
* Default topology is SAME-ORIGIN: the server serves the built client and the
|
|
5
5
|
* API/WS from one origin, so API calls go to "/api" and the WebSocket derives
|
|
6
|
-
* its host from window.location.
|
|
6
|
+
* its host from window.location. Nothing needs configuring.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* A SPLIT topology (client and backend on different origins) sets
|
|
9
|
+
* `backendUrl` through `configureBrainUi()`. These are functions rather than
|
|
10
|
+
* module constants on purpose: a constant would freeze the value at import
|
|
11
|
+
* time, and ES imports are hoisted, so it would always capture the default
|
|
12
|
+
* instead of what the shell configured.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
const BACKEND_URL = viteEnv?.VITE_BACKEND_URL?.replace(/\/$/, "") ?? "";
|
|
14
|
+
import { uiConfig } from "../config.js";
|
|
16
15
|
|
|
17
|
-
/** Base URL for API calls. Empty
|
|
18
|
-
export
|
|
16
|
+
/** Base URL for API calls. Empty backendUrl = same-origin "/api". */
|
|
17
|
+
export function apiBase(): string {
|
|
18
|
+
return `${uiConfig.backendUrl}/api`;
|
|
19
|
+
}
|
|
19
20
|
|
|
20
|
-
/** WebSocket URL. Derives wss/ws + host from
|
|
21
|
+
/** WebSocket URL. Derives wss/ws + host from backendUrl, else same-origin. */
|
|
21
22
|
export function getWsUrl(): string {
|
|
22
|
-
if (
|
|
23
|
-
const url = new URL("/ws",
|
|
23
|
+
if (uiConfig.backendUrl) {
|
|
24
|
+
const url = new URL("/ws", uiConfig.backendUrl);
|
|
24
25
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
25
26
|
return url.toString();
|
|
26
27
|
}
|
|
@@ -31,5 +32,5 @@ export function getWsUrl(): string {
|
|
|
31
32
|
|
|
32
33
|
/** Base URL for direct fetch calls (streaming endpoints). */
|
|
33
34
|
export function getBackendUrl(path: string): string {
|
|
34
|
-
return `${
|
|
35
|
+
return `${uiConfig.backendUrl}${path}`;
|
|
35
36
|
}
|
package/src/lib/share-intake.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type ShareIntakeResult,
|
|
5
5
|
} from "@schlessera/brain-ui-sdk/protocol";
|
|
6
6
|
import type { StoredShare } from "@schlessera/brain-ui-sdk/share-target";
|
|
7
|
-
import {
|
|
7
|
+
import { apiBase } from "./backend.js";
|
|
8
8
|
import {
|
|
9
9
|
fileToAttachment,
|
|
10
10
|
validateAttachments,
|
|
@@ -85,7 +85,7 @@ export async function uploadShare(
|
|
|
85
85
|
|
|
86
86
|
let response: Response;
|
|
87
87
|
try {
|
|
88
|
-
response = await fetchImpl(`${
|
|
88
|
+
response = await fetchImpl(`${apiBase()}/share`, {
|
|
89
89
|
method: "POST",
|
|
90
90
|
body: form,
|
|
91
91
|
});
|
package/src/lib/share.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { apiBase } from "./backend.js";
|
|
2
2
|
import type { RenderRequest } from "@schlessera/brain-ui-sdk/protocol";
|
|
3
3
|
|
|
4
4
|
export type ShareKind = "file" | "text" | "richtext";
|
|
@@ -125,7 +125,7 @@ export async function renderToFile(
|
|
|
125
125
|
req: RenderRequest,
|
|
126
126
|
filename: string
|
|
127
127
|
): Promise<File> {
|
|
128
|
-
const res = await fetch(`${
|
|
128
|
+
const res = await fetch(`${apiBase()}/render`, {
|
|
129
129
|
method: "POST",
|
|
130
130
|
headers: { "Content-Type": "application/json" },
|
|
131
131
|
body: JSON.stringify(req),
|
package/src/stores/chat-store.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
AskUserAnnotation,
|
|
7
7
|
} from "@schlessera/brain-ui-sdk/protocol";
|
|
8
8
|
import { useProviderStore } from "./provider-store.js";
|
|
9
|
+
import { registerDevHandle } from "../config.js";
|
|
9
10
|
|
|
10
11
|
export type { MessagePart };
|
|
11
12
|
|
|
@@ -631,10 +632,12 @@ export const useChatStore = create<ChatState>((set, get) => {
|
|
|
631
632
|
});
|
|
632
633
|
|
|
633
634
|
// Dev-only handle so browser automation / manual debugging can inject
|
|
634
|
-
// fixture messages without a live Claude session.
|
|
635
|
-
//
|
|
636
|
-
|
|
637
|
-
|
|
635
|
+
// fixture messages without a live Claude session. Registered rather than
|
|
636
|
+
// installed: whether this is a development build is the shell's call
|
|
637
|
+
// (`configureBrainUi({ devTools: true })`), not something a component library
|
|
638
|
+
// infers from its bundler.
|
|
639
|
+
registerDevHandle(() => {
|
|
640
|
+
if (typeof window === "undefined") return;
|
|
638
641
|
(window as unknown as { __chatStore?: typeof useChatStore }).__chatStore =
|
|
639
642
|
useChatStore;
|
|
640
|
-
}
|
|
643
|
+
});
|
package/src/stores/file-store.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
WikilinkMapResponse,
|
|
7
7
|
} from "@schlessera/brain-ui-sdk/protocol";
|
|
8
8
|
import { FILE_SIZE_CAP_BYTES } from "@schlessera/brain-ui-sdk/protocol";
|
|
9
|
-
import {
|
|
9
|
+
import { apiBase } from "../lib/backend.js";
|
|
10
10
|
import { isMermaidPath } from "../lib/mermaid.js";
|
|
11
11
|
|
|
12
12
|
export type ViewMode = "preview" | "raw";
|
|
@@ -76,7 +76,7 @@ interface FileState {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
async function fetchTree(path: string): Promise<FileEntry[]> {
|
|
79
|
-
const url = `${
|
|
79
|
+
const url = `${apiBase()}/files/tree${path ? `?path=${encodeURIComponent(path)}` : ""}`;
|
|
80
80
|
const res = await fetch(url);
|
|
81
81
|
if (!res.ok) {
|
|
82
82
|
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
@@ -87,7 +87,7 @@ async function fetchTree(path: string): Promise<FileEntry[]> {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
async function fetchContent(path: string): Promise<FileContentResponse> {
|
|
90
|
-
const res = await fetch(`${
|
|
90
|
+
const res = await fetch(`${apiBase()}/files/content?path=${encodeURIComponent(path)}`);
|
|
91
91
|
if (!res.ok) {
|
|
92
92
|
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
93
93
|
const err = new Error(body.error || `HTTP ${res.status}`);
|
|
@@ -99,7 +99,7 @@ async function fetchContent(path: string): Promise<FileContentResponse> {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
async function fetchResolve(path: string): Promise<FileResolveResponse> {
|
|
102
|
-
const res = await fetch(`${
|
|
102
|
+
const res = await fetch(`${apiBase()}/files/resolve?path=${encodeURIComponent(path)}`);
|
|
103
103
|
if (!res.ok) {
|
|
104
104
|
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
105
105
|
throw new Error(body.error || `HTTP ${res.status}`);
|
|
@@ -108,7 +108,7 @@ async function fetchResolve(path: string): Promise<FileResolveResponse> {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
async function fetchWikilinks(): Promise<WikilinkMapResponse> {
|
|
111
|
-
const res = await fetch(`${
|
|
111
|
+
const res = await fetch(`${apiBase()}/files/wikilinks`);
|
|
112
112
|
if (!res.ok) {
|
|
113
113
|
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
114
114
|
throw new Error(body.error || `HTTP ${res.status}`);
|
|
@@ -4,7 +4,8 @@ import type {
|
|
|
4
4
|
GraphSubgraphResponse,
|
|
5
5
|
GraphMaintenanceResponse,
|
|
6
6
|
} from "@schlessera/brain-ui-sdk/protocol";
|
|
7
|
-
import {
|
|
7
|
+
import { apiBase } from "../lib/backend.js";
|
|
8
|
+
import { registerDevHandle } from "../config.js";
|
|
8
9
|
import { buildQuery, mergeSubgraphs } from "../components/graph/lib/graph-helpers.js";
|
|
9
10
|
|
|
10
11
|
export type GraphMode = "clusters" | "discovery" | "local" | "maintenance";
|
|
@@ -135,7 +136,7 @@ function cachePut(key: string, value: GraphSubgraphResponse | GraphMaintenanceRe
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
async function fetchGraphJson<T>(pathAndQuery: string): Promise<T> {
|
|
138
|
-
const res = await fetch(`${
|
|
139
|
+
const res = await fetch(`${apiBase()}/graph${pathAndQuery}`);
|
|
139
140
|
if (!res.ok) {
|
|
140
141
|
const body = await res.json().catch(() => ({}) as Record<string, unknown>);
|
|
141
142
|
const err = new Error(
|
|
@@ -336,10 +337,9 @@ export const useGraphStore = create<GraphState>((set, get) => ({
|
|
|
336
337
|
}));
|
|
337
338
|
|
|
338
339
|
// Dev-only handle for exercising the view with injected fixtures (the
|
|
339
|
-
// window.__chatStore precedent)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (typeof window !== "undefined" && devEnv?.DEV) {
|
|
340
|
+
// window.__chatStore precedent), gated on the shell's `devTools` flag.
|
|
341
|
+
registerDevHandle(() => {
|
|
342
|
+
if (typeof window === "undefined") return;
|
|
343
343
|
(window as unknown as { __graphStore?: typeof useGraphStore }).__graphStore =
|
|
344
344
|
useGraphStore;
|
|
345
|
-
}
|
|
345
|
+
});
|