jamdesk 1.1.220 → 1.1.222
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/dist/__tests__/unit/custom-assets.test.js +157 -6
- package/dist/__tests__/unit/custom-assets.test.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +26 -8
- package/dist/commands/dev.js.map +1 -1
- package/dist/lib/custom-assets.d.ts +17 -0
- package/dist/lib/custom-assets.d.ts.map +1 -1
- package/dist/lib/custom-assets.js +109 -1
- package/dist/lib/custom-assets.js.map +1 -1
- package/dist/lib/svg-text.d.ts.map +1 -1
- package/dist/lib/svg-text.js +42 -5
- package/dist/lib/svg-text.js.map +1 -1
- package/package.json +1 -1
- package/vendored/app/api/chat/[project]/route.ts +31 -7
- package/vendored/components/chat/ChatBrandIcon.tsx +17 -0
- package/vendored/components/chat/ChatEmptyState.tsx +5 -5
- package/vendored/components/chat/ChatPanel.tsx +10 -7
- package/vendored/components/chat/useChatLabel.ts +9 -0
- package/vendored/components/layout/LayoutWrapper.tsx +3 -0
- package/vendored/components/mdx/Mermaid.tsx +16 -3
- package/vendored/components/mdx/MermaidInner.tsx +272 -19
- package/vendored/components/mdx/MermaidZoomModal.tsx +87 -0
- package/vendored/components/mdx/ZoomableImage.tsx +8 -1
- package/vendored/components/navigation/Header.tsx +15 -10
- package/vendored/components/navigation/Sidebar.tsx +6 -3
- package/vendored/components/navigation/TabsNav.tsx +9 -2
- package/vendored/components/search/SearchModal.tsx +6 -3
- package/vendored/hooks/useChat.ts +2 -0
- package/vendored/hooks/useChatPanel.tsx +9 -2
- package/vendored/lib/anthropic-client.ts +7 -0
- package/vendored/lib/build/error-parser.ts +17 -0
- package/vendored/lib/build/extract-tarball.ts +1 -29
- package/vendored/lib/build/validate-no-symlink-escape.ts +100 -0
- package/vendored/lib/chat-branding.ts +26 -0
- package/vendored/lib/chat-prompt.ts +2 -2
- package/vendored/lib/docs-types.ts +20 -0
- package/vendored/lib/generate-starter-questions.ts +24 -6
- package/vendored/lib/github-git-auth.ts +47 -0
- package/vendored/lib/hedge-strip.ts +1 -1
- package/vendored/lib/isr-build-executor.ts +63 -12
- package/vendored/lib/layout-helpers.tsx +13 -6
- package/vendored/lib/link-rewriter.ts +1 -1
- package/vendored/lib/mcp-auth.ts +67 -0
- package/vendored/lib/openapi/cache.ts +5 -2
- package/vendored/lib/openapi/expand-spec-pages.ts +5 -3
- package/vendored/lib/openapi/safe-ref-resolvers.ts +455 -0
- package/vendored/lib/openapi/validator.ts +46 -9
- package/vendored/lib/openapi-isr.ts +24 -11
- package/vendored/lib/page-timestamps.ts +29 -6
- package/vendored/lib/query-rewriter.ts +7 -3
- package/vendored/lib/r2-manifest.ts +2 -0
- package/vendored/lib/runtime-flags.ts +54 -0
- package/vendored/lib/svg-text.ts +41 -5
- package/vendored/schema/docs-schema.json +34 -0
- package/vendored/scripts/copy-files.cjs +90 -37
- package/vendored/scripts/openapi-safe-ref-resolvers.cjs +354 -0
- package/vendored/scripts/openapi-spec-loader.cjs +10 -5
- package/vendored/themes/base.css +48 -0
- package/vendored/workspace-package-lock.json +18 -18
- package/vendored/components/chat/ChatFAB.tsx +0 -36
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { output } from './output.js';
|
|
3
4
|
/**
|
|
4
5
|
* Whether an fs.watch event on the project root warrants a custom-CSS re-sync.
|
|
5
6
|
* Matches any .css file (covers style.css plus atomic-save temp .css names) and
|
|
@@ -13,6 +14,89 @@ export function shouldResyncCss(filename) {
|
|
|
13
14
|
return true;
|
|
14
15
|
return filename.endsWith('.css');
|
|
15
16
|
}
|
|
17
|
+
/** Re-sync trigger for the watcher: any .css or .js change (or an unknown filename). */
|
|
18
|
+
export function shouldResyncAssets(filename) {
|
|
19
|
+
if (!filename)
|
|
20
|
+
return true;
|
|
21
|
+
return filename.endsWith('.css') || filename.endsWith('.js');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Whether `target` (a real path) is strictly inside `dir` (a real path).
|
|
25
|
+
* Callers must pass realpaths so a symlink cannot hop out of the project.
|
|
26
|
+
*/
|
|
27
|
+
function isRealPathInside(dir, target) {
|
|
28
|
+
const rel = path.relative(dir, target);
|
|
29
|
+
return rel !== '' && rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether `target` (a real path inside `dir`) has a `.git` segment. When the
|
|
33
|
+
* project is the repo root, .git is inside it and its files (config, logs,
|
|
34
|
+
* FETCH_HEAD) can hold credentials, so custom JS never reads from there.
|
|
35
|
+
* Case-insensitive because macOS opens `.GIT/config` as `.git/config`.
|
|
36
|
+
*/
|
|
37
|
+
function isRealPathInGitDir(dir, target) {
|
|
38
|
+
return path.relative(dir, target).split(path.sep).some((seg) => seg.toLowerCase() === '.git');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Assemble custom JS the way copy-files.cjs does for the production build:
|
|
42
|
+
* the docs.json `styling.js` list in the given order (leading `/` stripped),
|
|
43
|
+
* else every root-level .js file (or symlink), sorted. Files are joined with a
|
|
44
|
+
* newline; missing files are skipped. Returns null when nothing was read.
|
|
45
|
+
*
|
|
46
|
+
* docs.json and the repo are untrusted, so a file is read only when its real
|
|
47
|
+
* path is inside the project's real path: a `/../` entry, a symlink out of
|
|
48
|
+
* the repo, or anything under .git is skipped with a warning. Symlinks that
|
|
49
|
+
* stay inside the project are allowed, matching the build-side assemblers.
|
|
50
|
+
*/
|
|
51
|
+
export async function collectCustomJs(projectDir, stylingJs) {
|
|
52
|
+
let files;
|
|
53
|
+
if (stylingJs) {
|
|
54
|
+
files = Array.isArray(stylingJs) ? stylingJs : [stylingJs];
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
try {
|
|
58
|
+
const entries = await fs.readdir(projectDir, { withFileTypes: true });
|
|
59
|
+
files = entries
|
|
60
|
+
.filter((d) => (d.isFile() || d.isSymbolicLink()) && d.name.endsWith('.js'))
|
|
61
|
+
.map((d) => d.name)
|
|
62
|
+
.sort();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (files.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
let realProjectDir;
|
|
71
|
+
try {
|
|
72
|
+
realProjectDir = await fs.realpath(projectDir);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const parts = [];
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
try {
|
|
80
|
+
const realPath = await fs.realpath(path.join(projectDir, file.replace(/^\//, '')));
|
|
81
|
+
if (!isRealPathInside(realProjectDir, realPath)) {
|
|
82
|
+
output.warn(`Skipping custom JS outside the project: ${file}`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (isRealPathInGitDir(realProjectDir, realPath)) {
|
|
86
|
+
output.warn(`Skipping custom JS inside .git: ${file}`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
parts.push(await fs.readFile(realPath, 'utf-8'));
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
// Missing (or vanished mid-atomic-save) files are skipped, like copy-files.cjs.
|
|
93
|
+
const code = err.code;
|
|
94
|
+
if (code !== 'ENOENT' && code !== 'EISDIR')
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return parts.length > 0 ? parts.join('\n') : null;
|
|
99
|
+
}
|
|
16
100
|
/**
|
|
17
101
|
* Collect every root-level .css file (sorted alphabetically) and concatenate
|
|
18
102
|
* their contents with a newline. Returns null when the project has no root
|
|
@@ -77,7 +161,12 @@ export async function syncCustomAssets(opts) {
|
|
|
77
161
|
const { projectDir, workspaceDir } = opts;
|
|
78
162
|
const publicDir = path.join(workspaceDir, 'public');
|
|
79
163
|
const cssDest = path.join(publicDir, 'custom.css');
|
|
80
|
-
const result = {
|
|
164
|
+
const result = {
|
|
165
|
+
cssWritten: false,
|
|
166
|
+
cssRemoved: false,
|
|
167
|
+
jsWritten: false,
|
|
168
|
+
jsRemoved: false,
|
|
169
|
+
};
|
|
81
170
|
const srcContent = await collectRootCss(projectDir);
|
|
82
171
|
if (srcContent !== null) {
|
|
83
172
|
const destContent = (await fs.pathExists(cssDest)) ? await fs.readFile(cssDest, 'utf-8') : null;
|
|
@@ -98,6 +187,25 @@ export async function syncCustomAssets(opts) {
|
|
|
98
187
|
result.cssRemoved = true;
|
|
99
188
|
}
|
|
100
189
|
}
|
|
190
|
+
// Custom JS: same compare/remove rules as CSS. The dev layout reads
|
|
191
|
+
// <workspace>/public/custom.js; only copy-files.cjs wrote it before.
|
|
192
|
+
const jsDest = path.join(publicDir, 'custom.js');
|
|
193
|
+
const jsContent = await collectCustomJs(projectDir, opts.stylingJs);
|
|
194
|
+
if (jsContent !== null) {
|
|
195
|
+
const destContent = (await fs.pathExists(jsDest)) ? await fs.readFile(jsDest, 'utf-8') : null;
|
|
196
|
+
if (jsContent !== destContent) {
|
|
197
|
+
await fs.ensureDir(publicDir);
|
|
198
|
+
await fs.writeFile(jsDest, jsContent);
|
|
199
|
+
result.jsWritten = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
const projectShipsPublicJs = await fs.pathExists(path.join(projectDir, 'public', 'custom.js'));
|
|
204
|
+
if (!projectShipsPublicJs && (await fs.pathExists(jsDest))) {
|
|
205
|
+
await fs.remove(jsDest);
|
|
206
|
+
result.jsRemoved = true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
101
209
|
return result;
|
|
102
210
|
}
|
|
103
211
|
//# sourceMappingURL=custom-assets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-assets.js","sourceRoot":"","sources":["../../src/lib/custom-assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"custom-assets.js","sourceRoot":"","sources":["../../src/lib/custom-assets.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg-text.d.ts","sourceRoot":"","sources":["../../src/lib/svg-text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,eAAO,MAAM,kBAAkB,UAAY,CAAC;AAC5C,eAAO,MAAM,gBAAgB,8IACgH,CAAC;AAC9I,eAAO,MAAM,aAAa,mEAAmE,CAAC;
|
|
1
|
+
{"version":3,"file":"svg-text.d.ts","sourceRoot":"","sources":["../../src/lib/svg-text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,eAAO,MAAM,kBAAkB,UAAY,CAAC;AAC5C,eAAO,MAAM,gBAAgB,8IACgH,CAAC;AAC9I,eAAO,MAAM,aAAa,mEAAmE,CAAC;AAwF9F;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAsF/C;AAED,KAAK,UAAU,GAAG,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC;AAEzE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE;IACrC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,GAAG,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,UAAU,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACpG,GAAG,MAAM,GAAG,IAAI,CAoBhB"}
|
package/dist/lib/svg-text.js
CHANGED
|
@@ -10,6 +10,11 @@ export const SVG_TEXT_MESSAGE = "This SVG logo has text in it that link previews
|
|
|
10
10
|
export const SVG_TEXT_LINK = 'https://jamdesk.com/docs/content/seo#customize-the-social-card';
|
|
11
11
|
// Elements whose contents are never painted, so a <text> inside them is not visible text.
|
|
12
12
|
const NON_RENDERED = new Set(['title', 'desc', 'metadata', 'script', 'style']);
|
|
13
|
+
// Entities that draw as blank space. Input is lowercased already. Any other
|
|
14
|
+
// entity (D and friends) is a visible character.
|
|
15
|
+
const BLANK_ENTITIES = [
|
|
16
|
+
' ', ' ', ' ', ' ', ' ', '	', '	', ' ', '
', ' ', '
',
|
|
17
|
+
];
|
|
13
18
|
/** Character data of a <text> body with nested tags (tspan etc.) dropped. */
|
|
14
19
|
function visibleData(body) {
|
|
15
20
|
let i = 0;
|
|
@@ -18,8 +23,17 @@ function visibleData(body) {
|
|
|
18
23
|
const end = lt === -1 ? body.length : lt;
|
|
19
24
|
for (let k = i; k < end; k++) {
|
|
20
25
|
const c = body.charCodeAt(k);
|
|
21
|
-
// Anything that is not
|
|
22
|
-
if (c
|
|
26
|
+
// Anything that is not whitespace counts. U+00A0 is blank, like its entities.
|
|
27
|
+
if (c === 38) {
|
|
28
|
+
// Fixed-size list, and k jumps past the match, so this stays linear.
|
|
29
|
+
const hit = BLANK_ENTITIES.find((e) => body.startsWith(e, k));
|
|
30
|
+
if (hit) {
|
|
31
|
+
k += hit.length - 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (c !== 32 && c !== 9 && c !== 10 && c !== 13 && c !== 12 && c !== 160)
|
|
23
37
|
return true;
|
|
24
38
|
}
|
|
25
39
|
if (lt === -1)
|
|
@@ -47,6 +61,28 @@ function findClose(lc, rawName, from) {
|
|
|
47
61
|
}
|
|
48
62
|
return -1;
|
|
49
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Index of the first '>' at or after `from` that is outside a quoted attribute
|
|
66
|
+
* value, or -1. A '>' inside "..." or '...' does not end the tag. One forward
|
|
67
|
+
* walk; an unterminated quote returns -1 (no later tag can be whole).
|
|
68
|
+
*/
|
|
69
|
+
function tagEnd(lc, from) {
|
|
70
|
+
let quote = 0;
|
|
71
|
+
for (let k = from; k < lc.length; k++) {
|
|
72
|
+
const c = lc.charCodeAt(k);
|
|
73
|
+
if (quote) {
|
|
74
|
+
if (c === quote)
|
|
75
|
+
quote = 0;
|
|
76
|
+
}
|
|
77
|
+
else if (c === 34 || c === 39) {
|
|
78
|
+
quote = c;
|
|
79
|
+
}
|
|
80
|
+
else if (c === 62) {
|
|
81
|
+
return k;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return -1;
|
|
85
|
+
}
|
|
50
86
|
// display:none on an element or its style hides the whole subtree. Design tools
|
|
51
87
|
// leave the original text in a hidden layer after outlining a copy of it.
|
|
52
88
|
// visibility:hidden is not here on purpose: a child can set it back to visible.
|
|
@@ -109,9 +145,9 @@ export function svgHasText(svg) {
|
|
|
109
145
|
n++;
|
|
110
146
|
}
|
|
111
147
|
const rawName = lc.slice(nameStart, n);
|
|
112
|
-
// Every tag is read up to its '>' and the scan resumes after it, so no
|
|
148
|
+
// Every tag is read up to its '>' (outside quotes) and the scan resumes after it, so no
|
|
113
149
|
// character is looked at twice. With no '>' left, no later tag can be whole.
|
|
114
|
-
const gt = lc
|
|
150
|
+
const gt = tagEnd(lc, n);
|
|
115
151
|
if (gt === -1)
|
|
116
152
|
return false;
|
|
117
153
|
const selfClosing = lc.charCodeAt(gt - 1) === 47;
|
|
@@ -122,7 +158,8 @@ export function svgHasText(svg) {
|
|
|
122
158
|
continue;
|
|
123
159
|
}
|
|
124
160
|
const local = rawName.slice(rawName.indexOf(':') + 1);
|
|
125
|
-
|
|
161
|
+
// HTML inside <foreignObject> is not drawn by the card either, so it counts like <text>.
|
|
162
|
+
const isText = local === 'text' || local === 'foreignobject';
|
|
126
163
|
if (isText || NON_RENDERED.has(local)) {
|
|
127
164
|
if (selfClosing) {
|
|
128
165
|
i = gt + 1;
|
package/dist/lib/svg-text.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg-text.js","sourceRoot":"","sources":["../../src/lib/svg-text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,oEAAoE;AAEpE,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAC5C,MAAM,CAAC,MAAM,gBAAgB,GAC3B,2IAA2I,CAAC;AAC9I,MAAM,CAAC,MAAM,aAAa,GAAG,gEAAgE,CAAC;AAE9F,0FAA0F;AAC1F,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAE/E,6EAA6E;AAC7E,SAAS,WAAW,CAAC,IAAY;IAC/B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAChC,MAAM,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC7B,
|
|
1
|
+
{"version":3,"file":"svg-text.js","sourceRoot":"","sources":["../../src/lib/svg-text.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.222",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Chat API Route for AI Documentation Q&A
|
|
3
3
|
*
|
|
4
4
|
* Handles POST requests for AI-powered documentation chat. Uses Upstash Vector
|
|
5
|
-
* for RAG context retrieval and Claude Haiku
|
|
5
|
+
* for RAG context retrieval and Claude Haiku 5.5 for streaming responses.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - SSE streaming (text chunks → clarification options → filtered citations → done)
|
|
@@ -25,7 +25,7 @@ import { querySimilarChunks } from '@/lib/vector-store';
|
|
|
25
25
|
import { buildSystemPrompt, isCannedIdentityReply, resolveSiteName } from '@/lib/chat-prompt';
|
|
26
26
|
import { stripHedge, isHedgeReply } from '@/lib/hedge-strip';
|
|
27
27
|
import { getDocsPath, getBaseUrl, trackChatAnalytics } from '@/lib/route-helpers';
|
|
28
|
-
import { getAnthropicClient } from '@/lib/anthropic-client';
|
|
28
|
+
import { getAnthropicClient, HAIKU_MODEL } from '@/lib/anthropic-client';
|
|
29
29
|
import { rewriteQueryForSearch } from '@/lib/query-rewriter';
|
|
30
30
|
import { fetchDocsConfig } from '@/lib/r2-content';
|
|
31
31
|
import { BCP47_LANGUAGE_RE } from '@/lib/language-utils';
|
|
@@ -37,7 +37,14 @@ import { log } from '@/lib/logger';
|
|
|
37
37
|
export const runtime = 'nodejs';
|
|
38
38
|
export const maxDuration = 30;
|
|
39
39
|
|
|
40
|
-
const CHAT_MODEL =
|
|
40
|
+
const CHAT_MODEL = HAIKU_MODEL;
|
|
41
|
+
|
|
42
|
+
/** The model turn ended without a complete tool call (refusal, max_tokens, ...). */
|
|
43
|
+
class ChatStopError extends Error {
|
|
44
|
+
constructor(readonly stopReason: string | null) {
|
|
45
|
+
super(`Model turn ended without a complete tool call (stop_reason: ${stopReason})`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
41
48
|
const RATE_LIMIT = 10;
|
|
42
49
|
const RATE_WINDOW_SECONDS = 60;
|
|
43
50
|
const MAX_HISTORY = 10;
|
|
@@ -240,8 +247,12 @@ export async function POST(
|
|
|
240
247
|
|
|
241
248
|
const stream = anthropic.messages.stream({
|
|
242
249
|
model: CHAT_MODEL,
|
|
243
|
-
|
|
244
|
-
|
|
250
|
+
// Haiku 5.5's tokenizer counts the same text as ~30% more tokens than
|
|
251
|
+
// 4.5 did, so the old 2048 cap is raised to keep the same answer length.
|
|
252
|
+
max_tokens: 3072,
|
|
253
|
+
// Haiku 5.5 400s on any non-default temperature. The forced tool_choice
|
|
254
|
+
// below also skips thinking, so effort only sets answer depth here.
|
|
255
|
+
output_config: { effort: 'medium' },
|
|
245
256
|
system: systemPrompt,
|
|
246
257
|
tools: CHAT_TOOLS,
|
|
247
258
|
tool_choice: { type: 'any' },
|
|
@@ -345,7 +356,13 @@ export async function POST(
|
|
|
345
356
|
// With tool_choice: 'any' there is exactly one tool_use block.
|
|
346
357
|
const toolUse = finalMessage.content.find(c => c.type === 'tool_use');
|
|
347
358
|
if (!toolUse || toolUse.type !== 'tool_use') {
|
|
348
|
-
throw new
|
|
359
|
+
throw new ChatStopError(finalMessage.stop_reason);
|
|
360
|
+
}
|
|
361
|
+
// A tool_use block can exist even when the turn did not finish: a
|
|
362
|
+
// max_tokens cutoff or a refusal mid-stream leaves partial input.
|
|
363
|
+
// Treating that as an answer would show a cut-off reply as complete.
|
|
364
|
+
if (finalMessage.stop_reason !== 'tool_use') {
|
|
365
|
+
throw new ChatStopError(finalMessage.stop_reason);
|
|
349
366
|
}
|
|
350
367
|
|
|
351
368
|
let sources: Citation[];
|
|
@@ -430,12 +447,19 @@ export async function POST(
|
|
|
430
447
|
userAgent: request.headers.get('user-agent') || undefined,
|
|
431
448
|
}).catch(() => {});
|
|
432
449
|
} catch (err) {
|
|
450
|
+
const stopReason = err instanceof ChatStopError ? err.stopReason : undefined;
|
|
433
451
|
log('warn', 'chat: stream interrupted', {
|
|
434
452
|
projectSlug: project,
|
|
435
453
|
toolName,
|
|
454
|
+
stopReason,
|
|
436
455
|
error: err instanceof Error ? err.message : String(err),
|
|
437
456
|
});
|
|
438
|
-
|
|
457
|
+
// Haiku 5.5 safety classifiers can decline, with no server-side
|
|
458
|
+
// fallback. Retrying gets the same refusal, so don't tell users to.
|
|
459
|
+
const message = stopReason === 'refusal'
|
|
460
|
+
? "I can't help with that question. Try rephrasing it or asking about a different topic."
|
|
461
|
+
: 'The response was interrupted. Please try again.';
|
|
462
|
+
controller.enqueue(sendEvent({ type: 'error', message }));
|
|
439
463
|
}
|
|
440
464
|
controller.close();
|
|
441
465
|
},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useChatPanelOptional } from '@/hooks/useChatPanel';
|
|
4
|
+
import { resolveChatIcon } from '@/lib/chat-branding';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The chat entry-point icon (`chat.icon`, sparkles by default). Reads the
|
|
8
|
+
* optional context so it also renders on pages outside the docs chrome.
|
|
9
|
+
*/
|
|
10
|
+
export function ChatBrandIcon({ className, sizePx }: { className?: string; sizePx: number }) {
|
|
11
|
+
const spec = useChatPanelOptional()?.chatIcon ?? resolveChatIcon(undefined);
|
|
12
|
+
if (spec.kind === 'img') {
|
|
13
|
+
// eslint-disable-next-line @next/next/no-img-element -- customer asset, size is the icon slot
|
|
14
|
+
return <img src={spec.src} alt="" width={sizePx} height={sizePx} className={`object-contain ${className ?? ''}`} />;
|
|
15
|
+
}
|
|
16
|
+
return <i className={`${spec.className} ${className ?? ''}`} aria-hidden="true" />;
|
|
17
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { memo, useMemo, useState, useCallback } from 'react';
|
|
4
|
+
import { ChatBrandIcon } from './ChatBrandIcon';
|
|
5
|
+
import { useChatLabel } from './useChatLabel';
|
|
4
6
|
|
|
5
7
|
interface ChatEmptyStateProps {
|
|
6
8
|
starterQuestions?: string[];
|
|
@@ -29,6 +31,7 @@ function starterButtonClass(question: string, selectedQuestion: string | null):
|
|
|
29
31
|
* defined in docs.json — see lib/generate-starter-questions.ts.
|
|
30
32
|
*/
|
|
31
33
|
export const ChatEmptyState = memo(function ChatEmptyState({ starterQuestions, onSelectQuestion, onTalkToHuman }: ChatEmptyStateProps) {
|
|
34
|
+
const chatLabel = useChatLabel('Ask AI');
|
|
32
35
|
const shortcutKey = useMemo(
|
|
33
36
|
() => typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.userAgent) ? '⌘' : 'Ctrl+',
|
|
34
37
|
[],
|
|
@@ -47,13 +50,10 @@ export const ChatEmptyState = memo(function ChatEmptyState({ starterQuestions, o
|
|
|
47
50
|
return (
|
|
48
51
|
<div className="flex flex-col items-center justify-center h-full px-6 py-12 text-center">
|
|
49
52
|
<div className="mb-6">
|
|
50
|
-
<
|
|
51
|
-
className="fa-solid fa-sparkles text-2xl text-[var(--color-accent)]"
|
|
52
|
-
aria-hidden="true"
|
|
53
|
-
/>
|
|
53
|
+
<ChatBrandIcon className="text-2xl text-[var(--color-accent)]" sizePx={24} />
|
|
54
54
|
</div>
|
|
55
55
|
<h2 className="text-lg font-semibold text-[var(--color-text-primary)] mb-1">
|
|
56
|
-
|
|
56
|
+
{chatLabel}
|
|
57
57
|
</h2>
|
|
58
58
|
<p className="text-sm text-[var(--color-text-secondary)] mb-6 sm:mb-1">
|
|
59
59
|
Get answers from the documentation
|
|
@@ -4,6 +4,8 @@ import { useRef, useEffect, useLayoutEffect, useCallback, useState } from 'react
|
|
|
4
4
|
import { useChat } from '@/hooks/useChat';
|
|
5
5
|
import { useBodyScrollLock } from '@/hooks/useBodyScrollLock';
|
|
6
6
|
import { useChatPanelOptional } from '@/hooks/useChatPanel';
|
|
7
|
+
import { ChatBrandIcon } from '@/components/chat/ChatBrandIcon';
|
|
8
|
+
import { useChatLabel } from '@/components/chat/useChatLabel';
|
|
7
9
|
import { ChatMessage } from './ChatMessage';
|
|
8
10
|
import { ChatInput } from './ChatInput';
|
|
9
11
|
import { ChatEmptyState } from './ChatEmptyState';
|
|
@@ -45,6 +47,7 @@ interface ChatPanelProps {
|
|
|
45
47
|
* mode="inline": Persistent column embedded in page layout (no positioning).
|
|
46
48
|
*/
|
|
47
49
|
export function ChatPanel({ isOpen, onClose, starterQuestions, chatEndpoint, mode = 'overlay' }: ChatPanelProps) {
|
|
50
|
+
const chatLabel = useChatLabel('AI Chat');
|
|
48
51
|
const { messages, sendMessage, isLoading, abort, retry, clearChat, error, markClarificationSelected } = useChat(chatEndpoint);
|
|
49
52
|
|
|
50
53
|
// Search (and any external trigger) hands a question to chat via the
|
|
@@ -200,13 +203,13 @@ export function ChatPanel({ isOpen, onClose, starterQuestions, chatEndpoint, mod
|
|
|
200
203
|
<>
|
|
201
204
|
{/* Header */}
|
|
202
205
|
<div className="flex items-center justify-between h-14 px-4 border-b border-[var(--color-border)] flex-shrink-0" style={{ borderBottomWidth: '1px' }}>
|
|
203
|
-
<div className="flex items-center gap-2">
|
|
204
|
-
<
|
|
205
|
-
<span className="text-sm font-semibold text-[var(--color-text-primary)]">
|
|
206
|
-
|
|
206
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
207
|
+
<ChatBrandIcon className="text-sm text-[var(--color-accent)] shrink-0" sizePx={14} />
|
|
208
|
+
<span title={chatLabel} className="text-sm font-semibold text-[var(--color-text-primary)] truncate">
|
|
209
|
+
{chatLabel}
|
|
207
210
|
</span>
|
|
208
211
|
</div>
|
|
209
|
-
<div className="flex items-center gap-1">
|
|
212
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
210
213
|
{/* R8: only when no message is mid-stream, so the transcript captures
|
|
211
214
|
the post-rewrite (absolute-URL) link form, not slug placeholders. */}
|
|
212
215
|
{hasMessages && !messages.some((m) => m.isStreaming) && (
|
|
@@ -303,7 +306,7 @@ export function ChatPanel({ isOpen, onClose, starterQuestions, chatEndpoint, mod
|
|
|
303
306
|
if (isInline) {
|
|
304
307
|
return (
|
|
305
308
|
<div
|
|
306
|
-
aria-label=
|
|
309
|
+
aria-label={chatLabel}
|
|
307
310
|
className="flex flex-col h-full"
|
|
308
311
|
style={{ backgroundColor: 'var(--color-bg-primary)', touchAction: 'manipulation', fontFamily: 'var(--font-primary, inherit)' }}
|
|
309
312
|
>
|
|
@@ -328,7 +331,7 @@ export function ChatPanel({ isOpen, onClose, starterQuestions, chatEndpoint, mod
|
|
|
328
331
|
<div
|
|
329
332
|
ref={overlayPanelRef}
|
|
330
333
|
role="dialog"
|
|
331
|
-
aria-label=
|
|
334
|
+
aria-label={chatLabel}
|
|
332
335
|
inert={!isOpen ? true : undefined}
|
|
333
336
|
aria-modal={isOpen || undefined}
|
|
334
337
|
data-open={isOpen}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useChatPanelOptional } from '@/hooks/useChatPanel';
|
|
4
|
+
import { resolveChatLabel } from '@/lib/chat-branding';
|
|
5
|
+
|
|
6
|
+
/** `chat.label` if configured, else the call site's own default text. */
|
|
7
|
+
export function useChatLabel(fallback: string): string {
|
|
8
|
+
return resolveChatLabel(useChatPanelOptional()?.chatLabel, fallback);
|
|
9
|
+
}
|
|
@@ -11,6 +11,7 @@ import { BannerBar } from '@/components/layout/BannerBar';
|
|
|
11
11
|
import { ChatPanelProvider, useChatPanel } from '@/hooks/useChatPanel';
|
|
12
12
|
import { useHashNavigation } from '@/hooks/useHashNavigation';
|
|
13
13
|
import { useWheelScrollChaining } from '@/hooks/useWheelScrollChaining';
|
|
14
|
+
import { resolveChatIcon } from '@/lib/chat-branding';
|
|
14
15
|
import { useLinkPrefix } from '@/lib/link-prefix-context';
|
|
15
16
|
import type { DocsConfig } from '@/lib/docs-types';
|
|
16
17
|
import { getTheme, type ThemeName } from '@/themes';
|
|
@@ -122,6 +123,8 @@ export function LayoutWrapper({ config, children, embed }: LayoutWrapperProps) {
|
|
|
122
123
|
chatEnabled={chatEnabled}
|
|
123
124
|
chatEndpoint={chatEndpoint}
|
|
124
125
|
starterQuestions={config.chat?.starterQuestions}
|
|
126
|
+
chatLabel={config.chat?.label}
|
|
127
|
+
chatIcon={resolveChatIcon(config.chat?.icon, config.assetVersion)}
|
|
125
128
|
>
|
|
126
129
|
{content}
|
|
127
130
|
</ChatPanelProvider>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import dynamic from 'next/dynamic';
|
|
4
4
|
import { useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { readCachedHeight } from './mermaidCache';
|
|
6
|
+
import { isZoomDisabled } from './MermaidZoomModal';
|
|
6
7
|
import { useIsomorphicLayoutEffect } from '../../hooks/useIsomorphicLayoutEffect';
|
|
7
8
|
|
|
8
9
|
// Neutral floor reserved in the SSR markup itself (present on the browser's
|
|
@@ -32,9 +33,21 @@ interface MermaidProps {
|
|
|
32
33
|
className?: string;
|
|
33
34
|
/** Minimum width for the SVG diagram (e.g., "700px", "100%") */
|
|
34
35
|
minWidth?: string;
|
|
36
|
+
/** Turns off click to expand. All three spellings: see isZoomDisabled. */
|
|
37
|
+
noZoom?: boolean | string;
|
|
38
|
+
nozoom?: boolean | string;
|
|
39
|
+
'data-no-zoom'?: boolean | string;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
export function Mermaid({
|
|
42
|
+
export function Mermaid({
|
|
43
|
+
children,
|
|
44
|
+
className,
|
|
45
|
+
minWidth,
|
|
46
|
+
noZoom,
|
|
47
|
+
nozoom,
|
|
48
|
+
'data-no-zoom': dataNoZoom,
|
|
49
|
+
}: MermaidProps) {
|
|
50
|
+
const zoomDisabled = isZoomDisabled(noZoom, nozoom, dataNoZoom);
|
|
38
51
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
39
52
|
|
|
40
53
|
// Server + first (hydration) render emit the constant default — identical
|
|
@@ -93,11 +106,11 @@ export function Mermaid({ children, className, minWidth }: MermaidProps) {
|
|
|
93
106
|
// would memoize an element closed over a stale component identity.
|
|
94
107
|
const inner = useMemo(
|
|
95
108
|
() => (
|
|
96
|
-
<MermaidInner className={className} minWidth={minWidth}>
|
|
109
|
+
<MermaidInner className={className} minWidth={minWidth} noZoom={zoomDisabled}>
|
|
97
110
|
{children}
|
|
98
111
|
</MermaidInner>
|
|
99
112
|
),
|
|
100
|
-
[children, className, minWidth]
|
|
113
|
+
[children, className, minWidth, zoomDisabled]
|
|
101
114
|
);
|
|
102
115
|
|
|
103
116
|
return (
|