Skip to content

Commit 8c5203e

Browse files
committed
Merge branch 'main' of github.com:twilio-labs/paste into chore-remoce-slack-integration-local
2 parents a7f97ce + f3a48a4 commit 8c5203e

File tree

24 files changed

+1073
-108
lines changed

24 files changed

+1073
-108
lines changed

.changeset/tough-moles-film.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@twilio-paste/ai-chat-log": minor
3+
"@twilio-paste/core": minor
4+
---
5+
6+
[AI Chat Log] added optional typewriter animation to AIChatMessageBody

.github/workflows/on_pull_request_open.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ jobs:
2020
# List of all teams for dsys
2121
team: "[Design Systems PD,design-systems,Design Systems Eng,Design Systems Eng Leads]"
2222

23-
- name: Debug in group
24-
run: echo "${{ github.actor }} is team member ${{ steps.teamAffiliation.outputs.isTeamMember }}"
25-
2623
- name: Auto contribution labeler
2724
if: ${{ steps.teamAffiliation.outputs.isTeamMember == 'false' }}
2825
uses: actions/labeler@v5

apps/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
"db:reset": "yarn supabase db reset"
1010
},
1111
"devDependencies": {
12-
"supabase": "^1.204.3"
12+
"supabase": "^2.6.8"
1313
}
1414
}

apps/backend/supabase/migrations/20230928013336_initial_schema.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
create extension if not exists "vector" with schema "public" version '0.5.0';
1+
create extension if not exists "vector" with schema "public";
22

33
create sequence "public"."page_id_seq";
44

apps/backend/supabase/schema.gen.ts

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,19 @@ export type Database = {
225225
[_ in never]: never
226226
}
227227
Functions: {
228+
binary_quantize:
229+
| {
230+
Args: {
231+
"": string
232+
}
233+
Returns: unknown
234+
}
235+
| {
236+
Args: {
237+
"": unknown
238+
}
239+
Returns: unknown
240+
}
228241
get_page_parents: {
229242
Args: {
230243
page_id: number
@@ -236,18 +249,104 @@ export type Database = {
236249
meta: Json
237250
}[]
238251
}
252+
halfvec_avg: {
253+
Args: {
254+
"": number[]
255+
}
256+
Returns: unknown
257+
}
258+
halfvec_out: {
259+
Args: {
260+
"": unknown
261+
}
262+
Returns: unknown
263+
}
264+
halfvec_send: {
265+
Args: {
266+
"": unknown
267+
}
268+
Returns: string
269+
}
270+
halfvec_typmod_in: {
271+
Args: {
272+
"": unknown[]
273+
}
274+
Returns: number
275+
}
276+
hnsw_bit_support: {
277+
Args: {
278+
"": unknown
279+
}
280+
Returns: unknown
281+
}
282+
hnsw_halfvec_support: {
283+
Args: {
284+
"": unknown
285+
}
286+
Returns: unknown
287+
}
288+
hnsw_sparsevec_support: {
289+
Args: {
290+
"": unknown
291+
}
292+
Returns: unknown
293+
}
239294
hnswhandler: {
240295
Args: {
241296
"": unknown
242297
}
243298
Returns: unknown
244299
}
300+
ivfflat_bit_support: {
301+
Args: {
302+
"": unknown
303+
}
304+
Returns: unknown
305+
}
306+
ivfflat_halfvec_support: {
307+
Args: {
308+
"": unknown
309+
}
310+
Returns: unknown
311+
}
245312
ivfflathandler: {
246313
Args: {
247314
"": unknown
248315
}
249316
Returns: unknown
250317
}
318+
l2_norm:
319+
| {
320+
Args: {
321+
"": unknown
322+
}
323+
Returns: number
324+
}
325+
| {
326+
Args: {
327+
"": unknown
328+
}
329+
Returns: number
330+
}
331+
l2_normalize:
332+
| {
333+
Args: {
334+
"": string
335+
}
336+
Returns: string
337+
}
338+
| {
339+
Args: {
340+
"": unknown
341+
}
342+
Returns: unknown
343+
}
344+
| {
345+
Args: {
346+
"": unknown
347+
}
348+
Returns: unknown
349+
}
251350
match_discussions: {
252351
Args: {
253352
embedding: string
@@ -320,6 +419,24 @@ export type Database = {
320419
count: number
321420
}[]
322421
}
422+
sparsevec_out: {
423+
Args: {
424+
"": unknown
425+
}
426+
Returns: unknown
427+
}
428+
sparsevec_send: {
429+
Args: {
430+
"": unknown
431+
}
432+
Returns: string
433+
}
434+
sparsevec_typmod_in: {
435+
Args: {
436+
"": unknown[]
437+
}
438+
Returns: number
439+
}
323440
upsert_story_and_create_story_render: {
324441
Args: {
325442
_storybook_id: string
@@ -338,12 +455,19 @@ export type Database = {
338455
}
339456
Returns: string
340457
}
341-
vector_dims: {
342-
Args: {
343-
"": string
344-
}
345-
Returns: number
346-
}
458+
vector_dims:
459+
| {
460+
Args: {
461+
"": string
462+
}
463+
Returns: number
464+
}
465+
| {
466+
Args: {
467+
"": unknown
468+
}
469+
Returns: number
470+
}
347471
vector_norm: {
348472
Args: {
349473
"": string

cypress/integration/sitemap-vrt/constants.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ export const SITEMAP = [
138138
"/components/input/",
139139
"/components/input/api",
140140
"/components/input/changelog",
141-
"components/keyboard-key",
142-
"components/keyboard-key/api",
143-
"components/keyboard-key/changelog",
141+
"/components/keyboard-key",
142+
"/components/keyboard-key/api",
143+
"/components/keyboard-key/changelog",
144144
"/components/label/",
145145
"/components/label/api",
146146
"/components/label/changelog",

packages/paste-core/components/ai-chat-log/src/AIChatMessageBody.tsx

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { HTMLPasteProps } from "@twilio-paste/types";
44
import * as React from "react";
55

66
import { AIMessageContext } from "./AIMessageContext";
7+
import { useAnimatedText } from "./utils";
78

89
const Sizes: Record<string, BoxStyleProps> = {
910
default: {
@@ -35,11 +36,59 @@ export interface AIChatMessageBodyProps extends HTMLPasteProps<"div"> {
3536
* @memberof AIChatMessageBodyProps
3637
*/
3738
size?: "default" | "fullScreen";
39+
/**
40+
* Whether the text should be animated with type writer effect
41+
*
42+
* @default false
43+
* @type {boolean}
44+
* @memberof AIChatMessageBodyProps
45+
*/
46+
animated?: boolean;
47+
/**
48+
* A callback when the animation is started
49+
*
50+
* @default false
51+
* @type {() => void}
52+
* @memberof AIChatMessageBodyProps
53+
*/
54+
onAnimationStart?: () => void;
55+
/**
56+
* A callback when the animation is complete
57+
*
58+
* @default false
59+
* @type {() => void}
60+
* @memberof AIChatMessageBodyProps
61+
*/
62+
onAnimationEnd?: () => void;
3863
}
3964

4065
export const AIChatMessageBody = React.forwardRef<HTMLDivElement, AIChatMessageBodyProps>(
41-
({ children, size = "default", element = "AI_CHAT_MESSAGE_BODY", ...props }, ref) => {
66+
(
67+
{
68+
children,
69+
size = "default",
70+
element = "AI_CHAT_MESSAGE_BODY",
71+
animated = false,
72+
onAnimationEnd,
73+
onAnimationStart,
74+
...props
75+
},
76+
ref,
77+
) => {
4278
const { id } = React.useContext(AIMessageContext);
79+
const [showAnimation] = React.useState(animated && children !== undefined);
80+
const animationSpeed = size === "fullScreen" ? 8 : 10;
81+
const { animatedChildren, isAnimating } = useAnimatedText(children, animationSpeed, showAnimation);
82+
83+
React.useEffect(() => {
84+
if (onAnimationStart && animated && isAnimating) {
85+
onAnimationStart();
86+
}
87+
88+
if (animated && !isAnimating && onAnimationEnd) {
89+
onAnimationEnd();
90+
}
91+
}, [isAnimating, showAnimation]);
4392

4493
return (
4594
<Box
@@ -55,7 +104,7 @@ export const AIChatMessageBody = React.forwardRef<HTMLDivElement, AIChatMessageB
55104
whiteSpace="pre-wrap"
56105
id={id}
57106
>
58-
{children}
107+
{animatedChildren}
59108
</Box>
60109
);
61110
},
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { useEffect, useState } from "react";
2+
3+
// Hook to animate text content of React elements
4+
export const useAnimatedText = (
5+
children: React.ReactNode,
6+
speed = 10,
7+
enabled = true,
8+
): { animatedChildren: React.ReactNode; isAnimating: boolean } => {
9+
const [animatedChildren, setAnimatedChildren] = useState<React.ReactNode>();
10+
const [textIndex, setTextIndex] = useState(0);
11+
12+
// Effect to increment textIndex at a specified speed
13+
useEffect(() => {
14+
const interval = setInterval(() => {
15+
setTextIndex((prevIndex) => prevIndex + 1);
16+
}, speed);
17+
18+
return () => clearInterval(interval);
19+
}, [speed]);
20+
21+
// Function to calculate the total length of text within nested elements
22+
const calculateTotalTextLength = (nodes: React.ReactNode): number => {
23+
let length = 0;
24+
React.Children.forEach(nodes, (child) => {
25+
if (typeof child === "string") {
26+
length += child.length;
27+
} else if (React.isValidElement(child)) {
28+
length += calculateTotalTextLength(child.props.children);
29+
}
30+
});
31+
return length;
32+
};
33+
34+
// Function to recursively clone children and apply text animation
35+
const cloneChildren = (nodes: React.ReactNode, currentIndex: number): React.ReactNode => {
36+
let currentTextIndex = currentIndex;
37+
return React.Children.map(nodes, (child) => {
38+
if (typeof child === "string") {
39+
// Only include text nodes if their animation has started
40+
if (currentTextIndex > 0) {
41+
const visibleText = child.slice(0, currentTextIndex);
42+
currentTextIndex -= child.length;
43+
return visibleText;
44+
}
45+
return null;
46+
} else if (React.isValidElement(child)) {
47+
const totalChildTextLength = calculateTotalTextLength(child.props.children);
48+
// Only include elements if their text animation has started
49+
if (currentTextIndex > 0) {
50+
const clonedChild = React.cloneElement(child, {}, cloneChildren(child.props.children, currentTextIndex));
51+
currentTextIndex -= totalChildTextLength;
52+
return clonedChild;
53+
} else if (currentTextIndex === 0 && totalChildTextLength === 0) {
54+
return child;
55+
}
56+
return null;
57+
}
58+
59+
return child;
60+
});
61+
};
62+
63+
// Effect to update animated children based on the current text index
64+
useEffect(() => {
65+
if (enabled) {
66+
const totaLength = calculateTotalTextLength(children);
67+
if (textIndex <= totaLength) {
68+
setAnimatedChildren(cloneChildren(children, textIndex));
69+
}
70+
}
71+
}, [children, textIndex, enabled]);
72+
73+
return {
74+
animatedChildren: enabled ? animatedChildren : children,
75+
isAnimating: enabled && textIndex < calculateTotalTextLength(children),
76+
};
77+
};
78+
79+
export default useAnimatedText;

packages/paste-core/components/ai-chat-log/stories/composer.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const BotMessage = (props): JSX.Element => {
5959
) : (
6060
<AIChatMessage variant="bot">
6161
<AIChatMessageAuthor aria-label="Bot said">Good Bot</AIChatMessageAuthor>
62-
<AIChatMessageBody>{props.message as string}</AIChatMessageBody>
62+
<AIChatMessageBody animated>{props.message as string}</AIChatMessageBody>
6363
</AIChatMessage>
6464
);
6565
};

0 commit comments

Comments
 (0)