@schlessera/brain-ui-server 0.9.0 → 0.10.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.
@@ -1,6 +1,6 @@
1
1
  import { Hono } from "hono";
2
2
  import { z } from "zod";
3
- import { buildHtmlDocument } from "../render/template.js";
3
+ import { buildHtmlDocument } from "@schlessera/brain-render-template";
4
4
  const MAX_CONTENT_BYTES = 512 * 1024;
5
5
  const bodySchema = z.object({
6
6
  content: z.string().min(1).max(MAX_CONTENT_BYTES),
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/routes/render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/routes/render.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schlessera/brain-ui-server",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "brain-kit chat-UI server: Hono app factory, WebSocket turn coordinator, auth (password/passkeys/tailscale/proxy), session catalog, and brain/files/voice routes",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -45,12 +45,12 @@
45
45
  "prepublishOnly": "bun ../../scripts/check-dist.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@schlessera/brain-backend-claude": "0.9.0",
49
- "@schlessera/brain-ui-sdk": "0.9.0",
48
+ "@schlessera/brain-backend-claude": "0.10.0",
49
+ "@schlessera/brain-render-template": "0.10.0",
50
+ "@schlessera/brain-ui-sdk": "0.10.0",
50
51
  "@simplewebauthn/server": "^13.3.2",
51
52
  "hono": "^4",
52
53
  "ignore": "^7.0.5",
53
- "marked": "^18.0.4",
54
54
  "zod": "^4.4.3"
55
55
  },
56
56
  "peerDependencies": {
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
2
  import { z } from "zod";
3
3
  import type { RenderRequest } from "@schlessera/brain-ui-sdk/protocol";
4
- import { buildHtmlDocument } from "../render/template.js";
4
+ import { buildHtmlDocument } from "@schlessera/brain-render-template";
5
5
 
6
6
  const MAX_CONTENT_BYTES = 512 * 1024;
7
7
 
@@ -1,7 +0,0 @@
1
- import type { RenderContentType } from "@schlessera/brain-ui-sdk/protocol";
2
- export declare function buildHtmlDocument(opts: {
3
- content: string;
4
- contentType: RenderContentType;
5
- title?: string;
6
- }): string;
7
- //# sourceMappingURL=template.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/render/template.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAwG3E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAiBT"}
@@ -1,115 +0,0 @@
1
- import { marked } from "marked";
2
- marked.setOptions({ gfm: true, breaks: false });
3
- const STYLES = `
4
- :root {
5
- color-scheme: light;
6
- --bg: #ffffff;
7
- --fg: #1f2937;
8
- --muted: #6b7280;
9
- --border: #e5e7eb;
10
- --surface: #f8fafc;
11
- --primary: #2563eb;
12
- --code-bg: #f3f4f6;
13
- }
14
- * { box-sizing: border-box; }
15
- html, body { margin: 0; padding: 0; background: var(--bg); }
16
- body {
17
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, "Helvetica Neue", Arial, sans-serif, "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji";
18
- color: var(--fg);
19
- font-size: 16px;
20
- line-height: 1.6;
21
- padding: 28px 32px;
22
- word-wrap: break-word;
23
- }
24
- h1, h2, h3, h4, h5, h6 { font-weight: 600; line-height: 1.25; margin: 1.4em 0 0.6em; color: var(--fg); }
25
- h1 { font-size: 1.75em; border-bottom: 1px solid var(--border); padding-bottom: 0.3em; }
26
- h2 { font-size: 1.4em; border-bottom: 1px solid var(--border); padding-bottom: 0.25em; }
27
- h3 { font-size: 1.2em; }
28
- h4 { font-size: 1.05em; }
29
- p { margin: 0.7em 0; }
30
- a { color: var(--primary); text-decoration: none; }
31
- a:hover { text-decoration: underline; }
32
- ul, ol { padding-left: 1.6em; margin: 0.6em 0; }
33
- li { margin: 0.2em 0; }
34
- blockquote {
35
- margin: 0.8em 0;
36
- padding: 0.4em 1em;
37
- border-left: 3px solid var(--border);
38
- color: var(--muted);
39
- background: var(--surface);
40
- border-radius: 0 6px 6px 0;
41
- }
42
- code {
43
- font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
44
- font-size: 0.9em;
45
- background: var(--code-bg);
46
- padding: 0.15em 0.35em;
47
- border-radius: 4px;
48
- }
49
- pre {
50
- background: var(--code-bg);
51
- border: 1px solid var(--border);
52
- border-radius: 8px;
53
- padding: 0.8em 1em;
54
- overflow-x: auto;
55
- font-size: 0.88em;
56
- line-height: 1.5;
57
- }
58
- pre code { background: transparent; padding: 0; border-radius: 0; }
59
- hr { border: 0; border-top: 1px solid var(--border); margin: 1.6em 0; }
60
- table { border-collapse: collapse; margin: 0.8em 0; width: 100%; }
61
- th, td { border: 1px solid var(--border); padding: 0.45em 0.7em; text-align: left; }
62
- th { background: var(--surface); font-weight: 600; }
63
- img { max-width: 100%; height: auto; }
64
- /* Mermaid diagrams arrive pre-rendered as inline SVG (the page runs no JS) */
65
- .mermaid-figure { margin: 1em 0; text-align: center; }
66
- .mermaid-figure svg { max-width: 100%; height: auto; }
67
- .remote-image {
68
- display: inline-block; padding: 2px 8px; border: 1px dashed #b0b0b0;
69
- border-radius: 4px; color: #6b6b6b; font-size: 0.9em;
70
- }
71
- /* Trim trailing whitespace at the bottom so screenshots crop tightly */
72
- body > *:last-child { margin-bottom: 0; }
73
- body > *:first-child { margin-top: 0; }
74
- `;
75
- function escapeHtml(s) {
76
- return s
77
- .replace(/&/g, "&")
78
- .replace(/</g, "&lt;")
79
- .replace(/>/g, "&gt;")
80
- .replace(/"/g, "&quot;")
81
- .replace(/'/g, "&#39;");
82
- }
83
- /**
84
- * The renderer denies the page all network access (see
85
- * @schlessera/brain-render-puppeteer), so a remote `<img>` would silently render
86
- * as a broken-image box. Replace those with a visible, honest placeholder —
87
- * inlined `data:` images still render normally.
88
- */
89
- function placeholderRemoteImages(html) {
90
- return html.replace(/<img\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi, (tag, src) => {
91
- if (/^data:/i.test(src))
92
- return tag;
93
- const alt = /\balt\s*=\s*["']([^"']*)["']/i.exec(tag)?.[1];
94
- const label = alt ? escapeHtml(alt) : "remote image";
95
- return `<span class="remote-image">[${label} — not embedded]</span>`;
96
- });
97
- }
98
- export function buildHtmlDocument(opts) {
99
- const rendered = opts.contentType === "markdown"
100
- ? marked.parse(opts.content, { async: false })
101
- : opts.content;
102
- const inner = placeholderRemoteImages(rendered);
103
- const title = opts.title ?? "Shared from Brain";
104
- return `<!doctype html>
105
- <html lang="en">
106
- <head>
107
- <meta charset="utf-8" />
108
- <meta name="viewport" content="width=device-width,initial-scale=1" />
109
- <title>${escapeHtml(title)}</title>
110
- <style>${STYLES}</style>
111
- </head>
112
- <body>${inner}</body>
113
- </html>`;
114
- }
115
- //# sourceMappingURL=template.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/render/template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,MAAM,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AAEhD,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuEd,CAAC;AAEF,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC;SACL,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,SAAS,uBAAuB,CAAC,IAAY;IAC3C,OAAO,IAAI,CAAC,OAAO,CACjB,iDAAiD,EACjD,CAAC,GAAG,EAAE,GAAW,EAAE,EAAE;QACnB,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;QACpC,MAAM,GAAG,GAAG,+BAA+B,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;QACrD,OAAO,+BAA+B,KAAK,yBAAyB,CAAC;IACvE,CAAC,CACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAIjC;IACC,MAAM,QAAQ,GACZ,IAAI,CAAC,WAAW,KAAK,UAAU;QAC7B,CAAC,CAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAY;QAC1D,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;IACnB,MAAM,KAAK,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,mBAAmB,CAAC;IAChD,OAAO;;;;;WAKE,UAAU,CAAC,KAAK,CAAC;WACjB,MAAM;;QAET,KAAK;QACL,CAAC;AACT,CAAC"}
@@ -1,127 +0,0 @@
1
- import { marked } from "marked";
2
- import type { RenderContentType } from "@schlessera/brain-ui-sdk/protocol";
3
-
4
- marked.setOptions({ gfm: true, breaks: false });
5
-
6
- const STYLES = `
7
- :root {
8
- color-scheme: light;
9
- --bg: #ffffff;
10
- --fg: #1f2937;
11
- --muted: #6b7280;
12
- --border: #e5e7eb;
13
- --surface: #f8fafc;
14
- --primary: #2563eb;
15
- --code-bg: #f3f4f6;
16
- }
17
- * { box-sizing: border-box; }
18
- html, body { margin: 0; padding: 0; background: var(--bg); }
19
- body {
20
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, "Helvetica Neue", Arial, sans-serif, "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji";
21
- color: var(--fg);
22
- font-size: 16px;
23
- line-height: 1.6;
24
- padding: 28px 32px;
25
- word-wrap: break-word;
26
- }
27
- h1, h2, h3, h4, h5, h6 { font-weight: 600; line-height: 1.25; margin: 1.4em 0 0.6em; color: var(--fg); }
28
- h1 { font-size: 1.75em; border-bottom: 1px solid var(--border); padding-bottom: 0.3em; }
29
- h2 { font-size: 1.4em; border-bottom: 1px solid var(--border); padding-bottom: 0.25em; }
30
- h3 { font-size: 1.2em; }
31
- h4 { font-size: 1.05em; }
32
- p { margin: 0.7em 0; }
33
- a { color: var(--primary); text-decoration: none; }
34
- a:hover { text-decoration: underline; }
35
- ul, ol { padding-left: 1.6em; margin: 0.6em 0; }
36
- li { margin: 0.2em 0; }
37
- blockquote {
38
- margin: 0.8em 0;
39
- padding: 0.4em 1em;
40
- border-left: 3px solid var(--border);
41
- color: var(--muted);
42
- background: var(--surface);
43
- border-radius: 0 6px 6px 0;
44
- }
45
- code {
46
- font-family: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
47
- font-size: 0.9em;
48
- background: var(--code-bg);
49
- padding: 0.15em 0.35em;
50
- border-radius: 4px;
51
- }
52
- pre {
53
- background: var(--code-bg);
54
- border: 1px solid var(--border);
55
- border-radius: 8px;
56
- padding: 0.8em 1em;
57
- overflow-x: auto;
58
- font-size: 0.88em;
59
- line-height: 1.5;
60
- }
61
- pre code { background: transparent; padding: 0; border-radius: 0; }
62
- hr { border: 0; border-top: 1px solid var(--border); margin: 1.6em 0; }
63
- table { border-collapse: collapse; margin: 0.8em 0; width: 100%; }
64
- th, td { border: 1px solid var(--border); padding: 0.45em 0.7em; text-align: left; }
65
- th { background: var(--surface); font-weight: 600; }
66
- img { max-width: 100%; height: auto; }
67
- /* Mermaid diagrams arrive pre-rendered as inline SVG (the page runs no JS) */
68
- .mermaid-figure { margin: 1em 0; text-align: center; }
69
- .mermaid-figure svg { max-width: 100%; height: auto; }
70
- .remote-image {
71
- display: inline-block; padding: 2px 8px; border: 1px dashed #b0b0b0;
72
- border-radius: 4px; color: #6b6b6b; font-size: 0.9em;
73
- }
74
- /* Trim trailing whitespace at the bottom so screenshots crop tightly */
75
- body > *:last-child { margin-bottom: 0; }
76
- body > *:first-child { margin-top: 0; }
77
- `;
78
-
79
- function escapeHtml(s: string): string {
80
- return s
81
- .replace(/&/g, "&amp;")
82
- .replace(/</g, "&lt;")
83
- .replace(/>/g, "&gt;")
84
- .replace(/"/g, "&quot;")
85
- .replace(/'/g, "&#39;");
86
- }
87
-
88
- /**
89
- * The renderer denies the page all network access (see
90
- * @schlessera/brain-render-puppeteer), so a remote `<img>` would silently render
91
- * as a broken-image box. Replace those with a visible, honest placeholder —
92
- * inlined `data:` images still render normally.
93
- */
94
- function placeholderRemoteImages(html: string): string {
95
- return html.replace(
96
- /<img\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi,
97
- (tag, src: string) => {
98
- if (/^data:/i.test(src)) return tag;
99
- const alt = /\balt\s*=\s*["']([^"']*)["']/i.exec(tag)?.[1];
100
- const label = alt ? escapeHtml(alt) : "remote image";
101
- return `<span class="remote-image">[${label} — not embedded]</span>`;
102
- }
103
- );
104
- }
105
-
106
- export function buildHtmlDocument(opts: {
107
- content: string;
108
- contentType: RenderContentType;
109
- title?: string;
110
- }): string {
111
- const rendered =
112
- opts.contentType === "markdown"
113
- ? (marked.parse(opts.content, { async: false }) as string)
114
- : opts.content;
115
- const inner = placeholderRemoteImages(rendered);
116
- const title = opts.title ?? "Shared from Brain";
117
- return `<!doctype html>
118
- <html lang="en">
119
- <head>
120
- <meta charset="utf-8" />
121
- <meta name="viewport" content="width=device-width,initial-scale=1" />
122
- <title>${escapeHtml(title)}</title>
123
- <style>${STYLES}</style>
124
- </head>
125
- <body>${inner}</body>
126
- </html>`;
127
- }