diff --git a/ui/desktop/src/ChatWindow.tsx b/ui/desktop/src/ChatWindow.tsx index a79a071d6d..4ab6e28a3c 100644 --- a/ui/desktop/src/ChatWindow.tsx +++ b/ui/desktop/src/ChatWindow.tsx @@ -10,8 +10,10 @@ import UserMessage from './components/UserMessage'; import Input from './components/Input'; import Tabs from './components/Tabs'; import MoreMenu from './components/MoreMenu'; +import { Bird } from './components/ui/icons'; import LoadingGoose from './components/LoadingGoose'; + export interface Chat { id: number; title: string; @@ -22,28 +24,29 @@ export interface Chat { }>; } -const handleResize = (mode: 'expanded' | 'compact') => { - if (window.electron) { - if (mode === 'expanded') { - const width = window.innerWidth; - const height = window.innerHeight; - window.electron.resizeWindow(width, height); - } else if (mode === 'compact') { - const width = window.innerWidth; - const height = 100; // Make it very thin - window.electron.resizeWindow(width, height); - } - } -}; +enum Working { + Idle = 'Idle', + Working = 'Working', +} -const WingView: React.FC<{ onExpand: () => void; status: string }> = ({ onExpand, status }) => { +const WingView: React.FC<{ + onExpand: () => void; + progressMessage: string; + working: Working; +}> = ({ onExpand, progressMessage, working }) => { return (
-
- {status} + className="flex items-center w-full h-28 bg-gradient-to-r from-gray-100 via-gray-200 to-gray-300 shadow-md rounded-lg p-4 cursor-pointer hover:shadow-lg transition-all duration-200"> + {working === Working.Working && ( +
+ +
+ )} + + {/* Status Text */} +
+ {progressMessage}
); @@ -55,19 +58,19 @@ function ChatContent({ selectedChatId, setSelectedChatId, initialQuery, - setStatus, + setProgressMessage, + setWorking, }: { chats: Chat[]; setChats: React.Dispatch>; selectedChatId: number; setSelectedChatId: React.Dispatch>; initialQuery: string | null; - setStatus: React.Dispatch>; + setProgressMessage: React.Dispatch>; + setWorking: React.Dispatch>; }) { const chat = chats.find((c: Chat) => c.id === selectedChatId); - //window.electron.logInfo('chats' + JSON.stringify(chats, null, 2)); - const [messageMetadata, setMessageMetadata] = useState>({}); const { @@ -78,23 +81,26 @@ function ChatContent({ append, stop, isLoading, - error + error, } = useChat({ api: getApiUrl('/reply'), initialMessages: chat?.messages || [], onToolCall: ({ toolCall }) => { - setStatus(`Executing tool: ${toolCall.toolName}`); - // Optionally handle tool call result here + setWorking(Working.Working); + setProgressMessage(`Executing tool: ${toolCall.toolName}`); }, onResponse: (response) => { if (!response.ok) { - setStatus('An error occurred while receiving the response.'); + setProgressMessage('An error occurred while receiving the response.'); + setWorking(Working.Idle); } else { - setStatus('Receiving response...'); + setProgressMessage('thinking...'); + setWorking(Working.Working); } }, onFinish: async (message, options) => { - setStatus('Goose is ready'); + setProgressMessage('Task finished. Click here to expand.'); + setWorking(Working.Idle); const promptTemplates = [ "Take a look at this content, if this looks like it could be asking for a confirmation, return QUESTION. If it looks like it is a list of options or plans to choose from, return OPTIONS. It can't just be a list, but clearly must be asking the user to pick one or more of the plan or option alternatives, otherwise return READY. \n ### Message Content:\n" + message.content, @@ -103,7 +109,7 @@ function ChatContent({ const fetchResponses = await askAi(promptTemplates); - setMessageMetadata(prev => ({ ...prev, [message.id]: fetchResponses })); + setMessageMetadata((prev) => ({ ...prev, [message.id]: fetchResponses })); console.log('All responses:', fetchResponses); }, @@ -191,7 +197,7 @@ function ChatContent({ {error && (
- {error.message|| 'An error occurred while processing your request'} + {error.message || 'An error occurred while processing your request'}
)} @@ -214,11 +220,8 @@ export default function ChatWindow() { // Get initial query and history from URL parameters const searchParams = new URLSearchParams(window.location.search); const initialQuery = searchParams.get('initialQuery'); - //window.electron.logInfo('initialQuery: ' + initialQuery); const historyParam = searchParams.get('history'); - const initialHistory = historyParam - ? JSON.parse(decodeURIComponent(historyParam)) - : []; + const initialHistory = historyParam ? JSON.parse(decodeURIComponent(historyParam)) : []; const [chats, setChats] = useState(() => { const firstChat = { @@ -235,13 +238,13 @@ export default function ChatWindow() { initialQuery ? 'compact' : 'expanded' ); - const [status, setStatus] = useState('Goose is ready'); + const [working, setWorking] = useState(Working.Idle); + const [progressMessage, setProgressMessage] = useState(''); const toggleMode = () => { const newMode = mode === 'expanded' ? 'compact' : 'expanded'; console.log(`Toggle to ${newMode}`); setMode(newMode); - handleResize(newMode); }; window.electron.logInfo('ChatWindow loaded'); @@ -261,7 +264,8 @@ export default function ChatWindow() { selectedChatId={selectedChatId} setSelectedChatId={setSelectedChatId} initialQuery={initialQuery} - setStatus={setStatus} // Pass setStatus to ChatContent + setProgressMessage={setProgressMessage} + setWorking={setWorking} /> } /> @@ -270,36 +274,36 @@ export default function ChatWindow() {
{/* Always render WingView but control its visibility */} -
- -
+ ); } /** - * Utillity to ask the LLM any question to clarify without wider context. + * Utility to ask the LLM any question to clarify without wider context. */ async function askAi(promptTemplates: string[]) { console.log('askAi called...'); - const responses = await Promise.all(promptTemplates.map(async (template) => { - const response = await fetch(getApiUrl('/ask'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ prompt: template }) - }); + const responses = await Promise.all( + promptTemplates.map(async (template) => { + const response = await fetch(getApiUrl('/ask'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ prompt: template }), + }); - if (!response.ok) { - throw new Error('Failed to get response'); - } + if (!response.ok) { + throw new Error('Failed to get response'); + } - const data = await response.json(); - console.log('ask Response:', data.response); + const data = await response.json(); + console.log('ask Response:', data.response); - return data.response; - })); + return data.response; + }) + ); return responses; } diff --git a/ui/desktop/src/components/Tabs.tsx b/ui/desktop/src/components/Tabs.tsx index 230b34604b..bee1ded786 100644 --- a/ui/desktop/src/components/Tabs.tsx +++ b/ui/desktop/src/components/Tabs.tsx @@ -199,7 +199,7 @@ export default function Tabs({ chats, selectedChatId, setSelectedChatId, setChat {/* Adjusted padding on the left side and reduced margin for the close button */}
- {chat.title} + {chat.title.length > 8 ? `${chat.title.slice(0, 7)}...` : chat.title} {chats.length > 1 && (