ui: feedback methods and ui

This commit is contained in:
nick-delirium 2025-04-25 17:02:30 +02:00
parent c6357e16e9
commit f1fc79c864
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
4 changed files with 50 additions and 10 deletions

View file

@ -69,11 +69,12 @@ export class ChatManager {
}
export interface BotChunk {
stage: 'chart' | 'final' | 'title';
stage: 'start' | 'chart' | 'final' | 'title';
content: string;
data?: any[];
messageId: string;
}
export interface Message {
text: string;
isUser: boolean;
messageId: string;
}

View file

@ -27,7 +27,7 @@ function ChatLog({
const chatManager = React.useRef<ChatManager | null>(null);
const chatRef = React.useRef<HTMLDivElement>(null);
const [messages, setMessages] = React.useState<Message[]>(
initialMsg ? [{ text: initialMsg, isUser: true }] : [],
initialMsg ? [{ text: initialMsg, isUser: true, messageId: '123' }] : [],
);
const [processingStage, setProcessing] = React.useState<BotChunk | null>(
null,
@ -49,6 +49,7 @@ function ChatLog({
return {
text: m.content,
isUser: isUser,
messageId: m.message_id,
};
}),
);
@ -69,6 +70,9 @@ function ChatLog({
if (msg.stage === 'chart') {
setProcessing(msg);
}
if (msg.stage === 'start') {
setProcessing({ ...msg, content: 'Processing your request...' });
}
if (msg.stage === 'final') {
setMessages((prev) => [
...prev,
@ -76,6 +80,7 @@ function ChatLog({
text: msg.content,
isUser: false,
userName: 'Kai',
messageId: msg.messageId,
},
]);
setProcessing(null);
@ -112,9 +117,8 @@ function ChatLog({
top: chatRef.current.scrollHeight,
behavior: 'smooth',
});
}, [messages.length]);
}, [messages.length, processingStage]);
const newChat = messages.length === 1 && processingStage === null;
return (
<Loader loading={isLoading} className={'w-full h-full'}>
<div
@ -130,14 +134,12 @@ function ChatLog({
text={msg.text}
isUser={msg.isUser}
userName={userLetter}
messageId={msg.messageId}
/>
))}
{processingStage ? (
<ChatNotice content={processingStage.content} />
) : null}
{newChat ? (
<ChatNotice content={'Processing your question...'} />
) : null}
</div>
<div className={'sticky bottom-0 pt-6 w-2/3'}>
<ChatInput onSubmit={onSubmit} />

View file

@ -4,19 +4,34 @@ import cn from 'classnames';
import Markdown from 'react-markdown';
import { Loader, ThumbsUp, ThumbsDown, ListRestart } from 'lucide-react';
import { toast } from 'react-toastify';
import { aiService } from 'App/services';
export function ChatMsg({
text,
isUser,
userName,
messageId,
}: {
text: string;
isUser: boolean;
messageId: string;
userName?: string;
}) {
const onClick = () => {
toast.info('I do nothing!');
};
const onFeedback = (feedback: 'like' | 'dislike', messageId: string) => {
const settings = { projectId: '2325', userId: '0' };
aiService
.feedback(feedback === 'like', messageId, settings.projectId, settings.userId)
.then(() => {
toast.success('Feedback saved.');
})
.catch((e) => {
console.error(e);
toast.error('Failed to send feedback. Please try again later.');
});
}
return (
<div
className={cn(
@ -45,10 +60,10 @@ export function ChatMsg({
<Markdown>{text}</Markdown>
{isUser ? null : (
<div className={'flex items-center gap-2'}>
<IconButton onClick={onClick}>
<IconButton onClick={() => onFeedback('like', messageId)}>
<ThumbsUp size={16} />
</IconButton>
<IconButton onClick={onClick}>
<IconButton onClick={() => onFeedback('dislike', messageId)}>
<ThumbsDown size={16} />
</IconButton>

View file

@ -151,4 +151,26 @@ export default class AiService extends BaseService {
const data = await r.json();
return data;
}
feedback = async (positive: boolean | null, messageId: string, projectId: string, userId: string) => {
const jwt = window.env.KAI_TESTING // this.client.getJwt()
const r = await fetch(`http://localhost:8700/kai/${projectId}/messages/feedback`, {
method: 'POST',
headers: new Headers({
Accept: 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwt}`
}),
body: JSON.stringify({
message_id: messageId,
value: positive,
user_id: userId,
}),
});
if (!r.ok) {
throw new Error('Failed to send feedback');
}
return await r.json()
}
}