Skip to content

Commit

Permalink
Merge branch 'main' of github.com:twilio-labs/paste into docs-icons
Browse files Browse the repository at this point in the history
  • Loading branch information
krisantrobus committed Jan 22, 2025
2 parents d8c4be2 + f3a48a4 commit 523ec87
Show file tree
Hide file tree
Showing 24 changed files with 1,073 additions and 108 deletions.
6 changes: 6 additions & 0 deletions .changeset/tough-moles-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@twilio-paste/ai-chat-log": minor
"@twilio-paste/core": minor
---

[AI Chat Log] added optional typewriter animation to AIChatMessageBody
3 changes: 0 additions & 3 deletions .github/workflows/on_pull_request_open.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ jobs:
# List of all teams for dsys
team: "[Design Systems PD,design-systems,Design Systems Eng,Design Systems Eng Leads]"

- name: Debug in group
run: echo "${{ github.actor }} is team member ${{ steps.teamAffiliation.outputs.isTeamMember }}"

- name: Auto contribution labeler
if: ${{ steps.teamAffiliation.outputs.isTeamMember == 'false' }}
uses: actions/labeler@v5
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
"db:reset": "yarn supabase db reset"
},
"devDependencies": {
"supabase": "^1.204.3"
"supabase": "^2.6.8"
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
create extension if not exists "vector" with schema "public" version '0.5.0';
create extension if not exists "vector" with schema "public";

create sequence "public"."page_id_seq";

Expand Down
136 changes: 130 additions & 6 deletions apps/backend/supabase/schema.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,19 @@ export type Database = {
[_ in never]: never
}
Functions: {
binary_quantize:
| {
Args: {
"": string
}
Returns: unknown
}
| {
Args: {
"": unknown
}
Returns: unknown
}
get_page_parents: {
Args: {
page_id: number
Expand All @@ -236,18 +249,104 @@ export type Database = {
meta: Json
}[]
}
halfvec_avg: {
Args: {
"": number[]
}
Returns: unknown
}
halfvec_out: {
Args: {
"": unknown
}
Returns: unknown
}
halfvec_send: {
Args: {
"": unknown
}
Returns: string
}
halfvec_typmod_in: {
Args: {
"": unknown[]
}
Returns: number
}
hnsw_bit_support: {
Args: {
"": unknown
}
Returns: unknown
}
hnsw_halfvec_support: {
Args: {
"": unknown
}
Returns: unknown
}
hnsw_sparsevec_support: {
Args: {
"": unknown
}
Returns: unknown
}
hnswhandler: {
Args: {
"": unknown
}
Returns: unknown
}
ivfflat_bit_support: {
Args: {
"": unknown
}
Returns: unknown
}
ivfflat_halfvec_support: {
Args: {
"": unknown
}
Returns: unknown
}
ivfflathandler: {
Args: {
"": unknown
}
Returns: unknown
}
l2_norm:
| {
Args: {
"": unknown
}
Returns: number
}
| {
Args: {
"": unknown
}
Returns: number
}
l2_normalize:
| {
Args: {
"": string
}
Returns: string
}
| {
Args: {
"": unknown
}
Returns: unknown
}
| {
Args: {
"": unknown
}
Returns: unknown
}
match_discussions: {
Args: {
embedding: string
Expand Down Expand Up @@ -320,6 +419,24 @@ export type Database = {
count: number
}[]
}
sparsevec_out: {
Args: {
"": unknown
}
Returns: unknown
}
sparsevec_send: {
Args: {
"": unknown
}
Returns: string
}
sparsevec_typmod_in: {
Args: {
"": unknown[]
}
Returns: number
}
upsert_story_and_create_story_render: {
Args: {
_storybook_id: string
Expand All @@ -338,12 +455,19 @@ export type Database = {
}
Returns: string
}
vector_dims: {
Args: {
"": string
}
Returns: number
}
vector_dims:
| {
Args: {
"": string
}
Returns: number
}
| {
Args: {
"": unknown
}
Returns: number
}
vector_norm: {
Args: {
"": string
Expand Down
6 changes: 3 additions & 3 deletions cypress/integration/sitemap-vrt/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ export const SITEMAP = [
"/components/input/",
"/components/input/api",
"/components/input/changelog",
"components/keyboard-key",
"components/keyboard-key/api",
"components/keyboard-key/changelog",
"/components/keyboard-key",
"/components/keyboard-key/api",
"/components/keyboard-key/changelog",
"/components/label/",
"/components/label/api",
"/components/label/changelog",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { HTMLPasteProps } from "@twilio-paste/types";
import * as React from "react";

import { AIMessageContext } from "./AIMessageContext";
import { useAnimatedText } from "./utils";

const Sizes: Record<string, BoxStyleProps> = {
default: {
Expand Down Expand Up @@ -35,11 +36,59 @@ export interface AIChatMessageBodyProps extends HTMLPasteProps<"div"> {
* @memberof AIChatMessageBodyProps
*/
size?: "default" | "fullScreen";
/**
* Whether the text should be animated with type writer effect
*
* @default false
* @type {boolean}
* @memberof AIChatMessageBodyProps
*/
animated?: boolean;
/**
* A callback when the animation is started
*
* @default false
* @type {() => void}
* @memberof AIChatMessageBodyProps
*/
onAnimationStart?: () => void;
/**
* A callback when the animation is complete
*
* @default false
* @type {() => void}
* @memberof AIChatMessageBodyProps
*/
onAnimationEnd?: () => void;
}

export const AIChatMessageBody = React.forwardRef<HTMLDivElement, AIChatMessageBodyProps>(
({ children, size = "default", element = "AI_CHAT_MESSAGE_BODY", ...props }, ref) => {
(
{
children,
size = "default",
element = "AI_CHAT_MESSAGE_BODY",
animated = false,
onAnimationEnd,
onAnimationStart,
...props
},
ref,
) => {
const { id } = React.useContext(AIMessageContext);
const [showAnimation] = React.useState(animated && children !== undefined);
const animationSpeed = size === "fullScreen" ? 8 : 10;
const { animatedChildren, isAnimating } = useAnimatedText(children, animationSpeed, showAnimation);

React.useEffect(() => {
if (onAnimationStart && animated && isAnimating) {
onAnimationStart();
}

if (animated && !isAnimating && onAnimationEnd) {
onAnimationEnd();
}
}, [isAnimating, showAnimation]);

return (
<Box
Expand All @@ -55,7 +104,7 @@ export const AIChatMessageBody = React.forwardRef<HTMLDivElement, AIChatMessageB
whiteSpace="pre-wrap"
id={id}
>
{children}
{animatedChildren}
</Box>
);
},
Expand Down
79 changes: 79 additions & 0 deletions packages/paste-core/components/ai-chat-log/src/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useEffect, useState } from "react";

// Hook to animate text content of React elements
export const useAnimatedText = (
children: React.ReactNode,
speed = 10,
enabled = true,
): { animatedChildren: React.ReactNode; isAnimating: boolean } => {
const [animatedChildren, setAnimatedChildren] = useState<React.ReactNode>();
const [textIndex, setTextIndex] = useState(0);

// Effect to increment textIndex at a specified speed
useEffect(() => {
const interval = setInterval(() => {
setTextIndex((prevIndex) => prevIndex + 1);
}, speed);

return () => clearInterval(interval);
}, [speed]);

// Function to calculate the total length of text within nested elements
const calculateTotalTextLength = (nodes: React.ReactNode): number => {
let length = 0;
React.Children.forEach(nodes, (child) => {
if (typeof child === "string") {
length += child.length;
} else if (React.isValidElement(child)) {
length += calculateTotalTextLength(child.props.children);
}
});
return length;
};

// Function to recursively clone children and apply text animation
const cloneChildren = (nodes: React.ReactNode, currentIndex: number): React.ReactNode => {
let currentTextIndex = currentIndex;
return React.Children.map(nodes, (child) => {
if (typeof child === "string") {
// Only include text nodes if their animation has started
if (currentTextIndex > 0) {
const visibleText = child.slice(0, currentTextIndex);
currentTextIndex -= child.length;
return visibleText;
}
return null;
} else if (React.isValidElement(child)) {
const totalChildTextLength = calculateTotalTextLength(child.props.children);
// Only include elements if their text animation has started
if (currentTextIndex > 0) {
const clonedChild = React.cloneElement(child, {}, cloneChildren(child.props.children, currentTextIndex));
currentTextIndex -= totalChildTextLength;
return clonedChild;
} else if (currentTextIndex === 0 && totalChildTextLength === 0) {
return child;
}
return null;
}

return child;
});
};

// Effect to update animated children based on the current text index
useEffect(() => {
if (enabled) {
const totaLength = calculateTotalTextLength(children);
if (textIndex <= totaLength) {
setAnimatedChildren(cloneChildren(children, textIndex));
}
}
}, [children, textIndex, enabled]);

return {
animatedChildren: enabled ? animatedChildren : children,
isAnimating: enabled && textIndex < calculateTotalTextLength(children),
};
};

export default useAnimatedText;
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const BotMessage = (props): JSX.Element => {
) : (
<AIChatMessage variant="bot">
<AIChatMessageAuthor aria-label="Bot said">Good Bot</AIChatMessageAuthor>
<AIChatMessageBody>{props.message as string}</AIChatMessageBody>
<AIChatMessageBody animated>{props.message as string}</AIChatMessageBody>
</AIChatMessage>
);
};
Expand Down
Loading

0 comments on commit 523ec87

Please sign in to comment.