diff --git a/src/modules/AieraChat/Header/Search/index.tsx b/src/modules/AieraChat/Header/Search/index.tsx index feeb442f2..8b8f4f485 100644 --- a/src/modules/AieraChat/Header/Search/index.tsx +++ b/src/modules/AieraChat/Header/Search/index.tsx @@ -4,11 +4,11 @@ import { MicroSearch } from '@aiera/client-sdk/components/Svg/MicroSearch'; import { VirtuosoMessageListMethods } from '@virtuoso.dev/message-list'; import classNames from 'classnames'; import React, { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useState } from 'react'; -import { Message } from '../../services/messages'; import { useChatStore } from '../../store'; import { IconButton } from '../../IconButton'; +import { ChatMessage } from '../../services/messages'; -export function Search({ virtuosoRef }: { virtuosoRef: RefObject> }) { +export function Search({ virtuosoRef }: { virtuosoRef: RefObject> }) { const { chatId, searchTerm, onSetSearchTerm } = useChatStore(); const { chatTitle, onSetTitle } = useChatStore(); const [showSearch, setShowSearch] = useState(false); diff --git a/src/modules/AieraChat/Header/index.tsx b/src/modules/AieraChat/Header/index.tsx index 77de0df75..104e6599d 100644 --- a/src/modules/AieraChat/Header/index.tsx +++ b/src/modules/AieraChat/Header/index.tsx @@ -2,14 +2,14 @@ import { MicroBars } from '@aiera/client-sdk/components/Svg/MicroBars'; import React, { RefObject } from 'react'; import { Search } from './Search'; import { VirtuosoMessageListMethods } from '@virtuoso.dev/message-list'; -import { Message } from '../services/messages'; import { IconButton } from '../IconButton'; +import { ChatMessage } from '../services/messages'; export function Header({ onOpenMenu, virtuosoRef, }: { - virtuosoRef: RefObject>; + virtuosoRef: RefObject>; onOpenMenu: () => void; }) { return ( diff --git a/src/modules/AieraChat/Messages/MessageFactory/MessagePrompt/index.tsx b/src/modules/AieraChat/Messages/MessageFactory/MessagePrompt/index.tsx new file mode 100644 index 000000000..de0987ca2 --- /dev/null +++ b/src/modules/AieraChat/Messages/MessageFactory/MessagePrompt/index.tsx @@ -0,0 +1,64 @@ +import { MicroQuestionMark } from '@aiera/client-sdk/components/Svg/MicroQuestionMark'; +import classNames from 'classnames'; +import React from 'react'; +import { useChatStore } from '../../../store'; +import { ChatMessage } from '../../../services/messages'; + +export const MessagePrompt = ({ + data, + className, + isStickyHeader, +}: { + data: ChatMessage; + className?: string; + isStickyHeader?: boolean; +}) => { + const { searchTerm } = useChatStore(); + const prompt = data.prompt; + if (!prompt) return null; + return ( +
+
+ +
+
+

+ {searchTerm + ? prompt + .split(new RegExp(`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi')) + .map((part, index) => + part.toLowerCase() === searchTerm.toLowerCase() ? ( + + {part} + + ) : ( + part + ) + ) + : prompt} +

+
+
+ ); +}; diff --git a/src/modules/AieraChat/Messages/MessageFactory/index.tsx b/src/modules/AieraChat/Messages/MessageFactory/index.tsx index ba7c8edf2..33ca099de 100644 --- a/src/modules/AieraChat/Messages/MessageFactory/index.tsx +++ b/src/modules/AieraChat/Messages/MessageFactory/index.tsx @@ -1,7 +1,7 @@ import { Button } from '@aiera/client-sdk/components/Button'; import { MicroCheck } from '@aiera/client-sdk/components/Svg/MicroCheck'; import { MicroCopy } from '@aiera/client-sdk/components/Svg/MicroCopy'; -import { MicroQuestionMark } from '@aiera/client-sdk/components/Svg/MicroQuestionMark'; +import { MicroFolder } from '@aiera/client-sdk/components/Svg/MicroFolder'; import { MicroRefresh } from '@aiera/client-sdk/components/Svg/MicroRefresh'; import { MicroSparkles } from '@aiera/client-sdk/components/Svg/MicroSparkles'; import { MicroThumbDown } from '@aiera/client-sdk/components/Svg/MicroThumbDown'; @@ -14,72 +14,13 @@ import { match } from 'ts-pattern'; import { MessageListContext } from '..'; import { AddSourceDialog } from '../../AddSourceDialog'; import { IconButton } from '../../IconButton'; -import { Message } from '../../services/messages'; import { Source, useChatStore } from '../../store'; -import { MicroFolder } from '@aiera/client-sdk/components/Svg/MicroFolder'; - -export const MessagePrompt = ({ - data, - className, - isStickyHeader, -}: { - data: Message; - className?: string; - isStickyHeader?: boolean; -}) => { - const { searchTerm } = useChatStore(); - const prompt = data.prompt; - if (!prompt) return null; - return ( -
-
- -
-
-

- {searchTerm - ? prompt - .split(new RegExp(`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi')) - .map((part, index) => - part.toLowerCase() === searchTerm.toLowerCase() ? ( - - {part} - - ) : ( - part - ) - ) - : prompt} -

-
-
- ); -}; +import { MessagePrompt } from './MessagePrompt'; +import { ChatMessage } from '../../services/messages'; type MessageFeedback = 'pos' | 'neg' | undefined; -const ItemContent = ({ data, onReRun }: { onReRun: (k: string) => void; data: Message }) => { +const ItemContent = ({ data, onReRun }: { onReRun: (k: string) => void; data: ChatMessage }) => { const { onSelectSource, searchTerm } = useChatStore(); const [copied, setCopied] = useState(false); // TODO getMessage from network / cache for managing feedback @@ -216,11 +157,11 @@ const ItemContent = ({ data, onReRun }: { onReRun: (k: string) => void; data: Me Icon={MicroFolder} onClick={() => setShowSourceDialog(true)} > - {localSources.length} + {localSources.length || ''} void; data: Me ); }; -const SourcesResponse = ({ data, onConfirm }: { onConfirm: (k: string) => void; data: Message }) => { +const SourcesResponse = ({ data, onConfirm }: { onConfirm: (k: string) => void; data: ChatMessage }) => { const { onSelectSource, onAddSource } = useChatStore(); const [showSourceDialog, setShowSourceDialog] = useState(false); const [localSources, setLocalSources] = useState([ @@ -354,7 +295,7 @@ const SourcesResponse = ({ data, onConfirm }: { onConfirm: (k: string) => void; ); }; -export const MessageFactory: VirtuosoMessageListProps['ItemContent'] = ({ +export const MessageFactory: VirtuosoMessageListProps['ItemContent'] = ({ data, context, }) => { diff --git a/src/modules/AieraChat/Messages/SuggestedPrompts/index.tsx b/src/modules/AieraChat/Messages/SuggestedPrompts/index.tsx index ac8f9672b..62d3b0d57 100644 --- a/src/modules/AieraChat/Messages/SuggestedPrompts/index.tsx +++ b/src/modules/AieraChat/Messages/SuggestedPrompts/index.tsx @@ -1,12 +1,12 @@ import React, { useCallback, useState } from 'react'; import { VirtuosoMessageListProps } from '@virtuoso.dev/message-list'; -import { Message } from '../../services/messages'; import { useSuggestedPrompts } from '../../services/suggestedPrompts'; import { LoadingSpinner } from '@aiera/client-sdk/components/LoadingSpinner'; import { Chevron } from '@aiera/client-sdk/components/Svg/Chevron'; import { MessageListContext } from '..'; +import { ChatMessage } from '../../services/messages'; -export const SuggestedPrompts: VirtuosoMessageListProps['EmptyPlaceholder'] = ({ +export const SuggestedPrompts: VirtuosoMessageListProps['EmptyPlaceholder'] = ({ context, }: { context: MessageListContext; diff --git a/src/modules/AieraChat/Messages/index.tsx b/src/modules/AieraChat/Messages/index.tsx index ce71667ad..493358397 100644 --- a/src/modules/AieraChat/Messages/index.tsx +++ b/src/modules/AieraChat/Messages/index.tsx @@ -10,15 +10,16 @@ import { import classNames from 'classnames'; import React, { Fragment, RefObject, useCallback, useEffect, useMemo } from 'react'; import { Prompt } from '../Prompt'; -import { Message, MessageStatus, useChatMessages } from '../services/messages'; +import { ChatMessage, ChatMessageStatus, useChatMessages } from '../services/messages'; import { useChatStore } from '../store'; -import { MessageFactory, MessagePrompt } from './MessageFactory'; +import { MessageFactory } from './MessageFactory'; import './styles.css'; import { SuggestedPrompts } from './SuggestedPrompts'; +import { MessagePrompt } from './MessageFactory/MessagePrompt'; let idCounter = 0; -function randomMessage(user: Message['user'], prompt: Message['prompt']): Message { +function randomMessage(user: ChatMessage['user'], prompt: ChatMessage['prompt']): ChatMessage { return { user, key: `${idCounter++}`, type: 'response', text: 'some other message', prompt, status: 'thinking' }; } @@ -28,8 +29,8 @@ export interface MessageListContext { onConfirm: (k: string) => void; } -const StickyHeader: VirtuosoMessageListProps['StickyHeader'] = () => { - const data: Message[] = useCurrentlyRenderedData(); +const StickyHeader: VirtuosoMessageListProps['StickyHeader'] = () => { + const data: ChatMessage[] = useCurrentlyRenderedData(); const { getScrollLocation } = useVirtuosoMethods(); const { listOffset } = getScrollLocation(); const firstPrompt = data[0]; @@ -56,7 +57,7 @@ export function Messages({ onOpenSources, virtuosoRef, }: { - virtuosoRef: RefObject>; + virtuosoRef: RefObject>; onOpenSources: () => void; }) { const { chatId, sources } = useChatStore(); @@ -77,7 +78,7 @@ export function Messages({ let counter = 0; let newMessage = ''; const interval = setInterval(() => { - let status: MessageStatus = 'thinking'; + let status: ChatMessageStatus = 'thinking'; if (counter++ > 80) { clearInterval(interval); status = 'finished'; @@ -126,7 +127,7 @@ export function Messages({ setTimeout(() => { let counter = 0; const interval = setInterval(() => { - let status: MessageStatus = 'updating'; + let status: ChatMessageStatus = 'updating'; if (counter++ > 80) { clearInterval(interval); status = 'finished'; @@ -153,7 +154,7 @@ export function Messages({ const onSubmit = useCallback( (prompt: string) => { - const myMessage: Message = { + const myMessage: ChatMessage = { user: 'me', key: `${idCounter++}`, text: prompt, @@ -171,7 +172,7 @@ export function Messages({ if (sources.length === 0) { const newKey = `${idCounter++}`; - const sourceMessage: Message = { + const sourceMessage: ChatMessage = { user: 'other', key: newKey, text: 'sourcess', @@ -204,7 +205,7 @@ export function Messages({ setTimeout(() => { let counter = 0; const interval = setInterval(() => { - let status: MessageStatus = 'updating'; + let status: ChatMessageStatus = 'updating'; if (counter++ > 80) { clearInterval(interval); status = 'finished'; @@ -251,11 +252,11 @@ export function Messages({ ) : ( - + key={chatId || 'new'} ref={virtuosoRef} style={{ flex: 1 }} - computeItemKey={({ data }: { data: Message }) => data.key} + computeItemKey={({ data }: { data: ChatMessage }) => data.key} className="px-4 messagesScrollBars" initialLocation={{ index: 'LAST', align: 'end' }} initialData={messages} diff --git a/src/modules/AieraChat/index.tsx b/src/modules/AieraChat/index.tsx index b72d7f9df..3a71fd7e9 100644 --- a/src/modules/AieraChat/index.tsx +++ b/src/modules/AieraChat/index.tsx @@ -8,9 +8,9 @@ import { Header } from './Header'; import { Menu } from './Menu'; import { Messages } from './Messages'; import { Sources } from './Sources'; -import { Message } from './services/messages'; import { useChatStore } from './store'; import './styles.css'; +import { ChatMessage } from './services/messages'; export function AieraChat(): ReactElement { const [showMenu, setShowMenu] = useState(false); @@ -18,7 +18,7 @@ export function AieraChat(): ReactElement { const [showConfirm, setShowConfirm] = useState(false); const { selectedSource, onSelectSource } = useChatStore(); const config = useConfig(); - const virtuosoRef = useRef>(null); + const virtuosoRef = useRef>(null); const [animateTranscriptExit, setAnimateTranscriptExit] = useState(false); diff --git a/src/modules/AieraChat/services/messages.ts b/src/modules/AieraChat/services/messages.ts index a6023bb2c..de5d0421d 100644 --- a/src/modules/AieraChat/services/messages.ts +++ b/src/modules/AieraChat/services/messages.ts @@ -1,26 +1,26 @@ import { useState, useEffect, useCallback } from 'react'; -type MessageType = 'prompt' | 'sources' | 'response'; -export type MessageStatus = 'finished' | 'thinking' | 'updating' | 'confirmed'; +type ChatMessageType = 'prompt' | 'sources' | 'response'; +export type ChatMessageStatus = 'finished' | 'thinking' | 'updating' | 'confirmed'; type User = 'me' | 'other'; -export interface Message { - type: MessageType; +export interface ChatMessage { + type: ChatMessageType; key: string; text: string; - status: MessageStatus; + status: ChatMessageStatus; prompt?: string; user: User; } interface UseChatMessagesReturn { - messages: Message[]; + messages: ChatMessage[]; isLoading: boolean; error: string | null; refresh: () => void; } -const sampleMessages: Message[] = [ +const sampleMessages: ChatMessage[] = [ { key: 'msg-1', type: 'prompt', @@ -113,8 +113,8 @@ const sampleMessages: Message[] = [ }, ]; -const generateRandomMessages = (count: number): Message[] => { - const messages: Message[] = []; +const generateRandomMessages = (count: number): ChatMessage[] => { + const messages: ChatMessage[] = []; const promptMessages = sampleMessages.filter((m) => m.type === 'prompt'); const responseMessages = sampleMessages.filter((m) => m.type === 'response'); @@ -139,14 +139,14 @@ const generateRandomMessages = (count: number): Message[] => { return messages; }; -const fetchChatMessages = async (_: string): Promise => { +const fetchChatMessages = async (_: string): Promise => { await new Promise((resolve) => setTimeout(resolve, 800)); const messageCount = (Math.floor(Math.random() * 4) + 4) * 2; // Generates 8, 10, 12, or 14 messages return generateRandomMessages(messageCount); }; export const useChatMessages = (sessionId: string | null): UseChatMessagesReturn => { - const [messages, setMessages] = useState([]); + const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null);