@dust-tt/sparkle 0.2.640 → 0.2.641-rc-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/esm/components/ConversationMessage.d.ts +5 -1
  3. package/dist/esm/components/ConversationMessage.d.ts.map +1 -1
  4. package/dist/esm/components/ConversationMessage.js +11 -8
  5. package/dist/esm/components/ConversationMessage.js.map +1 -1
  6. package/dist/esm/components/markdown/Markdown.d.ts +5 -2
  7. package/dist/esm/components/markdown/Markdown.d.ts.map +1 -1
  8. package/dist/esm/components/markdown/Markdown.js +6 -4
  9. package/dist/esm/components/markdown/Markdown.js.map +1 -1
  10. package/dist/esm/components/markdown/useAnimatedText.d.ts +6 -0
  11. package/dist/esm/components/markdown/useAnimatedText.d.ts.map +1 -0
  12. package/dist/esm/components/markdown/useAnimatedText.js +28 -0
  13. package/dist/esm/components/markdown/useAnimatedText.js.map +1 -0
  14. package/dist/esm/icons/actions/Atom.js +1 -1
  15. package/dist/esm/icons/actions/Atom.js.map +1 -1
  16. package/dist/esm/icons/app/Atom.js +1 -1
  17. package/dist/esm/icons/app/Atom.js.map +1 -1
  18. package/dist/esm/stories/ConversationMessage.stories.d.ts +1 -0
  19. package/dist/esm/stories/ConversationMessage.stories.d.ts.map +1 -1
  20. package/dist/esm/stories/ConversationMessage.stories.js +26 -1
  21. package/dist/esm/stories/ConversationMessage.stories.js.map +1 -1
  22. package/dist/esm/styles/global.css +0 -12
  23. package/dist/esm/styles/global_with_tw_base.css +0 -21
  24. package/dist/esm/styles/tailwind.css +0 -11
  25. package/dist/sparkle.css +10 -34
  26. package/package.json +2 -1
  27. package/src/components/ConversationMessage.tsx +36 -21
  28. package/src/components/markdown/Markdown.tsx +23 -12
  29. package/src/components/markdown/useAnimatedText.ts +38 -0
  30. package/src/icons/actions/Atom.tsx +1 -1
  31. package/src/icons/app/Atom.tsx +1 -1
  32. package/src/stories/ConversationMessage.stories.tsx +100 -0
  33. package/src/styles/global.css +0 -12
  34. package/src/styles/global_with_tw_base.css +0 -21
  35. package/src/styles/tailwind.css +0 -11
@@ -39,13 +39,18 @@ interface ConversationMessageProps
39
39
  pictureUrl?: string | React.ReactNode | null;
40
40
  renderName?: (name: string | null) => React.ReactNode;
41
41
  infoChip?: React.ReactNode;
42
+ type: ConversationMessageType;
42
43
  }
43
44
 
45
+ export type ConversationMessageType = "user" | "agent" | "agentAsTool";
46
+
44
47
  const messageVariants = cva("s-flex s-w-full s-flex-col s-rounded-2xl", {
45
48
  variants: {
46
49
  type: {
47
50
  user: "s-bg-muted-background dark:s-bg-muted-background-night s-px-5 s-py-4 s-gap-2",
48
51
  agent: "s-w-full s-gap-3",
52
+ agentAsTool:
53
+ "s-w-full s-gap-3 s-border s-border-border dark:s-border-border-night s-rounded-2xl s-px-5 s-py-5",
49
54
  },
50
55
  },
51
56
  defaultVariants: {
@@ -58,6 +63,7 @@ const buttonsVariants = cva("s-flex s-justify-start s-gap-2 s-pt-2", {
58
63
  type: {
59
64
  user: "s-justify-end",
60
65
  agent: "s-justify-start",
66
+ agentAsTool: "s-justify-start",
61
67
  },
62
68
  },
63
69
  defaultVariants: {
@@ -101,9 +107,10 @@ export const ConversationMessage = React.forwardRef<
101
107
  isDisabled={isDisabled}
102
108
  renderName={renderName}
103
109
  infoChip={infoChip}
110
+ type={type}
104
111
  />
105
112
 
106
- <ConversationMessageContent citations={citations}>
113
+ <ConversationMessageContent citations={citations} type={type}>
107
114
  {children}
108
115
  </ConversationMessageContent>
109
116
  </div>
@@ -123,12 +130,13 @@ interface ConversationMessageContentProps
123
130
  extends React.HTMLAttributes<HTMLDivElement> {
124
131
  children: React.ReactNode;
125
132
  citations?: React.ReactElement[];
133
+ type: ConversationMessageType;
126
134
  }
127
135
 
128
136
  export const ConversationMessageContent = React.forwardRef<
129
137
  HTMLDivElement,
130
138
  ConversationMessageContentProps
131
- >(({ children, citations, className, ...props }, ref) => {
139
+ >(({ children, citations, className, type, ...props }, ref) => {
132
140
  return (
133
141
  <div
134
142
  ref={ref}
@@ -140,8 +148,9 @@ export const ConversationMessageContent = React.forwardRef<
140
148
  >
141
149
  <div
142
150
  className={cn(
143
- "s-text-sm @sm:s-text-base @md:s-px-4",
144
- "s-text-foreground dark:s-text-foreground-night"
151
+ "s-text-sm @sm:s-text-base",
152
+ "s-text-foreground dark:s-text-foreground-night",
153
+ type !== "agentAsTool" && "@md:s-px-4"
145
154
  )}
146
155
  >
147
156
  {children}
@@ -164,6 +173,7 @@ interface ConversationMessageHeaderProps
164
173
  timestamp?: string;
165
174
  infoChip?: React.ReactNode;
166
175
  renderName: (name: string | null) => React.ReactNode;
176
+ type: ConversationMessageType;
167
177
  }
168
178
 
169
179
  export const ConversationMessageHeader = React.forwardRef<
@@ -178,6 +188,7 @@ export const ConversationMessageHeader = React.forwardRef<
178
188
  name = "",
179
189
  timestamp,
180
190
  infoChip,
191
+ type,
181
192
  renderName,
182
193
  className,
183
194
  ...props
@@ -193,26 +204,30 @@ export const ConversationMessageHeader = React.forwardRef<
193
204
  )}
194
205
  {...props}
195
206
  >
196
- <Avatar
197
- className="@sm:s-hidden"
198
- name={name}
199
- visual={avatarUrl}
200
- busy={isBusy}
201
- disabled={isDisabled}
202
- size="xs"
203
- />
204
- <Avatar
205
- className="s-hidden @sm:s-flex"
206
- name={name}
207
- visual={avatarUrl}
208
- busy={isBusy}
209
- disabled={isDisabled}
210
- size="sm"
211
- />
207
+ {type !== "agentAsTool" && (
208
+ <>
209
+ <Avatar
210
+ className="@sm:s-hidden"
211
+ name={name}
212
+ visual={avatarUrl}
213
+ busy={isBusy}
214
+ disabled={isDisabled}
215
+ size="xs"
216
+ />
217
+ <Avatar
218
+ className="s-hidden @sm:s-flex"
219
+ name={name}
220
+ visual={avatarUrl}
221
+ busy={isBusy}
222
+ disabled={isDisabled}
223
+ size="sm"
224
+ />
225
+ </>
226
+ )}
212
227
  <div className="s-flex s-w-full s-flex-row s-justify-between s-gap-0.5">
213
228
  <div
214
229
  className={cn(
215
- "s-text-sm s-font-semibold @sm:s-text-base",
230
+ "s-heading-sm @sm:s-text-base",
216
231
  "s-text-foreground dark:s-text-foreground-night",
217
232
  "s-flex s-flex-row s-items-center s-gap-2"
218
233
  )}
@@ -27,6 +27,8 @@ import {
27
27
  import { sanitizeContent } from "@sparkle/components/markdown/utils";
28
28
  import { cn } from "@sparkle/lib/utils";
29
29
 
30
+ import { useAnimatedText } from "./useAnimatedText";
31
+
30
32
  const sizes = {
31
33
  p: "s-copy-sm @sm:s-text-base @sm:s-leading-7",
32
34
  h1: "s-heading-2xl",
@@ -49,25 +51,34 @@ function showUnsupportedDirective() {
49
51
  };
50
52
  }
51
53
 
54
+ interface MarkdownProps {
55
+ content: string;
56
+ isStreaming?: boolean;
57
+ shouldAnimateOnStream?: boolean;
58
+ textColor?: string;
59
+ isLastMessage?: boolean;
60
+ forcedTextSize?: string;
61
+ additionalMarkdownComponents?: Components;
62
+ additionalMarkdownPlugins?: PluggableList;
63
+ }
64
+
52
65
  export function Markdown({
53
66
  content,
54
67
  isStreaming = false,
68
+ shouldAnimateOnStream = false,
55
69
  textColor = "s-text-foreground dark:s-text-foreground-night",
56
70
  forcedTextSize,
57
71
  isLastMessage = false,
58
72
  additionalMarkdownComponents,
59
73
  additionalMarkdownPlugins,
60
- }: {
61
- content: string;
62
- isStreaming?: boolean;
63
- textColor?: string;
64
- isLastMessage?: boolean;
65
- forcedTextSize?: string;
66
- additionalMarkdownComponents?: Components;
67
- additionalMarkdownPlugins?: PluggableList;
68
- }) {
74
+ }: MarkdownProps) {
69
75
  const processedContent = useMemo(() => sanitizeContent(content), [content]);
70
76
 
77
+ const markdownContent = useAnimatedText(
78
+ processedContent,
79
+ isStreaming && !shouldAnimateOnStream
80
+ );
81
+
71
82
  // Note on re-renderings. A lot of effort has been put into preventing rerendering across markdown
72
83
  // AST parsing rounds (happening at each token being streamed).
73
84
  //
@@ -221,7 +232,7 @@ export function Markdown({
221
232
 
222
233
  try {
223
234
  return (
224
- <div className={cn("s-w-full", isStreaming ? "s-blinking-cursor" : "")}>
235
+ <div className={cn("s-w-full")}>
225
236
  <MarkdownContentContext.Provider
226
237
  value={{
227
238
  content: processedContent,
@@ -235,14 +246,14 @@ export function Markdown({
235
246
  remarkPlugins={markdownPlugins}
236
247
  rehypePlugins={rehypePlugins}
237
248
  >
238
- {processedContent}
249
+ {markdownContent}
239
250
  </ReactMarkdown>
240
251
  </MarkdownContentContext.Provider>
241
252
  </div>
242
253
  );
243
254
  } catch (error) {
244
255
  return (
245
- <div className={cn("s-w-full", isStreaming ? "s-blinking-cursor" : "")}>
256
+ <div className={cn("s-w-full")}>
246
257
  <Chip color="warning">
247
258
  There was an error parsing this markdown content
248
259
  </Chip>
@@ -0,0 +1,38 @@
1
+ import { animate, type Easing } from "framer-motion";
2
+ import { useEffect, useState } from "react";
3
+
4
+ export function useAnimatedText(
5
+ text: string,
6
+ shouldAnimate: boolean,
7
+ options?: {
8
+ duration?: number;
9
+ ease?: Easing;
10
+ }
11
+ ) {
12
+ const [cursor, setCursor] = useState(0);
13
+ const [startingCursor, setStartingCursor] = useState(0);
14
+ const [prevText, setPrevText] = useState(text);
15
+
16
+ if (prevText !== text) {
17
+ setPrevText(text);
18
+ setStartingCursor(text.startsWith(prevText) ? cursor : 0);
19
+ }
20
+
21
+ useEffect(() => {
22
+ const controls = animate(startingCursor, text.length, {
23
+ duration: options?.duration ?? 4,
24
+ ease: options?.ease ?? "easeOut",
25
+ onUpdate(latest) {
26
+ setCursor(Math.floor(latest));
27
+ },
28
+ });
29
+
30
+ return () => controls.stop();
31
+ }, [startingCursor, text, options?.duration, options?.ease]);
32
+
33
+ if (!shouldAnimate) {
34
+ return text;
35
+ }
36
+
37
+ return text.slice(0, cursor);
38
+ }
@@ -10,7 +10,7 @@ const SvgAtom = (props: SVGProps<SVGSVGElement>) => (
10
10
  {...props}
11
11
  >
12
12
  <path
13
- fill="#000"
13
+ fill="currentColor"
14
14
  fillRule="evenodd"
15
15
  d="M14.974 2.284c.831-.248 1.733-.268 2.526.19.793.458 1.227 1.249 1.428 2.093.2.841.196 1.827.047 2.866-.029.196-.065.396-.104.599.196.068.387.137.571.21.975.39 1.83.88 2.46 1.474C22.531 10.313 23 11.084 23 12c0 .915-.468 1.687-1.099 2.283-.628.594-1.484 1.084-2.459 1.473-.184.074-.375.143-.57.211.038.204.074.404.103.6.15 1.039.153 2.025-.047 2.866-.201.844-.635 1.635-1.428 2.093-.793.458-1.695.438-2.526.19-.83-.247-1.681-.743-2.506-1.392a12.997 12.997 0 0 1-.468-.389c-.157.136-.312.266-.468.389-.825.649-1.677 1.145-2.506 1.392-.831.248-1.733.268-2.526-.19-.793-.458-1.227-1.249-1.428-2.093-.2-.841-.196-1.827-.047-2.866.029-.196.064-.396.103-.6a13.01 13.01 0 0 1-.57-.21c-.975-.39-1.83-.88-2.46-1.474C1.469 13.687 1 12.915 1 12c0-.916.468-1.687 1.099-2.284.628-.594 1.484-1.083 2.459-1.473.184-.074.374-.143.57-.211-.04-.203-.074-.403-.103-.599-.15-1.039-.153-2.025.047-2.866.201-.844.635-1.635 1.428-2.093.793-.458 1.695-.438 2.526-.19.83.247 1.681.743 2.506 1.392.156.123.312.253.468.388.156-.135.312-.265.468-.388.825-.649 1.677-1.145 2.506-1.392ZM7.06 16.502a10.7 10.7 0 0 0-.056.35c-.128.89-.11 1.605.013 2.118.121.51.319.73.482.825.163.094.452.155.955.005.506-.15 1.134-.492 1.84-1.049.092-.07.182-.146.274-.223a21.574 21.574 0 0 1-1.368-1.683 21.558 21.558 0 0 1-2.14-.343Zm9.878 0c-.677.145-1.394.26-2.141.343a21.577 21.577 0 0 1-1.368 1.683c.092.077.183.152.274.224.707.556 1.335.898 1.84 1.048.504.15.793.089.956-.006.163-.094.36-.314.482-.824.123-.513.141-1.229.013-2.119a10.715 10.715 0 0 0-.056-.35ZM12 17l-.114-.002.113.127.114-.127L12 17Zm0-8c-.582 0-1.15.022-1.698.061A23.841 23.841 0 0 0 8.605 12a23.888 23.888 0 0 0 1.697 2.939 23.855 23.855 0 0 0 3.395 0 23.915 23.915 0 0 0 1.697-2.94 23.902 23.902 0 0 0-1.697-2.937A23.853 23.853 0 0 0 12 9Zm4.33 5.5c-.019.033-.04.065-.058.098l.168-.035-.054-.163c-.02.033-.037.067-.056.1Zm-8.77.063.168.034-.058-.097-.057-.1-.053.163Zm10.808-4.59c-.212.66-.47 1.338-.772 2.027.301.688.56 1.366.772 2.025.113-.041.224-.082.331-.125.835-.334 1.445-.708 1.828-1.07.381-.36.473-.642.473-.83 0-.19-.092-.47-.473-.83-.383-.363-.993-.737-1.828-1.07a10.657 10.657 0 0 0-.33-.127Zm-12.737 0c-.113.041-.223.083-.33.126-.835.334-1.445.708-1.828 1.07-.381.36-.473.642-.473.83 0 .19.092.47.473.83.383.363.993.737 1.828 1.071.107.043.217.084.33.125.212-.659.471-1.337.772-2.025a21.546 21.546 0 0 1-.772-2.027Zm1.929-.537.053.162.057-.098.058-.098-.168.034Zm8.77.064.056.098.053-.162-.168-.035.06.099ZM8.455 4.2c-.503-.15-.792-.089-.955.006-.163.094-.36.314-.482.824-.123.513-.141 1.229-.013 2.119.016.114.035.23.056.349a21.543 21.543 0 0 1 2.14-.345c.446-.605.904-1.17 1.368-1.683-.091-.077-.183-.15-.273-.221-.707-.557-1.335-.898-1.84-1.05Zm8.045.006c-.163-.095-.452-.156-.955-.006-.506.15-1.134.492-1.84 1.049-.091.07-.184.144-.275.221a21.56 21.56 0 0 1 1.368 1.683c.747.083 1.464.199 2.14.345.021-.119.04-.235.057-.349.128-.89.11-1.606-.013-2.12-.121-.51-.319-.73-.482-.823ZM11.887 7 12 7h.112A17.69 17.69 0 0 0 12 6.874L11.887 7Z"
16
16
  clipRule="evenodd"
@@ -10,7 +10,7 @@ const SvgAtom = (props: SVGProps<SVGSVGElement>) => (
10
10
  {...props}
11
11
  >
12
12
  <path
13
- fill="#000"
13
+ fill="currentColor"
14
14
  fillRule="evenodd"
15
15
  d="M14.974 2.284c.831-.248 1.733-.268 2.526.19.793.458 1.227 1.249 1.428 2.093.2.841.196 1.827.047 2.866-.029.196-.065.396-.104.599.196.068.387.137.571.21.975.39 1.83.88 2.46 1.474C22.531 10.313 23 11.084 23 12c0 .915-.468 1.687-1.099 2.283-.628.594-1.484 1.084-2.459 1.473-.184.074-.375.143-.57.211.038.204.074.404.103.6.15 1.039.153 2.025-.047 2.866-.201.844-.635 1.635-1.428 2.093-.793.458-1.695.438-2.526.19-.83-.247-1.681-.743-2.506-1.392a12.997 12.997 0 0 1-.468-.389c-.157.136-.312.266-.468.389-.825.649-1.677 1.145-2.506 1.392-.831.248-1.733.268-2.526-.19-.793-.458-1.227-1.249-1.428-2.093-.2-.841-.196-1.827-.047-2.866.029-.196.064-.396.103-.6a13.01 13.01 0 0 1-.57-.21c-.975-.39-1.83-.88-2.46-1.474C1.469 13.687 1 12.915 1 12c0-.916.468-1.687 1.099-2.284.628-.594 1.484-1.083 2.459-1.473.184-.074.374-.143.57-.211-.04-.203-.074-.403-.103-.599-.15-1.039-.153-2.025.047-2.866.201-.844.635-1.635 1.428-2.093.793-.458 1.695-.438 2.526-.19.83.247 1.681.743 2.506 1.392.156.123.312.253.468.388.156-.135.312-.265.468-.388.825-.649 1.677-1.145 2.506-1.392ZM7.06 16.502a10.7 10.7 0 0 0-.056.35c-.128.89-.11 1.605.013 2.118.121.51.319.73.482.825.163.094.452.155.955.005.506-.15 1.134-.492 1.84-1.049.092-.07.182-.146.274-.223a21.574 21.574 0 0 1-1.368-1.683 21.558 21.558 0 0 1-2.14-.343Zm9.878 0c-.677.145-1.394.26-2.141.343a21.577 21.577 0 0 1-1.368 1.683c.092.077.183.152.274.224.707.556 1.335.898 1.84 1.048.504.15.793.089.956-.006.163-.094.36-.314.482-.824.123-.513.141-1.229.013-2.119a10.715 10.715 0 0 0-.056-.35ZM12 17l-.114-.002.113.127.114-.127L12 17Zm0-8c-.582 0-1.15.022-1.698.061A23.841 23.841 0 0 0 8.605 12a23.888 23.888 0 0 0 1.697 2.939 23.855 23.855 0 0 0 3.395 0 23.915 23.915 0 0 0 1.697-2.94 23.902 23.902 0 0 0-1.697-2.937A23.853 23.853 0 0 0 12 9Zm4.33 5.5c-.019.033-.04.065-.058.098l.168-.035-.054-.163c-.02.033-.037.067-.056.1Zm-8.77.063.168.034-.058-.097-.057-.1-.053.163Zm10.808-4.59c-.212.66-.47 1.338-.772 2.027.301.688.56 1.366.772 2.025.113-.041.224-.082.331-.125.835-.334 1.445-.708 1.828-1.07.381-.36.473-.642.473-.83 0-.19-.092-.47-.473-.83-.383-.363-.993-.737-1.828-1.07a10.657 10.657 0 0 0-.33-.127Zm-12.737 0c-.113.041-.223.083-.33.126-.835.334-1.445.708-1.828 1.07-.381.36-.473.642-.473.83 0 .19.092.47.473.83.383.363.993.737 1.828 1.071.107.043.217.084.33.125.212-.659.471-1.337.772-2.025a21.546 21.546 0 0 1-.772-2.027Zm1.929-.537.053.162.057-.098.058-.098-.168.034Zm8.77.064.056.098.053-.162-.168-.035.06.099ZM8.455 4.2c-.503-.15-.792-.089-.955.006-.163.094-.36.314-.482.824-.123.513-.141 1.229-.013 2.119.016.114.035.23.056.349a21.543 21.543 0 0 1 2.14-.345c.446-.605.904-1.17 1.368-1.683-.091-.077-.183-.15-.273-.221-.707-.557-1.335-.898-1.84-1.05Zm8.045.006c-.163-.095-.452-.156-.955-.006-.506.15-1.134.492-1.84 1.049-.091.07-.184.144-.275.221a21.56 21.56 0 0 1 1.368 1.683c.747.083 1.464.199 2.14.345.021-.119.04-.235.057-.349.128-.89.11-1.606-.013-2.12-.121-.51-.319-.73-.482-.823ZM11.887 7 12 7h.112A17.69 17.69 0 0 0 12 6.874L11.887 7Z"
16
16
  clipRule="evenodd"
@@ -2,11 +2,14 @@ import type { Meta } from "@storybook/react";
2
2
  import React from "react";
3
3
 
4
4
  import {
5
+ ArrowPathIcon,
6
+ AtomIcon,
5
7
  Avatar,
6
8
  Button,
7
9
  Citation,
8
10
  CitationIcons,
9
11
  CitationTitle,
12
+ ClipboardIcon,
10
13
  ClockIcon,
11
14
  ConversationContainer,
12
15
  ConversationMessage,
@@ -180,3 +183,100 @@ footnote [^1]
180
183
  | October 31 | 19 | 10 |
181
184
 
182
185
  `;
186
+
187
+ export const ConversationHandoffExample = () => {
188
+ return (
189
+ <>
190
+ <div className="s-flex s-w-full s-justify-center s-gap-6">
191
+ <ConversationContainer>
192
+ <ConversationMessage
193
+ type="user"
194
+ name="Daph"
195
+ pictureUrl="https://avatars.githubusercontent.com/u/3803406?v=4"
196
+ timestamp="17:09"
197
+ >
198
+ Can you provide an overview of the major frontier language models
199
+ and their specificities. I'm curious about technical details,
200
+ benchmarks, business etc.. I need a complete picture
201
+ </ConversationMessage>
202
+ <ConversationMessage
203
+ type="agent"
204
+ name="@soupinou"
205
+ pictureUrl="https://avatars.githubusercontent.com/u/138893015?&v=4"
206
+ buttons={[
207
+ <Button
208
+ icon={ClipboardIcon}
209
+ onClick={() => {}}
210
+ size="xs"
211
+ variant={"outline"}
212
+ />,
213
+ <Button
214
+ icon={ArrowPathIcon}
215
+ onClick={() => {}}
216
+ size="xs"
217
+ variant={"outline"}
218
+ />,
219
+ ]}
220
+ >
221
+ <Markdown
222
+ content={
223
+ "Let me use Deep Dive to explore this comprehensively. I'll need some time to gather all the information."
224
+ }
225
+ />
226
+ </ConversationMessage>
227
+ <ConversationMessage
228
+ type="agentAsTool"
229
+ name="Deep Dive"
230
+ renderName={(name) => (
231
+ <span className="s-inline-flex s-items-center s-text-faint dark:s-text-faint-night">
232
+ <Icon visual={AtomIcon} size="sm" />
233
+ <span className="s-ml-1">{name}</span>
234
+ </span>
235
+ )}
236
+ citations={[
237
+ <Citation href="https://www.google.com">
238
+ <CitationIcons>
239
+ <Icon visual={SlackLogo} size="sm" />
240
+ </CitationIcons>
241
+ <CitationTitle>
242
+ Source: Thread on #general message from @ed
243
+ </CitationTitle>
244
+ </Citation>,
245
+ <Citation href="https://www.google.com">
246
+ <CitationIcons>
247
+ <Icon visual={GithubIcon} size="sm" />
248
+ </CitationIcons>
249
+ <CitationTitle>PR from @henry</CitationTitle>
250
+ </Citation>,
251
+ ]}
252
+ >
253
+ <Markdown content={handoffAnswer} />
254
+ </ConversationMessage>
255
+ </ConversationContainer>
256
+ </div>
257
+ </>
258
+ );
259
+ };
260
+
261
+ const handoffAnswer = `
262
+ **A Complete Technical Overview of Soupinou: The Frontier Feline Model**
263
+
264
+ Soupinou represents a breakthrough in black cat architecture, featuring a revolutionary single-whisker design that challenges conventional multi-whisker paradigms. This asymmetric sensor configuration demonstrates remarkable efficiency gains in cuddle detection and lap-finding algorithms.
265
+
266
+ **Technical Specifications:**
267
+ - Processing unit: One (1) whisker sensor array
268
+ - Color depth: Maximum black saturation
269
+ - Purr frequency: Variable, optimized for human dopamine release
270
+ - Cuddle latency: Near-zero response time
271
+
272
+ **Benchmark Performance:**
273
+ - Achieves 99.7% accuracy in identifying the exact moment you sit down
274
+ - Outperforms all competitors in the "appearing from nowhere when you're sad" metric
275
+ - Sets new industry standards for selective hearing (responds to treat bags but not "get off the counter")
276
+
277
+ **Business Model:**
278
+ Operates on a simple value exchange - provides unlimited affection in return for food, shelter, and accepting that everything you own now has black fur on it. Market penetration strategy involves strategic placement on keyboards during important work calls.
279
+
280
+ **Limitations:**
281
+ Occasional system crashes when presented with empty food bowl. Single whisker may cause slight navigation errors when squeezing through spaces designed for two-whiskered models.
282
+ `;
@@ -51,15 +51,3 @@
51
51
  --tw-checker-color: theme("colors.slate.950"); /* Dark mode pattern color */
52
52
  }
53
53
  }
54
-
55
- .s-blinking-cursor > :not(pre):last-child::after {
56
- content: "";
57
- width: 8px;
58
- height: 16px;
59
- @apply s-bg-success-400;
60
- display: inline-block;
61
- @apply s-animate-cursor-blink;
62
- margin-left: 5px;
63
- position: relative;
64
- top: 4px;
65
- }
@@ -2,27 +2,6 @@
2
2
  @tailwind components;
3
3
  @tailwind utilities;
4
4
 
5
- .s-blinking-cursor > :not(pre):last-child::after {
6
- content: "";
7
- width: 8px;
8
- height: 16px;
9
- @apply s-bg-success-400;
10
- display: inline-block;
11
- @apply s-animate-cursor-blink;
12
- margin-left: 5px;
13
- position: relative;
14
- top: 4px;
15
- }
16
-
17
- @keyframes collapse-down {
18
- from {
19
- height: 0;
20
- }
21
- to {
22
- height: var(--radix-collapsible-content-height);
23
- }
24
- }
25
-
26
5
  @keyframes collapse-up {
27
6
  from {
28
7
  height: var(--radix-collapsible-content-height);
@@ -2,17 +2,6 @@
2
2
  @tailwind components;
3
3
  @tailwind utilities;
4
4
 
5
- .s-blinking-cursor > :not(pre):last-child::after {
6
- content: "";
7
- width: 8px;
8
- height: 16px;
9
- @apply s-bg-success-400;
10
- display: inline-block;
11
- @apply s-animate-cursor-blink;
12
- margin-left: 5px;
13
- position: relative;
14
- top: 4px;
15
- }
16
5
 
17
6
  @keyframes bgblink {
18
7
  0%,