ai-design-system 0.1.77 → 0.1.78

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.
@@ -41,13 +41,9 @@ import type { FileDownloadResult, FileTreeNode } from '@/components/composites/F
41
41
  import { cn } from '@/lib/utils'
42
42
  import type { JSONContent } from '@tiptap/core'
43
43
  import type { Annotation, User } from '@/types/ai-editor/annotations'
44
+ import type { DocumentFile, DocumentWithAnnotations } from '@/types/ai-editor/editor'
44
45
  import { StreamingMarkdown } from '@/components/composites/StreamingMarkdown'
45
-
46
- interface DocumentWithAnnotations {
47
- file: { id: string; name: string; isDirty?: boolean; format?: 'json' | 'markdown' | string; lastModified?: number }
48
- content: JSONContent | string
49
- annotations: Annotation[]
50
- }
46
+ import { MediaPreview, isMediaFile } from '@/components/composites/MediaPreview'
51
47
 
52
48
  /**
53
49
  * Props for TextEditor feature component - Single document mode (backward compatible)
@@ -298,13 +294,6 @@ export const TextEditor = React.memo<TextEditorProps>(
298
294
  targetId = targetId.slice(2)
299
295
  }
300
296
 
301
- console.log('[TextEditor:LinkComponent:handleClick]', {
302
- href,
303
- activeDocumentId: props.activeDocumentId,
304
- computedTargetId: targetId,
305
- documentsCount: props.documents?.length
306
- })
307
-
308
297
  if (isMultiTab && props.documents) {
309
298
  const cleanTarget = targetId.replace(/^\.\//, '').replace(/^\//, '')
310
299
  const matchedDoc = props.documents.find((doc) => {
@@ -316,18 +305,12 @@ export const TextEditor = React.memo<TextEditorProps>(
316
305
  doc.file.name === targetId
317
306
  )
318
307
  })
319
- console.log('[TextEditor:LinkComponent:matching]', {
320
- cleanTarget,
321
- matchedDocId: matchedDoc?.file?.id,
322
- availableDocIds: props.documents.map(d => d.file.id)
323
- })
324
308
  if (matchedDoc) {
325
309
  targetId = matchedDoc.file.id
326
310
  }
327
311
  }
328
312
 
329
313
  if (handleTabSelect) {
330
- console.log('[TextEditor:LinkComponent:onTabSelect]', targetId)
331
314
  handleTabSelect(targetId)
332
315
  }
333
316
  }
@@ -339,6 +322,8 @@ export const TextEditor = React.memo<TextEditorProps>(
339
322
  return <a href={href} onClick={handleClick} className="text-primary hover:underline cursor-pointer" {...rest} />
340
323
  }, [handleTabSelect, isMultiTab, props.activeDocumentId, props.documents])
341
324
 
325
+ const markdownComponents = useMemo(() => ({ a: LinkComponent }), [LinkComponent])
326
+
342
327
  /**
343
328
  * Single-document mode
344
329
  */
@@ -354,7 +339,7 @@ export const TextEditor = React.memo<TextEditorProps>(
354
339
  : contentStr
355
340
  return (
356
341
  <div className={cn('text-editor p-6 flex flex-col h-screen w-full flex-1', className)}>
357
- <StreamingMarkdown mode="streaming" components={{ a: LinkComponent }}>
342
+ <StreamingMarkdown mode="static" isAnimating={false} components={markdownComponents}>
358
343
  {content}
359
344
  </StreamingMarkdown>
360
345
  </div>
@@ -402,6 +387,23 @@ export const TextEditor = React.memo<TextEditorProps>(
402
387
  </div>
403
388
  </div>
404
389
  )
390
+ } else if (isMediaFile(currentDocument.file)) {
391
+ editorPane = (
392
+ <div className="text-editor flex flex-col h-full w-full">
393
+ {!hideTabBar && (
394
+ <DocumentTabBar
395
+ className="w-full"
396
+ tabs={props.documents?.map((doc) => doc.file) || []}
397
+ activeTabId={props.activeDocumentId}
398
+ onTabSelect={props.onTabSelect}
399
+ onTabClose={props.onTabClose}
400
+ />
401
+ )}
402
+ <div className="flex-1 overflow-auto">
403
+ <MediaPreview file={currentDocument.file} />
404
+ </div>
405
+ </div>
406
+ )
405
407
  } else {
406
408
  const format = currentDocument.file.format || 'markdown'
407
409
  const isMarkdownMulti = format === 'markdown' || format === 'md' || format === 'mdx'
@@ -435,10 +437,10 @@ export const TextEditor = React.memo<TextEditorProps>(
435
437
  ) : renderAsStreamdown ? (
436
438
  <div className="p-6">
437
439
  <StreamingMarkdown
438
- mode="streaming"
439
- isAnimating
440
+ mode="static"
441
+ isAnimating={false}
440
442
  className="[&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
441
- components={{ a: LinkComponent }}
443
+ components={markdownComponents}
442
444
  >
443
445
  {isCodeMulti
444
446
  ? `\`\`\`${format}\n${format === 'json' ? formatJson(currentDocument.content as string) : currentDocument.content}\n\`\`\``
@@ -22,3 +22,6 @@ export {
22
22
  sampleDocumentFiles,
23
23
  sampleMultiTabDocuments,
24
24
  } from './TextEditor.mocks'
25
+
26
+ export { MediaPreview, isMediaFile, isVideoFile, isAudioFile, isImageFile, isPdfFile } from '@/components/composites/MediaPreview'
27
+ export type { MediaPreviewProps } from '@/components/composites/MediaPreview'
@@ -8,24 +8,11 @@
8
8
  import React, { useState, useCallback } from 'react'
9
9
  import type { JSONContent } from '@tiptap/core'
10
10
  import type { Annotation } from '@/types/ai-editor/annotations'
11
+ import type { DocumentFile, DocumentWithAnnotations } from '@/types/ai-editor/editor'
11
12
  import type { FileTreeNode } from '@/components/composites/FileTreeExplorer'
12
13
  import type { UseTextEditorReturn, UseAIMultiTabDocEditorReturn } from './useTextEditor'
13
14
  import { sampleDocumentFiles, sampleMultiTabDocuments } from './TextEditor.mocks'
14
15
 
15
- interface DocumentFile {
16
- id: string
17
- name: string
18
- isDirty: boolean
19
- format?: 'json' | 'markdown' | string
20
- lastModified: number
21
- }
22
-
23
- interface DocumentWithAnnotations {
24
- file: DocumentFile
25
- content: JSONContent | string
26
- annotations: Annotation[]
27
- }
28
-
29
16
  type MultiDocMockConfig = {
30
17
  multiDoc: true
31
18
  initialDocuments?: DocumentWithAnnotations[]
package/dist/index.cjs CHANGED
@@ -1400,6 +1400,38 @@ var defaultIcons = {
1400
1400
  { type: "path", attrs: { d: "M3 5v14a2 2 0 0 0 2 2h16v-5" } },
1401
1401
  { type: "path", attrs: { d: "M18 12a2 2 0 0 0 0 4h4v-4Z" } }
1402
1402
  ]
1403
+ },
1404
+ "film": {
1405
+ name: "film",
1406
+ viewBox: "0 0 24 24",
1407
+ elements: [
1408
+ { type: "rect", attrs: { width: "18", height: "18", x: "3", y: "3", rx: "2" } },
1409
+ { type: "path", attrs: { d: "M7 3v18" } },
1410
+ { type: "path", attrs: { d: "M3 7.5h4" } },
1411
+ { type: "path", attrs: { d: "M3 12h18" } },
1412
+ { type: "path", attrs: { d: "M3 16.5h4" } },
1413
+ { type: "path", attrs: { d: "M17 3v18" } },
1414
+ { type: "path", attrs: { d: "M17 7.5h4" } },
1415
+ { type: "path", attrs: { d: "M17 16.5h4" } }
1416
+ ]
1417
+ },
1418
+ "music": {
1419
+ name: "music",
1420
+ viewBox: "0 0 24 24",
1421
+ elements: [
1422
+ { type: "path", attrs: { d: "M9 18V5l12-2v13" } },
1423
+ { type: "circle", attrs: { cx: "6", cy: "18", r: "3" } },
1424
+ { type: "circle", attrs: { cx: "18", cy: "16", r: "3" } }
1425
+ ]
1426
+ },
1427
+ "image": {
1428
+ name: "image",
1429
+ viewBox: "0 0 24 24",
1430
+ elements: [
1431
+ { type: "rect", attrs: { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" } },
1432
+ { type: "circle", attrs: { cx: "9", cy: "9", r: "2" } },
1433
+ { type: "path", attrs: { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" } }
1434
+ ]
1403
1435
  }
1404
1436
  };
1405
1437
  var IconRegistry = class {
@@ -1875,13 +1907,12 @@ var AdjustableLayout = React3__namespace.memo(
1875
1907
  return raw.map((size) => size / total * 100);
1876
1908
  }, [sections]);
1877
1909
  const [sizes, setSizes] = React3__namespace.useState(defaultSizes);
1910
+ const prevSectionsLengthRef = React3__namespace.useRef(sections.length);
1878
1911
  React3__namespace.useEffect(() => {
1879
- setSizes((prevSizes) => {
1880
- if (prevSizes.length !== sections.length) {
1881
- return defaultSizes;
1882
- }
1883
- return prevSizes;
1884
- });
1912
+ if (prevSectionsLengthRef.current !== sections.length) {
1913
+ prevSectionsLengthRef.current = sections.length;
1914
+ setSizes(defaultSizes);
1915
+ }
1885
1916
  }, [sections.length, defaultSizes]);
1886
1917
  React3__namespace.useEffect(() => {
1887
1918
  if (!storageKey) return;
@@ -1894,7 +1925,7 @@ var AdjustableLayout = React3__namespace.memo(
1894
1925
  }
1895
1926
  } catch (e) {
1896
1927
  }
1897
- }, [storageKey]);
1928
+ }, [storageKey, sections.length]);
1898
1929
  const [draggingIndex, setDraggingIndex] = React3__namespace.useState(null);
1899
1930
  const [startX, setStartX] = React3__namespace.useState(0);
1900
1931
  const [startSizes, setStartSizes] = React3__namespace.useState([]);
@@ -3641,6 +3672,14 @@ var customRemarkPlugins = [
3641
3672
  ...Object.values(streamdown.defaultRemarkPlugins),
3642
3673
  remarkFrontmatter__default.default
3643
3674
  ];
3675
+ var DEFAULT_SHIKI_THEME = [
3676
+ "vitesse-light",
3677
+ "vitesse-dark"
3678
+ ];
3679
+ var DEFAULT_CONTROLS = {
3680
+ code: true,
3681
+ mermaid: true
3682
+ };
3644
3683
  var StreamingMarkdown = React3.memo(
3645
3684
  (_a) => {
3646
3685
  var _b = _a, {
@@ -3660,6 +3699,7 @@ var StreamingMarkdown = React3.memo(
3660
3699
  "status",
3661
3700
  "controls"
3662
3701
  ]);
3702
+ var _a2;
3663
3703
  const [copied, setCopied] = React3.useState(false);
3664
3704
  let content = children;
3665
3705
  let title = propsTitle;
@@ -3749,11 +3789,8 @@ var StreamingMarkdown = React3.memo(
3749
3789
  streamdown.Streamdown,
3750
3790
  __spreadProps(__spreadValues({
3751
3791
  remarkPlugins: remarkPlugins || customRemarkPlugins,
3752
- shikiTheme: ["vitesse-light", "vitesse-dark"],
3753
- controls: propsControls != null ? propsControls : {
3754
- code: true,
3755
- mermaid: true
3756
- }
3792
+ shikiTheme: (_a2 = props.shikiTheme) != null ? _a2 : DEFAULT_SHIKI_THEME,
3793
+ controls: propsControls != null ? propsControls : DEFAULT_CONTROLS
3757
3794
  }, props), {
3758
3795
  children: content
3759
3796
  })
@@ -3789,6 +3826,10 @@ var SpecialistMessage = React3__namespace.memo(
3789
3826
  () => message.toolCalls !== void 0 && message.toolCalls.length > 0,
3790
3827
  [message.toolCalls]
3791
3828
  );
3829
+ const hasSubAgents = React3__namespace.useMemo(
3830
+ () => message.subAgents !== void 0 && message.subAgents.length > 0,
3831
+ [message.subAgents]
3832
+ );
3792
3833
  const toolState = React3__namespace.useMemo(() => {
3793
3834
  switch (message.status) {
3794
3835
  case "pending":
@@ -3834,7 +3875,22 @@ var SpecialistMessage = React3__namespace.memo(
3834
3875
  children: message.content
3835
3876
  }
3836
3877
  ),
3837
- hasToolCalls && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-2", hasContent && "mt-4"), children: message.toolCalls.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsx(ToolCallDisplay, { toolCall }, toolCall.id)) })
3878
+ hasToolCalls && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-2", hasContent && "mt-4"), children: message.toolCalls.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsx(ToolCallDisplay, { toolCall }, toolCall.id)) }),
3879
+ hasSubAgents && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-2 pl-3 border-l border-border/50", (hasContent || hasToolCalls) && "mt-4"), children: message.subAgents.map((sa) => /* @__PURE__ */ jsxRuntime.jsx(
3880
+ SpecialistMessage,
3881
+ {
3882
+ isNested: true,
3883
+ message: {
3884
+ id: sa.id,
3885
+ name: sa.subAgentName,
3886
+ content: typeof sa.output === "string" ? sa.output : "",
3887
+ status: sa.status,
3888
+ input: sa.input,
3889
+ subAgents: sa.subAgents
3890
+ }
3891
+ },
3892
+ sa.id
3893
+ )) })
3838
3894
  ] }) })
3839
3895
  ]
3840
3896
  }
@@ -3864,12 +3920,51 @@ var OrchestratorMessage = React3__namespace.memo(
3864
3920
  }
3865
3921
  );
3866
3922
  OrchestratorMessage.displayName = "OrchestratorMessage";
3923
+ function parseSystemMessageContent(content) {
3924
+ try {
3925
+ const parsed = JSON.parse(content);
3926
+ if (parsed && typeof parsed === "object") {
3927
+ const url = typeof parsed.url === "string" ? parsed.url : void 0;
3928
+ const mediaType = typeof parsed.mediaType === "string" ? parsed.mediaType : typeof parsed.type === "string" ? parsed.type : void 0;
3929
+ const title = typeof parsed.title === "string" ? parsed.title : typeof parsed.message === "string" ? parsed.message : void 0;
3930
+ if (url) {
3931
+ if ((mediaType == null ? void 0 : mediaType.startsWith("video/")) || /\.(mp4|webm|mov)(\?.*)?$/i.test(url)) {
3932
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
3933
+ title && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: title }),
3934
+ /* @__PURE__ */ jsxRuntime.jsxs("video", { controls: true, className: "w-full max-w-[360px] rounded-md shadow-sm", children: [
3935
+ /* @__PURE__ */ jsxRuntime.jsx("source", { src: url, type: mediaType || "video/mp4" }),
3936
+ "Your browser does not support the video element."
3937
+ ] })
3938
+ ] });
3939
+ }
3940
+ if ((mediaType == null ? void 0 : mediaType.startsWith("audio/")) || /\.(wav|mp3|ogg|m4a)(\?.*)?$/i.test(url)) {
3941
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
3942
+ title && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: title }),
3943
+ /* @__PURE__ */ jsxRuntime.jsxs("audio", { controls: true, className: "w-full max-w-[360px] h-8", children: [
3944
+ /* @__PURE__ */ jsxRuntime.jsx("source", { src: url, type: mediaType || "audio/wav" }),
3945
+ "Your browser does not support the audio element."
3946
+ ] })
3947
+ ] });
3948
+ }
3949
+ if ((mediaType == null ? void 0 : mediaType.startsWith("image/")) || /\.(png|jpg|jpeg|webp|gif)(\?.*)?$/i.test(url)) {
3950
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
3951
+ title && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: title }),
3952
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: url, alt: title || "Image output", className: "max-w-[360px] rounded-md shadow-sm" })
3953
+ ] });
3954
+ }
3955
+ }
3956
+ if (title) return /* @__PURE__ */ jsxRuntime.jsx("span", { children: title });
3957
+ }
3958
+ } catch (e) {
3959
+ }
3960
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: content });
3961
+ }
3867
3962
  var SystemMessage = React3__namespace.memo(
3868
3963
  ({ message, renderContent }) => {
3869
3964
  if (!message.content || !message.content.trim()) {
3870
3965
  return null;
3871
3966
  }
3872
- const contentNode = renderContent ? renderContent(message.content) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: message.content });
3967
+ const contentNode = renderContent ? renderContent(message.content) : parseSystemMessageContent(message.content);
3873
3968
  if (!contentNode) {
3874
3969
  return null;
3875
3970
  }
@@ -4088,6 +4183,16 @@ var ReasoningDisplay = ({
4088
4183
  )
4089
4184
  ] });
4090
4185
  };
4186
+ function toNestedSubAgents(agents) {
4187
+ return agents == null ? void 0 : agents.map((a) => ({
4188
+ id: a.id,
4189
+ subAgentName: a.subAgentName,
4190
+ status: a.status,
4191
+ input: a.input,
4192
+ output: typeof a.output === "string" ? a.output : a.output ? JSON.stringify(a.output) : void 0,
4193
+ subAgents: toNestedSubAgents(a.subAgents)
4194
+ }));
4195
+ }
4091
4196
  var AIConversation = React3__namespace.memo(
4092
4197
  (_a) => {
4093
4198
  var _b = _a, {
@@ -4312,7 +4417,8 @@ ${newStr}` : newStr;
4312
4417
  input: subAgent.input,
4313
4418
  content: typeof subAgent.output === "string" ? subAgent.output : subAgent.output ? JSON.stringify(subAgent.output) : "",
4314
4419
  status: subAgent.status,
4315
- toolCalls: []
4420
+ toolCalls: [],
4421
+ subAgents: toNestedSubAgents(subAgent.subAgents)
4316
4422
  },
4317
4423
  isNested: true
4318
4424
  },
@@ -4332,7 +4438,8 @@ ${newStr}` : newStr;
4332
4438
  input: subAgent.input,
4333
4439
  content: typeof subAgent.output === "string" ? subAgent.output : subAgent.output ? JSON.stringify(subAgent.output) : "",
4334
4440
  status: subAgent.status,
4335
- toolCalls: []
4441
+ toolCalls: [],
4442
+ subAgents: toNestedSubAgents(subAgent.subAgents)
4336
4443
  },
4337
4444
  isNested: true
4338
4445
  },
@@ -12638,6 +12745,145 @@ var AuthCard = React3__namespace.memo(
12638
12745
  }
12639
12746
  );
12640
12747
  AuthCard.displayName = "AuthCard";
12748
+ function isVideoFile(file) {
12749
+ var _a, _b, _c, _d;
12750
+ const format = ((_a = file.format) == null ? void 0 : _a.toLowerCase()) || "";
12751
+ const ext = ((_c = (_b = file.name) == null ? void 0 : _b.split(".").pop()) == null ? void 0 : _c.toLowerCase()) || "";
12752
+ if (["mp4", "webm", "mov", "mkv", "avi"].includes(format) || ["mp4", "webm", "mov", "mkv", "avi"].includes(ext)) return true;
12753
+ if ((_d = file.mediaType) == null ? void 0 : _d.startsWith("video/")) return true;
12754
+ if (file.url && /\.(mp4|webm|mov|mkv)(\?.*)?$/i.test(file.url)) return true;
12755
+ return false;
12756
+ }
12757
+ function isAudioFile(file) {
12758
+ var _a, _b, _c, _d;
12759
+ const format = ((_a = file.format) == null ? void 0 : _a.toLowerCase()) || "";
12760
+ const ext = ((_c = (_b = file.name) == null ? void 0 : _b.split(".").pop()) == null ? void 0 : _c.toLowerCase()) || "";
12761
+ if (["wav", "mp3", "ogg", "m4a", "flac", "aac", "wma"].includes(format) || ["wav", "mp3", "ogg", "m4a", "flac", "aac", "wma"].includes(ext)) return true;
12762
+ if ((_d = file.mediaType) == null ? void 0 : _d.startsWith("audio/")) return true;
12763
+ if (file.url && /\.(wav|mp3|ogg|m4a|flac|aac)(\?.*)?$/i.test(file.url)) return true;
12764
+ return false;
12765
+ }
12766
+ function isImageFile(file) {
12767
+ var _a, _b, _c, _d;
12768
+ const format = ((_a = file.format) == null ? void 0 : _a.toLowerCase()) || "";
12769
+ const ext = ((_c = (_b = file.name) == null ? void 0 : _b.split(".").pop()) == null ? void 0 : _c.toLowerCase()) || "";
12770
+ if (["png", "jpg", "jpeg", "webp", "gif", "svg", "ico", "bmp"].includes(format) || ["png", "jpg", "jpeg", "webp", "gif", "svg", "ico", "bmp"].includes(ext)) return true;
12771
+ if ((_d = file.mediaType) == null ? void 0 : _d.startsWith("image/")) return true;
12772
+ if (file.url && /\.(png|jpg|jpeg|webp|gif|svg)(\?.*)?$/i.test(file.url)) return true;
12773
+ return false;
12774
+ }
12775
+ function isPdfFile(file) {
12776
+ var _a, _b, _c;
12777
+ const format = ((_a = file.format) == null ? void 0 : _a.toLowerCase()) || "";
12778
+ const ext = ((_c = (_b = file.name) == null ? void 0 : _b.split(".").pop()) == null ? void 0 : _c.toLowerCase()) || "";
12779
+ return format === "pdf" || ext === "pdf" || file.mediaType === "application/pdf";
12780
+ }
12781
+ function isMediaFile(file) {
12782
+ return isVideoFile(file) || isAudioFile(file) || isImageFile(file) || isPdfFile(file);
12783
+ }
12784
+ var MediaPreview = ({ file, className }) => {
12785
+ var _a;
12786
+ const isVideo = isVideoFile(file);
12787
+ const isAudio = isAudioFile(file);
12788
+ const isImage = isImageFile(file);
12789
+ const isPdf = isPdfFile(file);
12790
+ const mediaUrl = file.url;
12791
+ if (!mediaUrl) {
12792
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col items-center justify-center h-full p-8 text-center", className), children: [
12793
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-16 w-16 items-center justify-center rounded-2xl bg-muted/60 mb-4 text-muted-foreground", children: [
12794
+ isVideo && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "film", className: "h-8 w-8" }),
12795
+ isAudio && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "music", className: "h-8 w-8" }),
12796
+ isImage && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "image", className: "h-8 w-8" }),
12797
+ isPdf && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "file-text", className: "h-8 w-8" }),
12798
+ !isVideo && !isAudio && !isImage && !isPdf && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "alert-circle", className: "h-8 w-8" })
12799
+ ] }),
12800
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-medium text-foreground mb-1", children: file.name || "Media File" }),
12801
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground max-w-sm", children: "No direct stream URL is available for this file yet." })
12802
+ ] });
12803
+ }
12804
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col h-full w-full overflow-y-auto p-6 bg-background/50", className), children: [
12805
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between pb-4 mb-6 border-b border-border", children: [
12806
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
12807
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10 text-primary", children: [
12808
+ isVideo && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "film", className: "h-5 w-5" }),
12809
+ isAudio && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "music", className: "h-5 w-5" }),
12810
+ isImage && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "image", className: "h-5 w-5" }),
12811
+ isPdf && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "file-text", className: "h-5 w-5" })
12812
+ ] }),
12813
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
12814
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold text-foreground tracking-tight", children: file.name }),
12815
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: file.mediaType || ((_a = file.format) == null ? void 0 : _a.toUpperCase()) || "Binary Media" })
12816
+ ] })
12817
+ ] }),
12818
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
12819
+ /* @__PURE__ */ jsxRuntime.jsxs(
12820
+ "a",
12821
+ {
12822
+ href: mediaUrl,
12823
+ target: "_blank",
12824
+ rel: "noopener noreferrer",
12825
+ download: file.name,
12826
+ className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border bg-background hover:bg-muted text-foreground transition-colors shadow-sm",
12827
+ children: [
12828
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "download", className: "h-3.5 w-3.5" }),
12829
+ "Download"
12830
+ ]
12831
+ }
12832
+ ),
12833
+ /* @__PURE__ */ jsxRuntime.jsxs(
12834
+ "a",
12835
+ {
12836
+ href: mediaUrl,
12837
+ target: "_blank",
12838
+ rel: "noopener noreferrer",
12839
+ className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border bg-background hover:bg-muted text-foreground transition-colors shadow-sm",
12840
+ children: [
12841
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "external-link", className: "h-3.5 w-3.5" }),
12842
+ "Open"
12843
+ ]
12844
+ }
12845
+ )
12846
+ ] })
12847
+ ] }),
12848
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center min-h-[300px]", children: [
12849
+ isVideo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full max-w-4xl flex flex-col items-center justify-center bg-black/95 rounded-2xl overflow-hidden shadow-lg border border-border/40", children: /* @__PURE__ */ jsxRuntime.jsx(
12850
+ "video",
12851
+ {
12852
+ controls: true,
12853
+ playsInline: true,
12854
+ preload: "metadata",
12855
+ src: mediaUrl,
12856
+ className: "max-h-[65vh] w-auto max-w-full rounded-2xl",
12857
+ children: "Your browser does not support the video tag."
12858
+ }
12859
+ ) }),
12860
+ isAudio && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-xl p-8 rounded-2xl border border-border bg-card shadow-sm flex flex-col items-center gap-6", children: [
12861
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded-full bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "music", className: "h-10 w-10 animate-pulse" }) }),
12862
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
12863
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-semibold text-foreground mb-1", children: file.name }),
12864
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground uppercase tracking-wider", children: "Audio Playback" })
12865
+ ] }),
12866
+ /* @__PURE__ */ jsxRuntime.jsx("audio", { controls: true, preload: "metadata", src: mediaUrl, className: "w-full h-12", children: "Your browser does not support the audio element." })
12867
+ ] }),
12868
+ isImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-full max-w-4xl rounded-2xl border border-border bg-muted/20 p-4 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(
12869
+ "img",
12870
+ {
12871
+ src: mediaUrl,
12872
+ alt: file.name,
12873
+ className: "max-h-[68vh] w-auto max-w-full rounded-xl object-contain shadow-md"
12874
+ }
12875
+ ) }),
12876
+ isPdf && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full min-h-[500px] rounded-2xl overflow-hidden border border-border shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(
12877
+ "iframe",
12878
+ {
12879
+ src: mediaUrl,
12880
+ title: file.name,
12881
+ className: "w-full h-full min-h-[500px]"
12882
+ }
12883
+ ) })
12884
+ ] })
12885
+ ] });
12886
+ };
12641
12887
  var SpecNavigator = React3__namespace.memo(
12642
12888
  ({ groups, selectedFileId, onFileSelect, className }) => {
12643
12889
  var _a, _b;
@@ -13674,7 +13920,7 @@ function formatJson(text) {
13674
13920
  }
13675
13921
  var TextEditor = React3__namespace.default.memo(
13676
13922
  (props) => {
13677
- var _a, _b;
13923
+ var _a, _b, _c;
13678
13924
  const {
13679
13925
  mode = "review",
13680
13926
  currentUser: currentUser2,
@@ -13748,7 +13994,6 @@ var TextEditor = React3__namespace.default.memo(
13748
13994
  const isExternal = (href == null ? void 0 : href.startsWith("http://")) || (href == null ? void 0 : href.startsWith("https://")) || (href == null ? void 0 : href.startsWith("mailto:"));
13749
13995
  const isAnchor = href == null ? void 0 : href.startsWith("#");
13750
13996
  const handleClick = (e) => {
13751
- var _a3, _b2;
13752
13997
  if (!isExternal && !isAnchor && href) {
13753
13998
  e.preventDefault();
13754
13999
  let targetId = href;
@@ -13778,29 +14023,17 @@ var TextEditor = React3__namespace.default.memo(
13778
14023
  } else if (targetId.startsWith("./")) {
13779
14024
  targetId = targetId.slice(2);
13780
14025
  }
13781
- console.log("[TextEditor:LinkComponent:handleClick]", {
13782
- href,
13783
- activeDocumentId: props.activeDocumentId,
13784
- computedTargetId: targetId,
13785
- documentsCount: (_a3 = props.documents) == null ? void 0 : _a3.length
13786
- });
13787
14026
  if (isMultiTab && props.documents) {
13788
14027
  const cleanTarget = targetId.replace(/^\.\//, "").replace(/^\//, "");
13789
14028
  const matchedDoc = props.documents.find((doc) => {
13790
14029
  const cleanDocId = doc.file.id.replace(/^\.\//, "").replace(/^\//, "");
13791
14030
  return cleanDocId === cleanTarget || cleanDocId.endsWith("/" + cleanTarget) || cleanTarget.endsWith("/" + cleanDocId) || doc.file.name === targetId;
13792
14031
  });
13793
- console.log("[TextEditor:LinkComponent:matching]", {
13794
- cleanTarget,
13795
- matchedDocId: (_b2 = matchedDoc == null ? void 0 : matchedDoc.file) == null ? void 0 : _b2.id,
13796
- availableDocIds: props.documents.map((d) => d.file.id)
13797
- });
13798
14032
  if (matchedDoc) {
13799
14033
  targetId = matchedDoc.file.id;
13800
14034
  }
13801
14035
  }
13802
14036
  if (handleTabSelect) {
13803
- console.log("[TextEditor:LinkComponent:onTabSelect]", targetId);
13804
14037
  handleTabSelect(targetId);
13805
14038
  }
13806
14039
  }
@@ -13810,6 +14043,7 @@ var TextEditor = React3__namespace.default.memo(
13810
14043
  }
13811
14044
  return /* @__PURE__ */ jsxRuntime.jsx("a", __spreadValues({ href, onClick: handleClick, className: "text-primary hover:underline cursor-pointer" }, rest));
13812
14045
  }, [handleTabSelect, isMultiTab, props.activeDocumentId, props.documents]);
14046
+ const markdownComponents = React3.useMemo(() => ({ a: LinkComponent }), [LinkComponent]);
13813
14047
  if (!isMultiTab) {
13814
14048
  const format = props.format || "markdown";
13815
14049
  const isMarkdown = format === "markdown" || format === "md" || format === "mdx";
@@ -13819,7 +14053,7 @@ var TextEditor = React3__namespace.default.memo(
13819
14053
  const content = isCode ? `\`\`\`${format}
13820
14054
  ${format === "json" ? formatJson(contentStr) : contentStr}
13821
14055
  \`\`\`` : contentStr;
13822
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("text-editor p-6 flex flex-col h-screen w-full flex-1", className), children: /* @__PURE__ */ jsxRuntime.jsx(StreamingMarkdown, { mode: "streaming", components: { a: LinkComponent }, children: content }) });
14056
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("text-editor p-6 flex flex-col h-screen w-full flex-1", className), children: /* @__PURE__ */ jsxRuntime.jsx(StreamingMarkdown, { mode: "static", isAnimating: false, components: markdownComponents, children: content }) });
13823
14057
  }
13824
14058
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("text-editor flex flex-col h-screen w-full flex-1", className), children: /* @__PURE__ */ jsxRuntime.jsx(
13825
14059
  DocumentEditorWithComments,
@@ -13853,6 +14087,20 @@ ${format === "json" ? formatJson(contentStr) : contentStr}
13853
14087
  ),
13854
14088
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center p-6 text-muted-foreground", children: "No documents selected" })
13855
14089
  ] });
14090
+ } else if (isMediaFile(currentDocument.file)) {
14091
+ editorPane = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-editor flex flex-col h-full w-full", children: [
14092
+ !hideTabBar && /* @__PURE__ */ jsxRuntime.jsx(
14093
+ DocumentTabBar,
14094
+ {
14095
+ className: "w-full",
14096
+ tabs: ((_b = props.documents) == null ? void 0 : _b.map((doc) => doc.file)) || [],
14097
+ activeTabId: props.activeDocumentId,
14098
+ onTabSelect: props.onTabSelect,
14099
+ onTabClose: props.onTabClose
14100
+ }
14101
+ ),
14102
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(MediaPreview, { file: currentDocument.file }) })
14103
+ ] });
13856
14104
  } else {
13857
14105
  const format = currentDocument.file.format || "markdown";
13858
14106
  const isMarkdownMulti = format === "markdown" || format === "md" || format === "mdx";
@@ -13870,7 +14118,7 @@ ${format === "json" ? formatJson(contentStr) : contentStr}
13870
14118
  DocumentTabBar,
13871
14119
  {
13872
14120
  className: "w-full",
13873
- tabs: ((_b = props.documents) == null ? void 0 : _b.map((doc) => doc.file)) || [],
14121
+ tabs: ((_c = props.documents) == null ? void 0 : _c.map((doc) => doc.file)) || [],
13874
14122
  activeTabId: props.activeDocumentId,
13875
14123
  onTabSelect: props.onTabSelect,
13876
14124
  onTabClose: props.onTabClose
@@ -13879,10 +14127,10 @@ ${format === "json" ? formatJson(contentStr) : contentStr}
13879
14127
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !hasContent ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-8 text-muted-foreground animate-pulse", children: "Loading document content..." }) : renderAsStreamdown ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(
13880
14128
  StreamingMarkdown,
13881
14129
  {
13882
- mode: "streaming",
13883
- isAnimating: true,
14130
+ mode: "static",
14131
+ isAnimating: false,
13884
14132
  className: "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
13885
- components: { a: LinkComponent },
14133
+ components: markdownComponents,
13886
14134
  children: isCodeMulti ? `\`\`\`${format}
13887
14135
  ${format === "json" ? formatJson(currentDocument.content) : currentDocument.content}
13888
14136
  \`\`\`` : currentDocument.content