radiant-docs 0.1.62 → 0.1.64

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.
@@ -10,18 +10,19 @@
10
10
  "preview": "astro preview",
11
11
  "astro": "astro",
12
12
  "search:index": "astro build && node scripts/remove-assistant-for-non-pro.mjs && node scripts/generate-og-metadata.mjs && node scripts/generate-og-images.mjs && node scripts/stamp-og-image-versions.mjs && node scripts/stamp-image-versions.mjs && pagefind --site dist && node scripts/stamp-pagefind-runtime-version.mjs && node scripts/generate-proxy-allowed-origins.mjs && node scripts/generate-robots-txt.mjs && cp -r dist/pagefind public/",
13
- "shiki:publish-platform-assets": "node scripts/publish-shiki-platform-assets.mjs",
14
- "shiki:publish-platform-assets:dev": "node scripts/publish-shiki-platform-assets.mjs --env-file .env --check-current",
15
- "shiki:publish-platform-assets:dev:dry-run": "node scripts/publish-shiki-platform-assets.mjs --env-file .env --check-current --dry-run"
13
+ "shiki:assets:update": "node scripts/publish-shiki-platform-assets.mjs --prepare-only --write-current",
14
+ "shiki:assets:upload": "node scripts/publish-shiki-platform-assets.mjs --check-current",
15
+ "shiki:assets:upload:dev": "node scripts/publish-shiki-platform-assets.mjs --env-file .env --check-current"
16
16
  },
17
17
  "dependencies": {
18
18
  "@alpinejs/collapse": "^3.15.2",
19
19
  "@alpinejs/focus": "^3.15.3",
20
20
  "@alpinejs/persist": "^3.15.2",
21
- "@astrojs/alpinejs": "^0.4.9",
22
- "@astrojs/mdx": "^4.3.12",
23
- "@astrojs/preact": "^4.1.3",
24
- "@astrojs/sitemap": "^3.7.2",
21
+ "@astrojs/alpinejs": "^1.0.0",
22
+ "@astrojs/markdown-remark": "^7.2.0",
23
+ "@astrojs/mdx": "^7.0.0",
24
+ "@astrojs/preact": "^6.0.0",
25
+ "@astrojs/sitemap": "^3.7.3",
25
26
  "@aws-sdk/client-s3": "^3.964.0",
26
27
  "@fontsource-variable/geist": "^5.2.9",
27
28
  "@fontsource-variable/geist-mono": "^5.2.8",
@@ -37,22 +38,21 @@
37
38
  "@iconify-json/lucide": "^1.2.79",
38
39
  "@iconify-json/simple-icons": "^1.2.69",
39
40
  "@iconify/react": "^6.0.2",
40
- "@jongwooo/prism-theme-github": "^1.15.1",
41
41
  "@paper-design/shaders": "^0.0.76",
42
42
  "@preact/preset-vite": "^2.10.3",
43
43
  "@readme/oas-to-snippet": "^29.3.0",
44
44
  "@resvg/resvg-js": "^2.6.2",
45
- "@shikijs/core": "3.23.0",
46
- "@shikijs/engine-javascript": "3.23.0",
47
- "@shikijs/langs": "3.23.0",
48
- "@shikijs/themes": "3.23.0",
45
+ "@shikijs/core": "4.2.0",
46
+ "@shikijs/engine-javascript": "4.2.0",
47
+ "@shikijs/langs": "4.2.0",
48
+ "@shikijs/themes": "4.2.0",
49
49
  "@stoplight/spectral-core": "^1.20.0",
50
50
  "@stoplight/spectral-rulesets": "^1.22.0",
51
51
  "@tailwindcss/typography": "^0.5.19",
52
52
  "@tailwindcss/vite": "^4.1.17",
53
53
  "@xt0rted/expressive-code-file-icons": "^1.0.0",
54
54
  "alpinejs": "^3.15.2",
55
- "astro": "^5.16.4",
55
+ "astro": "^7.0.2",
56
56
  "astro-icon": "^1.1.5",
57
57
  "fontkit": "^2.0.4",
58
58
  "fs-extra": "^11.3.3",
@@ -62,23 +62,19 @@
62
62
  "oas": "^28.7.0",
63
63
  "openapi-sampler": "^1.6.2",
64
64
  "preact": "^10.29.0",
65
- "prism-themes": "^1.9.0",
66
- "prismjs": "^1.30.0",
67
- "radiant-docs-validator": "^0.1.28",
65
+ "radiant-docs-validator": "^0.1.29",
68
66
  "rehype-autolink-headings": "^7.1.0",
69
67
  "rehype-slug": "^6.0.0",
70
68
  "remark-gfm": "^4.0.1",
69
+ "shiki": "4.2.0",
71
70
  "simple-git": "^3.30.0",
72
- "shiki": "3.23.0",
73
71
  "tailwindcss": "^4.2.2",
74
72
  "wawoff2": "^2.0.1",
75
- "yaml": "^2.8.2",
76
- "zod": "^3.25.76"
73
+ "yaml": "^2.8.2"
77
74
  },
78
75
  "devDependencies": {
79
76
  "@types/fs-extra": "^11.0.4",
80
77
  "@types/mime-types": "^3.0.1",
81
- "@types/prismjs": "^1.26.5",
82
78
  "esbuild": "^0.25.12",
83
79
  "pagefind": "^1.4.0",
84
80
  "tsx": "^4.21.0"
@@ -1,201 +1,24 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import YAML from "yaml";
3
+ import { buildAllowedOrigins } from "../src/lib/proxy-allowed-origins.mjs";
4
4
 
5
5
  const CWD = process.cwd();
6
- const DOCS_DIR = path.join(CWD, "src/content/docs");
7
- const DOCS_CONFIG_PATH = path.join(DOCS_DIR, "docs.json");
8
6
  const OUTPUT_DIR = path.join(CWD, ".radiant");
9
7
  const OUTPUT_FILE = path.join(OUTPUT_DIR, "proxy-allowed-origins.json");
10
8
 
11
- function isRecord(value) {
12
- return typeof value === "object" && value !== null;
13
- }
14
-
15
- function isHttpUrl(value) {
16
- return /^https?:\/\//i.test(String(value).trim());
17
- }
18
-
19
- function collectOpenApiSourcesFromNavigation(navigationNode, target) {
20
- if (!isRecord(navigationNode)) {
21
- return;
22
- }
23
-
24
- const openApiConfig = navigationNode.openapi;
25
- if (typeof openApiConfig === "string") {
26
- const trimmed = openApiConfig.trim();
27
- if (trimmed) {
28
- target.add(trimmed);
29
- }
30
- } else if (
31
- isRecord(openApiConfig) &&
32
- typeof openApiConfig.source === "string"
33
- ) {
34
- const trimmed = openApiConfig.source.trim();
35
- if (trimmed) {
36
- target.add(trimmed);
37
- }
38
- }
39
-
40
- const menu = navigationNode.menu;
41
- if (isRecord(menu) && Array.isArray(menu.items)) {
42
- for (const menuItem of menu.items) {
43
- if (!isRecord(menuItem)) {
44
- continue;
45
- }
46
- collectOpenApiSourcesFromNavigation(menuItem, target);
47
- }
48
- }
49
-
50
- const tabs = navigationNode.tabs;
51
- if (isRecord(tabs) && Array.isArray(tabs.items)) {
52
- for (const tabItem of tabs.items) {
53
- if (!isRecord(tabItem)) {
54
- continue;
55
- }
56
- collectOpenApiSourcesFromNavigation(tabItem, target);
57
- }
58
- }
59
- }
60
-
61
- function resolveServerTemplateUrl(rawUrl, rawVariables) {
62
- let unresolved = false;
63
- const variables = isRecord(rawVariables) ? rawVariables : null;
64
-
65
- const resolved = rawUrl.replace(/\{([^}]+)\}/g, (_match, tokenName) => {
66
- if (!variables) {
67
- unresolved = true;
68
- return "";
69
- }
70
-
71
- const variableConfig = variables[tokenName];
72
- if (
73
- !isRecord(variableConfig) ||
74
- typeof variableConfig.default !== "string"
75
- ) {
76
- unresolved = true;
77
- return "";
78
- }
79
-
80
- const defaultValue = variableConfig.default.trim();
81
- if (!defaultValue) {
82
- unresolved = true;
83
- return "";
84
- }
85
-
86
- return defaultValue;
87
- });
88
-
89
- if (unresolved) {
90
- return null;
91
- }
92
-
93
- return resolved;
94
- }
95
-
96
- function normalizeAllowedOrigin(rawUrl) {
97
- let parsed;
98
- try {
99
- parsed = new URL(rawUrl);
100
- } catch {
101
- return null;
102
- }
103
-
104
- if (parsed.protocol !== "https:") {
105
- return null;
106
- }
107
-
108
- if (!parsed.hostname || parsed.username || parsed.password) {
109
- return null;
110
- }
111
-
112
- return parsed.origin.toLowerCase();
113
- }
114
-
115
- async function loadOpenApiSpec(source) {
116
- let fileContent;
117
-
118
- if (isHttpUrl(source)) {
119
- const response = await fetch(source);
120
- if (!response.ok) {
121
- throw new Error(
122
- `Failed to fetch OpenAPI spec (${response.status} ${response.statusText})`,
123
- );
124
- }
125
- fileContent = await response.text();
126
- } else {
127
- const absolutePath = path.join(DOCS_DIR, source);
128
- fileContent = await fs.readFile(absolutePath, "utf8");
129
- }
130
-
131
- const trimmed = fileContent.trim();
132
- if (trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html")) {
133
- throw new Error("OpenAPI source returned HTML instead of JSON or YAML");
134
- }
135
-
136
- try {
137
- return JSON.parse(fileContent);
138
- } catch {
139
- return YAML.parse(fileContent);
140
- }
141
- }
142
-
143
- async function buildAllowedOrigins() {
144
- const docsConfigRaw = await fs.readFile(DOCS_CONFIG_PATH, "utf8");
145
- const docsConfig = JSON.parse(docsConfigRaw);
146
-
147
- if (!isRecord(docsConfig) || !isRecord(docsConfig.navigation)) {
148
- return [];
149
- }
150
-
151
- const sources = new Set();
152
- collectOpenApiSourcesFromNavigation(docsConfig.navigation, sources);
153
- if (sources.size === 0) {
154
- return [];
155
- }
156
-
157
- const allowedOrigins = new Set();
158
-
159
- for (const source of sources) {
160
- try {
161
- const spec = await loadOpenApiSpec(source);
162
- const servers =
163
- isRecord(spec) && Array.isArray(spec.servers) ? spec.servers : [];
164
-
165
- for (const server of servers) {
166
- if (!isRecord(server) || typeof server.url !== "string") {
167
- continue;
168
- }
169
-
170
- const resolvedUrl = resolveServerTemplateUrl(
171
- server.url,
172
- server.variables,
173
- );
174
- if (!resolvedUrl) {
175
- continue;
176
- }
177
-
178
- const normalizedOrigin = normalizeAllowedOrigin(resolvedUrl);
179
- if (normalizedOrigin) {
180
- allowedOrigins.add(normalizedOrigin);
181
- }
182
- }
183
- } catch (error) {
184
- console.warn(
185
- `⚠️ Failed to extract OpenAPI server origins from "${source}":`,
186
- error instanceof Error ? error.message : String(error),
187
- );
188
- }
189
- }
190
-
191
- return Array.from(allowedOrigins).sort();
192
- }
193
-
194
9
  async function run() {
195
10
  let allowedOrigins = [];
196
11
 
197
12
  try {
198
- allowedOrigins = await buildAllowedOrigins();
13
+ allowedOrigins = await buildAllowedOrigins({
14
+ cwd: CWD,
15
+ onSourceError(source, error) {
16
+ console.warn(
17
+ `⚠️ Failed to extract OpenAPI server origins from "${source}":`,
18
+ error instanceof Error ? error.message : String(error),
19
+ );
20
+ },
21
+ });
199
22
  } catch (error) {
200
23
  console.warn(
201
24
  "⚠️ Failed to generate proxy allowed origins. Falling back to empty allowlist.",
@@ -39,7 +39,7 @@ const DEFAULT_R2_REQUEST_TIMEOUT_MS = 30_000;
39
39
  const IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
40
40
  const MARKER_CACHE_CONTROL = "no-store";
41
41
  const UPLOAD_COMPLETE_MARKER = ".radiant-upload-complete.json";
42
- const UPLOAD_MARKER_VERSION = 1;
42
+ const UPLOAD_MARKER_VERSION = 2;
43
43
 
44
44
  const LICENSE_PACKAGES = [
45
45
  "shiki",
@@ -658,7 +658,7 @@ async function checkCurrentAssetFile(filePath, descriptor) {
658
658
  if (!(await pathExists(filePath))) {
659
659
  throw new Error(
660
660
  `Current Shiki platform asset file is missing: ${filePath}\n` +
661
- "Run: npm --prefix framework run shiki:publish-platform-assets -- --prepare-only --write-current",
661
+ "Run: npm --prefix framework run shiki:assets:update",
662
662
  );
663
663
  }
664
664
 
@@ -671,7 +671,7 @@ async function checkCurrentAssetFile(filePath, descriptor) {
671
671
  `Current Shiki platform asset file is out of date: ${filePath}\n` +
672
672
  `Expected: ${describeCurrentDescriptor(descriptor)}\n` +
673
673
  `Found: ${describeCurrentDescriptor(current)}\n` +
674
- "Run: npm --prefix framework run shiki:publish-platform-assets -- --prepare-only --write-current",
674
+ "Run: npm --prefix framework run shiki:assets:update",
675
675
  );
676
676
  }
677
677
 
@@ -808,7 +808,24 @@ async function sendR2CommandWithTimeout(s3, command, { label, timeoutMs }) {
808
808
  }
809
809
  }
810
810
 
811
- async function objectHasSameHash(s3, bucket, key, sha256, requestTimeoutMs) {
811
+ function normalizeCacheControl(value) {
812
+ return String(value ?? "")
813
+ .trim()
814
+ .toLowerCase()
815
+ .replace(/\s*,\s*/g, ",");
816
+ }
817
+
818
+ function cacheControlMatches(actual, expected) {
819
+ return normalizeCacheControl(actual) === normalizeCacheControl(expected);
820
+ }
821
+
822
+ async function objectMatchesExpectedAssetMetadata(
823
+ s3,
824
+ bucket,
825
+ key,
826
+ sha256,
827
+ requestTimeoutMs,
828
+ ) {
812
829
  try {
813
830
  const response = await sendR2CommandWithTimeout(
814
831
  s3,
@@ -821,7 +838,10 @@ async function objectHasSameHash(s3, bucket, key, sha256, requestTimeoutMs) {
821
838
  timeoutMs: requestTimeoutMs,
822
839
  },
823
840
  );
824
- return response.Metadata?.["radiant-sha256"] === sha256;
841
+ return (
842
+ response.Metadata?.["radiant-sha256"] === sha256 &&
843
+ cacheControlMatches(response.CacheControl, IMMUTABLE_CACHE_CONTROL)
844
+ );
825
845
  } catch (error) {
826
846
  const statusCode = error?.$metadata?.httpStatusCode;
827
847
  if (statusCode === 403 || statusCode === 404) return false;
@@ -856,6 +876,10 @@ async function completionMarkerMatches({
856
876
  metadata["radiant-shiki-asset-version"] === assetVersion &&
857
877
  metadata["radiant-shiki-file-count"] === String(fileCount) &&
858
878
  metadata["radiant-shiki-manifest-sha256"] === manifestSha256 &&
879
+ cacheControlMatches(
880
+ metadata["radiant-shiki-cache-control"],
881
+ IMMUTABLE_CACHE_CONTROL,
882
+ ) &&
859
883
  metadata["radiant-shiki-marker-version"] ===
860
884
  String(UPLOAD_MARKER_VERSION)
861
885
  );
@@ -875,6 +899,7 @@ function buildCompletionMarker({
875
899
  }) {
876
900
  return {
877
901
  assetVersion,
902
+ assetCacheControl: IMMUTABLE_CACHE_CONTROL,
878
903
  completedAt: new Date().toISOString(),
879
904
  fileCount,
880
905
  manifest: {
@@ -918,6 +943,7 @@ async function uploadCompletionMarker({
918
943
  Metadata: {
919
944
  "radiant-sha256": hashBuffer(body),
920
945
  "radiant-shiki-asset-version": assetVersion,
946
+ "radiant-shiki-cache-control": IMMUTABLE_CACHE_CONTROL,
921
947
  "radiant-shiki-file-count": String(fileCount),
922
948
  "radiant-shiki-manifest-sha256": manifestSha256,
923
949
  "radiant-shiki-marker-version": String(UPLOAD_MARKER_VERSION),
@@ -986,7 +1012,7 @@ async function uploadAssets({
986
1012
  const body = await fs.readFile(absolutePath);
987
1013
  const sha256 = hashBuffer(body);
988
1014
  const key = joinPosix(uploadPrefix, relativePath);
989
- const shouldSkip = await objectHasSameHash(
1015
+ const shouldSkip = await objectMatchesExpectedAssetMetadata(
990
1016
  s3,
991
1017
  args.bucket,
992
1018
  key,
@@ -10,6 +10,7 @@ import { getDocsBasePath, withBasePath } from "../../lib/base-path";
10
10
  import {
11
11
  highlightAssistantCodeToHtml,
12
12
  normalizeAssistantCodeLanguage,
13
+ warmAssistantShikiRuntime,
13
14
  type AssistantShikiRuntimeConfig,
14
15
  } from "../../lib/assistant-shiki-client";
15
16
 
@@ -56,15 +57,22 @@ type AssistantColorByMode = {
56
57
  dark: string;
57
58
  };
58
59
 
60
+ type AssistantMessageSource = {
61
+ href: string;
62
+ label: string;
63
+ };
64
+
59
65
  type ChatMessage = {
60
66
  id: string;
61
67
  role: "user" | "assistant";
62
68
  content: string;
69
+ sources?: AssistantMessageSource[];
63
70
  };
64
71
 
65
72
  type AssistantStreamEvent = {
66
73
  type?: string;
67
74
  delta?: string;
75
+ sources?: unknown;
68
76
  };
69
77
 
70
78
  type ChatInputKeyEvent = JSX.TargetedKeyboardEvent<HTMLTextAreaElement>;
@@ -351,6 +359,33 @@ function createMessageId(): string {
351
359
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
352
360
  }
353
361
 
362
+ function normalizeAssistantMessageSources(
363
+ rawSources: unknown,
364
+ ): AssistantMessageSource[] {
365
+ if (!Array.isArray(rawSources)) {
366
+ return [];
367
+ }
368
+
369
+ return rawSources.flatMap((rawSource) => {
370
+ if (!rawSource || typeof rawSource !== "object") {
371
+ return [];
372
+ }
373
+
374
+ const source = rawSource as Partial<AssistantMessageSource>;
375
+ const href = typeof source.href === "string" ? source.href.trim() : "";
376
+ const label =
377
+ typeof source.label === "string" && source.label.trim()
378
+ ? source.label.trim()
379
+ : href;
380
+
381
+ if (!href || !label) {
382
+ return [];
383
+ }
384
+
385
+ return [{ href, label }];
386
+ });
387
+ }
388
+
354
389
  function normalizePersistedMessages(rawMessages: unknown): ChatMessage[] {
355
390
  if (!Array.isArray(rawMessages)) {
356
391
  return [];
@@ -372,6 +407,7 @@ function normalizePersistedMessages(rawMessages: unknown): ChatMessage[] {
372
407
  id: message.id,
373
408
  role: message.role,
374
409
  content: message.content,
410
+ sources: normalizeAssistantMessageSources(message.sources),
375
411
  }));
376
412
  }
377
413
 
@@ -953,6 +989,43 @@ function AssistantMarkdownBlock({
953
989
  );
954
990
  }
955
991
 
992
+ function AssistantSourcesBlock({
993
+ linkTarget,
994
+ onClick,
995
+ sources,
996
+ }: {
997
+ linkTarget: AssistantLinkTarget;
998
+ onClick: (event: JSX.TargetedMouseEvent<HTMLDivElement>) => void;
999
+ sources: AssistantMessageSource[] | undefined;
1000
+ }) {
1001
+ const visibleSources = normalizeAssistantMessageSources(sources);
1002
+ if (visibleSources.length === 0) {
1003
+ return null;
1004
+ }
1005
+
1006
+ return (
1007
+ <div
1008
+ className="ask-ai-sources ask-ai-markdown prose-rules mt-3 max-w-full min-w-0 wrap-break-word text-[15px]!"
1009
+ onClick={onClick}
1010
+ >
1011
+ <p>Sources:</p>
1012
+ <ul>
1013
+ {visibleSources.map((source) => (
1014
+ <li key={`${source.href}\0${source.label}`}>
1015
+ <a
1016
+ href={source.href}
1017
+ target={linkTarget === "blank" ? "_blank" : undefined}
1018
+ rel={linkTarget === "blank" ? "noopener noreferrer" : undefined}
1019
+ >
1020
+ {source.label}
1021
+ </a>
1022
+ </li>
1023
+ ))}
1024
+ </ul>
1025
+ </div>
1026
+ );
1027
+ }
1028
+
956
1029
  function extractErrorMessage(rawBody: string): string {
957
1030
  if (!rawBody.trim()) {
958
1031
  return "";
@@ -1110,6 +1183,7 @@ export default function AssistantEmbedPanel({
1110
1183
  const panelSizeRef = useRef<AssistantPanelSize>(
1111
1184
  panelSize ?? initialPanelState.panelSize,
1112
1185
  );
1186
+ const hasPrewarmedShikiRef = useRef(false);
1113
1187
  const skipNextMessagesPersistRef = useRef(false);
1114
1188
  const inputRef = useRef<HTMLTextAreaElement | null>(null);
1115
1189
  const resolvedApiPathRef = useRef(
@@ -1799,6 +1873,13 @@ export default function AssistantEmbedPanel({
1799
1873
  setMessages(nextConversation);
1800
1874
  queueThreadScrollToBottom(nextConversation);
1801
1875
 
1876
+ if (shiki && !hasPrewarmedShikiRef.current) {
1877
+ hasPrewarmedShikiRef.current = true;
1878
+ void warmAssistantShikiRuntime(shiki).catch((error) => {
1879
+ console.warn("Assistant embed Shiki runtime prewarm failed", error);
1880
+ });
1881
+ }
1882
+
1802
1883
  activeRequestAbortRef.current?.abort();
1803
1884
  const abortController = new AbortController();
1804
1885
  activeRequestAbortRef.current = abortController;
@@ -1916,6 +1997,49 @@ export default function AssistantEmbedPanel({
1916
1997
  : message,
1917
1998
  );
1918
1999
  });
2000
+ continue;
2001
+ }
2002
+
2003
+ if (parsed.type === "sources") {
2004
+ const nextSources = normalizeAssistantMessageSources(
2005
+ parsed.sources,
2006
+ );
2007
+ if (nextSources.length === 0) {
2008
+ continue;
2009
+ }
2010
+
2011
+ if (!hasReceivedFirstTextDelta) {
2012
+ hasReceivedFirstTextDelta = true;
2013
+ setSharedAwaitingFirstToken(false);
2014
+ }
2015
+
2016
+ setMessages((previous) => {
2017
+ const existingAssistantMessage = previous.find(
2018
+ (message) => message.id === assistantId,
2019
+ );
2020
+
2021
+ const nextMessages = existingAssistantMessage
2022
+ ? previous.map((message) =>
2023
+ message.id === assistantId
2024
+ ? {
2025
+ ...message,
2026
+ sources: nextSources,
2027
+ }
2028
+ : message,
2029
+ )
2030
+ : [
2031
+ ...previous,
2032
+ {
2033
+ id: assistantId,
2034
+ role: "assistant" as const,
2035
+ content: "",
2036
+ sources: nextSources,
2037
+ },
2038
+ ];
2039
+
2040
+ queueThreadScrollToBottom(nextMessages);
2041
+ return nextMessages;
2042
+ });
1919
2043
  }
1920
2044
  }
1921
2045
  }
@@ -2242,7 +2366,8 @@ export default function AssistantEmbedPanel({
2242
2366
  {messages.map((message) => {
2243
2367
  if (
2244
2368
  message.role === "assistant" &&
2245
- message.content.length === 0
2369
+ message.content.length === 0 &&
2370
+ normalizeAssistantMessageSources(message.sources).length === 0
2246
2371
  ) {
2247
2372
  return null;
2248
2373
  }
@@ -2265,6 +2390,13 @@ export default function AssistantEmbedPanel({
2265
2390
  onRendered={handleMarkdownRendered}
2266
2391
  shiki={shiki}
2267
2392
  />
2393
+ {!isUser ? (
2394
+ <AssistantSourcesBlock
2395
+ linkTarget={linkTarget}
2396
+ onClick={handleRenderedMarkdownClick}
2397
+ sources={message.sources}
2398
+ />
2399
+ ) : null}
2268
2400
  </div>
2269
2401
  );
2270
2402
  })}