Skip to content
This repository has been archived by the owner on Feb 5, 2025. It is now read-only.

feat: working simple streaming (DSN-2619) #485

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/documentation/src/components/ChatScript/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export const ChatScript = ({ projectID, embedded = false }: { projectID: string;
const s = d.getElementsByTagName(t)[0];
v.onload = function () {
window.voiceflow.chat.load({
url: 'https://general-runtime-review-new-widget.us-2.development.voiceflow.com',
verify: { projectID: "${projectID}" },
assistant: {
url: 'https://general-runtime.voiceflow.com',
verify: { projectID: "${projectID}" },
assistant: {
stylesheet: '../../bundle/style.css',
}
${embedded ? ', render: { mode: "embedded", target: document.getElementById("chat_embed") }' : ''}
Expand Down
2 changes: 1 addition & 1 deletion apps/documentation/src/pages/_meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export default {
title: 'Chat',
type: 'page',
// eslint-disable-next-line no-secrets/no-secrets
href: '/chat?projectID=672a5d31e7c18363c7363e3f',
href: '/chat?projectID=67646b005e623ff04fdf1f05',
},
};
4 changes: 3 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import baseConfig from '@voiceflow/eslint-config';

/** @type {import('eslint').Linter.FlatConfig[]} */
/** @type {import('eslint').Linter.Config[]} */
export default [
...baseConfig,
{
Expand All @@ -10,6 +10,8 @@ export default [
{
rules: {
'no-console': ['error', { allow: ['info', 'warn', 'error'] }],

'sonarjs/no-one-iteration-loop': 'off',
},
},
];
2 changes: 2 additions & 0 deletions packages/chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"test:unit": "yarn g:vitest run --coverage"
},
"dependencies": {
"@paralleldrive/cuid2": "2.2.2",
"@vanilla-extract/css": "1.15.5",
"@vanilla-extract/recipes": "0.5.5",
"@voiceflow/base-types": "2.113.1",
Expand All @@ -94,6 +95,7 @@
"react-textarea-autosize": "8.5.3",
"regenerator-runtime": "0.13.11",
"remark-gfm": "4.0.0",
"remeda": "1.0.1",
"slate": "0.94.1",
"ts-pattern": "4.3.0",
"zod": "3.22.4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const agentMessageContainer = style({
color: COLORS.NEUTRAL_DARK[900],
fontFamily: THEME.fontFamily,
position: 'relative',
minHeight: '41px',
fontSize: '14px',
lineHeight: '20px',
borderRadius: SIZES.radius.sm,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export const Small: Story = {
},
};

export const Empty: Story = {
args: {
text: '',
},
};

export const AIGenerated: Story = {
args: {
text: SAMPLE_SLATE_TEXT as unknown as string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export enum FeedbackButtonVariant {
}

export interface IFeedbackButton {
onClick: (feedback: FeedbackName) => void;
onClick?: (feedback: FeedbackName) => void;
variant?: FeedbackButtonVariant;
active?: boolean;
textContent?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/chat/src/components/FeedbackButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const FeedbackButton: React.FC<IFeedbackButton> = ({ variant, onClick, te
};

const handleOnClick = (type: FeedbackName) => {
onClick(type);
onClick?.(type);
setIsPositiveOrNegativeSelected(type);
};

Expand Down
29 changes: 13 additions & 16 deletions packages/chat/src/components/SystemResponse/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ export const SystemResponse: React.FC<SystemResponseProps> = ({
aiDisclaimer,
Message = SystemMessage,
}) => {
const runtime = useContext(RuntimeStateAPIContext);

const { showIndicator, visibleMessages, complete } = useAnimatedMessages({
// TODO: undo changes to this file
const { showIndicator, complete } = useAnimatedMessages({
messages,
isLast,
});

useAutoScroll([showIndicator, complete, visibleMessages.length]);
const runtime = useContext(RuntimeStateAPIContext);
useAutoScroll([showIndicator, complete, messages.length]);

if (!messages.length && !actions.length) return null;

Expand All @@ -108,9 +108,9 @@ export const SystemResponse: React.FC<SystemResponseProps> = ({
}
return -1;
};
const lastTextMessageIndex = getLastTextMessageIndex(visibleMessages);
const lastTextMessageIndex = getLastTextMessageIndex(messages);

const allTextContentForMessage = visibleMessages.reduce<string>((acc, message) => {
const allTextContentForMessage = messages.reduce<string>((acc, message) => {
if (message.type === MessageType.TEXT) {
return (
acc + (acc ? '\n' : '') + (typeof message.text !== 'string' ? serializeToText(message.text) : message.text)
Expand All @@ -119,22 +119,19 @@ export const SystemResponse: React.FC<SystemResponseProps> = ({
return acc;
}, '');

const messagesDisplayedToUser = messages.filter((message) => message.type !== MessageType.END);

return (
<MessageContainer isLast={isLast}>
{visibleMessages.map((message, index) => {
{messages.map((message, index) => {
const endConversation = message?.type === MessageType.END;
if (endConversation) {
return <EndState />;
}

const lastMessageInGroup = index === visibleMessages.length - 1;

// Showing feedback on previous messages that were in the chat
const showFeedback = index === lastTextMessageIndex; // lastMessageInGroup && message.type === MessageType.TEXT;

// Showing feedback on the most recent system message of the chat
const addFeedback = feedback && isLast && complete && lastMessageInGroup;

const lastMessageInGroup = index === messagesDisplayedToUser.length - 1;
const showFeedback = index === lastTextMessageIndex;
const addFeedback = feedback && isLast && complete && showFeedback;
return (
<>
<Message
Expand All @@ -148,7 +145,7 @@ export const SystemResponse: React.FC<SystemResponseProps> = ({
key={index}
aiDisclaimer={aiDisclaimer}
/>
{addFeedback && message.type !== MessageType.CAROUSEL && (
{showFeedback && message.type !== MessageType.CAROUSEL && (
<div className={feedbackContainer({ withAvatar: !!avatar })}>
<FeedbackButton
{...feedback}
Expand Down
14 changes: 2 additions & 12 deletions packages/chat/src/components/SystemResponse/styles.css.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
import { keyframes, style } from '@vanilla-extract/css';
import { style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';

import { duration } from '@/styles/animations';
import { SIZES } from '@/styles/sizes';

import { SMALL_AVATAR_SIZE } from '../Avatar/styles.css';
import { fadeInSlideUp } from '../UserResponse/styles.css';

export const MESSAGE_PADDING = 12;

export const hide = style({
visibility: 'hidden',
});

const fadeInSlideUp = keyframes({
from: {
opacity: 0,
transform: 'translateY(5px)',
},
to: {
opacity: 1,
transform: 'translateY(0)',
},
});

export const systemMessageContainer = style({
display: 'flex',
alignItems: 'flex-end',
Expand Down
1 change: 1 addition & 0 deletions packages/chat/src/components/SystemResponse/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface BaseMessageProps {
export interface TextMessageProps extends BaseMessageProps {
type: StringifiedEnum<MessageType.TEXT>;
text: string | Text.SlateTextValue;
stream?: TransformStream | null;
audio?: { src: string };
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { TraceDeclaration } from '@voiceflow/sdk-runtime';
import { match } from 'ts-pattern';

import type { TextMessageProps } from '@/components/SystemResponse';
import { MessageType } from '@/components/SystemResponse/constants';

import type { RuntimeMessage } from '../messages';

enum CompletionState {
START = 'start',
CONTENT = 'content',
END = 'end',
}

interface StartEvent {
state: CompletionState.START;
}

interface EndEvent {
state: CompletionState.END;
}

interface ContentEvent {
state: CompletionState.CONTENT;
content: string;
}

interface CompletionTrace {
type: 'completion';
payload: StartEvent | EndEvent | ContentEvent;
}

export const StreamedMessage = (): TraceDeclaration<RuntimeMessage, any> => {
let message:
| (TextMessageProps & {
stream: TransformStream;
})
| null = null;

return {
canHandle: ({ type }) => type === 'completion',
handle: ({ context }, trace: CompletionTrace) => {
match(trace.payload)
.with({ state: CompletionState.START }, () => {
const stream = new TransformStream();

message = {
type: MessageType.TEXT,
text: '',
stream,
};
context.messages.push(message);
})
.with({ state: CompletionState.END }, () => {
if (!message) return;

message.stream.writable.close();
message = null;
})
.with({ state: CompletionState.CONTENT }, ({ content }) => {
if (!message) return;

const writer = message.stream.writable.getWriter();
writer.write(content);
writer.releaseLock();

message.text += content;
})
.exhaustive();

return context;
},
};
};
20 changes: 18 additions & 2 deletions packages/chat/src/contexts/RuntimeContext/useRuntimeAPI.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { RuntimeAction } from '@voiceflow/sdk-runtime';
import { VoiceflowRuntime } from '@voiceflow/sdk-runtime';
import { RuntimeClient, VoiceflowRuntime } from '@voiceflow/sdk-runtime';
import { serializeToText } from '@voiceflow/slate-serializer/text';
import { useMemo } from 'react';

Expand Down Expand Up @@ -29,6 +29,14 @@ export const useRuntimeAPI = ({
versionID,
traceHandlers = [],
}: ChatConfig & Pick<SessionOptions, 'userID'> & { traceHandlers?: typeof MESSAGE_TRACES }) => {
const client: RuntimeClient<RuntimeMessage> = useMemo(
() =>
new RuntimeClient({
baseURL: url,
traces: [...MESSAGE_TRACES, ...traceHandlers],
}),
[]
);
const runtime: VoiceflowRuntime<RuntimeMessage> = useMemo(
() =>
new VoiceflowRuntime<RuntimeMessage>({
Expand All @@ -47,6 +55,14 @@ export const useRuntimeAPI = ({
...(versionID && { versionID }),
});

const interactStream = (action: RuntimeAction) =>
client.interactStream(createContext, {
userID,
action,
projectID: verify.projectID,
...(versionID && { environment: versionID }),
});

const saveFeedback = async (name: FeedbackName, lastTurnMessages: MessageProps[], userTurn: UserTurnProps | null) => {
const aiMessages: string[] = [];

Expand Down Expand Up @@ -82,5 +98,5 @@ export const useRuntimeAPI = ({
});
};

return { interact, saveFeedback, saveTranscript };
return { interact, interactStream, saveFeedback, saveTranscript };
};
Loading
Loading