@nextclaw/agent-chat-ui 0.3.9 → 0.3.11

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.
Files changed (2) hide show
  1. package/dist/index.js +205 -43
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -17,6 +17,19 @@ import { $applyNodeReplacement, $createLineBreakNode, $createParagraphNode, $cre
17
17
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
18
18
  import ReactMarkdown from "react-markdown";
19
19
  import remarkGfm from "remark-gfm";
20
+ import hljs from "highlight.js/lib/core";
21
+ import bash from "highlight.js/lib/languages/bash";
22
+ import css from "highlight.js/lib/languages/css";
23
+ import diff from "highlight.js/lib/languages/diff";
24
+ import javascript from "highlight.js/lib/languages/javascript";
25
+ import json from "highlight.js/lib/languages/json";
26
+ import markdown from "highlight.js/lib/languages/markdown";
27
+ import python from "highlight.js/lib/languages/python";
28
+ import shell from "highlight.js/lib/languages/shell";
29
+ import sql from "highlight.js/lib/languages/sql";
30
+ import typescript from "highlight.js/lib/languages/typescript";
31
+ import xml from "highlight.js/lib/languages/xml";
32
+ import yaml from "highlight.js/lib/languages/yaml";
20
33
  //#region src/components/chat/hooks/use-active-item-scroll.ts
21
34
  const defaultGetItemSelector = (index) => `[data-item-index="${index}"]`;
22
35
  function useActiveItemScroll(params) {
@@ -360,7 +373,7 @@ function ChatInputBarActions({ sendError, sendErrorDetailsLabel, isSending, canS
360
373
  const sendErrorPreview = normalizedSendError ? buildSendErrorPreview(normalizedSendError) : "";
361
374
  const resolvedSendErrorDetailsLabel = sendErrorDetailsLabel?.trim() || "Details";
362
375
  return /* @__PURE__ */ jsxs("div", {
363
- className: "flex flex-col items-end gap-1",
376
+ className: "flex shrink-0 flex-col items-end gap-1",
364
377
  children: [normalizedSendError ? /* @__PURE__ */ jsxs("div", {
365
378
  className: "flex max-w-[420px] items-start justify-end gap-2 text-right",
366
379
  children: [/* @__PURE__ */ jsx("div", {
@@ -507,12 +520,16 @@ function ChatInputBarSkillPicker(props) {
507
520
  children: /* @__PURE__ */ jsxs("button", {
508
521
  type: "button",
509
522
  "aria-haspopup": "listbox",
510
- className: "inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900",
523
+ "aria-label": picker.title,
524
+ className: "relative inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg px-0 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 sm:w-auto sm:gap-1.5 sm:px-3",
511
525
  children: [
512
526
  /* @__PURE__ */ jsx(BrainCircuit, { className: "h-4 w-4" }),
513
- /* @__PURE__ */ jsx("span", { children: picker.title }),
527
+ /* @__PURE__ */ jsx("span", {
528
+ className: "hidden sm:inline",
529
+ children: picker.title
530
+ }),
514
531
  selectedCount > 0 ? /* @__PURE__ */ jsx("span", {
515
- className: "ml-0.5 flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary",
532
+ className: "absolute -right-1 -top-1 flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-white shadow-sm sm:static sm:ml-0.5 sm:bg-primary/10 sm:text-primary sm:shadow-none",
516
533
  children: selectedCount
517
534
  }) : null
518
535
  ]
@@ -628,25 +645,23 @@ function ChatInputBarSkillPicker(props) {
628
645
  //#endregion
629
646
  //#region src/components/chat/ui/chat-input-bar/chat-input-bar-toolbar.tsx
630
647
  function ToolbarIcon({ icon }) {
631
- if (icon === "sparkles") return /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" });
632
- if (icon === "brain") return /* @__PURE__ */ jsx(Brain, { className: "h-3.5 w-3.5 shrink-0 text-gray-500" });
633
- return null;
648
+ return icon === "sparkles" ? /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" }) : icon === "brain" ? /* @__PURE__ */ jsx(Brain, { className: "h-3.5 w-3.5 shrink-0 text-gray-500" }) : null;
634
649
  }
635
650
  function AccessoryIcon({ icon }) {
636
- if (icon === "paperclip") return /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" });
637
- return /* @__PURE__ */ jsx(ToolbarIcon, { icon });
638
- }
639
- function resolveTriggerWidth(key) {
640
- if (key === "model") return "min-w-[220px]";
641
- if (key === "session-type") return "min-w-[140px]";
642
- if (key === "thinking") return "min-w-[150px]";
643
- return "";
651
+ return icon === "paperclip" ? /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ToolbarIcon, { icon });
644
652
  }
645
- function resolveContentWidth(key) {
646
- if (key === "model") return "w-[320px]";
647
- if (key === "session-type") return "w-[220px]";
648
- if (key === "thinking") return "w-[180px]";
649
- return "";
653
+ const TRIGGER_WIDTH_BY_KEY = {
654
+ model: "min-w-0 flex-1 sm:min-w-[220px] sm:flex-none",
655
+ "session-type": "shrink-0",
656
+ thinking: "shrink-0"
657
+ };
658
+ const CONTENT_WIDTH_BY_KEY = {
659
+ model: "w-[min(18rem,calc(100vw-1rem))] sm:w-[320px]",
660
+ "session-type": "w-[220px]",
661
+ thinking: "w-[180px]"
662
+ };
663
+ function resolveMobileSelectedLabel(item) {
664
+ return item.key === "model" && item.selectedLabel ? item.selectedLabel.split("/").slice(1).join("/").trim() || item.selectedLabel : item.selectedLabel;
650
665
  }
651
666
  function ToolbarSelect({ item }) {
652
667
  const { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } = ChatUiPrimitives;
@@ -655,21 +670,29 @@ function ToolbarSelect({ item }) {
655
670
  options: item.options
656
671
  }] : []);
657
672
  const hasOptions = groups.some((group) => group.options.length > 0);
673
+ const mobileSelectedLabel = resolveMobileSelectedLabel(item);
658
674
  return /* @__PURE__ */ jsxs(Select, {
659
675
  value: item.value,
660
676
  onValueChange: item.onValueChange,
661
677
  disabled: item.disabled,
662
678
  children: [/* @__PURE__ */ jsx(SelectTrigger, {
663
- className: `h-8 w-auto rounded-lg border-0 bg-transparent px-3 text-xs font-medium text-gray-600 shadow-none hover:bg-gray-100 focus:ring-0 ${resolveTriggerWidth(item.key)}`,
679
+ className: `h-8 w-auto rounded-lg border-0 bg-transparent px-2 text-xs font-medium text-gray-600 shadow-none hover:bg-gray-100 focus:ring-0 sm:px-3 ${TRIGGER_WIDTH_BY_KEY[item.key] ?? ""}`,
664
680
  children: item.selectedLabel ? /* @__PURE__ */ jsxs("div", {
665
681
  className: "flex min-w-0 items-center gap-2 text-left",
666
- children: [/* @__PURE__ */ jsx(ToolbarIcon, { icon: item.icon }), /* @__PURE__ */ jsx("span", {
667
- className: "truncate text-xs font-semibold text-gray-700",
668
- children: item.selectedLabel
669
- })]
682
+ children: [
683
+ /* @__PURE__ */ jsx(ToolbarIcon, { icon: item.icon }),
684
+ /* @__PURE__ */ jsx("span", {
685
+ className: "truncate text-xs font-semibold text-gray-700 sm:hidden",
686
+ children: mobileSelectedLabel
687
+ }),
688
+ /* @__PURE__ */ jsx("span", {
689
+ className: "hidden truncate text-xs font-semibold text-gray-700 sm:inline",
690
+ children: item.selectedLabel
691
+ })
692
+ ]
670
693
  }) : item.loading ? /* @__PURE__ */ jsx("div", { className: "h-3 w-24 animate-pulse rounded bg-gray-200" }) : /* @__PURE__ */ jsx(SelectValue, { placeholder: item.placeholder })
671
694
  }), /* @__PURE__ */ jsxs(SelectContent, {
672
- className: resolveContentWidth(item.key),
695
+ className: CONTENT_WIDTH_BY_KEY[item.key] ?? "",
673
696
  children: [!hasOptions ? item.loading ? /* @__PURE__ */ jsxs("div", {
674
697
  className: "space-y-2 px-3 py-2",
675
698
  children: [
@@ -700,16 +723,16 @@ function ToolbarSelect({ item }) {
700
723
  })]
701
724
  });
702
725
  }
703
- function ChatInputBarToolbar(props) {
726
+ function ChatInputBarToolbar({ actions, accessories, selects, skillPicker }) {
704
727
  const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
705
728
  return /* @__PURE__ */ jsxs("div", {
706
- className: "flex items-center justify-between px-3 pb-3",
729
+ className: "flex items-center justify-between gap-2 px-3 pb-3",
707
730
  children: [/* @__PURE__ */ jsxs("div", {
708
- className: "flex items-center gap-1",
731
+ className: "flex min-w-0 flex-1 items-center gap-1 overflow-hidden",
709
732
  children: [
710
- props.skillPicker ? /* @__PURE__ */ jsx(ChatInputBarSkillPicker, { picker: props.skillPicker }) : null,
711
- props.selects.map((item) => /* @__PURE__ */ jsx(ToolbarSelect, { item }, item.key)),
712
- props.accessories?.map((item) => {
733
+ skillPicker ? /* @__PURE__ */ jsx(ChatInputBarSkillPicker, { picker: skillPicker }) : null,
734
+ selects.map((item) => /* @__PURE__ */ jsx(ToolbarSelect, { item }, item.key)),
735
+ accessories?.map((item) => {
713
736
  const button = /* @__PURE__ */ jsxs("button", {
714
737
  type: "button",
715
738
  className: `inline-flex items-center rounded-lg py-1.5 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 disabled:cursor-not-allowed disabled:text-gray-400 ${item.iconOnly ? "h-8 w-8 justify-center px-0" : "gap-1.5 px-3"}`,
@@ -734,7 +757,7 @@ function ChatInputBarToolbar(props) {
734
757
  })] }) }, item.key);
735
758
  })
736
759
  ]
737
- }), /* @__PURE__ */ jsx(ChatInputBarActions, { ...props.actions })]
760
+ }), /* @__PURE__ */ jsx(ChatInputBarActions, { ...actions })]
738
761
  });
739
762
  }
740
763
  function createComposerNodeId() {
@@ -1700,9 +1723,9 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1700
1723
  }), [props.disabled, props.nodes]),
1701
1724
  children: [
1702
1725
  /* @__PURE__ */ jsx("div", {
1703
- className: "px-4 py-2.5",
1726
+ className: "px-3 py-2 sm:px-4 sm:py-2.5",
1704
1727
  children: /* @__PURE__ */ jsx("div", {
1705
- className: "min-h-[60px]",
1728
+ className: "min-h-11 sm:min-h-[60px]",
1706
1729
  children: /* @__PURE__ */ jsx(PlainTextPlugin, {
1707
1730
  contentEditable: /* @__PURE__ */ jsx(ContentEditable, {
1708
1731
  className: "min-h-7 max-h-[188px] w-full overflow-y-auto whitespace-pre-wrap break-words bg-transparent py-0.5 text-sm leading-6 text-gray-800 outline-none",
@@ -1740,7 +1763,7 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1740
1763
  }
1741
1764
  }),
1742
1765
  placeholder: /* @__PURE__ */ jsx("div", {
1743
- className: "pointer-events-none absolute left-4 top-2.5 select-none text-sm leading-6 text-gray-400",
1766
+ className: "pointer-events-none absolute left-3 top-2 select-none text-sm leading-6 text-gray-400 sm:left-4 sm:top-2.5",
1744
1767
  children: props.placeholder
1745
1768
  }),
1746
1769
  ErrorBoundary: LexicalErrorBoundary
@@ -2015,6 +2038,139 @@ function useCopyFeedback(params) {
2015
2038
  };
2016
2039
  }
2017
2040
  //#endregion
2041
+ //#region src/components/chat/ui/chat-message-list/code-block/chat-code-syntax-highlighter.ts
2042
+ const PLAIN_TEXT_LANGUAGES = new Set([
2043
+ "text",
2044
+ "txt",
2045
+ "plain",
2046
+ "plaintext",
2047
+ "none",
2048
+ "output"
2049
+ ]);
2050
+ const AUTO_DETECT_LANGUAGES = [
2051
+ "typescript",
2052
+ "javascript",
2053
+ "json",
2054
+ "bash",
2055
+ "shell",
2056
+ "python",
2057
+ "css",
2058
+ "xml",
2059
+ "markdown",
2060
+ "yaml",
2061
+ "sql",
2062
+ "diff"
2063
+ ];
2064
+ const AUTO_DETECT_MIN_RELEVANCE = 4;
2065
+ const REGISTERED_LANGUAGES = [
2066
+ {
2067
+ name: "typescript",
2068
+ language: typescript,
2069
+ aliases: ["ts", "tsx"]
2070
+ },
2071
+ {
2072
+ name: "javascript",
2073
+ language: javascript,
2074
+ aliases: [
2075
+ "js",
2076
+ "jsx",
2077
+ "mjs",
2078
+ "cjs"
2079
+ ]
2080
+ },
2081
+ {
2082
+ name: "json",
2083
+ language: json,
2084
+ aliases: ["jsonc"]
2085
+ },
2086
+ {
2087
+ name: "bash",
2088
+ language: bash,
2089
+ aliases: ["sh", "zsh"]
2090
+ },
2091
+ {
2092
+ name: "shell",
2093
+ language: shell,
2094
+ aliases: ["console", "terminal"]
2095
+ },
2096
+ {
2097
+ name: "python",
2098
+ language: python,
2099
+ aliases: ["py"]
2100
+ },
2101
+ {
2102
+ name: "css",
2103
+ language: css
2104
+ },
2105
+ {
2106
+ name: "xml",
2107
+ language: xml,
2108
+ aliases: ["html", "svg"]
2109
+ },
2110
+ {
2111
+ name: "markdown",
2112
+ language: markdown,
2113
+ aliases: ["md", "mdx"]
2114
+ },
2115
+ {
2116
+ name: "yaml",
2117
+ language: yaml,
2118
+ aliases: ["yml"]
2119
+ },
2120
+ {
2121
+ name: "sql",
2122
+ language: sql
2123
+ },
2124
+ {
2125
+ name: "diff",
2126
+ language: diff,
2127
+ aliases: ["patch"]
2128
+ }
2129
+ ];
2130
+ function escapeHtml(value) {
2131
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2132
+ }
2133
+ function normalizeLanguage(value) {
2134
+ return value.trim().toLowerCase();
2135
+ }
2136
+ var ChatCodeSyntaxHighlighter = class {
2137
+ constructor() {
2138
+ this.highlight = (code, language) => {
2139
+ const normalizedLanguage = normalizeLanguage(language);
2140
+ if (!code || PLAIN_TEXT_LANGUAGES.has(normalizedLanguage)) return this.createPlainTextResult(code, normalizedLanguage || "text");
2141
+ if (this.engine.getLanguage(normalizedLanguage)) {
2142
+ const result = this.engine.highlight(code, {
2143
+ language: normalizedLanguage,
2144
+ ignoreIllegals: true
2145
+ });
2146
+ return {
2147
+ html: result.value,
2148
+ language: result.language ?? normalizedLanguage,
2149
+ highlighted: true
2150
+ };
2151
+ }
2152
+ const autoDetected = this.engine.highlightAuto(code, AUTO_DETECT_LANGUAGES);
2153
+ if (autoDetected.language && autoDetected.relevance >= AUTO_DETECT_MIN_RELEVANCE) return {
2154
+ html: autoDetected.value,
2155
+ language: autoDetected.language,
2156
+ highlighted: true
2157
+ };
2158
+ return this.createPlainTextResult(code, normalizedLanguage || "text");
2159
+ };
2160
+ this.createPlainTextResult = (code, language) => ({
2161
+ html: escapeHtml(code),
2162
+ language,
2163
+ highlighted: false
2164
+ });
2165
+ this.engine = hljs.newInstance();
2166
+ REGISTERED_LANGUAGES.forEach((entry) => {
2167
+ this.engine.registerLanguage(entry.name, entry.language);
2168
+ if (entry.aliases) this.engine.registerAliases(entry.aliases, { languageName: entry.name });
2169
+ });
2170
+ }
2171
+ };
2172
+ const chatCodeSyntaxHighlighter = new ChatCodeSyntaxHighlighter();
2173
+ //#endregion
2018
2174
  //#region src/components/chat/ui/chat-message-list/chat-code-block.tsx
2019
2175
  const CODE_LANGUAGE_REGEX = /language-([a-z0-9-]+)/i;
2020
2176
  function flattenNodeText(value) {
@@ -2029,9 +2185,14 @@ function normalizeCodeText(value) {
2029
2185
  function resolveCodeLanguage(className) {
2030
2186
  return (className ? CODE_LANGUAGE_REGEX.exec(className) : null)?.[1]?.toLowerCase() || "text";
2031
2187
  }
2032
- function ChatCodeBlock(props) {
2033
- const language = useMemo(() => resolveCodeLanguage(props.className), [props.className]);
2034
- const codeText = useMemo(() => normalizeCodeText(props.children), [props.children]);
2188
+ function ChatCodeBlock({ className, children, texts }) {
2189
+ const language = useMemo(() => resolveCodeLanguage(className), [className]);
2190
+ const codeText = useMemo(() => normalizeCodeText(children), [children]);
2191
+ const highlightedCode = useMemo(() => chatCodeSyntaxHighlighter.highlight(codeText, language), [codeText, language]);
2192
+ const codeClassName = useMemo(() => {
2193
+ const highlightedLanguageClass = `language-${highlightedCode.language}`;
2194
+ return cn("hljs", className, className?.includes("language-") ? void 0 : highlightedLanguageClass);
2195
+ }, [className, highlightedCode.language]);
2035
2196
  const { copied, copy } = useCopyFeedback({ text: codeText });
2036
2197
  return /* @__PURE__ */ jsxs("div", {
2037
2198
  className: "chat-codeblock",
@@ -2044,12 +2205,13 @@ function ChatCodeBlock(props) {
2044
2205
  type: "button",
2045
2206
  className: "chat-codeblock-copy",
2046
2207
  onClick: () => void copy(),
2047
- "aria-label": copied ? props.texts.copiedCodeLabel : props.texts.copyCodeLabel,
2048
- children: [copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ jsx("span", { children: copied ? props.texts.copiedCodeLabel : props.texts.copyCodeLabel })]
2208
+ "aria-label": copied ? texts.copiedCodeLabel : texts.copyCodeLabel,
2209
+ children: [copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ jsx("span", { children: copied ? texts.copiedCodeLabel : texts.copyCodeLabel })]
2049
2210
  })]
2050
2211
  }), /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", {
2051
- className: props.className,
2052
- children: codeText
2212
+ className: codeClassName,
2213
+ "data-highlighted": highlightedCode.highlighted ? "true" : "false",
2214
+ dangerouslySetInnerHTML: { __html: highlightedCode.html }
2053
2215
  }) })]
2054
2216
  });
2055
2217
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/agent-chat-ui",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",
@@ -39,6 +39,7 @@
39
39
  "@radix-ui/react-tooltip": "^1.1.11",
40
40
  "class-variance-authority": "^0.7.1",
41
41
  "clsx": "^2.1.1",
42
+ "highlight.js": "^11.11.1",
42
43
  "lexical": "^0.43.0",
43
44
  "lucide-react": "^0.462.0",
44
45
  "react-markdown": "^10.1.0",