mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-30 17:38:17 -05:00
feat(chat): update chat page layout and enhance message handling
- Refactored the chat page to utilize a new `ChatSidebar` component for better organization and user experience. - Improved message handling by simplifying session creation logic and ensuring proper state management. - Updated UI elements for consistency, including button labels and input handling. - Enhanced message rendering to support tool call outputs, improving the chat interaction flow. These changes aim to streamline the chat interface and improve overall usability.
This commit is contained in:
62
autogpt_platform/frontend/pnpm-lock.yaml
generated
62
autogpt_platform/frontend/pnpm-lock.yaml
generated
@@ -191,6 +191,9 @@ importers:
|
||||
moment:
|
||||
specifier: 2.30.1
|
||||
version: 2.30.1
|
||||
motion:
|
||||
specifier: 12.29.2
|
||||
version: 12.29.2(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next:
|
||||
specifier: 15.4.10
|
||||
version: 15.4.10(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -5164,6 +5167,20 @@ packages:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
framer-motion@12.29.2:
|
||||
resolution: {integrity: sha512-lSNRzBJk4wuIy0emYQ/nfZ7eWhqud2umPKw2QAQki6uKhZPKm2hRQHeQoHTG9MIvfobb+A/LbEWPJU794ZUKrg==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
fs-extra@10.1.0:
|
||||
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -6175,9 +6192,29 @@ packages:
|
||||
motion-dom@12.24.8:
|
||||
resolution: {integrity: sha512-wX64WITk6gKOhaTqhsFqmIkayLAAx45SVFiMnJIxIrH5uqyrwrxjrfo8WX9Kh8CaUAixjeMn82iH0W0QT9wD5w==}
|
||||
|
||||
motion-dom@12.29.2:
|
||||
resolution: {integrity: sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA==}
|
||||
|
||||
motion-utils@12.23.28:
|
||||
resolution: {integrity: sha512-0W6cWd5Okoyf8jmessVK3spOmbyE0yTdNKujHctHH9XdAE4QDuZ1/LjSXC68rrhsJU+TkzXURC5OdSWh9ibOwQ==}
|
||||
|
||||
motion-utils@12.29.2:
|
||||
resolution: {integrity: sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==}
|
||||
|
||||
motion@12.29.2:
|
||||
resolution: {integrity: sha512-jMpHdAzEDF1QQ055cB+1lOBLdJ6ialVWl6QQzpJI2OvmHequ7zFVHM2mx0HNAy+Tu4omUlApfC+4vnkX0geEOg==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -13602,6 +13639,16 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
framer-motion@12.29.2(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
motion-dom: 12.29.2
|
||||
motion-utils: 12.29.2
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@emotion/is-prop-valid': 1.2.2
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
fs-extra@10.1.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@@ -14896,8 +14943,23 @@ snapshots:
|
||||
dependencies:
|
||||
motion-utils: 12.23.28
|
||||
|
||||
motion-dom@12.29.2:
|
||||
dependencies:
|
||||
motion-utils: 12.29.2
|
||||
|
||||
motion-utils@12.23.28: {}
|
||||
|
||||
motion-utils@12.29.2: {}
|
||||
|
||||
motion@12.29.2(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
framer-motion: 12.29.2(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@emotion/is-prop-valid': 1.2.2
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
msw-storybook-addon@2.0.6(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3)):
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useChat } from '@ai-sdk/react';
|
||||
import { DefaultChatTransport } from 'ai';
|
||||
import { useState, useMemo } from 'react';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { postV2CreateSession } from '@/app/api/__generated__/endpoints/chat/chat';
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { DefaultChatTransport } from "ai";
|
||||
import { useState, useMemo } from "react";
|
||||
import { parseAsString, useQueryState } from "nuqs";
|
||||
import { ChatSidebar } from "./tools/components/ChatSidebar/ChatSidebar";
|
||||
|
||||
export default function Page() {
|
||||
const [sessionId, setSessionId] = useQueryState('sessionId', parseAsString);
|
||||
const [sessionId] = useQueryState("sessionId", parseAsString);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [input, setInput] = useState('');
|
||||
const [input, setInput] = useState("");
|
||||
|
||||
const transport = useMemo(() => {
|
||||
if (!sessionId) return null;
|
||||
return new DefaultChatTransport({
|
||||
api: `/api/chat/sessions/${sessionId}/stream`,
|
||||
prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => {
|
||||
prepareSendMessagesRequest: ({ messages }) => {
|
||||
const last = messages[messages.length - 1];
|
||||
return {
|
||||
body: {
|
||||
message: last.parts?.map((p) => (p.type === 'text' ? p.text : '')).join(''),
|
||||
is_user_message: last.role === 'user',
|
||||
message: last.parts
|
||||
?.map((p) => (p.type === "text" ? p.text : ""))
|
||||
.join(""),
|
||||
is_user_message: last.role === "user",
|
||||
context: null,
|
||||
},
|
||||
};
|
||||
@@ -32,59 +34,22 @@ export default function Page() {
|
||||
transport: transport ?? undefined,
|
||||
});
|
||||
|
||||
async function createSession(): Promise<string | null> {
|
||||
setIsCreating(true);
|
||||
try {
|
||||
const response = await postV2CreateSession({
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
if (response.status === 200 && response.data?.id) {
|
||||
return response.data.id;
|
||||
}
|
||||
console.error('[Copilot2] Failed to create session:', response);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('[Copilot2] Error creating session:', error);
|
||||
return null;
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNewSession() {
|
||||
const newSessionId = await createSession();
|
||||
if (newSessionId) {
|
||||
setSessionId(newSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStartChat(prompt: string) {
|
||||
if (!prompt.trim() || isCreating) return;
|
||||
|
||||
const newSessionId = await createSession();
|
||||
if (newSessionId) {
|
||||
await setSessionId(newSessionId);
|
||||
sendMessage({ text: prompt });
|
||||
setInput('');
|
||||
}
|
||||
if (!prompt.trim()) return;
|
||||
sendMessage({ text: prompt });
|
||||
setInput("");
|
||||
}
|
||||
|
||||
// Show landing page when no session exists
|
||||
if (!sessionId) {
|
||||
return (
|
||||
<div className="flex h-full">
|
||||
<div className="flex w-64 flex-col border-r border-zinc-200 bg-zinc-50 p-4">
|
||||
<button
|
||||
onClick={handleNewSession}
|
||||
disabled={isCreating}
|
||||
className="rounded-md bg-blue-600 px-4 py-2 text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{isCreating ? 'Creating...' : 'New Session'}
|
||||
</button>
|
||||
</div>
|
||||
<ChatSidebar isCreating={isCreating} setIsCreating={setIsCreating} />
|
||||
|
||||
<div className="flex h-full flex-1 flex-col items-center justify-center bg-zinc-100 p-4">
|
||||
<h2 className="mb-4 text-xl font-semibold text-zinc-700">Start a new conversation</h2>
|
||||
<h2 className="mb-4 text-xl font-semibold text-zinc-700">
|
||||
Start a new conversation
|
||||
</h2>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -104,7 +69,7 @@ export default function Page() {
|
||||
disabled={isCreating || !input.trim()}
|
||||
className="mt-2 w-full rounded-md bg-blue-600 px-4 py-2 text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{isCreating ? 'Starting...' : 'Start Chat'}
|
||||
{isCreating ? "Starting..." : "Start Chat"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -115,28 +80,55 @@ export default function Page() {
|
||||
return (
|
||||
<div className="flex h-full">
|
||||
{/* Sidebar */}
|
||||
<div className="flex w-64 flex-col border-r border-zinc-200 bg-zinc-50 p-4">
|
||||
<button
|
||||
onClick={handleNewSession}
|
||||
disabled={isCreating}
|
||||
className="rounded-md bg-blue-600 px-4 py-2 text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{isCreating ? 'Creating...' : 'New Session'}
|
||||
</button>
|
||||
</div>
|
||||
<ChatSidebar isCreating={isCreating} setIsCreating={setIsCreating} />
|
||||
|
||||
{/* Chat area */}
|
||||
<div className="flex h-full flex-1 flex-col bg-zinc-100 p-4">
|
||||
<div className="text-sm text-zinc-500 mb-2">Session ID: {sessionId}</div>
|
||||
<div className="flex h-full flex-1 flex-col p-4">
|
||||
<div className="mb-2 text-sm text-zinc-500">
|
||||
Session ID: {sessionId}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{messages.map((message) => (
|
||||
<div key={message.id}>
|
||||
{message.role === 'user' ? 'User: ' : 'AI: '}
|
||||
{message.parts.map((part, index) =>
|
||||
part.type === 'text' ? <p key={index}>{part.text}</p> : null,
|
||||
)}
|
||||
<div key={message.id} className="flex flex-col gap-4">
|
||||
{message.parts.map((part, index) => {
|
||||
if (part.type === "tool-find_block") {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="w-fit rounded-xl border border-zinc-200 bg-zinc-100 p-2 text-xs text-zinc-700"
|
||||
>
|
||||
{part.state === "input-streaming" && (
|
||||
<p>Finding blocks for you</p>
|
||||
)}
|
||||
{part.state === "input-available" && (
|
||||
<p>
|
||||
Searching blocks for{" "}
|
||||
{(part.input as { query: string }).query}
|
||||
</p>
|
||||
)}
|
||||
{part.state === "output-available" && (
|
||||
<p>
|
||||
Found
|
||||
{
|
||||
(
|
||||
JSON.parse(part.output as string) as {
|
||||
count: number;
|
||||
}
|
||||
).count
|
||||
}
|
||||
blocks
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else if (part.type === "text") {
|
||||
return <p key={index}>{part.text}</p>;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
{status === "submitted" && <p>Thinking....</p>}
|
||||
{error && <div className="text-red-500">Error: {error.message}</div>}
|
||||
</div>
|
||||
|
||||
@@ -145,21 +137,21 @@ export default function Page() {
|
||||
e.preventDefault();
|
||||
if (input.trim()) {
|
||||
sendMessage({ text: input });
|
||||
setInput('');
|
||||
setInput("");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
disabled={status !== 'ready'}
|
||||
disabled={status !== "ready"}
|
||||
placeholder="Say something..."
|
||||
/>
|
||||
<button type="submit" disabled={status !== 'ready'}>
|
||||
<button type="submit" disabled={status !== "ready"}>
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useState } from "react";
|
||||
import { postV2CreateSession } from "@/app/api/__generated__/endpoints/chat/chat";
|
||||
import { parseAsString, useQueryState } from "nuqs";
|
||||
|
||||
export const ChatSidebar = ({ isCreating, setIsCreating }: { isCreating: boolean, setIsCreating: (isCreating: boolean) => void }) => {
|
||||
const [sessionId, setSessionId] = useQueryState("sessionId", parseAsString);
|
||||
|
||||
async function createSession(): Promise<string | null> {
|
||||
setIsCreating(true);
|
||||
try {
|
||||
const response = await postV2CreateSession({
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
if (response.status === 200 && response.data?.id) {
|
||||
return response.data.id;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNewSession() {
|
||||
const newSessionId = await createSession();
|
||||
if (newSessionId) {
|
||||
setSessionId(newSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex w-64 flex-col border-r border-zinc-200 bg-zinc-50 p-4">
|
||||
<button
|
||||
onClick={handleNewSession}
|
||||
disabled={isCreating}
|
||||
className="rounded-md bg-blue-600 px-4 py-2 text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{isCreating ? "Creating..." : "New Session"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user