@copilotz/chat-ui 0.1.20 → 0.1.22

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/index.d.cts CHANGED
@@ -38,6 +38,8 @@ interface ChatMessage {
38
38
  id: string;
39
39
  role: 'user' | 'assistant' | 'system';
40
40
  content: string;
41
+ /** Optional model reasoning stream (when the backend emits reasoning tokens separately). */
42
+ reasoning?: string;
41
43
  timestamp: number;
42
44
  attachments?: MediaAttachment[];
43
45
  isStreaming?: boolean;
package/dist/index.d.ts CHANGED
@@ -38,6 +38,8 @@ interface ChatMessage {
38
38
  id: string;
39
39
  role: 'user' | 'assistant' | 'system';
40
40
  content: string;
41
+ /** Optional model reasoning stream (when the backend emits reasoning tokens separately). */
42
+ reasoning?: string;
41
43
  timestamp: number;
42
44
  attachments?: MediaAttachment[];
43
45
  isStreaming?: boolean;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/chat/ChatUI.tsx
2
- import { useState as useState8, useEffect as useEffect10, useRef as useRef6, useCallback as useCallback4, useMemo as useMemo4 } from "react";
2
+ import { useState as useState8, useEffect as useEffect10, useRef as useRef7, useCallback as useCallback4, useMemo as useMemo4 } from "react";
3
3
  import { useVirtualizer } from "@tanstack/react-virtual";
4
4
 
5
5
  // src/config/chatConfig.ts
@@ -245,7 +245,7 @@ var configUtils = {
245
245
  };
246
246
 
247
247
  // src/components/chat/Message.tsx
248
- import React, { useState, useMemo, useEffect, memo } from "react";
248
+ import React, { useState, useMemo, useEffect, useRef, memo } from "react";
249
249
  import ReactMarkdown from "react-markdown";
250
250
  import remarkGfm from "remark-gfm";
251
251
  import rehypeHighlight from "rehype-highlight";
@@ -553,7 +553,8 @@ import {
553
553
  Wrench,
554
554
  Clock,
555
555
  ChevronRight,
556
- ChevronDown
556
+ ChevronDown,
557
+ BrainCircuit
557
558
  } from "lucide-react";
558
559
  import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
559
560
  var ThinkingIndicator = memo(function ThinkingIndicator2({ label = "Thinking..." }) {
@@ -584,6 +585,90 @@ var ThinkingIndicator = memo(function ThinkingIndicator2({ label = "Thinking..."
584
585
  /* @__PURE__ */ jsx7("span", { className: "text-sm text-muted-foreground animate-pulse", children: label })
585
586
  ] });
586
587
  });
588
+ var ThinkingBlock = memo(function ThinkingBlock2({ content, isStreaming = false, label = "Thinking", chunkSize = 12e3 }) {
589
+ const [open, setOpen] = useState(isStreaming);
590
+ const [thinkingActive, setThinkingActive] = useState(false);
591
+ const contentLenRef = useRef(0);
592
+ useEffect(() => {
593
+ if (!isStreaming) {
594
+ setThinkingActive(false);
595
+ contentLenRef.current = 0;
596
+ return;
597
+ }
598
+ if (content.length > contentLenRef.current) {
599
+ contentLenRef.current = content.length;
600
+ setThinkingActive(true);
601
+ }
602
+ }, [content.length, isStreaming]);
603
+ useEffect(() => {
604
+ if (!thinkingActive || !isStreaming) return;
605
+ const timer = setTimeout(() => setThinkingActive(false), 1e3);
606
+ return () => clearTimeout(timer);
607
+ }, [thinkingActive, isStreaming, content.length]);
608
+ useEffect(() => {
609
+ if (isStreaming && content.length > 0) setOpen(true);
610
+ }, [isStreaming, content.length]);
611
+ return /* @__PURE__ */ jsxs2("div", { className: "mb-3 relative rounded-lg overflow-hidden", children: [
612
+ thinkingActive && /* @__PURE__ */ jsx7(
613
+ "div",
614
+ {
615
+ className: "absolute inset-0 rounded-lg pointer-events-none",
616
+ style: {
617
+ padding: "1px",
618
+ background: "linear-gradient(135deg, hsl(var(--primary) / 0.4), hsl(var(--muted-foreground) / 0.2), hsl(var(--primary) / 0.4))",
619
+ backgroundSize: "200% 200%",
620
+ animation: "thinking-shimmer 2s ease-in-out infinite"
621
+ }
622
+ }
623
+ ),
624
+ /* @__PURE__ */ jsxs2(
625
+ "div",
626
+ {
627
+ className: `relative rounded-lg border bg-muted/30 ${thinkingActive ? "border-transparent" : "border-muted-foreground/20"}`,
628
+ style: thinkingActive ? { margin: "1px" } : void 0,
629
+ children: [
630
+ /* @__PURE__ */ jsxs2(
631
+ "button",
632
+ {
633
+ type: "button",
634
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left text-xs font-medium text-muted-foreground hover:text-foreground/70 transition-colors",
635
+ onClick: () => setOpen((v) => !v),
636
+ children: [
637
+ /* @__PURE__ */ jsx7(BrainCircuit, { className: `h-3.5 w-3.5 flex-shrink-0 ${thinkingActive ? "animate-pulse text-primary" : ""}` }),
638
+ /* @__PURE__ */ jsxs2("span", { className: "flex-1", children: [
639
+ label,
640
+ thinkingActive && /* @__PURE__ */ jsxs2("span", { className: "ml-1 inline-flex gap-0.5", children: [
641
+ /* @__PURE__ */ jsx7("span", { className: "inline-block w-1 h-1 bg-current rounded-full animate-bounce", style: { animationDelay: "0ms" } }),
642
+ /* @__PURE__ */ jsx7("span", { className: "inline-block w-1 h-1 bg-current rounded-full animate-bounce", style: { animationDelay: "150ms" } }),
643
+ /* @__PURE__ */ jsx7("span", { className: "inline-block w-1 h-1 bg-current rounded-full animate-bounce", style: { animationDelay: "300ms" } })
644
+ ] })
645
+ ] }),
646
+ open ? /* @__PURE__ */ jsx7(ChevronDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx7(ChevronRight, { className: "h-3 w-3" })
647
+ ]
648
+ }
649
+ ),
650
+ open && /* @__PURE__ */ jsx7("div", { className: "px-3 pb-3", children: /* @__PURE__ */ jsxs2("div", { className: "border-l-2 border-muted-foreground/20 pl-3", children: [
651
+ /* @__PURE__ */ jsx7(
652
+ PlainTextContent,
653
+ {
654
+ content,
655
+ className: "text-xs text-muted-foreground/80 leading-5",
656
+ chunkSize
657
+ }
658
+ ),
659
+ thinkingActive && /* @__PURE__ */ jsx7("span", { className: "inline-block w-1.5 h-3 bg-muted-foreground/40 animate-pulse ml-0.5" })
660
+ ] }) })
661
+ ]
662
+ }
663
+ ),
664
+ /* @__PURE__ */ jsx7("style", { children: `
665
+ @keyframes thinking-shimmer {
666
+ 0%, 100% { background-position: 0% 50%; }
667
+ 50% { background-position: 100% 50%; }
668
+ }
669
+ ` })
670
+ ] });
671
+ });
587
672
  var markdownComponents = {
588
673
  code: ({ node, className, children, ...props }) => {
589
674
  const inline = props.inline;
@@ -974,6 +1059,15 @@ var Message = memo(({
974
1059
  ] })
975
1060
  ] })
976
1061
  ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1062
+ !messageIsUser && message.reasoning && message.reasoning.trim().length > 0 && /* @__PURE__ */ jsx7(
1063
+ ThinkingBlock,
1064
+ {
1065
+ content: message.reasoning,
1066
+ isStreaming: message.isStreaming,
1067
+ label: thinkingLabel,
1068
+ chunkSize: normalizedChunkChars
1069
+ }
1070
+ ),
977
1071
  enableToolCallsDisplay && message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mb-3", children: /* @__PURE__ */ jsx7(ToolCallsDisplay, { toolCalls: message.toolCalls, label: toolUsedLabel }) }),
978
1072
  /* @__PURE__ */ jsx7(
979
1073
  StreamingText,
@@ -1048,7 +1142,7 @@ var Message = memo(({
1048
1142
  }, arePropsEqual);
1049
1143
 
1050
1144
  // src/components/chat/Sidebar.tsx
1051
- import { useState as useState4, useRef as useRef4, useEffect as useEffect7 } from "react";
1145
+ import { useState as useState4, useRef as useRef5, useEffect as useEffect7 } from "react";
1052
1146
 
1053
1147
  // src/components/ui/input.tsx
1054
1148
  import { jsx as jsx8 } from "react/jsx-runtime";
@@ -2297,7 +2391,7 @@ var Sidebar2 = ({
2297
2391
  const [deleteThreadId, setDeleteThreadId] = useState4(null);
2298
2392
  const [editingThreadId, setEditingThreadId] = useState4(null);
2299
2393
  const [editTitle, setEditTitle] = useState4("");
2300
- const inputRef = useRef4(null);
2394
+ const inputRef = useRef5(null);
2301
2395
  const { setOpen } = useSidebar();
2302
2396
  useEffect7(() => {
2303
2397
  if (editingThreadId && inputRef.current) {
@@ -2716,7 +2810,7 @@ var ChatHeader = ({
2716
2810
  };
2717
2811
 
2718
2812
  // src/components/chat/ChatInput.tsx
2719
- import { useState as useState6, useRef as useRef5, useCallback as useCallback3, useEffect as useEffect9, memo as memo2 } from "react";
2813
+ import { useState as useState6, useRef as useRef6, useCallback as useCallback3, useEffect as useEffect9, memo as memo2 } from "react";
2720
2814
 
2721
2815
  // src/components/chat/UserContext.tsx
2722
2816
  import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect8, useMemo as useMemo3, useState as useState5 } from "react";
@@ -2860,7 +2954,7 @@ var FileUploadItem = memo2(function FileUploadItem2({ file, progress, onCancel }
2860
2954
  var AttachmentPreview = memo2(function AttachmentPreview2({ attachment, onRemove }) {
2861
2955
  const [isPlaying, setIsPlaying] = useState6(false);
2862
2956
  const [audioPlaybackSrc, setAudioPlaybackSrc] = useState6(attachment.dataUrl);
2863
- const audioRef = useRef5(null);
2957
+ const audioRef = useRef6(null);
2864
2958
  useEffect9(() => {
2865
2959
  if (attachment.kind !== "audio" || !attachment.dataUrl.startsWith("data:")) {
2866
2960
  setAudioPlaybackSrc(attachment.dataUrl);
@@ -3053,12 +3147,12 @@ var ChatInput = memo2(function ChatInput2({
3053
3147
  const { setContext } = useChatUserContext();
3054
3148
  const [recordingDuration, setRecordingDuration] = useState6(0);
3055
3149
  const [uploadProgress, setUploadProgress] = useState6(/* @__PURE__ */ new Map());
3056
- const textareaRef = useRef5(null);
3057
- const fileInputRef = useRef5(null);
3058
- const mediaRecorderRef = useRef5(null);
3059
- const recordingStartTime = useRef5(0);
3060
- const recordingInterval = useRef5(null);
3061
- const mediaStreamRef = useRef5(null);
3150
+ const textareaRef = useRef6(null);
3151
+ const fileInputRef = useRef6(null);
3152
+ const mediaRecorderRef = useRef6(null);
3153
+ const recordingStartTime = useRef6(0);
3154
+ const recordingInterval = useRef6(null);
3155
+ const mediaStreamRef = useRef6(null);
3062
3156
  useEffect9(() => {
3063
3157
  return () => {
3064
3158
  if (mediaStreamRef.current) {
@@ -3922,18 +4016,18 @@ var ChatUI = ({
3922
4016
  setState((prev) => ({ ...prev, selectedThreadId: currentThreadId }));
3923
4017
  }
3924
4018
  }, [currentThreadId]);
3925
- const initialInputApplied = useRef6(false);
3926
- const initialInputConsumedRef = useRef6(false);
4019
+ const initialInputApplied = useRef7(false);
4020
+ const initialInputConsumedRef = useRef7(false);
3927
4021
  useEffect10(() => {
3928
4022
  if (initialInput && !initialInputApplied.current) {
3929
4023
  setInputValue(initialInput);
3930
4024
  initialInputApplied.current = true;
3931
4025
  }
3932
4026
  }, [initialInput]);
3933
- const scrollAreaRef = useRef6(null);
3934
- const stateRef = useRef6(state);
3935
- const inputValueRef = useRef6(inputValue);
3936
- const attachmentsRef = useRef6(attachments);
4027
+ const scrollAreaRef = useRef7(null);
4028
+ const stateRef = useRef7(state);
4029
+ const inputValueRef = useRef7(inputValue);
4030
+ const attachmentsRef = useRef7(attachments);
3937
4031
  useEffect10(() => {
3938
4032
  stateRef.current = state;
3939
4033
  }, [state]);
@@ -3982,7 +4076,7 @@ var ChatUI = ({
3982
4076
  return () => clearTimeout(t);
3983
4077
  }
3984
4078
  }, [state.showSidebar, isMobile, config.customComponent]);
3985
- const prevMessageCountRef = useRef6(0);
4079
+ const prevMessageCountRef = useRef7(0);
3986
4080
  useEffect10(() => {
3987
4081
  if (messages.length === 0) {
3988
4082
  prevMessageCountRef.current = 0;
@@ -4395,7 +4489,7 @@ var ChatUI = ({
4395
4489
  };
4396
4490
 
4397
4491
  // src/components/chat/ThreadManager.tsx
4398
- import { useState as useState9, useRef as useRef7, useEffect as useEffect11 } from "react";
4492
+ import { useState as useState9, useRef as useRef8, useEffect as useEffect11 } from "react";
4399
4493
  import {
4400
4494
  Plus as Plus4,
4401
4495
  MessageSquare as MessageSquare2,
@@ -4414,7 +4508,7 @@ import { Fragment as Fragment6, jsx as jsx25, jsxs as jsxs15 } from "react/jsx-r
4414
4508
  var ThreadItem = ({ thread, isActive, config, onSelect, onRename, onDelete, onArchive }) => {
4415
4509
  const [isEditing, setIsEditing] = useState9(false);
4416
4510
  const [editTitle, setEditTitle] = useState9(thread.title);
4417
- const inputRef = useRef7(null);
4511
+ const inputRef = useRef8(null);
4418
4512
  useEffect11(() => {
4419
4513
  if (isEditing && inputRef.current) {
4420
4514
  inputRef.current.focus();