@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.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkW6KQT7YZ_cjs = require('../chunk-W6KQT7YZ.cjs');
4
- require('../chunk-2ZCWVAAK.cjs');
3
+ var chunkBN75ZW24_cjs = require('../chunk-BN75ZW24.cjs');
4
+ var chunk42YQ4ATO_cjs = require('../chunk-42YQ4ATO.cjs');
5
5
  var clsx = require('clsx');
6
6
  var tailwindMerge = require('tailwind-merge');
7
7
  var jsxRuntime = require('react/jsx-runtime');
@@ -17,6 +17,7 @@ var useStickToBottom = require('use-stick-to-bottom');
17
17
  var tooltip = require('@base-ui/react/tooltip');
18
18
  var AvatarPrimitive = require('@radix-ui/react-avatar');
19
19
  var HoverCardPrimitive = require('@radix-ui/react-hover-card');
20
+ var popover = require('@base-ui/react/popover');
20
21
 
21
22
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
22
23
 
@@ -893,12 +894,13 @@ var MessageAvatar = ({
893
894
  src,
894
895
  alt,
895
896
  fallback,
897
+ fallbackIcon,
896
898
  delayMs,
897
899
  className
898
900
  }) => {
899
901
  return /* @__PURE__ */ jsxRuntime.jsxs(Avatar, { className: cn("size-7 shrink-0", className), children: [
900
902
  /* @__PURE__ */ jsxRuntime.jsx(AvatarImage, { src, alt }),
901
- fallback && /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { delayMs, children: fallback })
903
+ /* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { delayMs, children: fallbackIcon || fallback })
902
904
  ] });
903
905
  };
904
906
  var proseSizeMap = {
@@ -1932,6 +1934,27 @@ function AlertTriangleIcon({ className }) {
1932
1934
  }
1933
1935
  );
1934
1936
  }
1937
+ function ArrowUpRightIcon({ className }) {
1938
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1939
+ "svg",
1940
+ {
1941
+ xmlns: "http://www.w3.org/2000/svg",
1942
+ width: "24",
1943
+ height: "24",
1944
+ viewBox: "0 0 24 24",
1945
+ fill: "none",
1946
+ stroke: "currentColor",
1947
+ strokeWidth: "2",
1948
+ strokeLinecap: "round",
1949
+ strokeLinejoin: "round",
1950
+ className,
1951
+ children: [
1952
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 7h10v10" }),
1953
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 17 17 7" })
1954
+ ]
1955
+ }
1956
+ );
1957
+ }
1935
1958
  var ConfirmationContext = React8__namespace.createContext(null);
1936
1959
  function useConfirmationContext() {
1937
1960
  const context = React8__namespace.useContext(ConfirmationContext);
@@ -3004,143 +3027,699 @@ function SimpleModelSelector({
3004
3027
  }
3005
3028
  );
3006
3029
  }
3007
- function ChatHeader({ title, onClose, className }) {
3008
- return /* @__PURE__ */ jsxRuntime.jsxs(
3009
- "div",
3030
+ function Popover({ children, open, defaultOpen, onOpenChange }) {
3031
+ return /* @__PURE__ */ jsxRuntime.jsx(
3032
+ popover.Popover.Root,
3033
+ {
3034
+ open,
3035
+ defaultOpen,
3036
+ onOpenChange,
3037
+ children
3038
+ }
3039
+ );
3040
+ }
3041
+ function PopoverTrigger({
3042
+ children,
3043
+ asChild,
3044
+ className,
3045
+ ...props
3046
+ }) {
3047
+ if (asChild && React8__namespace.isValidElement(children)) {
3048
+ return /* @__PURE__ */ jsxRuntime.jsx(popover.Popover.Trigger, { render: children, className, ...props });
3049
+ }
3050
+ return /* @__PURE__ */ jsxRuntime.jsx(popover.Popover.Trigger, { className, ...props, children });
3051
+ }
3052
+ function PopoverContent({
3053
+ children,
3054
+ className,
3055
+ side = "bottom",
3056
+ align = "start",
3057
+ sideOffset = 4
3058
+ }) {
3059
+ return /* @__PURE__ */ jsxRuntime.jsx(popover.Popover.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(popover.Popover.Positioner, { side, align, sideOffset, children: /* @__PURE__ */ jsxRuntime.jsx(
3060
+ popover.Popover.Popup,
3010
3061
  {
3011
3062
  className: cn(
3012
- "flex items-center justify-between border-b px-4 py-3",
3063
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
3064
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
3065
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
3066
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
3067
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
3068
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
3013
3069
  className
3014
3070
  ),
3071
+ children
3072
+ }
3073
+ ) }) });
3074
+ }
3075
+ function formatDate(date) {
3076
+ const now = /* @__PURE__ */ new Date();
3077
+ const diff = now.getTime() - date.getTime();
3078
+ if (diff < 60 * 1e3) {
3079
+ return "Just now";
3080
+ }
3081
+ if (diff < 60 * 60 * 1e3) {
3082
+ const mins = Math.floor(diff / (60 * 1e3));
3083
+ return `${mins}m ago`;
3084
+ }
3085
+ if (diff < 24 * 60 * 60 * 1e3) {
3086
+ const hours = Math.floor(diff / (60 * 60 * 1e3));
3087
+ return `${hours}h ago`;
3088
+ }
3089
+ if (diff < 7 * 24 * 60 * 60 * 1e3) {
3090
+ const days = Math.floor(diff / (24 * 60 * 60 * 1e3));
3091
+ return `${days}d ago`;
3092
+ }
3093
+ return date.toLocaleDateString();
3094
+ }
3095
+ function ChevronIcon({ className }) {
3096
+ return /* @__PURE__ */ jsxRuntime.jsx(
3097
+ "svg",
3098
+ {
3099
+ className: cn("w-4 h-4", className),
3100
+ fill: "none",
3101
+ viewBox: "0 0 24 24",
3102
+ stroke: "currentColor",
3103
+ strokeWidth: 2,
3104
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" })
3105
+ }
3106
+ );
3107
+ }
3108
+ function PlusIcon2({ className }) {
3109
+ return /* @__PURE__ */ jsxRuntime.jsx(
3110
+ "svg",
3111
+ {
3112
+ className: cn("w-4 h-4", className),
3113
+ fill: "none",
3114
+ viewBox: "0 0 24 24",
3115
+ stroke: "currentColor",
3116
+ strokeWidth: 2,
3117
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4v16m8-8H4" })
3118
+ }
3119
+ );
3120
+ }
3121
+ function CheckIcon2({ className }) {
3122
+ return /* @__PURE__ */ jsxRuntime.jsx(
3123
+ "svg",
3124
+ {
3125
+ className: cn("w-4 h-4", className),
3126
+ fill: "none",
3127
+ viewBox: "0 0 24 24",
3128
+ stroke: "currentColor",
3129
+ strokeWidth: 2,
3130
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" })
3131
+ }
3132
+ );
3133
+ }
3134
+ function TrashIcon({ className }) {
3135
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3136
+ "svg",
3137
+ {
3138
+ className: cn("w-4 h-4", className),
3139
+ fill: "none",
3140
+ viewBox: "0 0 24 24",
3141
+ stroke: "currentColor",
3142
+ strokeWidth: 2,
3015
3143
  children: [
3016
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "font-semibold text-foreground", children: title || "Chat" }),
3017
- onClose && /* @__PURE__ */ jsxRuntime.jsx(
3018
- "button",
3144
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 6h18" }),
3145
+ /* @__PURE__ */ jsxRuntime.jsx(
3146
+ "path",
3019
3147
  {
3020
- type: "button",
3021
- onClick: onClose,
3022
- className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
3023
- "aria-label": "Close chat",
3024
- children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { className: "h-5 w-5" })
3148
+ strokeLinecap: "round",
3149
+ strokeLinejoin: "round",
3150
+ d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"
3151
+ }
3152
+ ),
3153
+ /* @__PURE__ */ jsxRuntime.jsx(
3154
+ "path",
3155
+ {
3156
+ strokeLinecap: "round",
3157
+ strokeLinejoin: "round",
3158
+ d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"
3025
3159
  }
3026
3160
  )
3027
3161
  ]
3028
3162
  }
3029
3163
  );
3030
3164
  }
3031
- function Suggestions({
3032
- suggestions,
3033
- onSuggestionClick,
3034
- className
3035
- }) {
3036
- if (!suggestions.length) return null;
3037
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-wrap gap-2 px-4 py-2", className), children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsxRuntime.jsx(
3038
- "button",
3039
- {
3040
- type: "button",
3041
- onClick: () => onSuggestionClick?.(suggestion),
3042
- className: "inline-flex items-center rounded-full border bg-background px-3 py-1.5 text-sm transition-colors hover:bg-muted",
3043
- children: suggestion
3044
- },
3045
- index
3046
- )) });
3047
- }
3048
- function DefaultMessage({
3049
- message,
3050
- userAvatar,
3051
- assistantAvatar,
3052
- showUserAvatar = false,
3053
- userMessageClassName,
3054
- assistantMessageClassName,
3055
- size = "sm",
3056
- isLastMessage = false,
3057
- isLoading = false,
3058
- toolRenderers,
3059
- onApproveToolExecution,
3060
- onRejectToolExecution,
3061
- showFollowUps = true,
3062
- onFollowUpClick,
3063
- followUpClassName,
3064
- followUpButtonClassName
3165
+ function ThreadPicker({
3166
+ value,
3167
+ threads,
3168
+ onSelect,
3169
+ onDeleteThread,
3170
+ onNewThread,
3171
+ placeholder = "Select conversation...",
3172
+ newThreadLabel = "New conversation",
3173
+ disabled = false,
3174
+ loading = false,
3175
+ size = "md",
3176
+ className,
3177
+ buttonClassName,
3178
+ dropdownClassName,
3179
+ itemClassName,
3180
+ newButtonClassName
3065
3181
  }) {
3066
- const isUser = message.role === "user";
3067
- const isStreaming = isLastMessage && isLoading;
3068
- const { cleanContent, followUps } = React8__namespace.useMemo(() => {
3069
- if (isUser || !message.content) {
3070
- return { cleanContent: message.content, followUps: [] };
3071
- }
3072
- return parseFollowUps(message.content);
3073
- }, [message.content, isUser]);
3074
- const shouldShowFollowUps = showFollowUps && !isUser && isLastMessage && !isLoading && followUps.length > 0 && onFollowUpClick;
3075
- if (isUser) {
3076
- const hasAttachments = message.attachments && message.attachments.length > 0;
3077
- return /* @__PURE__ */ jsxRuntime.jsxs(
3078
- Message,
3182
+ const [isOpen, setIsOpen] = React8__namespace.useState(false);
3183
+ const selectedThread = React8__namespace.useMemo(() => {
3184
+ if (!value) return null;
3185
+ return threads.find((t) => t.id === value) ?? null;
3186
+ }, [value, threads]);
3187
+ const handleSelect = (threadId) => {
3188
+ onSelect?.(threadId);
3189
+ setIsOpen(false);
3190
+ };
3191
+ const handleNewThread = () => {
3192
+ onNewThread?.();
3193
+ setIsOpen(false);
3194
+ };
3195
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: isOpen, onOpenChange: setIsOpen, children: [
3196
+ /* @__PURE__ */ jsxRuntime.jsxs(
3197
+ PopoverTrigger,
3079
3198
  {
3199
+ disabled: disabled || loading,
3080
3200
  className: cn(
3081
- "flex gap-2",
3082
- showUserAvatar ? "justify-end" : "justify-end"
3201
+ "flex items-center gap-1 w-full",
3202
+ disabled && "opacity-50 cursor-not-allowed",
3203
+ className,
3204
+ buttonClassName
3083
3205
  ),
3084
3206
  children: [
3085
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-end max-w-[80%]", children: [
3086
- message.content && /* @__PURE__ */ jsxRuntime.jsx(
3087
- MessageContent,
3088
- {
3089
- className: cn(
3090
- "rounded-lg px-4 py-2 bg-primary text-primary-foreground",
3091
- userMessageClassName
3092
- ),
3093
- size,
3094
- children: message.content
3095
- }
3096
- ),
3097
- hasAttachments && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex flex-wrap gap-2 justify-end", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreview, { attachment }, index)) })
3098
- ] }),
3099
- showUserAvatar && /* @__PURE__ */ jsxRuntime.jsx(
3100
- MessageAvatar,
3207
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1 text-xs ", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Loading..." }) : selectedThread ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium text-muted-foreground hover:text-foreground", children: selectedThread.title || "Untitled conversation" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: placeholder }) }),
3208
+ /* @__PURE__ */ jsxRuntime.jsx(
3209
+ ChevronIcon,
3101
3210
  {
3102
- src: userAvatar.src || "",
3103
- alt: "User",
3104
- fallback: userAvatar.fallback
3211
+ className: cn(
3212
+ "flex-shrink-0 size-3 text-muted-foreground transition-transform",
3213
+ isOpen && "rotate-180"
3214
+ )
3105
3215
  }
3106
3216
  )
3107
3217
  ]
3108
3218
  }
3109
- );
3110
- }
3111
- const pendingApprovalTools = message.toolExecutions?.filter(
3112
- (exec) => exec.approvalStatus === "required"
3113
- );
3114
- const completedTools = message.toolExecutions?.filter(
3115
- (exec) => exec.approvalStatus !== "required"
3116
- );
3117
- const toolsWithCustomRenderer = completedTools?.filter(
3118
- (exec) => toolRenderers && toolRenderers[exec.name]
3119
- );
3120
- const toolsWithoutCustomRenderer = completedTools?.filter(
3121
- (exec) => !toolRenderers || !toolRenderers[exec.name]
3122
- );
3123
- const toolSteps = toolsWithoutCustomRenderer?.map((exec) => ({
3124
- id: exec.id,
3125
- name: exec.name,
3126
- args: exec.args,
3127
- status: exec.status,
3128
- result: exec.result,
3129
- error: exec.error
3130
- }));
3131
- return /* @__PURE__ */ jsxRuntime.jsxs(Message, { className: "flex gap-2", children: [
3132
- /* @__PURE__ */ jsxRuntime.jsx(
3133
- MessageAvatar,
3219
+ ),
3220
+ /* @__PURE__ */ jsxRuntime.jsxs(
3221
+ PopoverContent,
3134
3222
  {
3135
- src: assistantAvatar.src || "",
3136
- alt: "Assistant",
3137
- fallback: assistantAvatar.fallback,
3138
- className: "bg-primary text-primary-foreground"
3223
+ align: "start",
3224
+ className: cn(
3225
+ "w-[var(--anchor-width)] min-w-[250px] p-0 max-h-[300px] overflow-auto",
3226
+ dropdownClassName
3227
+ ),
3228
+ children: [
3229
+ onNewThread && /* @__PURE__ */ jsxRuntime.jsxs(
3230
+ "button",
3231
+ {
3232
+ type: "button",
3233
+ onClick: handleNewThread,
3234
+ className: cn(
3235
+ "flex items-center gap-2 w-full px-2.5 py-1.5 text-left",
3236
+ "hover:bg-accent hover:text-accent-foreground",
3237
+ "focus:bg-accent focus:text-accent-foreground focus:outline-none",
3238
+ "border-b",
3239
+ newButtonClassName
3240
+ ),
3241
+ children: [
3242
+ /* @__PURE__ */ jsxRuntime.jsx(PlusIcon2, { className: "text-primary size-3" }),
3243
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-xs", children: newThreadLabel })
3244
+ ]
3245
+ }
3246
+ ),
3247
+ threads.length > 0 ? threads.map((thread) => /* @__PURE__ */ jsxRuntime.jsxs(
3248
+ "div",
3249
+ {
3250
+ className: cn(
3251
+ "group flex items-center gap-1 w-full px-2.5 py-1.5",
3252
+ "hover:bg-accent hover:text-accent-foreground",
3253
+ "focus-within:bg-accent focus-within:text-accent-foreground",
3254
+ value === thread.id && "bg-accent",
3255
+ itemClassName
3256
+ ),
3257
+ children: [
3258
+ /* @__PURE__ */ jsxRuntime.jsxs(
3259
+ "button",
3260
+ {
3261
+ type: "button",
3262
+ onClick: () => handleSelect(thread.id),
3263
+ className: "flex-1 flex flex-col gap-0.5 text-left focus:outline-none min-w-0",
3264
+ children: [
3265
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
3266
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-xs truncate", children: thread.title || "Untitled conversation" }),
3267
+ value === thread.id && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon2, { className: "flex-shrink-0 text-primary size-3" })
3268
+ ] }),
3269
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
3270
+ thread.preview && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[180px]", children: thread.preview }),
3271
+ thread.preview && thread.updatedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: "\xB7" }),
3272
+ thread.updatedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: formatDate(thread.updatedAt) })
3273
+ ] })
3274
+ ]
3275
+ }
3276
+ ),
3277
+ onDeleteThread && /* @__PURE__ */ jsxRuntime.jsx(
3278
+ "button",
3279
+ {
3280
+ type: "button",
3281
+ onClick: (e) => {
3282
+ e.stopPropagation();
3283
+ onDeleteThread(thread.id);
3284
+ },
3285
+ 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",
3286
+ "aria-label": "Delete thread",
3287
+ children: /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, { className: "size-3" })
3288
+ }
3289
+ )
3290
+ ]
3291
+ },
3292
+ thread.id
3293
+ )) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2.5 py-3 text-center text-xs text-muted-foreground", children: "No conversations yet" })
3294
+ ]
3139
3295
  }
3140
- ),
3141
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 max-w-[80%]", children: [
3142
- message.thinking && /* @__PURE__ */ jsxRuntime.jsx(
3143
- SimpleReasoning,
3296
+ )
3297
+ ] });
3298
+ }
3299
+ function formatDate2(date) {
3300
+ const now = /* @__PURE__ */ new Date();
3301
+ const diff = now.getTime() - date.getTime();
3302
+ if (diff < 60 * 1e3) {
3303
+ return "Just now";
3304
+ }
3305
+ if (diff < 60 * 60 * 1e3) {
3306
+ const mins = Math.floor(diff / (60 * 1e3));
3307
+ return `${mins}m ago`;
3308
+ }
3309
+ if (diff < 24 * 60 * 60 * 1e3) {
3310
+ const hours = Math.floor(diff / (60 * 60 * 1e3));
3311
+ return `${hours}h ago`;
3312
+ }
3313
+ if (diff < 7 * 24 * 60 * 60 * 1e3) {
3314
+ const days = Math.floor(diff / (24 * 60 * 60 * 1e3));
3315
+ return `${days}d ago`;
3316
+ }
3317
+ return date.toLocaleDateString();
3318
+ }
3319
+ function TrashIcon2({ className }) {
3320
+ return /* @__PURE__ */ jsxRuntime.jsx(
3321
+ "svg",
3322
+ {
3323
+ className: cn("w-4 h-4", className),
3324
+ fill: "none",
3325
+ viewBox: "0 0 24 24",
3326
+ stroke: "currentColor",
3327
+ strokeWidth: 2,
3328
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3329
+ "path",
3330
+ {
3331
+ strokeLinecap: "round",
3332
+ strokeLinejoin: "round",
3333
+ 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"
3334
+ }
3335
+ )
3336
+ }
3337
+ );
3338
+ }
3339
+ function PlusIcon3({ className }) {
3340
+ return /* @__PURE__ */ jsxRuntime.jsx(
3341
+ "svg",
3342
+ {
3343
+ className: cn("w-4 h-4", className),
3344
+ fill: "none",
3345
+ viewBox: "0 0 24 24",
3346
+ stroke: "currentColor",
3347
+ strokeWidth: 2,
3348
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4v16m8-8H4" })
3349
+ }
3350
+ );
3351
+ }
3352
+ function MessageIcon({ className }) {
3353
+ return /* @__PURE__ */ jsxRuntime.jsx(
3354
+ "svg",
3355
+ {
3356
+ className: cn("w-4 h-4", className),
3357
+ fill: "none",
3358
+ viewBox: "0 0 24 24",
3359
+ stroke: "currentColor",
3360
+ strokeWidth: 2,
3361
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3362
+ "path",
3363
+ {
3364
+ strokeLinecap: "round",
3365
+ strokeLinejoin: "round",
3366
+ 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"
3367
+ }
3368
+ )
3369
+ }
3370
+ );
3371
+ }
3372
+ function ThreadCardSkeleton() {
3373
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3 rounded-lg border bg-card animate-pulse", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
3374
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 rounded-full bg-muted" }),
3375
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-2", children: [
3376
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-3/4 bg-muted rounded" }),
3377
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-1/2 bg-muted rounded" })
3378
+ ] })
3379
+ ] }) });
3380
+ }
3381
+ function ThreadCard({
3382
+ thread,
3383
+ selected = false,
3384
+ onClick,
3385
+ onDelete,
3386
+ showDelete = true,
3387
+ className
3388
+ }) {
3389
+ const [isHovered, setIsHovered] = React8__namespace.useState(false);
3390
+ const handleDelete = (e) => {
3391
+ e.stopPropagation();
3392
+ onDelete?.();
3393
+ };
3394
+ return /* @__PURE__ */ jsxRuntime.jsx(
3395
+ "button",
3396
+ {
3397
+ type: "button",
3398
+ onClick,
3399
+ onMouseEnter: () => setIsHovered(true),
3400
+ onMouseLeave: () => setIsHovered(false),
3401
+ className: cn(
3402
+ "w-full p-3 rounded-lg border bg-card text-left transition-colors",
3403
+ "hover:bg-accent hover:border-accent-foreground/20",
3404
+ "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
3405
+ selected && "bg-accent border-primary/50",
3406
+ className
3407
+ ),
3408
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
3409
+ /* @__PURE__ */ jsxRuntime.jsx(
3410
+ "div",
3411
+ {
3412
+ className: cn(
3413
+ "flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center",
3414
+ selected ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground"
3415
+ ),
3416
+ children: /* @__PURE__ */ jsxRuntime.jsx(MessageIcon, {})
3417
+ }
3418
+ ),
3419
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
3420
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
3421
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-medium text-sm truncate", children: thread.title || "Untitled conversation" }),
3422
+ showDelete && isHovered && onDelete && /* @__PURE__ */ jsxRuntime.jsx(
3423
+ "button",
3424
+ {
3425
+ type: "button",
3426
+ onClick: handleDelete,
3427
+ className: cn(
3428
+ "flex-shrink-0 p-1 rounded",
3429
+ "hover:bg-destructive/10 hover:text-destructive",
3430
+ "focus:outline-none focus:ring-2 focus:ring-destructive"
3431
+ ),
3432
+ "aria-label": "Delete conversation",
3433
+ children: /* @__PURE__ */ jsxRuntime.jsx(TrashIcon2, { className: "w-3.5 h-3.5" })
3434
+ }
3435
+ )
3436
+ ] }),
3437
+ thread.preview && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground truncate mt-0.5", children: thread.preview }),
3438
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-1 text-xs text-muted-foreground", children: [
3439
+ thread.messageCount !== void 0 && thread.messageCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3440
+ thread.messageCount,
3441
+ " messages"
3442
+ ] }),
3443
+ thread.messageCount !== void 0 && thread.messageCount > 0 && thread.updatedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
3444
+ thread.updatedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatDate2(thread.updatedAt) })
3445
+ ] })
3446
+ ] })
3447
+ ] })
3448
+ }
3449
+ );
3450
+ }
3451
+ function ThreadList({
3452
+ threads,
3453
+ selectedId,
3454
+ onSelect,
3455
+ onDelete,
3456
+ onNewThread,
3457
+ newThreadLabel = "New conversation",
3458
+ loading = false,
3459
+ emptyText = "No conversations yet",
3460
+ showDelete = true,
3461
+ className
3462
+ }) {
3463
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-2", className), children: [
3464
+ onNewThread && /* @__PURE__ */ jsxRuntime.jsxs(
3465
+ "button",
3466
+ {
3467
+ type: "button",
3468
+ onClick: onNewThread,
3469
+ className: cn(
3470
+ "flex items-center gap-2 p-3 rounded-lg border border-dashed",
3471
+ "hover:bg-accent hover:border-accent-foreground/20",
3472
+ "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
3473
+ "transition-colors"
3474
+ ),
3475
+ children: [
3476
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(PlusIcon3, { className: "text-primary" }) }),
3477
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-sm", children: newThreadLabel })
3478
+ ]
3479
+ }
3480
+ ),
3481
+ loading && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3482
+ /* @__PURE__ */ jsxRuntime.jsx(ThreadCardSkeleton, {}),
3483
+ /* @__PURE__ */ jsxRuntime.jsx(ThreadCardSkeleton, {}),
3484
+ /* @__PURE__ */ jsxRuntime.jsx(ThreadCardSkeleton, {})
3485
+ ] }),
3486
+ !loading && threads.length > 0 && threads.map((thread) => /* @__PURE__ */ jsxRuntime.jsx(
3487
+ ThreadCard,
3488
+ {
3489
+ thread,
3490
+ selected: selectedId === thread.id,
3491
+ onClick: () => onSelect?.(thread.id),
3492
+ onDelete: () => onDelete?.(thread.id),
3493
+ showDelete
3494
+ },
3495
+ thread.id
3496
+ )),
3497
+ !loading && threads.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: emptyText })
3498
+ ] });
3499
+ }
3500
+ var CopilotSDKLogo = (props) => /* @__PURE__ */ jsxRuntime.jsxs(
3501
+ "svg",
3502
+ {
3503
+ width: props.width || 170,
3504
+ height: props.height || 170,
3505
+ viewBox: "0 0 170 170",
3506
+ fill: "none",
3507
+ xmlns: "http://www.w3.org/2000/svg",
3508
+ ...props,
3509
+ className: cn("w-auto h-[30px]", props.className),
3510
+ children: [
3511
+ /* @__PURE__ */ jsxRuntime.jsx(
3512
+ "path",
3513
+ {
3514
+ opacity: 0.4,
3515
+ 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",
3516
+ fill: "#6352FF"
3517
+ }
3518
+ ),
3519
+ /* @__PURE__ */ jsxRuntime.jsx(
3520
+ "path",
3521
+ {
3522
+ 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",
3523
+ fill: "#523FFF"
3524
+ }
3525
+ ),
3526
+ /* @__PURE__ */ jsxRuntime.jsx(
3527
+ "line",
3528
+ {
3529
+ x1: 137.266,
3530
+ y1: 162.316,
3531
+ x2: 83.1281,
3532
+ y2: 162.316,
3533
+ stroke: "currentColor",
3534
+ strokeWidth: 11.8231
3535
+ }
3536
+ ),
3537
+ /* @__PURE__ */ jsxRuntime.jsx(
3538
+ "line",
3539
+ {
3540
+ x1: 160.188,
3541
+ y1: 162.316,
3542
+ x2: 140.811,
3543
+ y2: 162.316,
3544
+ stroke: "currentColor",
3545
+ strokeOpacity: 0.26,
3546
+ strokeWidth: 11.8231
3547
+ }
3548
+ )
3549
+ ]
3550
+ }
3551
+ );
3552
+ var copilot_sdk_logo_default = CopilotSDKLogo;
3553
+ var DEFAULT_NAME = "AI Copilot";
3554
+ function ChatHeader({
3555
+ logo,
3556
+ name,
3557
+ title,
3558
+ threadPicker,
3559
+ onClose,
3560
+ className
3561
+ }) {
3562
+ const displayName = name || title || DEFAULT_NAME;
3563
+ const showDefaultLogo = logo === void 0;
3564
+ const showCustomLogo = typeof logo === "string" && logo.length > 0;
3565
+ return /* @__PURE__ */ jsxRuntime.jsx(
3566
+ "div",
3567
+ {
3568
+ className: cn(
3569
+ "flex flex-col border-b border-border bg-background",
3570
+ className
3571
+ ),
3572
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-2", children: [
3573
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 shrink-0", children: [
3574
+ showDefaultLogo && /* @__PURE__ */ jsxRuntime.jsx(copilot_sdk_logo_default, { className: "h-6 w-auto" }),
3575
+ showCustomLogo && /* @__PURE__ */ jsxRuntime.jsx(
3576
+ "img",
3577
+ {
3578
+ src: logo,
3579
+ alt: displayName,
3580
+ className: "size-6 rounded-md object-contain"
3581
+ }
3582
+ ),
3583
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3584
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-semibold text-foreground text-sm mb-0.5", children: displayName }),
3585
+ threadPicker && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "", children: threadPicker })
3586
+ ] })
3587
+ ] }),
3588
+ onClose && /* @__PURE__ */ jsxRuntime.jsx(
3589
+ "button",
3590
+ {
3591
+ type: "button",
3592
+ onClick: onClose,
3593
+ className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
3594
+ "aria-label": "Close chat",
3595
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { className: "h-5 w-5" })
3596
+ }
3597
+ )
3598
+ ] })
3599
+ }
3600
+ );
3601
+ }
3602
+ function Suggestions({
3603
+ suggestions,
3604
+ onSuggestionClick,
3605
+ className
3606
+ }) {
3607
+ if (!suggestions.length) return null;
3608
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-wrap gap-2 px-4 py-2", className), children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsxRuntime.jsx(
3609
+ "button",
3610
+ {
3611
+ type: "button",
3612
+ onClick: () => onSuggestionClick?.(suggestion),
3613
+ className: "inline-flex items-center rounded-full border bg-background px-3 py-1.5 text-sm transition-colors hover:bg-muted",
3614
+ children: suggestion
3615
+ },
3616
+ index
3617
+ )) });
3618
+ }
3619
+ function DefaultMessage({
3620
+ message,
3621
+ userAvatar,
3622
+ assistantAvatar,
3623
+ showUserAvatar = false,
3624
+ userMessageClassName,
3625
+ assistantMessageClassName,
3626
+ size = "sm",
3627
+ isLastMessage = false,
3628
+ isLoading = false,
3629
+ registeredTools,
3630
+ toolRenderers,
3631
+ onApproveToolExecution,
3632
+ onRejectToolExecution,
3633
+ showFollowUps = true,
3634
+ onFollowUpClick,
3635
+ followUpClassName,
3636
+ followUpButtonClassName
3637
+ }) {
3638
+ const isUser = message.role === "user";
3639
+ const isStreaming = isLastMessage && isLoading;
3640
+ const { cleanContent, followUps } = React8__namespace.useMemo(() => {
3641
+ if (isUser || !message.content) {
3642
+ return { cleanContent: message.content, followUps: [] };
3643
+ }
3644
+ return parseFollowUps(message.content);
3645
+ }, [message.content, isUser]);
3646
+ const shouldShowFollowUps = showFollowUps && !isUser && isLastMessage && !isLoading && followUps.length > 0 && onFollowUpClick;
3647
+ if (isUser) {
3648
+ const hasAttachments = message.attachments && message.attachments.length > 0;
3649
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3650
+ Message,
3651
+ {
3652
+ className: cn(
3653
+ "flex gap-2",
3654
+ showUserAvatar ? "justify-end" : "justify-end"
3655
+ ),
3656
+ children: [
3657
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-end max-w-[80%]", children: [
3658
+ message.content && /* @__PURE__ */ jsxRuntime.jsx(
3659
+ MessageContent,
3660
+ {
3661
+ className: cn(
3662
+ "rounded-lg px-4 py-2 bg-primary text-primary-foreground",
3663
+ userMessageClassName
3664
+ ),
3665
+ size,
3666
+ children: message.content
3667
+ }
3668
+ ),
3669
+ hasAttachments && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex flex-wrap gap-2 justify-end", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreview, { attachment }, index)) })
3670
+ ] }),
3671
+ showUserAvatar && /* @__PURE__ */ jsxRuntime.jsx(
3672
+ MessageAvatar,
3673
+ {
3674
+ src: userAvatar.src || "",
3675
+ alt: "User",
3676
+ fallback: userAvatar.fallback
3677
+ }
3678
+ )
3679
+ ]
3680
+ }
3681
+ );
3682
+ }
3683
+ const pendingApprovalTools = message.toolExecutions?.filter(
3684
+ (exec) => exec.approvalStatus === "required"
3685
+ );
3686
+ const completedTools = message.toolExecutions?.filter(
3687
+ (exec) => exec.approvalStatus !== "required"
3688
+ );
3689
+ const hasCustomRender = (toolName) => {
3690
+ if (toolRenderers?.[toolName]) return true;
3691
+ const toolDef = registeredTools?.find((t) => t.name === toolName);
3692
+ if (toolDef?.render) return true;
3693
+ return false;
3694
+ };
3695
+ const toolsWithCustomRender = completedTools?.filter(
3696
+ (exec) => hasCustomRender(exec.name)
3697
+ );
3698
+ const toolsWithoutCustomRender = completedTools?.filter(
3699
+ (exec) => !hasCustomRender(exec.name)
3700
+ );
3701
+ const toolSteps = toolsWithoutCustomRender?.map((exec) => ({
3702
+ id: exec.id,
3703
+ name: exec.name,
3704
+ args: exec.args,
3705
+ status: exec.status,
3706
+ result: exec.result,
3707
+ error: exec.error
3708
+ }));
3709
+ return /* @__PURE__ */ jsxRuntime.jsxs(Message, { className: "flex gap-2", children: [
3710
+ /* @__PURE__ */ jsxRuntime.jsx(
3711
+ MessageAvatar,
3712
+ {
3713
+ src: assistantAvatar.src || "",
3714
+ alt: "Assistant",
3715
+ fallback: assistantAvatar.fallback,
3716
+ fallbackIcon: !assistantAvatar.src ? /* @__PURE__ */ jsxRuntime.jsx(copilot_sdk_logo_default, { className: "size-5" }) : void 0,
3717
+ className: "bg-background"
3718
+ }
3719
+ ),
3720
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 max-w-[80%]", children: [
3721
+ message.thinking && /* @__PURE__ */ jsxRuntime.jsx(
3722
+ SimpleReasoning,
3144
3723
  {
3145
3724
  content: message.thinking,
3146
3725
  isStreaming,
@@ -3159,39 +3738,100 @@ function DefaultMessage({
3159
3738
  children: cleanContent
3160
3739
  }
3161
3740
  ),
3162
- toolsWithCustomRenderer && toolsWithCustomRenderer.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 space-y-2", children: toolsWithCustomRenderer.map((exec) => {
3163
- const Renderer = toolRenderers[exec.name];
3164
- return /* @__PURE__ */ jsxRuntime.jsx(
3165
- Renderer,
3166
- {
3167
- execution: {
3168
- id: exec.id,
3169
- name: exec.name,
3170
- args: exec.args,
3171
- status: exec.status,
3172
- result: exec.result,
3173
- error: exec.error,
3174
- approvalStatus: exec.approvalStatus
3175
- }
3176
- },
3177
- exec.id
3741
+ toolsWithCustomRender && toolsWithCustomRender.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 space-y-2", children: toolsWithCustomRender.map((exec) => {
3742
+ const Renderer = toolRenderers?.[exec.name];
3743
+ if (Renderer) {
3744
+ return /* @__PURE__ */ jsxRuntime.jsx(
3745
+ Renderer,
3746
+ {
3747
+ execution: {
3748
+ id: exec.id,
3749
+ name: exec.name,
3750
+ args: exec.args,
3751
+ status: exec.status,
3752
+ result: exec.result,
3753
+ error: exec.error,
3754
+ approvalStatus: exec.approvalStatus
3755
+ }
3756
+ },
3757
+ exec.id
3758
+ );
3759
+ }
3760
+ const toolDef = registeredTools?.find(
3761
+ (t) => t.name === exec.name
3178
3762
  );
3763
+ if (toolDef?.render) {
3764
+ let status = "pending";
3765
+ if (exec.status === "executing") status = "executing";
3766
+ else if (exec.status === "completed") status = "completed";
3767
+ else if (exec.status === "error" || exec.status === "failed" || exec.status === "rejected")
3768
+ status = "error";
3769
+ const renderProps = {
3770
+ status,
3771
+ args: exec.args,
3772
+ result: exec.result,
3773
+ error: exec.error,
3774
+ toolCallId: exec.id,
3775
+ toolName: exec.name
3776
+ };
3777
+ const output = toolDef.render(renderProps);
3778
+ return /* @__PURE__ */ jsxRuntime.jsx(React8__namespace.Fragment, { children: output }, exec.id);
3779
+ }
3780
+ return null;
3179
3781
  }) }),
3180
3782
  toolSteps && toolSteps.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 rounded-lg bg-muted/50 px-3 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(ToolSteps, { steps: toolSteps }) }),
3181
- pendingApprovalTools && pendingApprovalTools.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 space-y-2", children: pendingApprovalTools.map((tool) => /* @__PURE__ */ jsxRuntime.jsx(
3182
- PermissionConfirmation,
3183
- {
3184
- state: "pending",
3185
- toolName: tool.name,
3186
- message: tool.approvalMessage || `This tool wants to execute. Do you approve?`,
3187
- onApprove: (permissionLevel) => onApproveToolExecution?.(tool.id, permissionLevel),
3188
- onReject: (permissionLevel) => onRejectToolExecution?.(tool.id, void 0, permissionLevel)
3189
- },
3190
- tool.id
3191
- )) }),
3192
- message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreview, { attachment }, index)) }),
3193
- shouldShowFollowUps && /* @__PURE__ */ jsxRuntime.jsx(
3194
- FollowUpQuestions,
3783
+ pendingApprovalTools && pendingApprovalTools.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 space-y-2", children: pendingApprovalTools.map((tool) => {
3784
+ const approvalCallbacks = {
3785
+ onApprove: (extraData) => onApproveToolExecution?.(tool.id, extraData),
3786
+ onReject: (reason) => onRejectToolExecution?.(tool.id, reason),
3787
+ message: tool.approvalMessage
3788
+ };
3789
+ const CustomRenderer = toolRenderers?.[tool.name];
3790
+ if (CustomRenderer) {
3791
+ return /* @__PURE__ */ jsxRuntime.jsx(
3792
+ CustomRenderer,
3793
+ {
3794
+ execution: tool,
3795
+ approval: approvalCallbacks
3796
+ },
3797
+ tool.id
3798
+ );
3799
+ }
3800
+ const toolDef = registeredTools?.find(
3801
+ (t) => t.name === tool.name
3802
+ );
3803
+ if (toolDef?.render) {
3804
+ const renderProps = {
3805
+ status: "approval-required",
3806
+ args: tool.args,
3807
+ result: tool.result,
3808
+ error: tool.error,
3809
+ toolCallId: tool.id,
3810
+ toolName: tool.name,
3811
+ approval: approvalCallbacks
3812
+ };
3813
+ const output = toolDef.render(renderProps);
3814
+ return /* @__PURE__ */ jsxRuntime.jsx(React8__namespace.Fragment, { children: output }, tool.id);
3815
+ }
3816
+ return /* @__PURE__ */ jsxRuntime.jsx(
3817
+ PermissionConfirmation,
3818
+ {
3819
+ state: "pending",
3820
+ toolName: tool.name,
3821
+ message: tool.approvalMessage || `This tool wants to execute. Do you approve?`,
3822
+ onApprove: (permissionLevel) => onApproveToolExecution?.(
3823
+ tool.id,
3824
+ void 0,
3825
+ permissionLevel
3826
+ ),
3827
+ onReject: (permissionLevel) => onRejectToolExecution?.(tool.id, void 0, permissionLevel)
3828
+ },
3829
+ tool.id
3830
+ );
3831
+ }) }),
3832
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreview, { attachment }, index)) }),
3833
+ shouldShowFollowUps && /* @__PURE__ */ jsxRuntime.jsx(
3834
+ FollowUpQuestions,
3195
3835
  {
3196
3836
  questions: followUps,
3197
3837
  onSelect: onFollowUpClick,
@@ -3285,13 +3925,353 @@ function AttachmentPreview({ attachment }) {
3285
3925
  }
3286
3926
  var DEFAULT_MAX_FILE_SIZE = 5 * 1024 * 1024;
3287
3927
  var DEFAULT_ALLOWED_TYPES = ["image/*", "application/pdf"];
3928
+ var DEFAULT_TITLE = "How can I help you today?";
3929
+ var DEFAULT_SUBTITLE = "Ask anything and get it done.";
3930
+ var DEFAULT_SUGGESTIONS_LABEL = "Try AI Copilot";
3931
+ var DEFAULT_RECENT_CHATS_LABEL = "Recent chats";
3932
+ var DEFAULT_MAX_RECENT_CHATS = 3;
3933
+ var DEFAULT_VIEW_MORE_LABEL = "View more..";
3288
3934
  function getAttachmentType(mimeType) {
3289
3935
  if (mimeType.startsWith("image/")) return "image";
3290
3936
  if (mimeType.startsWith("audio/")) return "audio";
3291
3937
  if (mimeType.startsWith("video/")) return "video";
3292
3938
  return "file";
3293
3939
  }
3294
- function fileToBase64(file) {
3940
+ function fileToBase64(file) {
3941
+ return new Promise((resolve, reject) => {
3942
+ const reader = new FileReader();
3943
+ reader.onload = () => {
3944
+ if (typeof reader.result === "string") {
3945
+ resolve(reader.result);
3946
+ } else {
3947
+ reject(new Error("Failed to read file"));
3948
+ }
3949
+ };
3950
+ reader.onerror = () => reject(new Error("Failed to read file"));
3951
+ reader.readAsDataURL(file);
3952
+ });
3953
+ }
3954
+ function generateAttachmentId() {
3955
+ return `att_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
3956
+ }
3957
+ function ChatWelcome({
3958
+ config,
3959
+ suggestions = [],
3960
+ recentThreads = [],
3961
+ onSendMessage,
3962
+ onSelectThread,
3963
+ onDeleteThread,
3964
+ onViewMoreThreads,
3965
+ isLoading = false,
3966
+ onStop,
3967
+ placeholder = "Type a message...",
3968
+ attachmentsEnabled = true,
3969
+ attachmentsDisabledTooltip = "Attachments not supported by this model",
3970
+ maxFileSize = DEFAULT_MAX_FILE_SIZE,
3971
+ allowedFileTypes = DEFAULT_ALLOWED_TYPES,
3972
+ processAttachment: processAttachmentProp,
3973
+ classNames = {}
3974
+ }) {
3975
+ const [input, setInput] = React8.useState("");
3976
+ const [pendingAttachments, setPendingAttachments] = React8.useState([]);
3977
+ const fileInputRef = React8.useRef(null);
3978
+ const fileInputId = React8.useId();
3979
+ const title = config?.title ?? DEFAULT_TITLE;
3980
+ const subtitle = config?.subtitle ?? DEFAULT_SUBTITLE;
3981
+ const logo = config?.logo;
3982
+ const suggestionsLabel = config?.suggestionsLabel ?? DEFAULT_SUGGESTIONS_LABEL;
3983
+ const showRecentChats = config?.showRecentChats ?? true;
3984
+ config?.recentChatsLabel ?? DEFAULT_RECENT_CHATS_LABEL;
3985
+ const maxRecentChats = config?.maxRecentChats ?? DEFAULT_MAX_RECENT_CHATS;
3986
+ config?.viewMoreLabel ?? DEFAULT_VIEW_MORE_LABEL;
3987
+ const isFileTypeAllowed = React8.useCallback(
3988
+ (file) => {
3989
+ for (const type of allowedFileTypes) {
3990
+ if (type.endsWith("/*")) {
3991
+ const category = type.slice(0, -2);
3992
+ if (file.type.startsWith(category + "/")) return true;
3993
+ } else if (file.type === type) {
3994
+ return true;
3995
+ }
3996
+ }
3997
+ return false;
3998
+ },
3999
+ [allowedFileTypes]
4000
+ );
4001
+ const handleFileSelect = React8.useCallback(
4002
+ async (files) => {
4003
+ if (!files || !attachmentsEnabled) return;
4004
+ for (const file of Array.from(files)) {
4005
+ if (file.size > maxFileSize) {
4006
+ const sizeMB = (maxFileSize / (1024 * 1024)).toFixed(0);
4007
+ console.warn(`File ${file.name} exceeds ${sizeMB}MB limit`);
4008
+ continue;
4009
+ }
4010
+ if (!isFileTypeAllowed(file)) {
4011
+ console.warn(`File type ${file.type} is not allowed`);
4012
+ continue;
4013
+ }
4014
+ const id = generateAttachmentId();
4015
+ const previewUrl = URL.createObjectURL(file);
4016
+ setPendingAttachments((prev) => [
4017
+ ...prev,
4018
+ {
4019
+ id,
4020
+ file,
4021
+ previewUrl,
4022
+ attachment: {
4023
+ type: getAttachmentType(file.type),
4024
+ data: "",
4025
+ mimeType: file.type,
4026
+ filename: file.name
4027
+ },
4028
+ status: "processing"
4029
+ }
4030
+ ]);
4031
+ try {
4032
+ let attachment;
4033
+ if (processAttachmentProp) {
4034
+ attachment = await processAttachmentProp(file);
4035
+ } else {
4036
+ const data = await fileToBase64(file);
4037
+ attachment = {
4038
+ type: getAttachmentType(file.type),
4039
+ data,
4040
+ mimeType: file.type,
4041
+ filename: file.name
4042
+ };
4043
+ }
4044
+ setPendingAttachments(
4045
+ (prev) => prev.map(
4046
+ (att) => att.id === id ? { ...att, status: "ready", attachment } : att
4047
+ )
4048
+ );
4049
+ } catch (error) {
4050
+ setPendingAttachments(
4051
+ (prev) => prev.map(
4052
+ (att) => att.id === id ? {
4053
+ ...att,
4054
+ status: "error",
4055
+ error: "Failed to process file"
4056
+ } : att
4057
+ )
4058
+ );
4059
+ }
4060
+ }
4061
+ },
4062
+ [attachmentsEnabled, maxFileSize, isFileTypeAllowed, processAttachmentProp]
4063
+ );
4064
+ const handleInputChange = React8.useCallback(
4065
+ (e) => {
4066
+ handleFileSelect(e.target.files);
4067
+ if (fileInputRef.current) {
4068
+ fileInputRef.current.value = "";
4069
+ }
4070
+ },
4071
+ [handleFileSelect]
4072
+ );
4073
+ const removePendingAttachment = React8.useCallback((id) => {
4074
+ setPendingAttachments((prev) => {
4075
+ const att = prev.find((a) => a.id === id);
4076
+ if (att) {
4077
+ URL.revokeObjectURL(att.previewUrl);
4078
+ }
4079
+ return prev.filter((a) => a.id !== id);
4080
+ });
4081
+ }, []);
4082
+ const handleSubmit = React8.useCallback(() => {
4083
+ const hasContent = input.trim();
4084
+ const hasAttachments = pendingAttachments.some(
4085
+ (att) => att.status === "ready"
4086
+ );
4087
+ if (!hasContent && !hasAttachments || isLoading) return;
4088
+ const attachments = pendingAttachments.filter((att) => att.status === "ready").map((att) => att.attachment);
4089
+ onSendMessage(input, attachments.length > 0 ? attachments : void 0);
4090
+ pendingAttachments.forEach((att) => URL.revokeObjectURL(att.previewUrl));
4091
+ setPendingAttachments([]);
4092
+ setInput("");
4093
+ }, [input, isLoading, onSendMessage, pendingAttachments]);
4094
+ const handleSuggestionClick = React8.useCallback(
4095
+ (suggestion) => {
4096
+ onSendMessage(suggestion);
4097
+ },
4098
+ [onSendMessage]
4099
+ );
4100
+ const acceptString = allowedFileTypes.join(",");
4101
+ showRecentChats ? recentThreads.slice(0, maxRecentChats) : [];
4102
+ recentThreads.length > maxRecentChats;
4103
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4104
+ "div",
4105
+ {
4106
+ className: cn(
4107
+ "flex flex-1 flex-col items-center justify-center px-4 py-8 overflow-auto",
4108
+ classNames.root
4109
+ ),
4110
+ children: [
4111
+ /* @__PURE__ */ jsxRuntime.jsxs(
4112
+ "div",
4113
+ {
4114
+ className: cn(
4115
+ "flex flex-col items-center text-center mb-8",
4116
+ classNames.hero
4117
+ ),
4118
+ children: [
4119
+ logo ? /* @__PURE__ */ jsxRuntime.jsx(
4120
+ "img",
4121
+ {
4122
+ src: logo,
4123
+ alt: "Logo",
4124
+ className: "size-12 rounded-lg object-contain mb-4"
4125
+ }
4126
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(copilot_sdk_logo_default, { className: "h-12 w-auto" }) }),
4127
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold text-foreground mb-2", children: title }),
4128
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
4129
+ ]
4130
+ }
4131
+ ),
4132
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("w-full max-w-lg mb-6", classNames.input), children: [
4133
+ pendingAttachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 p-2 mb-2 bg-muted/30 rounded-lg", children: pendingAttachments.map((att) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
4134
+ att.attachment.type === "image" ? /* @__PURE__ */ jsxRuntime.jsx(
4135
+ "img",
4136
+ {
4137
+ src: att.previewUrl,
4138
+ alt: att.file.name,
4139
+ className: "w-16 h-16 object-cover rounded-lg border"
4140
+ }
4141
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-16 h-16 bg-muted rounded-lg border flex flex-col items-center justify-center p-1", children: [
4142
+ /* @__PURE__ */ jsxRuntime.jsx(
4143
+ "svg",
4144
+ {
4145
+ className: "w-6 h-6 text-muted-foreground",
4146
+ fill: "none",
4147
+ viewBox: "0 0 24 24",
4148
+ stroke: "currentColor",
4149
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4150
+ "path",
4151
+ {
4152
+ strokeLinecap: "round",
4153
+ strokeLinejoin: "round",
4154
+ strokeWidth: 1.5,
4155
+ 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"
4156
+ }
4157
+ )
4158
+ }
4159
+ ),
4160
+ /* @__PURE__ */ jsxRuntime.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 })
4161
+ ] }),
4162
+ att.status === "processing" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-background/80 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(Loader, { variant: "dots", size: "sm" }) }),
4163
+ att.status === "error" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-destructive/20 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive text-xs", children: "Error" }) }),
4164
+ /* @__PURE__ */ jsxRuntime.jsx(
4165
+ "button",
4166
+ {
4167
+ onClick: () => removePendingAttachment(att.id),
4168
+ 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",
4169
+ type: "button",
4170
+ children: /* @__PURE__ */ jsxRuntime.jsx(XIcon2, { className: "w-3 h-3" })
4171
+ }
4172
+ )
4173
+ ] }, att.id)) }),
4174
+ /* @__PURE__ */ jsxRuntime.jsxs(
4175
+ PromptInput,
4176
+ {
4177
+ value: input,
4178
+ onValueChange: setInput,
4179
+ isLoading,
4180
+ onSubmit: handleSubmit,
4181
+ children: [
4182
+ /* @__PURE__ */ jsxRuntime.jsx(PromptInputTextarea, { placeholder }),
4183
+ /* @__PURE__ */ jsxRuntime.jsxs(PromptInputActions, { className: "flex justify-between", children: [
4184
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
4185
+ PromptInputAction,
4186
+ {
4187
+ tooltip: attachmentsEnabled ? "Attach files" : attachmentsDisabledTooltip,
4188
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4189
+ "label",
4190
+ {
4191
+ htmlFor: fileInputId,
4192
+ className: cn(
4193
+ "flex h-8 w-8 items-center justify-center rounded-2xl",
4194
+ attachmentsEnabled ? "hover:bg-secondary-foreground/10 cursor-pointer" : "opacity-50 cursor-not-allowed"
4195
+ ),
4196
+ children: [
4197
+ /* @__PURE__ */ jsxRuntime.jsx(
4198
+ "input",
4199
+ {
4200
+ ref: fileInputRef,
4201
+ type: "file",
4202
+ multiple: true,
4203
+ accept: acceptString,
4204
+ onChange: handleInputChange,
4205
+ className: "hidden",
4206
+ id: fileInputId,
4207
+ disabled: !attachmentsEnabled
4208
+ }
4209
+ ),
4210
+ /* @__PURE__ */ jsxRuntime.jsx(PlusIcon, { className: "text-primary size-5" })
4211
+ ]
4212
+ }
4213
+ )
4214
+ }
4215
+ ) }),
4216
+ /* @__PURE__ */ jsxRuntime.jsx(PromptInputAction, { tooltip: isLoading ? "Stop" : "Send", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
4217
+ Button,
4218
+ {
4219
+ size: "sm",
4220
+ variant: "destructive",
4221
+ className: "rounded-full size-9",
4222
+ onClick: onStop,
4223
+ children: /* @__PURE__ */ jsxRuntime.jsx(StopIcon, { className: "h-4 w-4" })
4224
+ }
4225
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
4226
+ Button,
4227
+ {
4228
+ size: "sm",
4229
+ className: "rounded-full size-9",
4230
+ onClick: handleSubmit,
4231
+ disabled: !input.trim() && !pendingAttachments.some((att) => att.status === "ready"),
4232
+ children: /* @__PURE__ */ jsxRuntime.jsx(ArrowUpIcon, { className: "h-4 w-4" })
4233
+ }
4234
+ ) })
4235
+ ] })
4236
+ ]
4237
+ }
4238
+ )
4239
+ ] }),
4240
+ suggestions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
4241
+ "div",
4242
+ {
4243
+ className: cn("w-full max-w-lg mb-6 px-3", classNames.suggestions),
4244
+ children: [
4245
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-muted-foreground mb-2", children: suggestionsLabel }),
4246
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "", children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsxRuntime.jsxs(
4247
+ "button",
4248
+ {
4249
+ type: "button",
4250
+ onClick: () => handleSuggestionClick(suggestion),
4251
+ 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",
4252
+ children: [
4253
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: suggestion }),
4254
+ /* @__PURE__ */ jsxRuntime.jsx(ArrowUpRightIcon, { className: "size-4 opacity-0 translate-y-1 group-hover:opacity-[0.6] group-hover:translate-y-0 transition-all duration-200" })
4255
+ ]
4256
+ },
4257
+ index
4258
+ )) })
4259
+ ]
4260
+ }
4261
+ )
4262
+ ]
4263
+ }
4264
+ );
4265
+ }
4266
+ var DEFAULT_MAX_FILE_SIZE2 = 5 * 1024 * 1024;
4267
+ var DEFAULT_ALLOWED_TYPES2 = ["image/*", "application/pdf"];
4268
+ function getAttachmentType2(mimeType) {
4269
+ if (mimeType.startsWith("image/")) return "image";
4270
+ if (mimeType.startsWith("audio/")) return "audio";
4271
+ if (mimeType.startsWith("video/")) return "video";
4272
+ return "file";
4273
+ }
4274
+ function fileToBase642(file) {
3295
4275
  return new Promise((resolve, reject) => {
3296
4276
  const reader = new FileReader();
3297
4277
  reader.onload = () => {
@@ -3305,7 +4285,7 @@ function fileToBase64(file) {
3305
4285
  reader.readAsDataURL(file);
3306
4286
  });
3307
4287
  }
3308
- function generateAttachmentId() {
4288
+ function generateAttachmentId2() {
3309
4289
  return `att_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
3310
4290
  }
3311
4291
  function Chat({
@@ -3320,6 +4300,11 @@ function Chat({
3320
4300
  title,
3321
4301
  // Header
3322
4302
  showHeader = false,
4303
+ header,
4304
+ threadPicker,
4305
+ // Deprecated header props (backwards compat)
4306
+ logo,
4307
+ name,
3323
4308
  onClose,
3324
4309
  // Appearance
3325
4310
  showPoweredBy = true,
@@ -3329,16 +4314,23 @@ function Chat({
3329
4314
  loaderVariant = "typing",
3330
4315
  fontSize = "sm",
3331
4316
  // Attachments
3332
- maxFileSize = DEFAULT_MAX_FILE_SIZE,
3333
- allowedFileTypes = DEFAULT_ALLOWED_TYPES,
4317
+ maxFileSize = DEFAULT_MAX_FILE_SIZE2,
4318
+ allowedFileTypes = DEFAULT_ALLOWED_TYPES2,
3334
4319
  attachmentsEnabled = true,
3335
4320
  attachmentsDisabledTooltip = "Attachments not supported by this model",
3336
4321
  processAttachment: processAttachmentProp,
3337
4322
  // Suggestions
3338
4323
  suggestions = [],
3339
4324
  onSuggestionClick,
4325
+ // Welcome Screen
4326
+ welcome,
4327
+ recentThreads = [],
4328
+ onSelectThread,
4329
+ onDeleteThread,
4330
+ onViewMoreThreads,
3340
4331
  // Tool Executions
3341
4332
  isProcessing = false,
4333
+ registeredTools,
3342
4334
  toolRenderers,
3343
4335
  onApproveToolExecution,
3344
4336
  onRejectToolExecution,
@@ -3386,7 +4378,7 @@ function Chat({
3386
4378
  console.warn(`File type ${file.type} is not allowed`);
3387
4379
  continue;
3388
4380
  }
3389
- const id = generateAttachmentId();
4381
+ const id = generateAttachmentId2();
3390
4382
  const previewUrl = URL.createObjectURL(file);
3391
4383
  setPendingAttachments((prev) => [
3392
4384
  ...prev,
@@ -3395,7 +4387,7 @@ function Chat({
3395
4387
  file,
3396
4388
  previewUrl,
3397
4389
  attachment: {
3398
- type: getAttachmentType(file.type),
4390
+ type: getAttachmentType2(file.type),
3399
4391
  data: "",
3400
4392
  mimeType: file.type,
3401
4393
  filename: file.name
@@ -3408,9 +4400,9 @@ function Chat({
3408
4400
  if (processAttachmentProp) {
3409
4401
  attachment = await processAttachmentProp(file);
3410
4402
  } else {
3411
- const data = await fileToBase64(file);
4403
+ const data = await fileToBase642(file);
3412
4404
  attachment = {
3413
- type: getAttachmentType(file.type),
4405
+ type: getAttachmentType2(file.type),
3414
4406
  data,
3415
4407
  mimeType: file.type,
3416
4408
  filename: file.name
@@ -3503,6 +4495,8 @@ function Chat({
3503
4495
  [onSuggestionClick, onSendMessage]
3504
4496
  );
3505
4497
  const acceptString = allowedFileTypes.join(",");
4498
+ const showWelcome = messages.length === 0 && welcome !== false;
4499
+ const welcomeConfig = typeof welcome === "object" ? welcome : void 0;
3506
4500
  return /* @__PURE__ */ jsxRuntime.jsxs(
3507
4501
  "div",
3508
4502
  {
@@ -3519,239 +4513,275 @@ function Chat({
3519
4513
  showHeader && (renderHeader ? renderHeader() : /* @__PURE__ */ jsxRuntime.jsx(
3520
4514
  ChatHeader,
3521
4515
  {
4516
+ logo: header?.logo ?? logo,
4517
+ name: header?.name ?? name,
3522
4518
  title,
3523
- onClose,
4519
+ threadPicker,
4520
+ onClose: header?.onClose ?? onClose,
3524
4521
  className: classNames.header
3525
4522
  }
3526
4523
  )),
3527
- /* @__PURE__ */ jsxRuntime.jsxs(
3528
- ChatContainerRoot,
3529
- {
3530
- className: cn("relative flex-1", classNames.container),
3531
- children: [
3532
- /* @__PURE__ */ jsxRuntime.jsxs(
3533
- ChatContainerContent,
3534
- {
3535
- className: cn("gap-4 p-4", classNames.messageList),
3536
- children: [
3537
- messages.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-8 text-center text-muted-foreground", children: welcomeMessage || "Send a message to start the conversation" }),
3538
- messages.map((message, index) => {
3539
- const isLastMessage = index === messages.length - 1;
3540
- const isEmptyAssistant = message.role === "assistant" && !message.content?.trim();
3541
- const hasToolCalls = message.tool_calls && message.tool_calls.length > 0;
3542
- const hasToolExecutions = message.toolExecutions && message.toolExecutions.length > 0;
3543
- const hasPendingApprovals = message.toolExecutions?.some(
3544
- (exec) => exec.approvalStatus === "required"
3545
- );
3546
- if (isEmptyAssistant) {
3547
- if (hasToolCalls || hasToolExecutions) ; else if (isLastMessage && hasPendingApprovals) ; else if (isLastMessage && isLoading && !isProcessing) {
3548
- return /* @__PURE__ */ jsxRuntime.jsxs(Message, { className: "flex gap-2", children: [
3549
- /* @__PURE__ */ jsxRuntime.jsx(
3550
- MessageAvatar,
3551
- {
3552
- src: assistantAvatar.src || "",
3553
- alt: "Assistant",
3554
- fallback: assistantAvatar.fallback,
3555
- className: "bg-primary text-primary-foreground"
3556
- }
3557
- ),
3558
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg bg-muted px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(Loader, { variant: loaderVariant, size: "sm" }) })
3559
- ] }, message.id);
3560
- } else {
3561
- return null;
3562
- }
3563
- }
3564
- const savedExecutions = message.metadata?.toolExecutions;
3565
- const messageToolExecutions = message.toolExecutions || savedExecutions;
3566
- const messageWithExecutions = messageToolExecutions ? { ...message, toolExecutions: messageToolExecutions } : message;
3567
- const handleFollowUpClick = (question) => {
3568
- if (onSuggestionClick) {
3569
- onSuggestionClick(question);
3570
- } else {
3571
- onSendMessage?.(question);
3572
- }
3573
- };
3574
- return renderMessage ? /* @__PURE__ */ jsxRuntime.jsx(React8__namespace.default.Fragment, { children: renderMessage(messageWithExecutions, index) }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(
3575
- DefaultMessage,
3576
- {
3577
- message: messageWithExecutions,
3578
- userAvatar,
3579
- assistantAvatar,
3580
- showUserAvatar,
3581
- userMessageClassName: classNames.userMessage,
3582
- assistantMessageClassName: classNames.assistantMessage,
3583
- size: fontSize,
3584
- isLastMessage,
3585
- isLoading,
3586
- toolRenderers,
3587
- onApproveToolExecution,
3588
- onRejectToolExecution,
3589
- showFollowUps,
3590
- onFollowUpClick: handleFollowUpClick,
3591
- followUpClassName,
3592
- followUpButtonClassName
3593
- },
3594
- message.id
3595
- );
3596
- }),
3597
- isProcessing && /* @__PURE__ */ jsxRuntime.jsxs(Message, { className: "flex gap-2", children: [
3598
- /* @__PURE__ */ jsxRuntime.jsx(
3599
- MessageAvatar,
3600
- {
3601
- src: assistantAvatar?.src || "",
3602
- alt: "Assistant",
3603
- fallback: assistantAvatar?.fallback || "AI",
3604
- className: "bg-primary text-primary-foreground"
3605
- }
3606
- ),
3607
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg bg-muted px-4 py-2 flex items-center gap-2", children: [
3608
- /* @__PURE__ */ jsxRuntime.jsx(Loader, { variant: "dots", size: "sm" }),
3609
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Continuing..." })
3610
- ] })
3611
- ] }),
3612
- isLoading && !isProcessing && (() => {
3613
- const lastMessage = messages[messages.length - 1];
3614
- if (lastMessage?.role === "user") {
3615
- return /* @__PURE__ */ jsxRuntime.jsxs(Message, { className: "flex gap-2", children: [
4524
+ showWelcome ? (
4525
+ /* Welcome Screen (centered input) */
4526
+ /* @__PURE__ */ jsxRuntime.jsx(
4527
+ ChatWelcome,
4528
+ {
4529
+ config: welcomeConfig,
4530
+ suggestions,
4531
+ recentThreads,
4532
+ onSendMessage: (msg, attachments) => onSendMessage?.(msg, attachments),
4533
+ onSelectThread,
4534
+ onDeleteThread,
4535
+ onViewMoreThreads,
4536
+ isLoading,
4537
+ onStop,
4538
+ placeholder,
4539
+ attachmentsEnabled,
4540
+ attachmentsDisabledTooltip,
4541
+ maxFileSize,
4542
+ allowedFileTypes,
4543
+ processAttachment: processAttachmentProp
4544
+ }
4545
+ )
4546
+ ) : (
4547
+ /* Normal Chat UI (messages + input at bottom) */
4548
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4549
+ /* @__PURE__ */ jsxRuntime.jsxs(
4550
+ ChatContainerRoot,
4551
+ {
4552
+ className: cn("relative flex-1", classNames.container),
4553
+ children: [
4554
+ /* @__PURE__ */ jsxRuntime.jsxs(
4555
+ ChatContainerContent,
4556
+ {
4557
+ className: cn("gap-4 p-4", classNames.messageList),
4558
+ children: [
4559
+ messages.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-8 text-center text-muted-foreground", children: welcomeMessage || "Send a message to start the conversation" }),
4560
+ messages.map((message, index) => {
4561
+ const isLastMessage = index === messages.length - 1;
4562
+ const isEmptyAssistant = message.role === "assistant" && !message.content?.trim();
4563
+ const hasToolCalls = message.tool_calls && message.tool_calls.length > 0;
4564
+ const hasToolExecutions = message.toolExecutions && message.toolExecutions.length > 0;
4565
+ const hasPendingApprovals = message.toolExecutions?.some(
4566
+ (exec) => exec.approvalStatus === "required"
4567
+ );
4568
+ if (isEmptyAssistant) {
4569
+ if (hasToolCalls || hasToolExecutions) ; else if (isLastMessage && hasPendingApprovals) ; else if (isLastMessage && isLoading && !isProcessing) {
4570
+ return /* @__PURE__ */ jsxRuntime.jsxs(Message, { className: "flex gap-2", children: [
4571
+ /* @__PURE__ */ jsxRuntime.jsx(
4572
+ MessageAvatar,
4573
+ {
4574
+ src: assistantAvatar.src || "",
4575
+ alt: "Assistant",
4576
+ fallback: assistantAvatar.fallback,
4577
+ fallbackIcon: !assistantAvatar.src ? /* @__PURE__ */ jsxRuntime.jsx(copilot_sdk_logo_default, { className: "size-5" }) : void 0,
4578
+ className: "bg-background"
4579
+ }
4580
+ ),
4581
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg bg-muted px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(Loader, { variant: loaderVariant, size: "sm" }) })
4582
+ ] }, message.id);
4583
+ } else {
4584
+ return null;
4585
+ }
4586
+ }
4587
+ const savedExecutions = message.metadata?.toolExecutions;
4588
+ const messageToolExecutions = message.toolExecutions || savedExecutions;
4589
+ const messageWithExecutions = messageToolExecutions ? { ...message, toolExecutions: messageToolExecutions } : message;
4590
+ const handleFollowUpClick = (question) => {
4591
+ if (onSuggestionClick) {
4592
+ onSuggestionClick(question);
4593
+ } else {
4594
+ onSendMessage?.(question);
4595
+ }
4596
+ };
4597
+ return renderMessage ? /* @__PURE__ */ jsxRuntime.jsx(React8__namespace.default.Fragment, { children: renderMessage(messageWithExecutions, index) }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(
4598
+ DefaultMessage,
4599
+ {
4600
+ message: messageWithExecutions,
4601
+ userAvatar,
4602
+ assistantAvatar,
4603
+ showUserAvatar,
4604
+ userMessageClassName: classNames.userMessage,
4605
+ assistantMessageClassName: classNames.assistantMessage,
4606
+ size: fontSize,
4607
+ isLastMessage,
4608
+ isLoading,
4609
+ registeredTools,
4610
+ toolRenderers,
4611
+ onApproveToolExecution,
4612
+ onRejectToolExecution,
4613
+ showFollowUps,
4614
+ onFollowUpClick: handleFollowUpClick,
4615
+ followUpClassName,
4616
+ followUpButtonClassName
4617
+ },
4618
+ message.id
4619
+ );
4620
+ }),
4621
+ isProcessing && /* @__PURE__ */ jsxRuntime.jsxs(Message, { className: "flex gap-2", children: [
3616
4622
  /* @__PURE__ */ jsxRuntime.jsx(
3617
4623
  MessageAvatar,
3618
4624
  {
3619
4625
  src: assistantAvatar?.src || "",
3620
4626
  alt: "Assistant",
3621
4627
  fallback: assistantAvatar?.fallback || "AI",
3622
- className: "bg-primary text-primary-foreground"
4628
+ fallbackIcon: !assistantAvatar?.src ? /* @__PURE__ */ jsxRuntime.jsx(copilot_sdk_logo_default, { className: "size-5" }) : void 0,
4629
+ className: "bg-background"
3623
4630
  }
3624
4631
  ),
3625
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg bg-muted px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(Loader, { variant: loaderVariant, size: "sm" }) })
3626
- ] });
3627
- }
3628
- return null;
3629
- })(),
3630
- /* @__PURE__ */ jsxRuntime.jsx(ChatContainerScrollAnchor, {})
3631
- ]
3632
- }
3633
- ),
3634
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-4 bottom-4", children: /* @__PURE__ */ jsxRuntime.jsx(ScrollButton, { className: "shadow-sm" }) })
3635
- ]
3636
- }
3637
- ),
3638
- suggestions.length > 0 && !isLoading && /* @__PURE__ */ jsxRuntime.jsx(
3639
- Suggestions,
3640
- {
3641
- suggestions,
3642
- onSuggestionClick: handleSuggestionClick,
3643
- className: classNames.suggestions
3644
- }
3645
- ),
3646
- renderInput ? renderInput() : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("p-2", classNames.input), children: [
3647
- pendingAttachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 p-2 mb-2 bg-muted/30 rounded-lg", children: pendingAttachments.map((att) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
3648
- att.attachment.type === "image" ? /* @__PURE__ */ jsxRuntime.jsx(
3649
- "img",
3650
- {
3651
- src: att.previewUrl,
3652
- alt: att.file.name,
3653
- className: "w-16 h-16 object-cover rounded-lg border"
3654
- }
3655
- ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-16 h-16 bg-muted rounded-lg border flex flex-col items-center justify-center p-1", children: [
3656
- /* @__PURE__ */ jsxRuntime.jsx(
3657
- "svg",
3658
- {
3659
- className: "w-6 h-6 text-muted-foreground",
3660
- fill: "none",
3661
- viewBox: "0 0 24 24",
3662
- stroke: "currentColor",
3663
- children: /* @__PURE__ */ jsxRuntime.jsx(
3664
- "path",
3665
- {
3666
- strokeLinecap: "round",
3667
- strokeLinejoin: "round",
3668
- strokeWidth: 1.5,
3669
- 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"
4632
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg bg-muted px-4 py-2 flex items-center gap-2", children: [
4633
+ /* @__PURE__ */ jsxRuntime.jsx(Loader, { variant: "dots", size: "sm" }),
4634
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Continuing..." })
4635
+ ] })
4636
+ ] }),
4637
+ isLoading && !isProcessing && (() => {
4638
+ const lastMessage = messages[messages.length - 1];
4639
+ if (lastMessage?.role === "user") {
4640
+ return /* @__PURE__ */ jsxRuntime.jsxs(Message, { className: "flex gap-2", children: [
4641
+ /* @__PURE__ */ jsxRuntime.jsx(
4642
+ MessageAvatar,
4643
+ {
4644
+ src: assistantAvatar?.src || "",
4645
+ alt: "Assistant",
4646
+ fallback: assistantAvatar?.fallback || "AI",
4647
+ fallbackIcon: !assistantAvatar?.src ? /* @__PURE__ */ jsxRuntime.jsx(copilot_sdk_logo_default, { className: "size-5" }) : void 0,
4648
+ className: "bg-background"
4649
+ }
4650
+ ),
4651
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg bg-muted px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(Loader, { variant: loaderVariant, size: "sm" }) })
4652
+ ] });
4653
+ }
4654
+ return null;
4655
+ })(),
4656
+ /* @__PURE__ */ jsxRuntime.jsx(ChatContainerScrollAnchor, {})
4657
+ ]
3670
4658
  }
3671
- )
3672
- }
3673
- ),
3674
- /* @__PURE__ */ jsxRuntime.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 })
3675
- ] }),
3676
- att.status === "processing" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-background/80 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(Loader, { variant: "dots", size: "sm" }) }),
3677
- att.status === "error" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-destructive/20 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive text-xs", children: "Error" }) }),
3678
- /* @__PURE__ */ jsxRuntime.jsx(
3679
- "button",
4659
+ ),
4660
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-4 bottom-4", children: /* @__PURE__ */ jsxRuntime.jsx(ScrollButton, { className: "shadow-sm" }) })
4661
+ ]
4662
+ }
4663
+ ),
4664
+ suggestions.length > 0 && !isLoading && /* @__PURE__ */ jsxRuntime.jsx(
4665
+ Suggestions,
3680
4666
  {
3681
- onClick: () => removePendingAttachment(att.id),
3682
- 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",
3683
- type: "button",
3684
- children: /* @__PURE__ */ jsxRuntime.jsx(XIcon2, { className: "w-3 h-3" })
4667
+ suggestions,
4668
+ onSuggestionClick: handleSuggestionClick,
4669
+ className: classNames.suggestions
3685
4670
  }
3686
- )
3687
- ] }, att.id)) }),
3688
- /* @__PURE__ */ jsxRuntime.jsxs(
3689
- PromptInput,
3690
- {
3691
- value: input,
3692
- onValueChange: setInput,
3693
- isLoading,
3694
- onSubmit: handleSubmit,
3695
- className: "",
3696
- children: [
3697
- /* @__PURE__ */ jsxRuntime.jsx(PromptInputTextarea, { placeholder }),
3698
- /* @__PURE__ */ jsxRuntime.jsxs(PromptInputActions, { className: "flex justify-between", children: [
3699
- /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
3700
- PromptInputAction,
4671
+ ),
4672
+ renderInput ? renderInput() : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("p-2 pt-0", classNames.input), children: [
4673
+ pendingAttachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 p-2 mb-2 bg-muted/30 rounded-lg", children: pendingAttachments.map((att) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
4674
+ att.attachment.type === "image" ? /* @__PURE__ */ jsxRuntime.jsx(
4675
+ "img",
4676
+ {
4677
+ src: att.previewUrl,
4678
+ alt: att.file.name,
4679
+ className: "w-16 h-16 object-cover rounded-lg border"
4680
+ }
4681
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-16 h-16 bg-muted rounded-lg border flex flex-col items-center justify-center p-1", children: [
4682
+ /* @__PURE__ */ jsxRuntime.jsx(
4683
+ "svg",
3701
4684
  {
3702
- tooltip: attachmentsEnabled ? "Attach files" : attachmentsDisabledTooltip,
3703
- children: /* @__PURE__ */ jsxRuntime.jsxs(
3704
- "label",
4685
+ className: "w-6 h-6 text-muted-foreground",
4686
+ fill: "none",
4687
+ viewBox: "0 0 24 24",
4688
+ stroke: "currentColor",
4689
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4690
+ "path",
3705
4691
  {
3706
- htmlFor: fileInputId,
3707
- className: cn(
3708
- "flex h-8 w-8 items-center justify-center rounded-2xl",
3709
- attachmentsEnabled ? "hover:bg-secondary-foreground/10 cursor-pointer" : "opacity-50 cursor-not-allowed"
3710
- ),
3711
- children: [
3712
- /* @__PURE__ */ jsxRuntime.jsx(
3713
- "input",
3714
- {
3715
- ref: fileInputRef,
3716
- type: "file",
3717
- multiple: true,
3718
- accept: acceptString,
3719
- onChange: handleInputChange,
3720
- className: "hidden",
3721
- id: fileInputId,
3722
- disabled: !attachmentsEnabled
3723
- }
3724
- ),
3725
- /* @__PURE__ */ jsxRuntime.jsx(PlusIcon, { className: "text-primary size-5" })
3726
- ]
4692
+ strokeLinecap: "round",
4693
+ strokeLinejoin: "round",
4694
+ strokeWidth: 1.5,
4695
+ 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"
3727
4696
  }
3728
4697
  )
3729
4698
  }
3730
- ) }),
3731
- /* @__PURE__ */ jsxRuntime.jsx(PromptInputAction, { tooltip: isLoading ? "Stop" : "Send", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
3732
- Button,
3733
- {
3734
- size: "sm",
3735
- variant: "destructive",
3736
- className: "rounded-full size-9",
3737
- onClick: onStop,
3738
- children: /* @__PURE__ */ jsxRuntime.jsx(StopIcon, { className: "h-4 w-4" })
3739
- }
3740
- ) : /* @__PURE__ */ jsxRuntime.jsx(
3741
- Button,
3742
- {
3743
- size: "sm",
3744
- className: "rounded-full size-9",
3745
- onClick: handleSubmit,
3746
- disabled: !input.trim() && !pendingAttachments.some((att) => att.status === "ready"),
3747
- children: /* @__PURE__ */ jsxRuntime.jsx(ArrowUpIcon, { className: "h-4 w-4" })
3748
- }
3749
- ) })
3750
- ] })
3751
- ]
3752
- }
3753
- )
3754
- ] })
4699
+ ),
4700
+ /* @__PURE__ */ jsxRuntime.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 })
4701
+ ] }),
4702
+ att.status === "processing" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-background/80 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(Loader, { variant: "dots", size: "sm" }) }),
4703
+ att.status === "error" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-destructive/20 rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive text-xs", children: "Error" }) }),
4704
+ /* @__PURE__ */ jsxRuntime.jsx(
4705
+ "button",
4706
+ {
4707
+ onClick: () => removePendingAttachment(att.id),
4708
+ 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",
4709
+ type: "button",
4710
+ children: /* @__PURE__ */ jsxRuntime.jsx(XIcon2, { className: "w-3 h-3" })
4711
+ }
4712
+ )
4713
+ ] }, att.id)) }),
4714
+ /* @__PURE__ */ jsxRuntime.jsxs(
4715
+ PromptInput,
4716
+ {
4717
+ value: input,
4718
+ onValueChange: setInput,
4719
+ isLoading,
4720
+ onSubmit: handleSubmit,
4721
+ className: "",
4722
+ children: [
4723
+ /* @__PURE__ */ jsxRuntime.jsx(PromptInputTextarea, { placeholder }),
4724
+ /* @__PURE__ */ jsxRuntime.jsxs(PromptInputActions, { className: "flex justify-between", children: [
4725
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
4726
+ PromptInputAction,
4727
+ {
4728
+ tooltip: attachmentsEnabled ? "Attach files" : attachmentsDisabledTooltip,
4729
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4730
+ "label",
4731
+ {
4732
+ htmlFor: fileInputId,
4733
+ className: cn(
4734
+ "flex h-8 w-8 items-center justify-center rounded-2xl",
4735
+ attachmentsEnabled ? "hover:bg-secondary-foreground/10 cursor-pointer" : "opacity-50 cursor-not-allowed"
4736
+ ),
4737
+ children: [
4738
+ /* @__PURE__ */ jsxRuntime.jsx(
4739
+ "input",
4740
+ {
4741
+ ref: fileInputRef,
4742
+ type: "file",
4743
+ multiple: true,
4744
+ accept: acceptString,
4745
+ onChange: handleInputChange,
4746
+ className: "hidden",
4747
+ id: fileInputId,
4748
+ disabled: !attachmentsEnabled
4749
+ }
4750
+ ),
4751
+ /* @__PURE__ */ jsxRuntime.jsx(PlusIcon, { className: "text-primary size-5" })
4752
+ ]
4753
+ }
4754
+ )
4755
+ }
4756
+ ) }),
4757
+ /* @__PURE__ */ jsxRuntime.jsx(PromptInputAction, { tooltip: isLoading ? "Stop" : "Send", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
4758
+ Button,
4759
+ {
4760
+ size: "sm",
4761
+ variant: "destructive",
4762
+ className: "rounded-full size-9",
4763
+ onClick: onStop,
4764
+ children: /* @__PURE__ */ jsxRuntime.jsx(StopIcon, { className: "h-4 w-4" })
4765
+ }
4766
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
4767
+ Button,
4768
+ {
4769
+ size: "sm",
4770
+ className: "rounded-full size-9",
4771
+ onClick: handleSubmit,
4772
+ disabled: !input.trim() && !pendingAttachments.some(
4773
+ (att) => att.status === "ready"
4774
+ ),
4775
+ children: /* @__PURE__ */ jsxRuntime.jsx(ArrowUpIcon, { className: "h-4 w-4" })
4776
+ }
4777
+ ) })
4778
+ ] })
4779
+ ]
4780
+ }
4781
+ )
4782
+ ] })
4783
+ ] })
4784
+ )
3755
4785
  ]
3756
4786
  }
3757
4787
  );
@@ -3761,6 +4791,7 @@ function ToolExecutionMessage({
3761
4791
  assistantAvatar = { fallback: "AI" },
3762
4792
  onApprove,
3763
4793
  onReject,
4794
+ toolRenderers,
3764
4795
  className
3765
4796
  }) {
3766
4797
  if (!executions || executions.length === 0) return null;
@@ -3844,22 +4875,261 @@ function ToolExecutionMessage({
3844
4875
  ),
3845
4876
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground", children: hasExecuting ? "Running tools..." : allCompleted ? `${executions.length} tool${executions.length > 1 ? "s" : ""} completed` : "Tools" })
3846
4877
  ] }) }),
3847
- pendingApprovals.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 space-y-2", children: pendingApprovals.map((tool) => /* @__PURE__ */ jsxRuntime.jsx(
3848
- PermissionConfirmation,
3849
- {
3850
- state: "pending",
3851
- toolName: tool.name,
3852
- message: tool.approvalMessage || `This tool wants to execute. Do you approve?`,
3853
- onApprove: (permissionLevel) => onApprove?.(tool.id, permissionLevel),
3854
- onReject: (permissionLevel) => onReject?.(tool.id, void 0, permissionLevel)
3855
- },
3856
- tool.id
3857
- )) }),
4878
+ pendingApprovals.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 space-y-2", children: pendingApprovals.map((tool) => {
4879
+ const CustomRenderer = toolRenderers?.[tool.name];
4880
+ if (CustomRenderer) {
4881
+ return /* @__PURE__ */ jsxRuntime.jsx(
4882
+ CustomRenderer,
4883
+ {
4884
+ execution: tool,
4885
+ approval: {
4886
+ onApprove: (extraData) => onApprove?.(tool.id, extraData),
4887
+ onReject: (reason) => onReject?.(tool.id, reason),
4888
+ message: tool.approvalMessage
4889
+ }
4890
+ },
4891
+ tool.id
4892
+ );
4893
+ }
4894
+ return /* @__PURE__ */ jsxRuntime.jsx(
4895
+ PermissionConfirmation,
4896
+ {
4897
+ state: "pending",
4898
+ toolName: tool.name,
4899
+ message: tool.approvalMessage || `This tool wants to execute. Do you approve?`,
4900
+ onApprove: (permissionLevel) => onApprove?.(tool.id, void 0, permissionLevel),
4901
+ onReject: (permissionLevel) => onReject?.(tool.id, void 0, permissionLevel)
4902
+ },
4903
+ tool.id
4904
+ );
4905
+ }) }),
3858
4906
  toolSteps.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border bg-card px-3 py-2.5 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ToolSteps, { steps: toolSteps }) })
3859
4907
  ] })
3860
4908
  ] });
3861
4909
  }
4910
+ function useInternalThreadManager(config = {}) {
4911
+ const {
4912
+ adapter,
4913
+ saveDebounce = 1e3,
4914
+ autoRestoreLastThread = true,
4915
+ onThreadChange
4916
+ } = config;
4917
+ const threadManagerConfig = {
4918
+ adapter,
4919
+ saveDebounce,
4920
+ autoRestoreLastThread
4921
+ };
4922
+ const threadManager = chunkBN75ZW24_cjs.useThreadManager(threadManagerConfig);
4923
+ const {
4924
+ currentThread,
4925
+ currentThreadId,
4926
+ createThread,
4927
+ switchThread,
4928
+ updateCurrentThread,
4929
+ clearCurrentThread,
4930
+ refreshThreads
4931
+ } = threadManager;
4932
+ const { messages, setMessages, status, isLoading } = chunkBN75ZW24_cjs.useCopilot();
4933
+ const isLoadingMessagesRef = React8.useRef(false);
4934
+ const savingToThreadRef = React8.useRef(null);
4935
+ const lastSavedSnapshotRef = React8.useRef("");
4936
+ const hasInitializedRef = React8.useRef(false);
4937
+ const getMessageSnapshot = React8.useCallback((msgs) => {
4938
+ return msgs.map((m) => {
4939
+ const contentPreview = (m.content ?? "").slice(0, 20);
4940
+ return `${m.id}:${contentPreview}:${m.content?.length ?? 0}`;
4941
+ }).join("|");
4942
+ }, []);
4943
+ const convertToCore = React8.useCallback((msgs) => {
4944
+ return msgs.map((m) => ({
4945
+ id: m.id,
4946
+ role: m.role,
4947
+ content: m.content,
4948
+ created_at: m.createdAt,
4949
+ tool_calls: m.toolCalls,
4950
+ tool_call_id: m.toolCallId,
4951
+ metadata: {
4952
+ attachments: m.attachments,
4953
+ thinking: m.thinking
4954
+ }
4955
+ }));
4956
+ }, []);
4957
+ const handleSwitchThread = React8.useCallback(
4958
+ async (threadId) => {
4959
+ isLoadingMessagesRef.current = true;
4960
+ const thread = await switchThread(threadId);
4961
+ if (thread?.messages) {
4962
+ const uiMessages = thread.messages.map((m) => ({
4963
+ id: m.id,
4964
+ role: m.role,
4965
+ content: m.content ?? "",
4966
+ createdAt: m.created_at ?? /* @__PURE__ */ new Date(),
4967
+ toolCalls: m.tool_calls,
4968
+ toolCallId: m.tool_call_id,
4969
+ attachments: m.metadata?.attachments
4970
+ }));
4971
+ lastSavedSnapshotRef.current = getMessageSnapshot(uiMessages);
4972
+ savingToThreadRef.current = threadId;
4973
+ setMessages(uiMessages);
4974
+ } else {
4975
+ lastSavedSnapshotRef.current = "";
4976
+ savingToThreadRef.current = threadId;
4977
+ setMessages([]);
4978
+ }
4979
+ onThreadChange?.(threadId);
4980
+ requestAnimationFrame(() => {
4981
+ isLoadingMessagesRef.current = false;
4982
+ });
4983
+ },
4984
+ [switchThread, setMessages, getMessageSnapshot, onThreadChange]
4985
+ );
4986
+ const handleNewThread = React8.useCallback(async () => {
4987
+ isLoadingMessagesRef.current = true;
4988
+ clearCurrentThread();
4989
+ lastSavedSnapshotRef.current = "";
4990
+ savingToThreadRef.current = null;
4991
+ setMessages([]);
4992
+ onThreadChange?.(null);
4993
+ requestAnimationFrame(() => {
4994
+ isLoadingMessagesRef.current = false;
4995
+ });
4996
+ }, [clearCurrentThread, setMessages, onThreadChange]);
4997
+ React8.useEffect(() => {
4998
+ if (hasInitializedRef.current || !currentThread) {
4999
+ return;
5000
+ }
5001
+ hasInitializedRef.current = true;
5002
+ isLoadingMessagesRef.current = true;
5003
+ if (currentThread.messages && currentThread.messages.length > 0) {
5004
+ const uiMessages = currentThread.messages.map((m) => ({
5005
+ id: m.id,
5006
+ role: m.role,
5007
+ content: m.content ?? "",
5008
+ createdAt: m.created_at ?? /* @__PURE__ */ new Date(),
5009
+ toolCalls: m.tool_calls,
5010
+ toolCallId: m.tool_call_id,
5011
+ attachments: m.metadata?.attachments
5012
+ }));
5013
+ lastSavedSnapshotRef.current = getMessageSnapshot(uiMessages);
5014
+ savingToThreadRef.current = currentThread.id;
5015
+ setMessages(uiMessages);
5016
+ } else {
5017
+ lastSavedSnapshotRef.current = "";
5018
+ savingToThreadRef.current = currentThread.id;
5019
+ }
5020
+ onThreadChange?.(currentThread.id);
5021
+ requestAnimationFrame(() => {
5022
+ isLoadingMessagesRef.current = false;
5023
+ });
5024
+ }, [currentThread, setMessages, getMessageSnapshot, onThreadChange]);
5025
+ React8.useEffect(() => {
5026
+ if (isLoadingMessagesRef.current) {
5027
+ return;
5028
+ }
5029
+ if (status === "streaming" || status === "submitted") {
5030
+ return;
5031
+ }
5032
+ if (messages.length === 0) {
5033
+ return;
5034
+ }
5035
+ const currentSnapshot = getMessageSnapshot(messages);
5036
+ if (currentSnapshot === lastSavedSnapshotRef.current) {
5037
+ return;
5038
+ }
5039
+ const coreMessages = convertToCore(messages);
5040
+ if (!currentThreadId && !savingToThreadRef.current) {
5041
+ savingToThreadRef.current = "creating";
5042
+ createThread({ messages: coreMessages }).then((thread) => {
5043
+ lastSavedSnapshotRef.current = currentSnapshot;
5044
+ savingToThreadRef.current = thread.id;
5045
+ onThreadChange?.(thread.id);
5046
+ });
5047
+ return;
5048
+ }
5049
+ if (savingToThreadRef.current && savingToThreadRef.current !== currentThreadId) {
5050
+ return;
5051
+ }
5052
+ updateCurrentThread({ messages: coreMessages });
5053
+ lastSavedSnapshotRef.current = currentSnapshot;
5054
+ }, [
5055
+ messages,
5056
+ currentThreadId,
5057
+ status,
5058
+ updateCurrentThread,
5059
+ createThread,
5060
+ refreshThreads,
5061
+ getMessageSnapshot,
5062
+ convertToCore,
5063
+ onThreadChange
5064
+ ]);
5065
+ const isBusy = isLoading || status === "streaming" || status === "submitted";
5066
+ return {
5067
+ threadManager,
5068
+ handleSwitchThread,
5069
+ handleNewThread,
5070
+ isBusy
5071
+ };
5072
+ }
5073
+ function parsePersistenceConfig(persistence, onThreadChange) {
5074
+ if (!persistence) {
5075
+ return void 0;
5076
+ }
5077
+ if (persistence === true) {
5078
+ return {
5079
+ onThreadChange,
5080
+ autoRestoreLastThread: true
5081
+ };
5082
+ }
5083
+ if ("type" in persistence) {
5084
+ switch (persistence.type) {
5085
+ case "local":
5086
+ return {
5087
+ saveDebounce: persistence.saveDebounce,
5088
+ autoRestoreLastThread: persistence.autoRestoreLastThread ?? true,
5089
+ onThreadChange
5090
+ };
5091
+ case "server":
5092
+ return {
5093
+ adapter: chunk42YQ4ATO_cjs.createServerAdapter({
5094
+ endpoint: persistence.endpoint,
5095
+ headers: persistence.headers
5096
+ }),
5097
+ saveDebounce: persistence.saveDebounce,
5098
+ autoRestoreLastThread: persistence.autoRestoreLastThread ?? true,
5099
+ onThreadChange
5100
+ };
5101
+ case "cloud":
5102
+ console.warn(
5103
+ "[Copilot SDK] Cloud persistence is not yet implemented. Falling back to localStorage."
5104
+ );
5105
+ return {
5106
+ onThreadChange,
5107
+ autoRestoreLastThread: true
5108
+ };
5109
+ }
5110
+ }
5111
+ const legacyConfig = persistence;
5112
+ return {
5113
+ adapter: legacyConfig.adapter,
5114
+ saveDebounce: legacyConfig.saveDebounce,
5115
+ autoRestoreLastThread: legacyConfig.autoRestoreLastThread ?? true,
5116
+ onThreadChange
5117
+ };
5118
+ }
3862
5119
  function CopilotChat(props) {
5120
+ const {
5121
+ persistence,
5122
+ showThreadPicker = false,
5123
+ onThreadChange,
5124
+ classNames,
5125
+ header,
5126
+ ...chatProps
5127
+ } = props;
5128
+ const persistenceConfig = parsePersistenceConfig(persistence, onThreadChange);
5129
+ const threadManagerResult = useInternalThreadManager(
5130
+ persistenceConfig ?? { autoRestoreLastThread: false }
5131
+ );
5132
+ const isPersistenceEnabled = !!persistence;
3863
5133
  const {
3864
5134
  messages,
3865
5135
  isLoading,
@@ -3867,8 +5137,9 @@ function CopilotChat(props) {
3867
5137
  stop,
3868
5138
  toolExecutions: rawToolExecutions,
3869
5139
  approveToolExecution,
3870
- rejectToolExecution
3871
- } = chunkW6KQT7YZ_cjs.useCopilot();
5140
+ rejectToolExecution,
5141
+ registeredTools
5142
+ } = chunkBN75ZW24_cjs.useCopilot();
3872
5143
  const toolExecutions = rawToolExecutions.map(
3873
5144
  (exec) => ({
3874
5145
  id: exec.id,
@@ -3958,7 +5229,7 @@ function CopilotChat(props) {
3958
5229
  toolExecutions: messageToolExecutions
3959
5230
  };
3960
5231
  });
3961
- const suggestions = visibleMessages.length === 0 && props.suggestions?.length ? props.suggestions : [];
5232
+ const suggestions = visibleMessages.length === 0 && chatProps.suggestions?.length ? chatProps.suggestions : [];
3962
5233
  const lastMessage = messages[messages.length - 1];
3963
5234
  const isInToolFlow = lastMessage?.role === "assistant" && lastMessage.toolCalls?.length;
3964
5235
  let isProcessingToolResults = false;
@@ -3979,19 +5250,70 @@ function CopilotChat(props) {
3979
5250
  );
3980
5251
  isProcessingToolResults = hasCompletedTools && !hasExecutingTools;
3981
5252
  }
5253
+ const chatClassNames = classNames ? {
5254
+ root: classNames.root,
5255
+ header: classNames.header,
5256
+ container: classNames.container,
5257
+ messageList: classNames.messageList,
5258
+ userMessage: classNames.userMessage,
5259
+ assistantMessage: classNames.assistantMessage,
5260
+ input: classNames.input,
5261
+ suggestions: classNames.suggestions,
5262
+ footer: classNames.footer
5263
+ } : void 0;
5264
+ const { threadManager, handleSwitchThread, handleNewThread, isBusy } = threadManagerResult;
5265
+ const handleDeleteThread = React8__namespace.default.useCallback(
5266
+ (threadId) => {
5267
+ const isCurrentThread = threadManager.currentThreadId === threadId;
5268
+ threadManager.deleteThread(threadId);
5269
+ if (isCurrentThread) {
5270
+ handleNewThread();
5271
+ }
5272
+ },
5273
+ [threadManager, handleNewThread]
5274
+ );
5275
+ const threadPickerElement = isPersistenceEnabled && showThreadPicker ? /* @__PURE__ */ jsxRuntime.jsx(
5276
+ ThreadPicker,
5277
+ {
5278
+ value: threadManager.currentThreadId,
5279
+ threads: threadManager.threads,
5280
+ onSelect: handleSwitchThread,
5281
+ onDeleteThread: handleDeleteThread,
5282
+ onNewThread: handleNewThread,
5283
+ loading: threadManager.isLoading,
5284
+ disabled: isBusy,
5285
+ size: "sm",
5286
+ className: classNames?.threadPicker,
5287
+ buttonClassName: classNames?.threadPickerButton,
5288
+ dropdownClassName: classNames?.threadPickerDropdown,
5289
+ itemClassName: classNames?.threadPickerItem,
5290
+ newButtonClassName: classNames?.threadPickerNewButton
5291
+ }
5292
+ ) : void 0;
5293
+ const shouldShowHeader = !!header || showThreadPicker || chatProps.showHeader;
5294
+ const useCustomHeader = chatProps.renderHeader && !header && !showThreadPicker;
3982
5295
  return /* @__PURE__ */ jsxRuntime.jsx(
3983
5296
  Chat,
3984
5297
  {
3985
- ...props,
5298
+ ...chatProps,
3986
5299
  messages: visibleMessages,
3987
5300
  onSendMessage: sendMessage,
3988
5301
  onStop: stop,
3989
5302
  isLoading,
3990
- showPoweredBy: props.showPoweredBy ?? true,
5303
+ showPoweredBy: chatProps.showPoweredBy ?? true,
3991
5304
  suggestions,
3992
5305
  isProcessing: isProcessingToolResults,
3993
5306
  onApproveToolExecution: approveToolExecution,
3994
- onRejectToolExecution: rejectToolExecution
5307
+ onRejectToolExecution: rejectToolExecution,
5308
+ registeredTools,
5309
+ classNames: chatClassNames,
5310
+ header,
5311
+ threadPicker: threadPickerElement,
5312
+ showHeader: shouldShowHeader,
5313
+ renderHeader: useCustomHeader ? chatProps.renderHeader : void 0,
5314
+ recentThreads: isPersistenceEnabled ? threadManager.threads : void 0,
5315
+ onSelectThread: isPersistenceEnabled ? handleSwitchThread : void 0,
5316
+ onDeleteThread: isPersistenceEnabled ? handleDeleteThread : void 0
3995
5317
  }
3996
5318
  );
3997
5319
  }
@@ -4051,6 +5373,7 @@ exports.Chat = Chat;
4051
5373
  exports.ChatContainerContent = ChatContainerContent;
4052
5374
  exports.ChatContainerRoot = ChatContainerRoot;
4053
5375
  exports.ChatContainerScrollAnchor = ChatContainerScrollAnchor;
5376
+ exports.ChatWelcome = ChatWelcome;
4054
5377
  exports.CheckIcon = CheckIcon;
4055
5378
  exports.ChevronDownIcon = ChevronDownIcon2;
4056
5379
  exports.ChevronUpIcon = ChevronUpIcon;
@@ -4097,6 +5420,9 @@ exports.Source = Source;
4097
5420
  exports.SourceContent = SourceContent;
4098
5421
  exports.SourceTrigger = SourceTrigger;
4099
5422
  exports.StopIcon = StopIcon;
5423
+ exports.ThreadCard = ThreadCard;
5424
+ exports.ThreadList = ThreadList;
5425
+ exports.ThreadPicker = ThreadPicker;
4100
5426
  exports.ThumbsDownIcon = ThumbsDownIcon2;
4101
5427
  exports.ThumbsUpIcon = ThumbsUpIcon2;
4102
5428
  exports.ToolExecutionMessage = ToolExecutionMessage;