Skip to content
Merged
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
61 changes: 35 additions & 26 deletions contrib/chat-plugin/src/app/ChatHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,42 +105,51 @@ const CustomMarkdown: React.FC<{ content: string }> = ({ content }) => {
}

// Message component
const Message: React.FC<{ message: ChatMessage, expand?: boolean }> = ({ message, expand = true }) => {
const [expanded, setExpanded] = useState(expand);
const [showButton, setShowButton] = useState(true);
const Message: React.FC<{ message: ChatMessage }> = ({ message }) => {
const [expanded, setExpanded] = useState(true);
const contentRef = useRef<HTMLDivElement>(null); // Reference to the content div

useEffect(() => {
setExpanded(expand); // Set expanded based on prop
}, [expand]);
const handleToggle = () => {
setExpanded((prev) => !prev);
};

const hasExpandedRef = useRef(false);

useEffect(() => {
// Check if the content height is less than a threshold (8 in this case)
const contentHeight = contentRef.current?.scrollHeight;
if (contentHeight && contentHeight <= 32) { // Assuming 32px is roughly equivalent to 2 lines of text
setShowButton(false);
} else {
setShowButton(true);
if (message.message.length > 0 && !hasExpandedRef.current) {
setExpanded(false);
hasExpandedRef.current = true;
}
}, [message]); // Depend on message so it re-checks when message changes

const toggleExpanded = () => {
setExpanded(!expanded);
};
}, [message.message.length]);

return (
<div className="flex-1 container relative mb-1 bg-gray-100 px-2 py-1 rounded word-wrap" >
{showButton && (
<button
onClick={toggleExpanded}
className="absolute top-0 right-0 mt-1 mr-1 text-blue-500 text-xs flex items-center justify-center bg-white border border-gray-300 rounded-full h-6 w-6"
>
{expanded ? <IoIosArrowUp /> : <IoIosArrowDown />}
</button>

{/* Reasoning part with fold/unfold */}
{message.reasoning && (
<div className="bg-gray-50 border-l-4 border-gray-400 px-2 py-1 mb-2 rounded text-gray-700">
<details open={expanded}>
<summary
className="cursor-pointer font-semibold"
onClick={e => {
e.preventDefault();
handleToggle();
}}
>
Reasoning
</summary>
<div
className="mt-1 overflow-auto"
style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontFamily: 'inherit' }}
>
{message.reasoning}
</div>
</details>
</div>
)}
<div
ref={contentRef}
className={`flex-1 ${expanded ? 'overflow-auto' : 'overflow-hidden max-h-12 mx-auto'} ${showButton ? 'pr-10' : ''}`}
className={`flex-1 pr-10 overflow-auto`}
style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontFamily: 'inherit' }}
>
{message.message}
Expand Down Expand Up @@ -186,7 +195,7 @@ const MessageGroup: React.FC<{ index: number, group: MessageGroup, isLast: boole
</div>
{group.messages.map((message, index) => (
// <Message key={index} message={message} expand={isLast} />
<Message key={index} message={message} expand={true} />
<Message key={index} message={message} />
))}

</div>
Expand Down
25 changes: 16 additions & 9 deletions contrib/chat-plugin/src/libs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,11 @@ export async function chatStreamRequest(abortSignal?: AbortSignal) {
})),
};

const newMsg = {
let newMsg = {
role: "assistant" as const,
message: "",
timestamp: new Date(),
reasoning: "",
};
// Add a new message to the chat store to show loading state
useChatStore.getState().addChatMessage(newMsg);
Expand All @@ -234,7 +235,6 @@ export async function chatStreamRequest(abortSignal?: AbortSignal) {
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
let fullResponse = "";
try {
while (true) {
const { value, done } = await reader.read();
Expand Down Expand Up @@ -265,16 +265,23 @@ export async function chatStreamRequest(abortSignal?: AbortSignal) {
const parsed = JSON.parse(data);

// Handle different response formats (e.g., chat completions vs completions)
const content = parsed.choices?.[0]?.delta?.content ||
let contentDelta = parsed.choices?.[0]?.delta?.content ||
parsed.choices?.[0]?.text ||
"";
const reasonDelta = parsed.choices?.[0]?.delta?.reasoning_content || "";

if (content) {
fullResponse += content;
if (contentDelta) {
if (newMsg.message.length === 0) {
contentDelta = contentDelta.trim();
}
newMsg.message += contentDelta;
}
if (reasonDelta) {
newMsg.reasoning += reasonDelta;
}
if (contentDelta || reasonDelta) {
// Update the last message in the chat store
useChatStore.getState().updateLastChatMessage(fullResponse);
// You could emit progress here if needed
// onProgress?.(content, fullResponse);
useChatStore.getState().updateLastChatMessage(newMsg);
}
} catch (e) {
toast.error("Failed to parse SSE data:" + e + " \nLine:" + data);
Expand All @@ -287,7 +294,7 @@ export async function chatStreamRequest(abortSignal?: AbortSignal) {
}
}
} catch (error) {
console.error("Stream reading error:" + error);
console.error("Stream reading error:" + error);
throw error;
} finally {
// Ensure reader is released
Expand Down
11 changes: 6 additions & 5 deletions contrib/chat-plugin/src/libs/state.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { create } from "zustand";
import { cn } from "./utils";

export type Status = "loading" | "online" | "offline" | "unknown";

export type ChatMessage = {
role: string;
message: string;
timestamp: Date;
reasoning?: string; // Optional field for reasoning support
};

export type Job = {
Expand Down Expand Up @@ -43,7 +43,7 @@ interface State {
setAllModelsInCurrentJob: (models: string[]) => void;
setCurrentModel: (model: string | null) => void;
addChatMessage: (chat: ChatMessage) => void;
updateLastChatMessage: (lastMessageContent: string) => void;
updateLastChatMessage: (lastChat: ChatMessage) => void;
cleanChat: () => void;
}

Expand Down Expand Up @@ -72,11 +72,12 @@ export const useChatStore = create<State>((set) => ({
setCurrentModel: (model) => set({ currentModel: model }),

addChatMessage: (log) => set((state) => ({ chatMsgs: [...state.chatMsgs, log] })),
updateLastChatMessage: (lastMessageContent) => set((state) => {
updateLastChatMessage: (lastChat) => set((state) => {
const chatMsgs = [...state.chatMsgs];
if (chatMsgs.length > 0) {
chatMsgs[chatMsgs.length - 1].message = lastMessageContent;
chatMsgs[chatMsgs.length - 1].timestamp = new Date();
chatMsgs[chatMsgs.length - 1] = lastChat;
} else {
chatMsgs.push(lastChat);
}
return { chatMsgs };
}),
Expand Down