mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
* feat(frontend): typing chat * fix linting * typo corrections - ChatInterface.tsx typo corrections in comments * refactor and fix typing
This commit is contained in:
parent
c6cc5f6b5a
commit
a00f604995
@ -6,6 +6,31 @@ import userAvatar from "../assets/user-avatar.png";
|
||||
import { sendChatMessage } from "../services/chatService";
|
||||
import { RootState } from "../store";
|
||||
import CogTooth from "../assets/cog-tooth";
|
||||
import { useTypingEffect } from "../hooks/useTypingEffect";
|
||||
import { Message } from "../state/chatSlice";
|
||||
|
||||
interface ITypingChatProps {
|
||||
msg: Message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param msg
|
||||
* @returns jsx
|
||||
*
|
||||
* component used for typing effect when assistant replies
|
||||
*
|
||||
* makes uses of useTypingEffect hook
|
||||
*
|
||||
*/
|
||||
function TypingChat({ msg }: ITypingChatProps) {
|
||||
return (
|
||||
msg?.content && (
|
||||
<Card>
|
||||
<CardBody>{useTypingEffect([msg?.content], { loop: false })}</CardBody>
|
||||
</Card>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function MessageList(): JSX.Element {
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
@ -18,20 +43,22 @@ function MessageList(): JSX.Element {
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{messages.map((msg, index) => (
|
||||
<div key={index} className="flex mb-2.5">
|
||||
<div key={index} className="flex mb-2.5 pr-5 pl-5">
|
||||
<div
|
||||
className={`${msg.sender === "user" ? "flex flex-row-reverse mt-2.5 mr-2.5 mb-0 ml-auto" : "flex"}`}
|
||||
className={`flex mt-2.5 mb-0 ${msg.sender === "user" && "flex-row-reverse ml-auto"}`}
|
||||
>
|
||||
<img
|
||||
src={msg.sender === "user" ? userAvatar : assistantAvatar}
|
||||
alt={`${msg.sender} avatar`}
|
||||
className="w-[40px] h-[40px] mx-2.5"
|
||||
/>
|
||||
<Card
|
||||
className={`w-4/5 ${msg.sender === "user" ? "bg-primary" : ""}`}
|
||||
>
|
||||
<CardBody>{msg.content}</CardBody>
|
||||
</Card>
|
||||
{msg.sender !== "user" ? (
|
||||
<TypingChat msg={msg} />
|
||||
) : (
|
||||
<Card className="bg-primary">
|
||||
<CardBody>{msg.content}</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
59
frontend/src/hooks/useTypingEffect.ts
Normal file
59
frontend/src/hooks/useTypingEffect.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { useEffect, useState } from "react";
|
||||
/**
|
||||
* hook to be used for typing chat effect
|
||||
*/
|
||||
export const useTypingEffect = (
|
||||
strings: string[] = [""],
|
||||
{
|
||||
loop = false,
|
||||
playbackRate = 0.1,
|
||||
}: { loop?: boolean; playbackRate?: number } = {
|
||||
loop: false,
|
||||
playbackRate: 0.1,
|
||||
},
|
||||
) => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [{ stringIndex, characterIndex }, setState] = useState<{
|
||||
stringIndex: number;
|
||||
characterIndex: number;
|
||||
}>({
|
||||
stringIndex: 0,
|
||||
characterIndex: 0,
|
||||
});
|
||||
|
||||
let timeoutId: number;
|
||||
const emulateKeyStroke = () => {
|
||||
// eslint-disable-next-line no-plusplus
|
||||
characterIndex++;
|
||||
if (characterIndex === strings[stringIndex].length) {
|
||||
characterIndex = 0;
|
||||
// eslint-disable-next-line no-plusplus
|
||||
stringIndex++;
|
||||
if (stringIndex === strings.length) {
|
||||
if (!loop) {
|
||||
return;
|
||||
}
|
||||
stringIndex = 0;
|
||||
}
|
||||
timeoutId = window.setTimeout(emulateKeyStroke, 100 * playbackRate);
|
||||
} else if (characterIndex === strings[stringIndex].length - 1) {
|
||||
timeoutId = window.setTimeout(emulateKeyStroke, 2000 * playbackRate);
|
||||
} else {
|
||||
timeoutId = window.setTimeout(emulateKeyStroke, 100 * playbackRate);
|
||||
}
|
||||
setState({
|
||||
characterIndex,
|
||||
stringIndex,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
emulateKeyStroke();
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const nonBreakingSpace = "\u00A0";
|
||||
return strings[stringIndex].slice(0, characterIndex + 1) || nonBreakingSpace;
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
type Message = {
|
||||
export type Message = {
|
||||
content: string;
|
||||
sender: "user" | "assistant";
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user