@yourgpt/copilot-sdk 0.1.0 → 1.0.0

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/ui/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useCopilot } from '../chunk-QUGTRQSS.js';
2
- import '../chunk-N4OA2J32.js';
1
+ import { useCopilot, useThreadManager } from '../chunk-XAVZZVUL.js';
2
+ import { createServerAdapter } from '../chunk-QSEGNATZ.js';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
@@ -16,6 +16,7 @@ import { useStickToBottomContext, StickToBottom as StickToBottom$1 } from 'use-s
16
16
  import { Tooltip as Tooltip$1 } from '@base-ui/react/tooltip';
17
17
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
18
18
  import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
19
+ import { Popover as Popover$1 } from '@base-ui/react/popover';
19
20
 
20
21
  function cn(...inputs) {
21
22
  return twMerge(clsx(inputs));
@@ -865,12 +866,13 @@ var MessageAvatar = ({
865
866
  src,
866
867
  alt,
867
868
  fallback,
869
+ fallbackIcon,
868
870
  delayMs,
869
871
  className
870
872
  }) => {
871
873
  return /* @__PURE__ */ jsxs(Avatar, { className: cn("size-7 shrink-0", className), children: [
872
874
  /* @__PURE__ */ jsx(AvatarImage, { src, alt }),
873
- fallback && /* @__PURE__ */ jsx(AvatarFallback, { delayMs, children: fallback })
875
+ /* @__PURE__ */ jsx(AvatarFallback, { delayMs, children: fallbackIcon || fallback })
874
876
  ] });
875
877
  };
876
878
  var proseSizeMap = {
@@ -1904,6 +1906,27 @@ function AlertTriangleIcon({ className }) {
1904
1906
  }
1905
1907
  );
1906
1908
  }
1909
+ function ArrowUpRightIcon({ className }) {
1910
+ return /* @__PURE__ */ jsxs(
1911
+ "svg",
1912
+ {
1913
+ xmlns: "http://www.w3.org/2000/svg",
1914
+ width: "24",
1915
+ height: "24",
1916
+ viewBox: "0 0 24 24",
1917
+ fill: "none",
1918
+ stroke: "currentColor",
1919
+ strokeWidth: "2",
1920
+ strokeLinecap: "round",
1921
+ strokeLinejoin: "round",
1922
+ className,
1923
+ children: [
1924
+ /* @__PURE__ */ jsx("path", { d: "M7 7h10v10" }),
1925
+ /* @__PURE__ */ jsx("path", { d: "M7 17 17 7" })
1926
+ ]
1927
+ }
1928
+ );
1929
+ }
1907
1930
  var ConfirmationContext = React8.createContext(null);
1908
1931
  function useConfirmationContext() {
1909
1932
  const context = React8.useContext(ConfirmationContext);
@@ -2976,143 +2999,699 @@ function SimpleModelSelector({
2976
2999
  }
2977
3000
  );
2978
3001
  }
2979
- function ChatHeader({ title, onClose, className }) {
2980
- return /* @__PURE__ */ jsxs(
2981
- "div",
3002
+ function Popover({ children, open, defaultOpen, onOpenChange }) {
3003
+ return /* @__PURE__ */ jsx(
3004
+ Popover$1.Root,
3005
+ {
3006
+ open,
3007
+ defaultOpen,
3008
+ onOpenChange,
3009
+ children
3010
+ }
3011
+ );
3012
+ }
3013
+ function PopoverTrigger({
3014
+ children,
3015
+ asChild,
3016
+ className,
3017
+ ...props
3018
+ }) {
3019
+ if (asChild && React8.isValidElement(children)) {
3020
+ return /* @__PURE__ */ jsx(Popover$1.Trigger, { render: children, className, ...props });
3021
+ }
3022
+ return /* @__PURE__ */ jsx(Popover$1.Trigger, { className, ...props, children });
3023
+ }
3024
+ function PopoverContent({
3025
+ children,
3026
+ className,
3027
+ side = "bottom",
3028
+ align = "start",
3029
+ sideOffset = 4
3030
+ }) {
3031
+ return /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(Popover$1.Positioner, { side, align, sideOffset, children: /* @__PURE__ */ jsx(
3032
+ Popover$1.Popup,
2982
3033
  {
2983
3034
  className: cn(
2984
- "flex items-center justify-between border-b px-4 py-3",
3035
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
3036
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
3037
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
3038
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
3039
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
3040
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
2985
3041
  className
2986
3042
  ),
3043
+ children
3044
+ }
3045
+ ) }) });
3046
+ }
3047
+ function formatDate(date) {
3048
+ const now = /* @__PURE__ */ new Date();
3049
+ const diff = now.getTime() - date.getTime();
3050
+ if (diff < 60 * 1e3) {
3051
+ return "Just now";
3052
+ }
3053
+ if (diff < 60 * 60 * 1e3) {
3054
+ const mins = Math.floor(diff / (60 * 1e3));
3055
+ return `${mins}m ago`;
3056
+ }
3057
+ if (diff < 24 * 60 * 60 * 1e3) {
3058
+ const hours = Math.floor(diff / (60 * 60 * 1e3));
3059
+ return `${hours}h ago`;
3060
+ }
3061
+ if (diff < 7 * 24 * 60 * 60 * 1e3) {
3062
+ const days = Math.floor(diff / (24 * 60 * 60 * 1e3));
3063
+ return `${days}d ago`;
3064
+ }
3065
+ return date.toLocaleDateString();
3066
+ }
3067
+ function ChevronIcon({ className }) {
3068
+ return /* @__PURE__ */ jsx(
3069
+ "svg",
3070
+ {
3071
+ className: cn("w-4 h-4", className),
3072
+ fill: "none",
3073
+ viewBox: "0 0 24 24",
3074
+ stroke: "currentColor",
3075
+ strokeWidth: 2,
3076
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" })
3077
+ }
3078
+ );
3079
+ }
3080
+ function PlusIcon2({ className }) {
3081
+ return /* @__PURE__ */ jsx(
3082
+ "svg",
3083
+ {
3084
+ className: cn("w-4 h-4", className),
3085
+ fill: "none",
3086
+ viewBox: "0 0 24 24",
3087
+ stroke: "currentColor",
3088
+ strokeWidth: 2,
3089
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4v16m8-8H4" })
3090
+ }
3091
+ );
3092
+ }
3093
+ function CheckIcon2({ className }) {
3094
+ return /* @__PURE__ */ jsx(
3095
+ "svg",
3096
+ {
3097
+ className: cn("w-4 h-4", className),
3098
+ fill: "none",
3099
+ viewBox: "0 0 24 24",
3100
+ stroke: "currentColor",
3101
+ strokeWidth: 2,
3102
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" })
3103
+ }
3104
+ );
3105
+ }
3106
+ function TrashIcon({ className }) {
3107
+ return /* @__PURE__ */ jsxs(
3108
+ "svg",
3109
+ {
3110
+ className: cn("w-4 h-4", className),
3111
+ fill: "none",
3112
+ viewBox: "0 0 24 24",
3113
+ stroke: "currentColor",
3114
+ strokeWidth: 2,
2987
3115
  children: [
2988
- /* @__PURE__ */ jsx("h2", { className: "font-semibold text-foreground", children: title || "Chat" }),
2989
- onClose && /* @__PURE__ */ jsx(
2990
- "button",
3116
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 6h18" }),
3117
+ /* @__PURE__ */ jsx(
3118
+ "path",
2991
3119
  {
2992
- type: "button",
2993
- onClick: onClose,
2994
- className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2995
- "aria-label": "Close chat",
2996
- children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-5 w-5" })
3120
+ strokeLinecap: "round",
3121
+ strokeLinejoin: "round",
3122
+ d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"
3123
+ }
3124
+ ),
3125
+ /* @__PURE__ */ jsx(
3126
+ "path",
3127
+ {
3128
+ strokeLinecap: "round",
3129
+ strokeLinejoin: "round",
3130
+ d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"
2997
3131
  }
2998
3132
  )
2999
3133
  ]
3000
3134
  }
3001
3135
  );
3002
3136
  }
3003
- function Suggestions({
3004
- suggestions,
3005
- onSuggestionClick,
3006
- className
3007
- }) {
3008
- if (!suggestions.length) return null;
3009
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-2 px-4 py-2", className), children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsx(
3010
- "button",
3011
- {
3012
- type: "button",
3013
- onClick: () => onSuggestionClick?.(suggestion),
3014
- className: "inline-flex items-center rounded-full border bg-background px-3 py-1.5 text-sm transition-colors hover:bg-muted",
3015
- children: suggestion
3016
- },
3017
- index
3018
- )) });
3019
- }
3020
- function DefaultMessage({
3021
- message,
3022
- userAvatar,
3023
- assistantAvatar,
3024
- showUserAvatar = false,
3025
- userMessageClassName,
3026
- assistantMessageClassName,
3027
- size = "sm",
3028
- isLastMessage = false,
3029
- isLoading = false,
3030
- toolRenderers,
3031
- onApproveToolExecution,
3032
- onRejectToolExecution,
3033
- showFollowUps = true,
3034
- onFollowUpClick,
3035
- followUpClassName,
3036
- followUpButtonClassName
3137
+ function ThreadPicker({
3138
+ value,
3139
+ threads,
3140
+ onSelect,
3141
+ onDeleteThread,
3142
+ onNewThread,
3143
+ placeholder = "Select conversation...",
3144
+ newThreadLabel = "New conversation",
3145
+ disabled = false,
3146
+ loading = false,
3147
+ size = "md",
3148
+ className,
3149
+ buttonClassName,
3150
+ dropdownClassName,
3151
+ itemClassName,
3152
+ newButtonClassName
3037
3153
  }) {
3038
- const isUser = message.role === "user";
3039
- const isStreaming = isLastMessage && isLoading;
3040
- const { cleanContent, followUps } = React8.useMemo(() => {
3041
- if (isUser || !message.content) {
3042
- return { cleanContent: message.content, followUps: [] };
3043
- }
3044
- return parseFollowUps(message.content);
3045
- }, [message.content, isUser]);
3046
- const shouldShowFollowUps = showFollowUps && !isUser && isLastMessage && !isLoading && followUps.length > 0 && onFollowUpClick;
3047
- if (isUser) {
3048
- const hasAttachments = message.attachments && message.attachments.length > 0;
3049
- return /* @__PURE__ */ jsxs(
3050
- Message,
3154
+ const [isOpen, setIsOpen] = React8.useState(false);
3155
+ const selectedThread = React8.useMemo(() => {
3156
+ if (!value) return null;
3157
+ return threads.find((t) => t.id === value) ?? null;
3158
+ }, [value, threads]);
3159
+ const handleSelect = (threadId) => {
3160
+ onSelect?.(threadId);
3161
+ setIsOpen(false);
3162
+ };
3163
+ const handleNewThread = () => {
3164
+ onNewThread?.();
3165
+ setIsOpen(false);
3166
+ };
3167
+ return /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: setIsOpen, children: [
3168
+ /* @__PURE__ */ jsxs(
3169
+ PopoverTrigger,
3051
3170
  {
3171
+ disabled: disabled || loading,
3052
3172
  className: cn(
3053
- "flex gap-2",
3054
- showUserAvatar ? "justify-end" : "justify-end"
3173
+ "flex items-center gap-1 w-full",
3174
+ disabled && "opacity-50 cursor-not-allowed",
3175
+ className,
3176
+ buttonClassName
3055
3177
  ),
3056
3178
  children: [
3057
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end max-w-[80%]", children: [
3058
- message.content && /* @__PURE__ */ jsx(
3059
- MessageContent,
3060
- {
3061
- className: cn(
3062
- "rounded-lg px-4 py-2 bg-primary text-primary-foreground",
3063
- userMessageClassName
3064
- ),
3065
- size,
3066
- children: message.content
3067
- }
3068
- ),
3069
- hasAttachments && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2 justify-end", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment }, index)) })
3070
- ] }),
3071
- showUserAvatar && /* @__PURE__ */ jsx(
3072
- MessageAvatar,
3179
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 text-xs ", children: loading ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Loading..." }) : selectedThread ? /* @__PURE__ */ jsx("span", { className: "truncate font-medium text-muted-foreground hover:text-foreground", children: selectedThread.title || "Untitled conversation" }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder }) }),
3180
+ /* @__PURE__ */ jsx(
3181
+ ChevronIcon,
3073
3182
  {
3074
- src: userAvatar.src || "",
3075
- alt: "User",
3076
- fallback: userAvatar.fallback
3183
+ className: cn(
3184
+ "flex-shrink-0 size-3 text-muted-foreground transition-transform",
3185
+ isOpen && "rotate-180"
3186
+ )
3077
3187
  }
3078
3188
  )
3079
3189
  ]
3080
3190
  }
3081
- );
3082
- }
3083
- const pendingApprovalTools = message.toolExecutions?.filter(
3084
- (exec) => exec.approvalStatus === "required"
3085
- );
3086
- const completedTools = message.toolExecutions?.filter(
3087
- (exec) => exec.approvalStatus !== "required"
3088
- );
3089
- const toolsWithCustomRenderer = completedTools?.filter(
3090
- (exec) => toolRenderers && toolRenderers[exec.name]
3091
- );
3092
- const toolsWithoutCustomRenderer = completedTools?.filter(
3093
- (exec) => !toolRenderers || !toolRenderers[exec.name]
3094
- );
3095
- const toolSteps = toolsWithoutCustomRenderer?.map((exec) => ({
3096
- id: exec.id,
3097
- name: exec.name,
3098
- args: exec.args,
3099
- status: exec.status,
3100
- result: exec.result,
3101
- error: exec.error
3102
- }));
3103
- return /* @__PURE__ */ jsxs(Message, { className: "flex gap-2", children: [
3104
- /* @__PURE__ */ jsx(
3105
- MessageAvatar,
3191
+ ),
3192
+ /* @__PURE__ */ jsxs(
3193
+ PopoverContent,
3106
3194
  {
3107
- src: assistantAvatar.src || "",
3108
- alt: "Assistant",
3109
- fallback: assistantAvatar.fallback,
3110
- className: "bg-primary text-primary-foreground"
3195
+ align: "start",
3196
+ className: cn(
3197
+ "w-[var(--anchor-width)] min-w-[250px] p-0 max-h-[300px] overflow-auto",
3198
+ dropdownClassName
3199
+ ),
3200
+ children: [
3201
+ onNewThread && /* @__PURE__ */ jsxs(
3202
+ "button",
3203
+ {
3204
+ type: "button",
3205
+ onClick: handleNewThread,
3206
+ className: cn(
3207
+ "flex items-center gap-2 w-full px-2.5 py-1.5 text-left",
3208
+ "hover:bg-accent hover:text-accent-foreground",
3209
+ "focus:bg-accent focus:text-accent-foreground focus:outline-none",
3210
+ "border-b",
3211
+ newButtonClassName
3212
+ ),
3213
+ children: [
3214
+ /* @__PURE__ */ jsx(PlusIcon2, { className: "text-primary size-3" }),
3215
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-xs", children: newThreadLabel })
3216
+ ]
3217
+ }
3218
+ ),
3219
+ threads.length > 0 ? threads.map((thread) => /* @__PURE__ */ jsxs(
3220
+ "div",
3221
+ {
3222
+ className: cn(
3223
+ "group flex items-center gap-1 w-full px-2.5 py-1.5",
3224
+ "hover:bg-accent hover:text-accent-foreground",
3225
+ "focus-within:bg-accent focus-within:text-accent-foreground",
3226
+ value === thread.id && "bg-accent",
3227
+ itemClassName
3228
+ ),
3229
+ children: [
3230
+ /* @__PURE__ */ jsxs(
3231
+ "button",
3232
+ {
3233
+ type: "button",
3234
+ onClick: () => handleSelect(thread.id),
3235
+ className: "flex-1 flex flex-col gap-0.5 text-left focus:outline-none min-w-0",
3236
+ children: [
3237
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
3238
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-xs truncate", children: thread.title || "Untitled conversation" }),
3239
+ value === thread.id && /* @__PURE__ */ jsx(CheckIcon2, { className: "flex-shrink-0 text-primary size-3" })
3240
+ ] }),
3241
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
3242
+ thread.preview && /* @__PURE__ */ jsx("span", { className: "truncate max-w-[180px]", children: thread.preview }),
3243
+ thread.preview && thread.updatedAt && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: "\xB7" }),
3244
+ thread.updatedAt && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: formatDate(thread.updatedAt) })
3245
+ ] })
3246
+ ]
3247
+ }
3248
+ ),
3249
+ onDeleteThread && /* @__PURE__ */ jsx(
3250
+ "button",
3251
+ {
3252
+ type: "button",
3253
+ onClick: (e) => {
3254
+ e.stopPropagation();
3255
+ onDeleteThread(thread.id);
3256
+ },
3257
+ className: "flex-shrink-0 p-1 rounded opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-all focus:opacity-100 focus:outline-none",
3258
+ "aria-label": "Delete thread",
3259
+ children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-3" })
3260
+ }
3261
+ )
3262
+ ]
3263
+ },
3264
+ thread.id
3265
+ )) : /* @__PURE__ */ jsx("div", { className: "px-2.5 py-3 text-center text-xs text-muted-foreground", children: "No conversations yet" })
3266
+ ]
3111
3267
  }
3112
- ),
3113
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 max-w-[80%]", children: [
3114
- message.thinking && /* @__PURE__ */ jsx(
3115
- SimpleReasoning,
3268
+ )
3269
+ ] });
3270
+ }
3271
+ function formatDate2(date) {
3272
+ const now = /* @__PURE__ */ new Date();
3273
+ const diff = now.getTime() - date.getTime();
3274
+ if (diff < 60 * 1e3) {
3275
+ return "Just now";
3276
+ }
3277
+ if (diff < 60 * 60 * 1e3) {
3278
+ const mins = Math.floor(diff / (60 * 1e3));
3279
+ return `${mins}m ago`;
3280
+ }
3281
+ if (diff < 24 * 60 * 60 * 1e3) {
3282
+ const hours = Math.floor(diff / (60 * 60 * 1e3));
3283
+ return `${hours}h ago`;
3284
+ }
3285
+ if (diff < 7 * 24 * 60 * 60 * 1e3) {
3286
+ const days = Math.floor(diff / (24 * 60 * 60 * 1e3));
3287
+ return `${days}d ago`;
3288
+ }
3289
+ return date.toLocaleDateString();
3290
+ }
3291
+ function TrashIcon2({ className }) {
3292
+ return /* @__PURE__ */ jsx(
3293
+ "svg",
3294
+ {
3295
+ className: cn("w-4 h-4", className),
3296
+ fill: "none",
3297
+ viewBox: "0 0 24 24",
3298
+ stroke: "currentColor",
3299
+ strokeWidth: 2,
3300
+ children: /* @__PURE__ */ jsx(
3301
+ "path",
3302
+ {
3303
+ strokeLinecap: "round",
3304
+ strokeLinejoin: "round",
3305
+ d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
3306
+ }
3307
+ )
3308
+ }
3309
+ );
3310
+ }
3311
+ function PlusIcon3({ className }) {
3312
+ return /* @__PURE__ */ jsx(
3313
+ "svg",
3314
+ {
3315
+ className: cn("w-4 h-4", className),
3316
+ fill: "none",
3317
+ viewBox: "0 0 24 24",
3318
+ stroke: "currentColor",
3319
+ strokeWidth: 2,
3320
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4v16m8-8H4" })
3321
+ }
3322
+ );
3323
+ }
3324
+ function MessageIcon({ className }) {
3325
+ return /* @__PURE__ */ jsx(
3326
+ "svg",
3327
+ {
3328
+ className: cn("w-4 h-4", className),
3329
+ fill: "none",
3330
+ viewBox: "0 0 24 24",
3331
+ stroke: "currentColor",
3332
+ strokeWidth: 2,
3333
+ children: /* @__PURE__ */ jsx(
3334
+ "path",
3335
+ {
3336
+ strokeLinecap: "round",
3337
+ strokeLinejoin: "round",
3338
+ d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
3339
+ }
3340
+ )
3341
+ }
3342
+ );
3343
+ }
3344
+ function ThreadCardSkeleton() {
3345
+ return /* @__PURE__ */ jsx("div", { className: "p-3 rounded-lg border bg-card animate-pulse", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3346
+ /* @__PURE__ */ jsx("div", { className: "w-8 h-8 rounded-full bg-muted" }),
3347
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2", children: [
3348
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-3/4 bg-muted rounded" }),
3349
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-1/2 bg-muted rounded" })
3350
+ ] })
3351
+ ] }) });
3352
+ }
3353
+ function ThreadCard({
3354
+ thread,
3355
+ selected = false,
3356
+ onClick,
3357
+ onDelete,
3358
+ showDelete = true,
3359
+ className
3360
+ }) {
3361
+ const [isHovered, setIsHovered] = React8.useState(false);
3362
+ const handleDelete = (e) => {
3363
+ e.stopPropagation();
3364
+ onDelete?.();
3365
+ };
3366
+ return /* @__PURE__ */ jsx(
3367
+ "button",
3368
+ {
3369
+ type: "button",
3370
+ onClick,
3371
+ onMouseEnter: () => setIsHovered(true),
3372
+ onMouseLeave: () => setIsHovered(false),
3373
+ className: cn(
3374
+ "w-full p-3 rounded-lg border bg-card text-left transition-colors",
3375
+ "hover:bg-accent hover:border-accent-foreground/20",
3376
+ "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
3377
+ selected && "bg-accent border-primary/50",
3378
+ className
3379
+ ),
3380
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3381
+ /* @__PURE__ */ jsx(
3382
+ "div",
3383
+ {
3384
+ className: cn(
3385
+ "flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center",
3386
+ selected ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground"
3387
+ ),
3388
+ children: /* @__PURE__ */ jsx(MessageIcon, {})
3389
+ }
3390
+ ),
3391
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
3392
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
3393
+ /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm truncate", children: thread.title || "Untitled conversation" }),
3394
+ showDelete && isHovered && onDelete && /* @__PURE__ */ jsx(
3395
+ "button",
3396
+ {
3397
+ type: "button",
3398
+ onClick: handleDelete,
3399
+ className: cn(
3400
+ "flex-shrink-0 p-1 rounded",
3401
+ "hover:bg-destructive/10 hover:text-destructive",
3402
+ "focus:outline-none focus:ring-2 focus:ring-destructive"
3403
+ ),
3404
+ "aria-label": "Delete conversation",
3405
+ children: /* @__PURE__ */ jsx(TrashIcon2, { className: "w-3.5 h-3.5" })
3406
+ }
3407
+ )
3408
+ ] }),
3409
+ thread.preview && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground truncate mt-0.5", children: thread.preview }),
3410
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1 text-xs text-muted-foreground", children: [
3411
+ thread.messageCount !== void 0 && thread.messageCount > 0 && /* @__PURE__ */ jsxs("span", { children: [
3412
+ thread.messageCount,
3413
+ " messages"
3414
+ ] }),
3415
+ thread.messageCount !== void 0 && thread.messageCount > 0 && thread.updatedAt && /* @__PURE__ */ jsx("span", { children: "\xB7" }),
3416
+ thread.updatedAt && /* @__PURE__ */ jsx("span", { children: formatDate2(thread.updatedAt) })
3417
+ ] })
3418
+ ] })
3419
+ ] })
3420
+ }
3421
+ );
3422
+ }
3423
+ function ThreadList({
3424
+ threads,
3425
+ selectedId,
3426
+ onSelect,
3427
+ onDelete,
3428
+ onNewThread,
3429
+ newThreadLabel = "New conversation",
3430
+ loading = false,
3431
+ emptyText = "No conversations yet",
3432
+ showDelete = true,
3433
+ className
3434
+ }) {
3435
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-2", className), children: [
3436
+ onNewThread && /* @__PURE__ */ jsxs(
3437
+ "button",
3438
+ {
3439
+ type: "button",
3440
+ onClick: onNewThread,
3441
+ className: cn(
3442
+ "flex items-center gap-2 p-3 rounded-lg border border-dashed",
3443
+ "hover:bg-accent hover:border-accent-foreground/20",
3444
+ "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
3445
+ "transition-colors"
3446
+ ),
3447
+ children: [
3448
+ /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center", children: /* @__PURE__ */ jsx(PlusIcon3, { className: "text-primary" }) }),
3449
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-sm", children: newThreadLabel })
3450
+ ]
3451
+ }
3452
+ ),
3453
+ loading && /* @__PURE__ */ jsxs(Fragment, { children: [
3454
+ /* @__PURE__ */ jsx(ThreadCardSkeleton, {}),
3455
+ /* @__PURE__ */ jsx(ThreadCardSkeleton, {}),
3456
+ /* @__PURE__ */ jsx(ThreadCardSkeleton, {})
3457
+ ] }),
3458
+ !loading && threads.length > 0 && threads.map((thread) => /* @__PURE__ */ jsx(
3459
+ ThreadCard,
3460
+ {
3461
+ thread,
3462
+ selected: selectedId === thread.id,
3463
+ onClick: () => onSelect?.(thread.id),
3464
+ onDelete: () => onDelete?.(thread.id),
3465
+ showDelete
3466
+ },
3467
+ thread.id
3468
+ )),
3469
+ !loading && threads.length === 0 && /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: emptyText })
3470
+ ] });
3471
+ }
3472
+ var CopilotSDKLogo = (props) => /* @__PURE__ */ jsxs(
3473
+ "svg",
3474
+ {
3475
+ width: props.width || 170,
3476
+ height: props.height || 170,
3477
+ viewBox: "0 0 170 170",
3478
+ fill: "none",
3479
+ xmlns: "http://www.w3.org/2000/svg",
3480
+ ...props,
3481
+ className: cn("w-auto h-[30px]", props.className),
3482
+ children: [
3483
+ /* @__PURE__ */ jsx(
3484
+ "path",
3485
+ {
3486
+ opacity: 0.4,
3487
+ d: "M108.379 0C111.143 0 113.538 1.91574 114.146 4.61243L118.392 23.4631C121.492 37.2253 132.239 47.9726 146.001 51.0727L164.852 55.319C167.549 55.9265 169.465 58.3218 169.465 61.0861C169.465 63.8504 167.549 66.2457 164.852 66.8532L146.001 71.0995C132.239 74.1995 121.492 84.9467 118.392 98.7088L114.146 117.56C113.538 120.257 111.143 122.172 108.379 122.172C105.614 122.172 103.219 120.257 102.611 117.56L98.3651 98.7088C95.2651 84.9467 84.5179 74.1995 70.7558 71.0995L51.9049 66.8532C49.2082 66.2457 47.2924 63.8504 47.2924 61.0861C47.2924 58.3218 49.2082 55.9265 51.9049 55.319L70.7558 51.0727C84.5179 47.9726 95.2651 37.2253 98.3651 23.4631L102.611 4.61243C103.219 1.91574 105.614 0 108.379 0Z",
3488
+ fill: "#6352FF"
3489
+ }
3490
+ ),
3491
+ /* @__PURE__ */ jsx(
3492
+ "path",
3493
+ {
3494
+ d: "M45.3219 78.8207C48.0863 78.8207 50.4816 80.736 51.089 83.4333L54.1221 96.8982C56.1931 106.092 63.3728 113.272 72.5663 115.342L86.0313 118.375C88.7285 118.983 90.6439 121.378 90.6439 124.143C90.6439 126.907 88.7285 129.302 86.0313 129.91L72.5663 132.943C63.3728 135.014 56.1931 142.193 54.1221 151.387L51.089 164.852C50.4816 167.549 48.0863 169.465 45.3219 169.465C42.5576 169.465 40.1623 167.549 39.5549 164.852L36.5218 151.387C34.4507 142.193 27.271 135.014 18.0772 132.943L4.61243 129.91C1.91574 129.302 0 126.907 0 124.143C0 121.378 1.91574 118.983 4.61243 118.375L18.0772 115.342C27.271 113.272 34.4507 106.092 36.5218 96.8982L39.5549 83.4333C40.1623 80.736 42.5576 78.8207 45.3219 78.8207Z",
3495
+ fill: "#523FFF"
3496
+ }
3497
+ ),
3498
+ /* @__PURE__ */ jsx(
3499
+ "line",
3500
+ {
3501
+ x1: 137.266,
3502
+ y1: 162.316,
3503
+ x2: 83.1281,
3504
+ y2: 162.316,
3505
+ stroke: "currentColor",
3506
+ strokeWidth: 11.8231
3507
+ }
3508
+ ),
3509
+ /* @__PURE__ */ jsx(
3510
+ "line",
3511
+ {
3512
+ x1: 160.188,
3513
+ y1: 162.316,
3514
+ x2: 140.811,
3515
+ y2: 162.316,
3516
+ stroke: "currentColor",
3517
+ strokeOpacity: 0.26,
3518
+ strokeWidth: 11.8231
3519
+ }
3520
+ )
3521
+ ]
3522
+ }
3523
+ );
3524
+ var copilot_sdk_logo_default = CopilotSDKLogo;
3525
+ var DEFAULT_NAME = "AI Copilot";
3526
+ function ChatHeader({
3527
+ logo,
3528
+ name,
3529
+ title,
3530
+ threadPicker,
3531
+ onClose,
3532
+ className
3533
+ }) {
3534
+ const displayName = name || title || DEFAULT_NAME;
3535
+ const showDefaultLogo = logo === void 0;
3536
+ const showCustomLogo = typeof logo === "string" && logo.length > 0;
3537
+ return /* @__PURE__ */ jsx(
3538
+ "div",
3539
+ {
3540
+ className: cn(
3541
+ "flex flex-col border-b border-border bg-background",
3542
+ className
3543
+ ),
3544
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2", children: [
3545
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 shrink-0", children: [
3546
+ showDefaultLogo && /* @__PURE__ */ jsx(copilot_sdk_logo_default, { className: "h-6 w-auto" }),
3547
+ showCustomLogo && /* @__PURE__ */ jsx(
3548
+ "img",
3549
+ {
3550
+ src: logo,
3551
+ alt: displayName,
3552
+ className: "size-6 rounded-md object-contain"
3553
+ }
3554
+ ),
3555
+ /* @__PURE__ */ jsxs("div", { children: [
3556
+ /* @__PURE__ */ jsx("div", { className: "font-semibold text-foreground text-sm mb-0.5", children: displayName }),
3557
+ threadPicker && /* @__PURE__ */ jsx("div", { className: "", children: threadPicker })
3558
+ ] })
3559
+ ] }),
3560
+ onClose && /* @__PURE__ */ jsx(
3561
+ "button",
3562
+ {
3563
+ type: "button",
3564
+ onClick: onClose,
3565
+ className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
3566
+ "aria-label": "Close chat",
3567
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-5 w-5" })
3568
+ }
3569
+ )
3570
+ ] })
3571
+ }
3572
+ );
3573
+ }
3574
+ function Suggestions({
3575
+ suggestions,
3576
+ onSuggestionClick,
3577
+ className
3578
+ }) {
3579
+ if (!suggestions.length) return null;
3580
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-2 px-4 py-2", className), children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsx(
3581
+ "button",
3582
+ {
3583
+ type: "button",
3584
+ onClick: () => onSuggestionClick?.(suggestion),
3585
+ className: "inline-flex items-center rounded-full border bg-background px-3 py-1.5 text-sm transition-colors hover:bg-muted",
3586
+ children: suggestion
3587
+ },
3588
+ index
3589
+ )) });
3590
+ }
3591
+ function DefaultMessage({
3592
+ message,
3593
+ userAvatar,
3594
+ assistantAvatar,
3595
+ showUserAvatar = false,
3596
+ userMessageClassName,
3597
+ assistantMessageClassName,
3598
+ size = "sm",
3599
+ isLastMessage = false,
3600
+ isLoading = false,
3601
+ registeredTools,
3602
+ toolRenderers,
3603
+ onApproveToolExecution,
3604
+ onRejectToolExecution,
3605
+ showFollowUps = true,
3606
+ onFollowUpClick,
3607
+ followUpClassName,
3608
+ followUpButtonClassName
3609
+ }) {
3610
+ const isUser = message.role === "user";
3611
+ const isStreaming = isLastMessage && isLoading;
3612
+ const { cleanContent, followUps } = React8.useMemo(() => {
3613
+ if (isUser || !message.content) {
3614
+ return { cleanContent: message.content, followUps: [] };
3615
+ }
3616
+ return parseFollowUps(message.content);
3617
+ }, [message.content, isUser]);
3618
+ const shouldShowFollowUps = showFollowUps && !isUser && isLastMessage && !isLoading && followUps.length > 0 && onFollowUpClick;
3619
+ if (isUser) {
3620
+ const hasAttachments = message.attachments && message.attachments.length > 0;
3621
+ return /* @__PURE__ */ jsxs(
3622
+ Message,
3623
+ {
3624
+ className: cn(
3625
+ "flex gap-2",
3626
+ showUserAvatar ? "justify-end" : "justify-end"
3627
+ ),
3628
+ children: [
3629
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end max-w-[80%]", children: [
3630
+ message.content && /* @__PURE__ */ jsx(
3631
+ MessageContent,
3632
+ {
3633
+ className: cn(
3634
+ "rounded-lg px-4 py-2 bg-primary text-primary-foreground",
3635
+ userMessageClassName
3636
+ ),
3637
+ size,
3638
+ children: message.content
3639
+ }
3640
+ ),
3641
+ hasAttachments && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2 justify-end", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment }, index)) })
3642
+ ] }),
3643
+ showUserAvatar && /* @__PURE__ */ jsx(
3644
+ MessageAvatar,
3645
+ {
3646
+ src: userAvatar.src || "",
3647
+ alt: "User",
3648
+ fallback: userAvatar.fallback
3649
+ }
3650
+ )
3651
+ ]
3652
+ }
3653
+ );
3654
+ }
3655
+ const pendingApprovalTools = message.toolExecutions?.filter(
3656
+ (exec) => exec.approvalStatus === "required"
3657
+ );
3658
+ const completedTools = message.toolExecutions?.filter(
3659
+ (exec) => exec.approvalStatus !== "required"
3660
+ );
3661
+ const hasCustomRender = (toolName) => {
3662
+ if (toolRenderers?.[toolName]) return true;
3663
+ const toolDef = registeredTools?.find((t) => t.name === toolName);
3664
+ if (toolDef?.render) return true;
3665
+ return false;
3666
+ };
3667
+ const toolsWithCustomRender = completedTools?.filter(
3668
+ (exec) => hasCustomRender(exec.name)
3669
+ );
3670
+ const toolsWithoutCustomRender = completedTools?.filter(
3671
+ (exec) => !hasCustomRender(exec.name)
3672
+ );
3673
+ const toolSteps = toolsWithoutCustomRender?.map((exec) => ({
3674
+ id: exec.id,
3675
+ name: exec.name,
3676
+ args: exec.args,
3677
+ status: exec.status,
3678
+ result: exec.result,
3679
+ error: exec.error
3680
+ }));
3681
+ return /* @__PURE__ */ jsxs(Message, { className: "flex gap-2", children: [
3682
+ /* @__PURE__ */ jsx(
3683
+ MessageAvatar,
3684
+ {
3685
+ src: assistantAvatar.src || "",
3686
+ alt: "Assistant",
3687
+ fallback: assistantAvatar.fallback,
3688
+ fallbackIcon: !assistantAvatar.src ? /* @__PURE__ */ jsx(copilot_sdk_logo_default, { className: "size-5" }) : void 0,
3689
+ className: "bg-background"
3690
+ }
3691
+ ),
3692
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 max-w-[80%]", children: [
3693
+ message.thinking && /* @__PURE__ */ jsx(
3694
+ SimpleReasoning,
3116
3695
  {
3117
3696
  content: message.thinking,
3118
3697
  isStreaming,
@@ -3131,39 +3710,100 @@ function DefaultMessage({
3131
3710
  children: cleanContent
3132
3711
  }
3133
3712
  ),
3134
- toolsWithCustomRenderer && toolsWithCustomRenderer.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-2", children: toolsWithCustomRenderer.map((exec) => {
3135
- const Renderer = toolRenderers[exec.name];
3136
- return /* @__PURE__ */ jsx(
3137
- Renderer,
3138
- {
3139
- execution: {
3140
- id: exec.id,
3141
- name: exec.name,
3142
- args: exec.args,
3143
- status: exec.status,
3144
- result: exec.result,
3145
- error: exec.error,
3146
- approvalStatus: exec.approvalStatus
3147
- }
3148
- },
3149
- exec.id
3713
+ toolsWithCustomRender && toolsWithCustomRender.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-2", children: toolsWithCustomRender.map((exec) => {
3714
+ const Renderer = toolRenderers?.[exec.name];
3715
+ if (Renderer) {
3716
+ return /* @__PURE__ */ jsx(
3717
+ Renderer,
3718
+ {
3719
+ execution: {
3720
+ id: exec.id,
3721
+ name: exec.name,
3722
+ args: exec.args,
3723
+ status: exec.status,
3724
+ result: exec.result,
3725
+ error: exec.error,
3726
+ approvalStatus: exec.approvalStatus
3727
+ }
3728
+ },
3729
+ exec.id
3730
+ );
3731
+ }
3732
+ const toolDef = registeredTools?.find(
3733
+ (t) => t.name === exec.name
3150
3734
  );
3735
+ if (toolDef?.render) {
3736
+ let status = "pending";
3737
+ if (exec.status === "executing") status = "executing";
3738
+ else if (exec.status === "completed") status = "completed";
3739
+ else if (exec.status === "error" || exec.status === "failed" || exec.status === "rejected")
3740
+ status = "error";
3741
+ const renderProps = {
3742
+ status,
3743
+ args: exec.args,
3744
+ result: exec.result,
3745
+ error: exec.error,
3746
+ toolCallId: exec.id,
3747
+ toolName: exec.name
3748
+ };
3749
+ const output = toolDef.render(renderProps);
3750
+ return /* @__PURE__ */ jsx(React8.Fragment, { children: output }, exec.id);
3751
+ }
3752
+ return null;
3151
3753
  }) }),
3152
3754
  toolSteps && toolSteps.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 rounded-lg bg-muted/50 px-3 py-2", children: /* @__PURE__ */ jsx(ToolSteps, { steps: toolSteps }) }),
3153
- pendingApprovalTools && pendingApprovalTools.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-2", children: pendingApprovalTools.map((tool) => /* @__PURE__ */ jsx(
3154
- PermissionConfirmation,
3155
- {
3156
- state: "pending",
3157
- toolName: tool.name,
3158
- message: tool.approvalMessage || `This tool wants to execute. Do you approve?`,
3159
- onApprove: (permissionLevel) => onApproveToolExecution?.(tool.id, permissionLevel),
3160
- onReject: (permissionLevel) => onRejectToolExecution?.(tool.id, void 0, permissionLevel)
3161
- },
3162
- tool.id
3163
- )) }),
3164
- message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment }, index)) }),
3165
- shouldShowFollowUps && /* @__PURE__ */ jsx(
3166
- FollowUpQuestions,
3755
+ pendingApprovalTools && pendingApprovalTools.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-2", children: pendingApprovalTools.map((tool) => {
3756
+ const approvalCallbacks = {
3757
+ onApprove: (extraData) => onApproveToolExecution?.(tool.id, extraData),
3758
+ onReject: (reason) => onRejectToolExecution?.(tool.id, reason),
3759
+ message: tool.approvalMessage
3760
+ };
3761
+ const CustomRenderer = toolRenderers?.[tool.name];
3762
+ if (CustomRenderer) {
3763
+ return /* @__PURE__ */ jsx(
3764
+ CustomRenderer,
3765
+ {
3766
+ execution: tool,
3767
+ approval: approvalCallbacks
3768
+ },
3769
+ tool.id
3770
+ );
3771
+ }
3772
+ const toolDef = registeredTools?.find(
3773
+ (t) => t.name === tool.name
3774
+ );
3775
+ if (toolDef?.render) {
3776
+ const renderProps = {
3777
+ status: "approval-required",
3778
+ args: tool.args,
3779
+ result: tool.result,
3780
+ error: tool.error,
3781
+ toolCallId: tool.id,
3782
+ toolName: tool.name,
3783
+ approval: approvalCallbacks
3784
+ };
3785
+ const output = toolDef.render(renderProps);
3786
+ return /* @__PURE__ */ jsx(React8.Fragment, { children: output }, tool.id);
3787
+ }
3788
+ return /* @__PURE__ */ jsx(
3789
+ PermissionConfirmation,
3790
+ {
3791
+ state: "pending",
3792
+ toolName: tool.name,
3793
+ message: tool.approvalMessage || `This tool wants to execute. Do you approve?`,
3794
+ onApprove: (permissionLevel) => onApproveToolExecution?.(
3795
+ tool.id,
3796
+ void 0,
3797
+ permissionLevel
3798
+ ),
3799
+ onReject: (permissionLevel) => onRejectToolExecution?.(tool.id, void 0, permissionLevel)
3800
+ },
3801
+ tool.id
3802
+ );
3803
+ }) }),
3804
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment }, index)) }),
3805
+ shouldShowFollowUps && /* @__PURE__ */ jsx(
3806
+ FollowUpQuestions,
3167
3807
  {
3168
3808
  questions: followUps,
3169
3809
  onSelect: onFollowUpClick,
@@ -3257,13 +3897,353 @@ function AttachmentPreview({ attachment }) {
3257
3897
  }
3258
3898
  var DEFAULT_MAX_FILE_SIZE = 5 * 1024 * 1024;
3259
3899
  var DEFAULT_ALLOWED_TYPES = ["image/*", "application/pdf"];
3900
+ var DEFAULT_TITLE = "How can I help you today?";
3901
+ var DEFAULT_SUBTITLE = "Ask anything and get it done.";
3902
+ var DEFAULT_SUGGESTIONS_LABEL = "Try AI Copilot";
3903
+ var DEFAULT_RECENT_CHATS_LABEL = "Recent chats";
3904
+ var DEFAULT_MAX_RECENT_CHATS = 3;
3905
+ var DEFAULT_VIEW_MORE_LABEL = "View more..";
3260
3906
  function getAttachmentType(mimeType) {
3261
3907
  if (mimeType.startsWith("image/")) return "image";
3262
3908
  if (mimeType.startsWith("audio/")) return "audio";
3263
3909
  if (mimeType.startsWith("video/")) return "video";
3264
3910
  return "file";
3265
3911
  }
3266
- function fileToBase64(file) {
3912
+ function fileToBase64(file) {
3913
+ return new Promise((resolve, reject) => {
3914
+ const reader = new FileReader();
3915
+ reader.onload = () => {
3916
+ if (typeof reader.result === "string") {
3917
+ resolve(reader.result);
3918
+ } else {
3919
+ reject(new Error("Failed to read file"));
3920
+ }
3921
+ };
3922
+ reader.onerror = () => reject(new Error("Failed to read file"));
3923
+ reader.readAsDataURL(file);
3924
+ });
3925
+ }
3926
+ function generateAttachmentId() {
3927
+ return `att_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
3928
+ }
3929
+ function ChatWelcome({
3930
+ config,
3931
+ suggestions = [],
3932
+ recentThreads = [],
3933
+ onSendMessage,
3934
+ onSelectThread,
3935
+ onDeleteThread,
3936
+ onViewMoreThreads,
3937
+ isLoading = false,
3938
+ onStop,
3939
+ placeholder = "Type a message...",
3940
+ attachmentsEnabled = true,
3941
+ attachmentsDisabledTooltip = "Attachments not supported by this model",
3942
+ maxFileSize = DEFAULT_MAX_FILE_SIZE,
3943
+ allowedFileTypes = DEFAULT_ALLOWED_TYPES,
3944
+ processAttachment: processAttachmentProp,
3945
+ classNames = {}
3946
+ }) {
3947
+ const [input, setInput] = useState("");
3948
+ const [pendingAttachments, setPendingAttachments] = useState([]);
3949
+ const fileInputRef = useRef(null);
3950
+ const fileInputId = useId();
3951
+ const title = config?.title ?? DEFAULT_TITLE;
3952
+ const subtitle = config?.subtitle ?? DEFAULT_SUBTITLE;
3953
+ const logo = config?.logo;
3954
+ const suggestionsLabel = config?.suggestionsLabel ?? DEFAULT_SUGGESTIONS_LABEL;
3955
+ const showRecentChats = config?.showRecentChats ?? true;
3956
+ config?.recentChatsLabel ?? DEFAULT_RECENT_CHATS_LABEL;
3957
+ const maxRecentChats = config?.maxRecentChats ?? DEFAULT_MAX_RECENT_CHATS;
3958
+ config?.viewMoreLabel ?? DEFAULT_VIEW_MORE_LABEL;
3959
+ const isFileTypeAllowed = useCallback(
3960
+ (file) => {
3961
+ for (const type of allowedFileTypes) {
3962
+ if (type.endsWith("/*")) {
3963
+ const category = type.slice(0, -2);
3964
+ if (file.type.startsWith(category + "/")) return true;
3965
+ } else if (file.type === type) {
3966
+ return true;
3967
+ }
3968
+ }
3969
+ return false;
3970
+ },
3971
+ [allowedFileTypes]
3972
+ );
3973
+ const handleFileSelect = useCallback(
3974
+ async (files) => {
3975
+ if (!files || !attachmentsEnabled) return;
3976
+ for (const file of Array.from(files)) {
3977
+ if (file.size > maxFileSize) {
3978
+ const sizeMB = (maxFileSize / (1024 * 1024)).toFixed(0);
3979
+ console.warn(`File ${file.name} exceeds ${sizeMB}MB limit`);
3980
+ continue;
3981
+ }
3982
+ if (!isFileTypeAllowed(file)) {
3983
+ console.warn(`File type ${file.type} is not allowed`);
3984
+ continue;
3985
+ }
3986
+ const id = generateAttachmentId();
3987
+ const previewUrl = URL.createObjectURL(file);
3988
+ setPendingAttachments((prev) => [
3989
+ ...prev,
3990
+ {
3991
+ id,
3992
+ file,
3993
+ previewUrl,
3994
+ attachment: {
3995
+ type: getAttachmentType(file.type),
3996
+ data: "",
3997
+ mimeType: file.type,
3998
+ filename: file.name
3999
+ },
4000
+ status: "processing"
4001
+ }
4002
+ ]);
4003
+ try {
4004
+ let attachment;
4005
+ if (processAttachmentProp) {
4006
+ attachment = await processAttachmentProp(file);
4007
+ } else {
4008
+ const data = await fileToBase64(file);
4009
+ attachment = {
4010
+ type: getAttachmentType(file.type),
4011
+ data,
4012
+ mimeType: file.type,
4013
+ filename: file.name
4014
+ };
4015
+ }
4016
+ setPendingAttachments(
4017
+ (prev) => prev.map(
4018
+ (att) => att.id === id ? { ...att, status: "ready", attachment } : att
4019
+ )
4020
+ );
4021
+ } catch (error) {
4022
+ setPendingAttachments(
4023
+ (prev) => prev.map(
4024
+ (att) => att.id === id ? {
4025
+ ...att,
4026
+ status: "error",
4027
+ error: "Failed to process file"
4028
+ } : att
4029
+ )
4030
+ );
4031
+ }
4032
+ }
4033
+ },
4034
+ [attachmentsEnabled, maxFileSize, isFileTypeAllowed, processAttachmentProp]
4035
+ );
4036
+ const handleInputChange = useCallback(
4037
+ (e) => {
4038
+ handleFileSelect(e.target.files);
4039
+ if (fileInputRef.current) {
4040
+ fileInputRef.current.value = "";
4041
+ }
4042
+ },
4043
+ [handleFileSelect]
4044
+ );
4045
+ const removePendingAttachment = useCallback((id) => {
4046
+ setPendingAttachments((prev) => {
4047
+ const att = prev.find((a) => a.id === id);
4048
+ if (att) {
4049
+ URL.revokeObjectURL(att.previewUrl);
4050
+ }
4051
+ return prev.filter((a) => a.id !== id);
4052
+ });
4053
+ }, []);
4054
+ const handleSubmit = useCallback(() => {
4055
+ const hasContent = input.trim();
4056
+ const hasAttachments = pendingAttachments.some(
4057
+ (att) => att.status === "ready"
4058
+ );
4059
+ if (!hasContent && !hasAttachments || isLoading) return;
4060
+ const attachments = pendingAttachments.filter((att) => att.status === "ready").map((att) => att.attachment);
4061
+ onSendMessage(input, attachments.length > 0 ? attachments : void 0);
4062
+ pendingAttachments.forEach((att) => URL.revokeObjectURL(att.previewUrl));
4063
+ setPendingAttachments([]);
4064
+ setInput("");
4065
+ }, [input, isLoading, onSendMessage, pendingAttachments]);
4066
+ const handleSuggestionClick = useCallback(
4067
+ (suggestion) => {
4068
+ onSendMessage(suggestion);
4069
+ },
4070
+ [onSendMessage]
4071
+ );
4072
+ const acceptString = allowedFileTypes.join(",");
4073
+ showRecentChats ? recentThreads.slice(0, maxRecentChats) : [];
4074
+ recentThreads.length > maxRecentChats;
4075
+ return /* @__PURE__ */ jsxs(
4076
+ "div",
4077
+ {
4078
+ className: cn(
4079
+ "flex flex-1 flex-col items-center justify-center px-4 py-8 overflow-auto",
4080
+ classNames.root
4081
+ ),
4082
+ children: [
4083
+ /* @__PURE__ */ jsxs(
4084
+ "div",
4085
+ {
4086
+ className: cn(
4087
+ "flex flex-col items-center text-center mb-8",
4088
+ classNames.hero
4089
+ ),
4090
+ children: [
4091
+ logo ? /* @__PURE__ */ jsx(
4092
+ "img",
4093
+ {
4094
+ src: logo,
4095
+ alt: "Logo",
4096
+ className: "size-12 rounded-lg object-contain mb-4"
4097
+ }
4098
+ ) : /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsx(copilot_sdk_logo_default, { className: "h-12 w-auto" }) }),
4099
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground mb-2", children: title }),
4100
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
4101
+ ]
4102
+ }
4103
+ ),
4104
+ /* @__PURE__ */ jsxs("div", { className: cn("w-full max-w-lg mb-6", classNames.input), children: [
4105
+ pendingAttachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 p-2 mb-2 bg-muted/30 rounded-lg", children: pendingAttachments.map((att) => /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
4106
+ att.attachment.type === "image" ? /* @__PURE__ */ jsx(
4107
+ "img",
4108
+ {
4109
+ src: att.previewUrl,
4110
+ alt: att.file.name,
4111
+ className: "w-16 h-16 object-cover rounded-lg border"
4112
+ }
4113
+ ) : /* @__PURE__ */ jsxs("div", { className: "w-16 h-16 bg-muted rounded-lg border flex flex-col items-center justify-center p-1", children: [
4114
+ /* @__PURE__ */ jsx(
4115
+ "svg",
4116
+ {
4117
+ className: "w-6 h-6 text-muted-foreground",
4118
+ fill: "none",
4119
+ viewBox: "0 0 24 24",
4120
+ stroke: "currentColor",
4121
+ children: /* @__PURE__ */ jsx(
4122
+ "path",
4123
+ {
4124
+ strokeLinecap: "round",
4125
+ strokeLinejoin: "round",
4126
+ strokeWidth: 1.5,
4127
+ d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
4128
+ }
4129
+ )
4130
+ }
4131
+ ),
4132
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground truncate w-full text-center mt-1", children: att.file.name.length > 10 ? att.file.name.slice(0, 8) + "..." : att.file.name })
4133
+ ] }),
4134
+ att.status === "processing" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-background/80 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsx(Loader, { variant: "dots", size: "sm" }) }),
4135
+ att.status === "error" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-destructive/20 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-destructive text-xs", children: "Error" }) }),
4136
+ /* @__PURE__ */ jsx(
4137
+ "button",
4138
+ {
4139
+ onClick: () => removePendingAttachment(att.id),
4140
+ className: "absolute -top-1.5 -right-1.5 bg-destructive text-destructive-foreground rounded-full p-0.5 opacity-0 group-hover:opacity-100 transition-opacity",
4141
+ type: "button",
4142
+ children: /* @__PURE__ */ jsx(XIcon2, { className: "w-3 h-3" })
4143
+ }
4144
+ )
4145
+ ] }, att.id)) }),
4146
+ /* @__PURE__ */ jsxs(
4147
+ PromptInput,
4148
+ {
4149
+ value: input,
4150
+ onValueChange: setInput,
4151
+ isLoading,
4152
+ onSubmit: handleSubmit,
4153
+ children: [
4154
+ /* @__PURE__ */ jsx(PromptInputTextarea, { placeholder }),
4155
+ /* @__PURE__ */ jsxs(PromptInputActions, { className: "flex justify-between", children: [
4156
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
4157
+ PromptInputAction,
4158
+ {
4159
+ tooltip: attachmentsEnabled ? "Attach files" : attachmentsDisabledTooltip,
4160
+ children: /* @__PURE__ */ jsxs(
4161
+ "label",
4162
+ {
4163
+ htmlFor: fileInputId,
4164
+ className: cn(
4165
+ "flex h-8 w-8 items-center justify-center rounded-2xl",
4166
+ attachmentsEnabled ? "hover:bg-secondary-foreground/10 cursor-pointer" : "opacity-50 cursor-not-allowed"
4167
+ ),
4168
+ children: [
4169
+ /* @__PURE__ */ jsx(
4170
+ "input",
4171
+ {
4172
+ ref: fileInputRef,
4173
+ type: "file",
4174
+ multiple: true,
4175
+ accept: acceptString,
4176
+ onChange: handleInputChange,
4177
+ className: "hidden",
4178
+ id: fileInputId,
4179
+ disabled: !attachmentsEnabled
4180
+ }
4181
+ ),
4182
+ /* @__PURE__ */ jsx(PlusIcon, { className: "text-primary size-5" })
4183
+ ]
4184
+ }
4185
+ )
4186
+ }
4187
+ ) }),
4188
+ /* @__PURE__ */ jsx(PromptInputAction, { tooltip: isLoading ? "Stop" : "Send", children: isLoading ? /* @__PURE__ */ jsx(
4189
+ Button,
4190
+ {
4191
+ size: "sm",
4192
+ variant: "destructive",
4193
+ className: "rounded-full size-9",
4194
+ onClick: onStop,
4195
+ children: /* @__PURE__ */ jsx(StopIcon, { className: "h-4 w-4" })
4196
+ }
4197
+ ) : /* @__PURE__ */ jsx(
4198
+ Button,
4199
+ {
4200
+ size: "sm",
4201
+ className: "rounded-full size-9",
4202
+ onClick: handleSubmit,
4203
+ disabled: !input.trim() && !pendingAttachments.some((att) => att.status === "ready"),
4204
+ children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-4 w-4" })
4205
+ }
4206
+ ) })
4207
+ ] })
4208
+ ]
4209
+ }
4210
+ )
4211
+ ] }),
4212
+ suggestions.length > 0 && /* @__PURE__ */ jsxs(
4213
+ "div",
4214
+ {
4215
+ className: cn("w-full max-w-lg mb-6 px-3", classNames.suggestions),
4216
+ children: [
4217
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-2", children: suggestionsLabel }),
4218
+ /* @__PURE__ */ jsx("div", { className: "", children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsxs(
4219
+ "button",
4220
+ {
4221
+ type: "button",
4222
+ onClick: () => handleSuggestionClick(suggestion),
4223
+ className: "group w-full cursor-pointer font-medium text-left py-2 text-sm hover:text-foreground text-foreground/70 transition-colors flex items-center gap-2",
4224
+ children: [
4225
+ /* @__PURE__ */ jsx("span", { children: suggestion }),
4226
+ /* @__PURE__ */ jsx(ArrowUpRightIcon, { className: "size-4 opacity-0 translate-y-1 group-hover:opacity-[0.6] group-hover:translate-y-0 transition-all duration-200" })
4227
+ ]
4228
+ },
4229
+ index
4230
+ )) })
4231
+ ]
4232
+ }
4233
+ )
4234
+ ]
4235
+ }
4236
+ );
4237
+ }
4238
+ var DEFAULT_MAX_FILE_SIZE2 = 5 * 1024 * 1024;
4239
+ var DEFAULT_ALLOWED_TYPES2 = ["image/*", "application/pdf"];
4240
+ function getAttachmentType2(mimeType) {
4241
+ if (mimeType.startsWith("image/")) return "image";
4242
+ if (mimeType.startsWith("audio/")) return "audio";
4243
+ if (mimeType.startsWith("video/")) return "video";
4244
+ return "file";
4245
+ }
4246
+ function fileToBase642(file) {
3267
4247
  return new Promise((resolve, reject) => {
3268
4248
  const reader = new FileReader();
3269
4249
  reader.onload = () => {
@@ -3277,7 +4257,7 @@ function fileToBase64(file) {
3277
4257
  reader.readAsDataURL(file);
3278
4258
  });
3279
4259
  }
3280
- function generateAttachmentId() {
4260
+ function generateAttachmentId2() {
3281
4261
  return `att_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
3282
4262
  }
3283
4263
  function Chat({
@@ -3292,6 +4272,11 @@ function Chat({
3292
4272
  title,
3293
4273
  // Header
3294
4274
  showHeader = false,
4275
+ header,
4276
+ threadPicker,
4277
+ // Deprecated header props (backwards compat)
4278
+ logo,
4279
+ name,
3295
4280
  onClose,
3296
4281
  // Appearance
3297
4282
  showPoweredBy = true,
@@ -3301,16 +4286,23 @@ function Chat({
3301
4286
  loaderVariant = "typing",
3302
4287
  fontSize = "sm",
3303
4288
  // Attachments
3304
- maxFileSize = DEFAULT_MAX_FILE_SIZE,
3305
- allowedFileTypes = DEFAULT_ALLOWED_TYPES,
4289
+ maxFileSize = DEFAULT_MAX_FILE_SIZE2,
4290
+ allowedFileTypes = DEFAULT_ALLOWED_TYPES2,
3306
4291
  attachmentsEnabled = true,
3307
4292
  attachmentsDisabledTooltip = "Attachments not supported by this model",
3308
4293
  processAttachment: processAttachmentProp,
3309
4294
  // Suggestions
3310
4295
  suggestions = [],
3311
4296
  onSuggestionClick,
4297
+ // Welcome Screen
4298
+ welcome,
4299
+ recentThreads = [],
4300
+ onSelectThread,
4301
+ onDeleteThread,
4302
+ onViewMoreThreads,
3312
4303
  // Tool Executions
3313
4304
  isProcessing = false,
4305
+ registeredTools,
3314
4306
  toolRenderers,
3315
4307
  onApproveToolExecution,
3316
4308
  onRejectToolExecution,
@@ -3358,7 +4350,7 @@ function Chat({
3358
4350
  console.warn(`File type ${file.type} is not allowed`);
3359
4351
  continue;
3360
4352
  }
3361
- const id = generateAttachmentId();
4353
+ const id = generateAttachmentId2();
3362
4354
  const previewUrl = URL.createObjectURL(file);
3363
4355
  setPendingAttachments((prev) => [
3364
4356
  ...prev,
@@ -3367,7 +4359,7 @@ function Chat({
3367
4359
  file,
3368
4360
  previewUrl,
3369
4361
  attachment: {
3370
- type: getAttachmentType(file.type),
4362
+ type: getAttachmentType2(file.type),
3371
4363
  data: "",
3372
4364
  mimeType: file.type,
3373
4365
  filename: file.name
@@ -3380,9 +4372,9 @@ function Chat({
3380
4372
  if (processAttachmentProp) {
3381
4373
  attachment = await processAttachmentProp(file);
3382
4374
  } else {
3383
- const data = await fileToBase64(file);
4375
+ const data = await fileToBase642(file);
3384
4376
  attachment = {
3385
- type: getAttachmentType(file.type),
4377
+ type: getAttachmentType2(file.type),
3386
4378
  data,
3387
4379
  mimeType: file.type,
3388
4380
  filename: file.name
@@ -3475,6 +4467,8 @@ function Chat({
3475
4467
  [onSuggestionClick, onSendMessage]
3476
4468
  );
3477
4469
  const acceptString = allowedFileTypes.join(",");
4470
+ const showWelcome = messages.length === 0 && welcome !== false;
4471
+ const welcomeConfig = typeof welcome === "object" ? welcome : void 0;
3478
4472
  return /* @__PURE__ */ jsxs(
3479
4473
  "div",
3480
4474
  {
@@ -3491,239 +4485,275 @@ function Chat({
3491
4485
  showHeader && (renderHeader ? renderHeader() : /* @__PURE__ */ jsx(
3492
4486
  ChatHeader,
3493
4487
  {
4488
+ logo: header?.logo ?? logo,
4489
+ name: header?.name ?? name,
3494
4490
  title,
3495
- onClose,
4491
+ threadPicker,
4492
+ onClose: header?.onClose ?? onClose,
3496
4493
  className: classNames.header
3497
4494
  }
3498
4495
  )),
3499
- /* @__PURE__ */ jsxs(
3500
- ChatContainerRoot,
3501
- {
3502
- className: cn("relative flex-1", classNames.container),
3503
- children: [
3504
- /* @__PURE__ */ jsxs(
3505
- ChatContainerContent,
3506
- {
3507
- className: cn("gap-4 p-4", classNames.messageList),
3508
- children: [
3509
- messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-muted-foreground", children: welcomeMessage || "Send a message to start the conversation" }),
3510
- messages.map((message, index) => {
3511
- const isLastMessage = index === messages.length - 1;
3512
- const isEmptyAssistant = message.role === "assistant" && !message.content?.trim();
3513
- const hasToolCalls = message.tool_calls && message.tool_calls.length > 0;
3514
- const hasToolExecutions = message.toolExecutions && message.toolExecutions.length > 0;
3515
- const hasPendingApprovals = message.toolExecutions?.some(
3516
- (exec) => exec.approvalStatus === "required"
3517
- );
3518
- if (isEmptyAssistant) {
3519
- if (hasToolCalls || hasToolExecutions) ; else if (isLastMessage && hasPendingApprovals) ; else if (isLastMessage && isLoading && !isProcessing) {
3520
- return /* @__PURE__ */ jsxs(Message, { className: "flex gap-2", children: [
3521
- /* @__PURE__ */ jsx(
3522
- MessageAvatar,
3523
- {
3524
- src: assistantAvatar.src || "",
3525
- alt: "Assistant",
3526
- fallback: assistantAvatar.fallback,
3527
- className: "bg-primary text-primary-foreground"
3528
- }
3529
- ),
3530
- /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-muted px-4 py-2", children: /* @__PURE__ */ jsx(Loader, { variant: loaderVariant, size: "sm" }) })
3531
- ] }, message.id);
3532
- } else {
3533
- return null;
3534
- }
3535
- }
3536
- const savedExecutions = message.metadata?.toolExecutions;
3537
- const messageToolExecutions = message.toolExecutions || savedExecutions;
3538
- const messageWithExecutions = messageToolExecutions ? { ...message, toolExecutions: messageToolExecutions } : message;
3539
- const handleFollowUpClick = (question) => {
3540
- if (onSuggestionClick) {
3541
- onSuggestionClick(question);
3542
- } else {
3543
- onSendMessage?.(question);
3544
- }
3545
- };
3546
- return renderMessage ? /* @__PURE__ */ jsx(React8__default.Fragment, { children: renderMessage(messageWithExecutions, index) }, message.id) : /* @__PURE__ */ jsx(
3547
- DefaultMessage,
3548
- {
3549
- message: messageWithExecutions,
3550
- userAvatar,
3551
- assistantAvatar,
3552
- showUserAvatar,
3553
- userMessageClassName: classNames.userMessage,
3554
- assistantMessageClassName: classNames.assistantMessage,
3555
- size: fontSize,
3556
- isLastMessage,
3557
- isLoading,
3558
- toolRenderers,
3559
- onApproveToolExecution,
3560
- onRejectToolExecution,
3561
- showFollowUps,
3562
- onFollowUpClick: handleFollowUpClick,
3563
- followUpClassName,
3564
- followUpButtonClassName
3565
- },
3566
- message.id
3567
- );
3568
- }),
3569
- isProcessing && /* @__PURE__ */ jsxs(Message, { className: "flex gap-2", children: [
3570
- /* @__PURE__ */ jsx(
3571
- MessageAvatar,
3572
- {
3573
- src: assistantAvatar?.src || "",
3574
- alt: "Assistant",
3575
- fallback: assistantAvatar?.fallback || "AI",
3576
- className: "bg-primary text-primary-foreground"
3577
- }
3578
- ),
3579
- /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-muted px-4 py-2 flex items-center gap-2", children: [
3580
- /* @__PURE__ */ jsx(Loader, { variant: "dots", size: "sm" }),
3581
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Continuing..." })
3582
- ] })
3583
- ] }),
3584
- isLoading && !isProcessing && (() => {
3585
- const lastMessage = messages[messages.length - 1];
3586
- if (lastMessage?.role === "user") {
3587
- return /* @__PURE__ */ jsxs(Message, { className: "flex gap-2", children: [
4496
+ showWelcome ? (
4497
+ /* Welcome Screen (centered input) */
4498
+ /* @__PURE__ */ jsx(
4499
+ ChatWelcome,
4500
+ {
4501
+ config: welcomeConfig,
4502
+ suggestions,
4503
+ recentThreads,
4504
+ onSendMessage: (msg, attachments) => onSendMessage?.(msg, attachments),
4505
+ onSelectThread,
4506
+ onDeleteThread,
4507
+ onViewMoreThreads,
4508
+ isLoading,
4509
+ onStop,
4510
+ placeholder,
4511
+ attachmentsEnabled,
4512
+ attachmentsDisabledTooltip,
4513
+ maxFileSize,
4514
+ allowedFileTypes,
4515
+ processAttachment: processAttachmentProp
4516
+ }
4517
+ )
4518
+ ) : (
4519
+ /* Normal Chat UI (messages + input at bottom) */
4520
+ /* @__PURE__ */ jsxs(Fragment, { children: [
4521
+ /* @__PURE__ */ jsxs(
4522
+ ChatContainerRoot,
4523
+ {
4524
+ className: cn("relative flex-1", classNames.container),
4525
+ children: [
4526
+ /* @__PURE__ */ jsxs(
4527
+ ChatContainerContent,
4528
+ {
4529
+ className: cn("gap-4 p-4", classNames.messageList),
4530
+ children: [
4531
+ messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-muted-foreground", children: welcomeMessage || "Send a message to start the conversation" }),
4532
+ messages.map((message, index) => {
4533
+ const isLastMessage = index === messages.length - 1;
4534
+ const isEmptyAssistant = message.role === "assistant" && !message.content?.trim();
4535
+ const hasToolCalls = message.tool_calls && message.tool_calls.length > 0;
4536
+ const hasToolExecutions = message.toolExecutions && message.toolExecutions.length > 0;
4537
+ const hasPendingApprovals = message.toolExecutions?.some(
4538
+ (exec) => exec.approvalStatus === "required"
4539
+ );
4540
+ if (isEmptyAssistant) {
4541
+ if (hasToolCalls || hasToolExecutions) ; else if (isLastMessage && hasPendingApprovals) ; else if (isLastMessage && isLoading && !isProcessing) {
4542
+ return /* @__PURE__ */ jsxs(Message, { className: "flex gap-2", children: [
4543
+ /* @__PURE__ */ jsx(
4544
+ MessageAvatar,
4545
+ {
4546
+ src: assistantAvatar.src || "",
4547
+ alt: "Assistant",
4548
+ fallback: assistantAvatar.fallback,
4549
+ fallbackIcon: !assistantAvatar.src ? /* @__PURE__ */ jsx(copilot_sdk_logo_default, { className: "size-5" }) : void 0,
4550
+ className: "bg-background"
4551
+ }
4552
+ ),
4553
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-muted px-4 py-2", children: /* @__PURE__ */ jsx(Loader, { variant: loaderVariant, size: "sm" }) })
4554
+ ] }, message.id);
4555
+ } else {
4556
+ return null;
4557
+ }
4558
+ }
4559
+ const savedExecutions = message.metadata?.toolExecutions;
4560
+ const messageToolExecutions = message.toolExecutions || savedExecutions;
4561
+ const messageWithExecutions = messageToolExecutions ? { ...message, toolExecutions: messageToolExecutions } : message;
4562
+ const handleFollowUpClick = (question) => {
4563
+ if (onSuggestionClick) {
4564
+ onSuggestionClick(question);
4565
+ } else {
4566
+ onSendMessage?.(question);
4567
+ }
4568
+ };
4569
+ return renderMessage ? /* @__PURE__ */ jsx(React8__default.Fragment, { children: renderMessage(messageWithExecutions, index) }, message.id) : /* @__PURE__ */ jsx(
4570
+ DefaultMessage,
4571
+ {
4572
+ message: messageWithExecutions,
4573
+ userAvatar,
4574
+ assistantAvatar,
4575
+ showUserAvatar,
4576
+ userMessageClassName: classNames.userMessage,
4577
+ assistantMessageClassName: classNames.assistantMessage,
4578
+ size: fontSize,
4579
+ isLastMessage,
4580
+ isLoading,
4581
+ registeredTools,
4582
+ toolRenderers,
4583
+ onApproveToolExecution,
4584
+ onRejectToolExecution,
4585
+ showFollowUps,
4586
+ onFollowUpClick: handleFollowUpClick,
4587
+ followUpClassName,
4588
+ followUpButtonClassName
4589
+ },
4590
+ message.id
4591
+ );
4592
+ }),
4593
+ isProcessing && /* @__PURE__ */ jsxs(Message, { className: "flex gap-2", children: [
3588
4594
  /* @__PURE__ */ jsx(
3589
4595
  MessageAvatar,
3590
4596
  {
3591
4597
  src: assistantAvatar?.src || "",
3592
4598
  alt: "Assistant",
3593
4599
  fallback: assistantAvatar?.fallback || "AI",
3594
- className: "bg-primary text-primary-foreground"
4600
+ fallbackIcon: !assistantAvatar?.src ? /* @__PURE__ */ jsx(copilot_sdk_logo_default, { className: "size-5" }) : void 0,
4601
+ className: "bg-background"
3595
4602
  }
3596
4603
  ),
3597
- /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-muted px-4 py-2", children: /* @__PURE__ */ jsx(Loader, { variant: loaderVariant, size: "sm" }) })
3598
- ] });
3599
- }
3600
- return null;
3601
- })(),
3602
- /* @__PURE__ */ jsx(ChatContainerScrollAnchor, {})
3603
- ]
3604
- }
3605
- ),
3606
- /* @__PURE__ */ jsx("div", { className: "absolute right-4 bottom-4", children: /* @__PURE__ */ jsx(ScrollButton, { className: "shadow-sm" }) })
3607
- ]
3608
- }
3609
- ),
3610
- suggestions.length > 0 && !isLoading && /* @__PURE__ */ jsx(
3611
- Suggestions,
3612
- {
3613
- suggestions,
3614
- onSuggestionClick: handleSuggestionClick,
3615
- className: classNames.suggestions
3616
- }
3617
- ),
3618
- renderInput ? renderInput() : /* @__PURE__ */ jsxs("div", { className: cn("p-2", classNames.input), children: [
3619
- pendingAttachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 p-2 mb-2 bg-muted/30 rounded-lg", children: pendingAttachments.map((att) => /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
3620
- att.attachment.type === "image" ? /* @__PURE__ */ jsx(
3621
- "img",
3622
- {
3623
- src: att.previewUrl,
3624
- alt: att.file.name,
3625
- className: "w-16 h-16 object-cover rounded-lg border"
3626
- }
3627
- ) : /* @__PURE__ */ jsxs("div", { className: "w-16 h-16 bg-muted rounded-lg border flex flex-col items-center justify-center p-1", children: [
3628
- /* @__PURE__ */ jsx(
3629
- "svg",
3630
- {
3631
- className: "w-6 h-6 text-muted-foreground",
3632
- fill: "none",
3633
- viewBox: "0 0 24 24",
3634
- stroke: "currentColor",
3635
- children: /* @__PURE__ */ jsx(
3636
- "path",
3637
- {
3638
- strokeLinecap: "round",
3639
- strokeLinejoin: "round",
3640
- strokeWidth: 1.5,
3641
- d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
4604
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-muted px-4 py-2 flex items-center gap-2", children: [
4605
+ /* @__PURE__ */ jsx(Loader, { variant: "dots", size: "sm" }),
4606
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Continuing..." })
4607
+ ] })
4608
+ ] }),
4609
+ isLoading && !isProcessing && (() => {
4610
+ const lastMessage = messages[messages.length - 1];
4611
+ if (lastMessage?.role === "user") {
4612
+ return /* @__PURE__ */ jsxs(Message, { className: "flex gap-2", children: [
4613
+ /* @__PURE__ */ jsx(
4614
+ MessageAvatar,
4615
+ {
4616
+ src: assistantAvatar?.src || "",
4617
+ alt: "Assistant",
4618
+ fallback: assistantAvatar?.fallback || "AI",
4619
+ fallbackIcon: !assistantAvatar?.src ? /* @__PURE__ */ jsx(copilot_sdk_logo_default, { className: "size-5" }) : void 0,
4620
+ className: "bg-background"
4621
+ }
4622
+ ),
4623
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-muted px-4 py-2", children: /* @__PURE__ */ jsx(Loader, { variant: loaderVariant, size: "sm" }) })
4624
+ ] });
4625
+ }
4626
+ return null;
4627
+ })(),
4628
+ /* @__PURE__ */ jsx(ChatContainerScrollAnchor, {})
4629
+ ]
3642
4630
  }
3643
- )
3644
- }
3645
- ),
3646
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground truncate w-full text-center mt-1", children: att.file.name.length > 10 ? att.file.name.slice(0, 8) + "..." : att.file.name })
3647
- ] }),
3648
- att.status === "processing" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-background/80 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsx(Loader, { variant: "dots", size: "sm" }) }),
3649
- att.status === "error" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-destructive/20 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-destructive text-xs", children: "Error" }) }),
3650
- /* @__PURE__ */ jsx(
3651
- "button",
4631
+ ),
4632
+ /* @__PURE__ */ jsx("div", { className: "absolute right-4 bottom-4", children: /* @__PURE__ */ jsx(ScrollButton, { className: "shadow-sm" }) })
4633
+ ]
4634
+ }
4635
+ ),
4636
+ suggestions.length > 0 && !isLoading && /* @__PURE__ */ jsx(
4637
+ Suggestions,
3652
4638
  {
3653
- onClick: () => removePendingAttachment(att.id),
3654
- className: "absolute -top-1.5 -right-1.5 bg-destructive text-destructive-foreground rounded-full p-0.5 opacity-0 group-hover:opacity-100 transition-opacity",
3655
- type: "button",
3656
- children: /* @__PURE__ */ jsx(XIcon2, { className: "w-3 h-3" })
4639
+ suggestions,
4640
+ onSuggestionClick: handleSuggestionClick,
4641
+ className: classNames.suggestions
3657
4642
  }
3658
- )
3659
- ] }, att.id)) }),
3660
- /* @__PURE__ */ jsxs(
3661
- PromptInput,
3662
- {
3663
- value: input,
3664
- onValueChange: setInput,
3665
- isLoading,
3666
- onSubmit: handleSubmit,
3667
- className: "",
3668
- children: [
3669
- /* @__PURE__ */ jsx(PromptInputTextarea, { placeholder }),
3670
- /* @__PURE__ */ jsxs(PromptInputActions, { className: "flex justify-between", children: [
3671
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
3672
- PromptInputAction,
4643
+ ),
4644
+ renderInput ? renderInput() : /* @__PURE__ */ jsxs("div", { className: cn("p-2 pt-0", classNames.input), children: [
4645
+ pendingAttachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 p-2 mb-2 bg-muted/30 rounded-lg", children: pendingAttachments.map((att) => /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
4646
+ att.attachment.type === "image" ? /* @__PURE__ */ jsx(
4647
+ "img",
4648
+ {
4649
+ src: att.previewUrl,
4650
+ alt: att.file.name,
4651
+ className: "w-16 h-16 object-cover rounded-lg border"
4652
+ }
4653
+ ) : /* @__PURE__ */ jsxs("div", { className: "w-16 h-16 bg-muted rounded-lg border flex flex-col items-center justify-center p-1", children: [
4654
+ /* @__PURE__ */ jsx(
4655
+ "svg",
3673
4656
  {
3674
- tooltip: attachmentsEnabled ? "Attach files" : attachmentsDisabledTooltip,
3675
- children: /* @__PURE__ */ jsxs(
3676
- "label",
4657
+ className: "w-6 h-6 text-muted-foreground",
4658
+ fill: "none",
4659
+ viewBox: "0 0 24 24",
4660
+ stroke: "currentColor",
4661
+ children: /* @__PURE__ */ jsx(
4662
+ "path",
3677
4663
  {
3678
- htmlFor: fileInputId,
3679
- className: cn(
3680
- "flex h-8 w-8 items-center justify-center rounded-2xl",
3681
- attachmentsEnabled ? "hover:bg-secondary-foreground/10 cursor-pointer" : "opacity-50 cursor-not-allowed"
3682
- ),
3683
- children: [
3684
- /* @__PURE__ */ jsx(
3685
- "input",
3686
- {
3687
- ref: fileInputRef,
3688
- type: "file",
3689
- multiple: true,
3690
- accept: acceptString,
3691
- onChange: handleInputChange,
3692
- className: "hidden",
3693
- id: fileInputId,
3694
- disabled: !attachmentsEnabled
3695
- }
3696
- ),
3697
- /* @__PURE__ */ jsx(PlusIcon, { className: "text-primary size-5" })
3698
- ]
4664
+ strokeLinecap: "round",
4665
+ strokeLinejoin: "round",
4666
+ strokeWidth: 1.5,
4667
+ d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
3699
4668
  }
3700
4669
  )
3701
4670
  }
3702
- ) }),
3703
- /* @__PURE__ */ jsx(PromptInputAction, { tooltip: isLoading ? "Stop" : "Send", children: isLoading ? /* @__PURE__ */ jsx(
3704
- Button,
3705
- {
3706
- size: "sm",
3707
- variant: "destructive",
3708
- className: "rounded-full size-9",
3709
- onClick: onStop,
3710
- children: /* @__PURE__ */ jsx(StopIcon, { className: "h-4 w-4" })
3711
- }
3712
- ) : /* @__PURE__ */ jsx(
3713
- Button,
3714
- {
3715
- size: "sm",
3716
- className: "rounded-full size-9",
3717
- onClick: handleSubmit,
3718
- disabled: !input.trim() && !pendingAttachments.some((att) => att.status === "ready"),
3719
- children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-4 w-4" })
3720
- }
3721
- ) })
3722
- ] })
3723
- ]
3724
- }
3725
- )
3726
- ] })
4671
+ ),
4672
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground truncate w-full text-center mt-1", children: att.file.name.length > 10 ? att.file.name.slice(0, 8) + "..." : att.file.name })
4673
+ ] }),
4674
+ att.status === "processing" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-background/80 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsx(Loader, { variant: "dots", size: "sm" }) }),
4675
+ att.status === "error" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-destructive/20 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-destructive text-xs", children: "Error" }) }),
4676
+ /* @__PURE__ */ jsx(
4677
+ "button",
4678
+ {
4679
+ onClick: () => removePendingAttachment(att.id),
4680
+ className: "absolute -top-1.5 -right-1.5 bg-destructive text-destructive-foreground rounded-full p-0.5 opacity-0 group-hover:opacity-100 transition-opacity",
4681
+ type: "button",
4682
+ children: /* @__PURE__ */ jsx(XIcon2, { className: "w-3 h-3" })
4683
+ }
4684
+ )
4685
+ ] }, att.id)) }),
4686
+ /* @__PURE__ */ jsxs(
4687
+ PromptInput,
4688
+ {
4689
+ value: input,
4690
+ onValueChange: setInput,
4691
+ isLoading,
4692
+ onSubmit: handleSubmit,
4693
+ className: "",
4694
+ children: [
4695
+ /* @__PURE__ */ jsx(PromptInputTextarea, { placeholder }),
4696
+ /* @__PURE__ */ jsxs(PromptInputActions, { className: "flex justify-between", children: [
4697
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
4698
+ PromptInputAction,
4699
+ {
4700
+ tooltip: attachmentsEnabled ? "Attach files" : attachmentsDisabledTooltip,
4701
+ children: /* @__PURE__ */ jsxs(
4702
+ "label",
4703
+ {
4704
+ htmlFor: fileInputId,
4705
+ className: cn(
4706
+ "flex h-8 w-8 items-center justify-center rounded-2xl",
4707
+ attachmentsEnabled ? "hover:bg-secondary-foreground/10 cursor-pointer" : "opacity-50 cursor-not-allowed"
4708
+ ),
4709
+ children: [
4710
+ /* @__PURE__ */ jsx(
4711
+ "input",
4712
+ {
4713
+ ref: fileInputRef,
4714
+ type: "file",
4715
+ multiple: true,
4716
+ accept: acceptString,
4717
+ onChange: handleInputChange,
4718
+ className: "hidden",
4719
+ id: fileInputId,
4720
+ disabled: !attachmentsEnabled
4721
+ }
4722
+ ),
4723
+ /* @__PURE__ */ jsx(PlusIcon, { className: "text-primary size-5" })
4724
+ ]
4725
+ }
4726
+ )
4727
+ }
4728
+ ) }),
4729
+ /* @__PURE__ */ jsx(PromptInputAction, { tooltip: isLoading ? "Stop" : "Send", children: isLoading ? /* @__PURE__ */ jsx(
4730
+ Button,
4731
+ {
4732
+ size: "sm",
4733
+ variant: "destructive",
4734
+ className: "rounded-full size-9",
4735
+ onClick: onStop,
4736
+ children: /* @__PURE__ */ jsx(StopIcon, { className: "h-4 w-4" })
4737
+ }
4738
+ ) : /* @__PURE__ */ jsx(
4739
+ Button,
4740
+ {
4741
+ size: "sm",
4742
+ className: "rounded-full size-9",
4743
+ onClick: handleSubmit,
4744
+ disabled: !input.trim() && !pendingAttachments.some(
4745
+ (att) => att.status === "ready"
4746
+ ),
4747
+ children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-4 w-4" })
4748
+ }
4749
+ ) })
4750
+ ] })
4751
+ ]
4752
+ }
4753
+ )
4754
+ ] })
4755
+ ] })
4756
+ )
3727
4757
  ]
3728
4758
  }
3729
4759
  );
@@ -3733,6 +4763,7 @@ function ToolExecutionMessage({
3733
4763
  assistantAvatar = { fallback: "AI" },
3734
4764
  onApprove,
3735
4765
  onReject,
4766
+ toolRenderers,
3736
4767
  className
3737
4768
  }) {
3738
4769
  if (!executions || executions.length === 0) return null;
@@ -3816,22 +4847,261 @@ function ToolExecutionMessage({
3816
4847
  ),
3817
4848
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: hasExecuting ? "Running tools..." : allCompleted ? `${executions.length} tool${executions.length > 1 ? "s" : ""} completed` : "Tools" })
3818
4849
  ] }) }),
3819
- pendingApprovals.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2 space-y-2", children: pendingApprovals.map((tool) => /* @__PURE__ */ jsx(
3820
- PermissionConfirmation,
3821
- {
3822
- state: "pending",
3823
- toolName: tool.name,
3824
- message: tool.approvalMessage || `This tool wants to execute. Do you approve?`,
3825
- onApprove: (permissionLevel) => onApprove?.(tool.id, permissionLevel),
3826
- onReject: (permissionLevel) => onReject?.(tool.id, void 0, permissionLevel)
3827
- },
3828
- tool.id
3829
- )) }),
4850
+ pendingApprovals.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2 space-y-2", children: pendingApprovals.map((tool) => {
4851
+ const CustomRenderer = toolRenderers?.[tool.name];
4852
+ if (CustomRenderer) {
4853
+ return /* @__PURE__ */ jsx(
4854
+ CustomRenderer,
4855
+ {
4856
+ execution: tool,
4857
+ approval: {
4858
+ onApprove: (extraData) => onApprove?.(tool.id, extraData),
4859
+ onReject: (reason) => onReject?.(tool.id, reason),
4860
+ message: tool.approvalMessage
4861
+ }
4862
+ },
4863
+ tool.id
4864
+ );
4865
+ }
4866
+ return /* @__PURE__ */ jsx(
4867
+ PermissionConfirmation,
4868
+ {
4869
+ state: "pending",
4870
+ toolName: tool.name,
4871
+ message: tool.approvalMessage || `This tool wants to execute. Do you approve?`,
4872
+ onApprove: (permissionLevel) => onApprove?.(tool.id, void 0, permissionLevel),
4873
+ onReject: (permissionLevel) => onReject?.(tool.id, void 0, permissionLevel)
4874
+ },
4875
+ tool.id
4876
+ );
4877
+ }) }),
3830
4878
  toolSteps.length > 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card px-3 py-2.5 shadow-sm", children: /* @__PURE__ */ jsx(ToolSteps, { steps: toolSteps }) })
3831
4879
  ] })
3832
4880
  ] });
3833
4881
  }
4882
+ function useInternalThreadManager(config = {}) {
4883
+ const {
4884
+ adapter,
4885
+ saveDebounce = 1e3,
4886
+ autoRestoreLastThread = true,
4887
+ onThreadChange
4888
+ } = config;
4889
+ const threadManagerConfig = {
4890
+ adapter,
4891
+ saveDebounce,
4892
+ autoRestoreLastThread
4893
+ };
4894
+ const threadManager = useThreadManager(threadManagerConfig);
4895
+ const {
4896
+ currentThread,
4897
+ currentThreadId,
4898
+ createThread,
4899
+ switchThread,
4900
+ updateCurrentThread,
4901
+ clearCurrentThread,
4902
+ refreshThreads
4903
+ } = threadManager;
4904
+ const { messages, setMessages, status, isLoading } = useCopilot();
4905
+ const isLoadingMessagesRef = useRef(false);
4906
+ const savingToThreadRef = useRef(null);
4907
+ const lastSavedSnapshotRef = useRef("");
4908
+ const hasInitializedRef = useRef(false);
4909
+ const getMessageSnapshot = useCallback((msgs) => {
4910
+ return msgs.map((m) => {
4911
+ const contentPreview = (m.content ?? "").slice(0, 20);
4912
+ return `${m.id}:${contentPreview}:${m.content?.length ?? 0}`;
4913
+ }).join("|");
4914
+ }, []);
4915
+ const convertToCore = useCallback((msgs) => {
4916
+ return msgs.map((m) => ({
4917
+ id: m.id,
4918
+ role: m.role,
4919
+ content: m.content,
4920
+ created_at: m.createdAt,
4921
+ tool_calls: m.toolCalls,
4922
+ tool_call_id: m.toolCallId,
4923
+ metadata: {
4924
+ attachments: m.attachments,
4925
+ thinking: m.thinking
4926
+ }
4927
+ }));
4928
+ }, []);
4929
+ const handleSwitchThread = useCallback(
4930
+ async (threadId) => {
4931
+ isLoadingMessagesRef.current = true;
4932
+ const thread = await switchThread(threadId);
4933
+ if (thread?.messages) {
4934
+ const uiMessages = thread.messages.map((m) => ({
4935
+ id: m.id,
4936
+ role: m.role,
4937
+ content: m.content ?? "",
4938
+ createdAt: m.created_at ?? /* @__PURE__ */ new Date(),
4939
+ toolCalls: m.tool_calls,
4940
+ toolCallId: m.tool_call_id,
4941
+ attachments: m.metadata?.attachments
4942
+ }));
4943
+ lastSavedSnapshotRef.current = getMessageSnapshot(uiMessages);
4944
+ savingToThreadRef.current = threadId;
4945
+ setMessages(uiMessages);
4946
+ } else {
4947
+ lastSavedSnapshotRef.current = "";
4948
+ savingToThreadRef.current = threadId;
4949
+ setMessages([]);
4950
+ }
4951
+ onThreadChange?.(threadId);
4952
+ requestAnimationFrame(() => {
4953
+ isLoadingMessagesRef.current = false;
4954
+ });
4955
+ },
4956
+ [switchThread, setMessages, getMessageSnapshot, onThreadChange]
4957
+ );
4958
+ const handleNewThread = useCallback(async () => {
4959
+ isLoadingMessagesRef.current = true;
4960
+ clearCurrentThread();
4961
+ lastSavedSnapshotRef.current = "";
4962
+ savingToThreadRef.current = null;
4963
+ setMessages([]);
4964
+ onThreadChange?.(null);
4965
+ requestAnimationFrame(() => {
4966
+ isLoadingMessagesRef.current = false;
4967
+ });
4968
+ }, [clearCurrentThread, setMessages, onThreadChange]);
4969
+ useEffect(() => {
4970
+ if (hasInitializedRef.current || !currentThread) {
4971
+ return;
4972
+ }
4973
+ hasInitializedRef.current = true;
4974
+ isLoadingMessagesRef.current = true;
4975
+ if (currentThread.messages && currentThread.messages.length > 0) {
4976
+ const uiMessages = currentThread.messages.map((m) => ({
4977
+ id: m.id,
4978
+ role: m.role,
4979
+ content: m.content ?? "",
4980
+ createdAt: m.created_at ?? /* @__PURE__ */ new Date(),
4981
+ toolCalls: m.tool_calls,
4982
+ toolCallId: m.tool_call_id,
4983
+ attachments: m.metadata?.attachments
4984
+ }));
4985
+ lastSavedSnapshotRef.current = getMessageSnapshot(uiMessages);
4986
+ savingToThreadRef.current = currentThread.id;
4987
+ setMessages(uiMessages);
4988
+ } else {
4989
+ lastSavedSnapshotRef.current = "";
4990
+ savingToThreadRef.current = currentThread.id;
4991
+ }
4992
+ onThreadChange?.(currentThread.id);
4993
+ requestAnimationFrame(() => {
4994
+ isLoadingMessagesRef.current = false;
4995
+ });
4996
+ }, [currentThread, setMessages, getMessageSnapshot, onThreadChange]);
4997
+ useEffect(() => {
4998
+ if (isLoadingMessagesRef.current) {
4999
+ return;
5000
+ }
5001
+ if (status === "streaming" || status === "submitted") {
5002
+ return;
5003
+ }
5004
+ if (messages.length === 0) {
5005
+ return;
5006
+ }
5007
+ const currentSnapshot = getMessageSnapshot(messages);
5008
+ if (currentSnapshot === lastSavedSnapshotRef.current) {
5009
+ return;
5010
+ }
5011
+ const coreMessages = convertToCore(messages);
5012
+ if (!currentThreadId && !savingToThreadRef.current) {
5013
+ savingToThreadRef.current = "creating";
5014
+ createThread({ messages: coreMessages }).then((thread) => {
5015
+ lastSavedSnapshotRef.current = currentSnapshot;
5016
+ savingToThreadRef.current = thread.id;
5017
+ onThreadChange?.(thread.id);
5018
+ });
5019
+ return;
5020
+ }
5021
+ if (savingToThreadRef.current && savingToThreadRef.current !== currentThreadId) {
5022
+ return;
5023
+ }
5024
+ updateCurrentThread({ messages: coreMessages });
5025
+ lastSavedSnapshotRef.current = currentSnapshot;
5026
+ }, [
5027
+ messages,
5028
+ currentThreadId,
5029
+ status,
5030
+ updateCurrentThread,
5031
+ createThread,
5032
+ refreshThreads,
5033
+ getMessageSnapshot,
5034
+ convertToCore,
5035
+ onThreadChange
5036
+ ]);
5037
+ const isBusy = isLoading || status === "streaming" || status === "submitted";
5038
+ return {
5039
+ threadManager,
5040
+ handleSwitchThread,
5041
+ handleNewThread,
5042
+ isBusy
5043
+ };
5044
+ }
5045
+ function parsePersistenceConfig(persistence, onThreadChange) {
5046
+ if (!persistence) {
5047
+ return void 0;
5048
+ }
5049
+ if (persistence === true) {
5050
+ return {
5051
+ onThreadChange,
5052
+ autoRestoreLastThread: true
5053
+ };
5054
+ }
5055
+ if ("type" in persistence) {
5056
+ switch (persistence.type) {
5057
+ case "local":
5058
+ return {
5059
+ saveDebounce: persistence.saveDebounce,
5060
+ autoRestoreLastThread: persistence.autoRestoreLastThread ?? true,
5061
+ onThreadChange
5062
+ };
5063
+ case "server":
5064
+ return {
5065
+ adapter: createServerAdapter({
5066
+ endpoint: persistence.endpoint,
5067
+ headers: persistence.headers
5068
+ }),
5069
+ saveDebounce: persistence.saveDebounce,
5070
+ autoRestoreLastThread: persistence.autoRestoreLastThread ?? true,
5071
+ onThreadChange
5072
+ };
5073
+ case "cloud":
5074
+ console.warn(
5075
+ "[Copilot SDK] Cloud persistence is not yet implemented. Falling back to localStorage."
5076
+ );
5077
+ return {
5078
+ onThreadChange,
5079
+ autoRestoreLastThread: true
5080
+ };
5081
+ }
5082
+ }
5083
+ const legacyConfig = persistence;
5084
+ return {
5085
+ adapter: legacyConfig.adapter,
5086
+ saveDebounce: legacyConfig.saveDebounce,
5087
+ autoRestoreLastThread: legacyConfig.autoRestoreLastThread ?? true,
5088
+ onThreadChange
5089
+ };
5090
+ }
3834
5091
  function CopilotChat(props) {
5092
+ const {
5093
+ persistence,
5094
+ showThreadPicker = false,
5095
+ onThreadChange,
5096
+ classNames,
5097
+ header,
5098
+ ...chatProps
5099
+ } = props;
5100
+ const persistenceConfig = parsePersistenceConfig(persistence, onThreadChange);
5101
+ const threadManagerResult = useInternalThreadManager(
5102
+ persistenceConfig ?? { autoRestoreLastThread: false }
5103
+ );
5104
+ const isPersistenceEnabled = !!persistence;
3835
5105
  const {
3836
5106
  messages,
3837
5107
  isLoading,
@@ -3839,7 +5109,8 @@ function CopilotChat(props) {
3839
5109
  stop,
3840
5110
  toolExecutions: rawToolExecutions,
3841
5111
  approveToolExecution,
3842
- rejectToolExecution
5112
+ rejectToolExecution,
5113
+ registeredTools
3843
5114
  } = useCopilot();
3844
5115
  const toolExecutions = rawToolExecutions.map(
3845
5116
  (exec) => ({
@@ -3930,7 +5201,7 @@ function CopilotChat(props) {
3930
5201
  toolExecutions: messageToolExecutions
3931
5202
  };
3932
5203
  });
3933
- const suggestions = visibleMessages.length === 0 && props.suggestions?.length ? props.suggestions : [];
5204
+ const suggestions = visibleMessages.length === 0 && chatProps.suggestions?.length ? chatProps.suggestions : [];
3934
5205
  const lastMessage = messages[messages.length - 1];
3935
5206
  const isInToolFlow = lastMessage?.role === "assistant" && lastMessage.toolCalls?.length;
3936
5207
  let isProcessingToolResults = false;
@@ -3951,19 +5222,70 @@ function CopilotChat(props) {
3951
5222
  );
3952
5223
  isProcessingToolResults = hasCompletedTools && !hasExecutingTools;
3953
5224
  }
5225
+ const chatClassNames = classNames ? {
5226
+ root: classNames.root,
5227
+ header: classNames.header,
5228
+ container: classNames.container,
5229
+ messageList: classNames.messageList,
5230
+ userMessage: classNames.userMessage,
5231
+ assistantMessage: classNames.assistantMessage,
5232
+ input: classNames.input,
5233
+ suggestions: classNames.suggestions,
5234
+ footer: classNames.footer
5235
+ } : void 0;
5236
+ const { threadManager, handleSwitchThread, handleNewThread, isBusy } = threadManagerResult;
5237
+ const handleDeleteThread = React8__default.useCallback(
5238
+ (threadId) => {
5239
+ const isCurrentThread = threadManager.currentThreadId === threadId;
5240
+ threadManager.deleteThread(threadId);
5241
+ if (isCurrentThread) {
5242
+ handleNewThread();
5243
+ }
5244
+ },
5245
+ [threadManager, handleNewThread]
5246
+ );
5247
+ const threadPickerElement = isPersistenceEnabled && showThreadPicker ? /* @__PURE__ */ jsx(
5248
+ ThreadPicker,
5249
+ {
5250
+ value: threadManager.currentThreadId,
5251
+ threads: threadManager.threads,
5252
+ onSelect: handleSwitchThread,
5253
+ onDeleteThread: handleDeleteThread,
5254
+ onNewThread: handleNewThread,
5255
+ loading: threadManager.isLoading,
5256
+ disabled: isBusy,
5257
+ size: "sm",
5258
+ className: classNames?.threadPicker,
5259
+ buttonClassName: classNames?.threadPickerButton,
5260
+ dropdownClassName: classNames?.threadPickerDropdown,
5261
+ itemClassName: classNames?.threadPickerItem,
5262
+ newButtonClassName: classNames?.threadPickerNewButton
5263
+ }
5264
+ ) : void 0;
5265
+ const shouldShowHeader = !!header || showThreadPicker || chatProps.showHeader;
5266
+ const useCustomHeader = chatProps.renderHeader && !header && !showThreadPicker;
3954
5267
  return /* @__PURE__ */ jsx(
3955
5268
  Chat,
3956
5269
  {
3957
- ...props,
5270
+ ...chatProps,
3958
5271
  messages: visibleMessages,
3959
5272
  onSendMessage: sendMessage,
3960
5273
  onStop: stop,
3961
5274
  isLoading,
3962
- showPoweredBy: props.showPoweredBy ?? true,
5275
+ showPoweredBy: chatProps.showPoweredBy ?? true,
3963
5276
  suggestions,
3964
5277
  isProcessing: isProcessingToolResults,
3965
5278
  onApproveToolExecution: approveToolExecution,
3966
- onRejectToolExecution: rejectToolExecution
5279
+ onRejectToolExecution: rejectToolExecution,
5280
+ registeredTools,
5281
+ classNames: chatClassNames,
5282
+ header,
5283
+ threadPicker: threadPickerElement,
5284
+ showHeader: shouldShowHeader,
5285
+ renderHeader: useCustomHeader ? chatProps.renderHeader : void 0,
5286
+ recentThreads: isPersistenceEnabled ? threadManager.threads : void 0,
5287
+ onSelectThread: isPersistenceEnabled ? handleSwitchThread : void 0,
5288
+ onDeleteThread: isPersistenceEnabled ? handleDeleteThread : void 0
3967
5289
  }
3968
5290
  );
3969
5291
  }
@@ -4014,6 +5336,6 @@ function PoweredBy({ className, showLogo = true }) {
4014
5336
  );
4015
5337
  }
4016
5338
 
4017
- export { AlertTriangleIcon, BotIcon, Button, CapabilityBadge, CapabilityList, Chat, ChatContainerContent, ChatContainerRoot, ChatContainerScrollAnchor, CheckIcon, ChevronDownIcon2 as ChevronDownIcon, ChevronUpIcon, CloseIcon, CodeBlock, CompactPermissionConfirmation, Confirmation, ConfirmationActions, ConfirmationApproved, ConfirmationMessage, ConfirmationPending, ConfirmationRejected, ConnectedChat, CopilotChat, CopilotUIProvider, CopyIcon, DEFAULT_PERMISSION_OPTIONS, DevLogger, FeedbackBar, FollowUpQuestions, InlineToolSteps, Loader, Markdown, MessageAvatar, MessageContent, Message as MessagePrimitive, ModelSelector, PermissionConfirmation, PoweredBy, PromptInput, PromptInputAction, PromptInputActions, PromptInputTextarea, Reasoning, ReasoningContent, ReasoningTrigger, RefreshIcon, ScrollButton, SendIcon, SimpleConfirmation, SimpleModelSelector, SimpleReasoning, Source, SourceContent, SourceTrigger, StopIcon, ThumbsDownIcon2 as ThumbsDownIcon, ThumbsUpIcon2 as ThumbsUpIcon, ToolExecutionMessage, ToolStep, ToolSteps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserIcon, XIcon2 as XIcon, cn, parseFollowUps, useChatContainer, useCopilotUI };
5339
+ export { AlertTriangleIcon, BotIcon, Button, CapabilityBadge, CapabilityList, Chat, ChatContainerContent, ChatContainerRoot, ChatContainerScrollAnchor, ChatWelcome, CheckIcon, ChevronDownIcon2 as ChevronDownIcon, ChevronUpIcon, CloseIcon, CodeBlock, CompactPermissionConfirmation, Confirmation, ConfirmationActions, ConfirmationApproved, ConfirmationMessage, ConfirmationPending, ConfirmationRejected, ConnectedChat, CopilotChat, CopilotUIProvider, CopyIcon, DEFAULT_PERMISSION_OPTIONS, DevLogger, FeedbackBar, FollowUpQuestions, InlineToolSteps, Loader, Markdown, MessageAvatar, MessageContent, Message as MessagePrimitive, ModelSelector, PermissionConfirmation, PoweredBy, PromptInput, PromptInputAction, PromptInputActions, PromptInputTextarea, Reasoning, ReasoningContent, ReasoningTrigger, RefreshIcon, ScrollButton, SendIcon, SimpleConfirmation, SimpleModelSelector, SimpleReasoning, Source, SourceContent, SourceTrigger, StopIcon, ThreadCard, ThreadList, ThreadPicker, ThumbsDownIcon2 as ThumbsDownIcon, ThumbsUpIcon2 as ThumbsUpIcon, ToolExecutionMessage, ToolStep, ToolSteps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserIcon, XIcon2 as XIcon, cn, parseFollowUps, useChatContainer, useCopilotUI };
4018
5340
  //# sourceMappingURL=index.js.map
4019
5341
  //# sourceMappingURL=index.js.map