Skip to content

Commit

Permalink
feat: launch from path, wing to wing etc (#311)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelneale authored Nov 25, 2024
1 parent 3f4943e commit 0e1368a
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 208 deletions.
2 changes: 1 addition & 1 deletion ui/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"test-e2e": "electron-forge start > /tmp/out.txt & ELECTRON_PID=$! && sleep 5 && if grep -q 'renderer: ChatWindow loaded' /tmp/out.txt; then echo 'process is running'; pkill -f electron; else echo 'not starting correctly'; cat /tmp/out.txt; pkill -f electron; exit 1; fi",
"test-e2e": "electron-forge start > /tmp/out.txt & ELECTRON_PID=$! && sleep 8 && if grep -q 'renderer: ChatWindow loaded' /tmp/out.txt; then echo 'process is running'; pkill -f electron; else echo 'not starting correctly'; cat /tmp/out.txt; pkill -f electron; exit 1; fi",
"sign-macos": "cd ./out/Goose-darwin-arm64 && codesign --deep --force --verify --sign \"Developer ID Application: Michael Neale (W2L75AE9HQ)\" Goose.app && ditto -c -k --sequesterRsrc --keepParent Goose.app Goose.zip"
},
"devDependencies": {
Expand Down
7 changes: 1 addition & 6 deletions ui/desktop/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import React from 'react';
import LauncherWindow from './LauncherWindow';
import WingToWingWindow from './WingToWingWindow';
import ChatWindow from './ChatWindow';

export default function App() {
const searchParams = new URLSearchParams(window.location.search);
const isLauncher = searchParams.get('window') === 'launcher';
const isWingToWing = searchParams.get('window') === 'wingToWing';

// TODO - Look at three separate renderers for this

if (isLauncher) {
return <LauncherWindow />;
} else if (isWingToWing) {
return <WingToWingWindow />;
} else {
return <ChatWindow />;
}
Expand Down
170 changes: 129 additions & 41 deletions ui/desktop/src/ChatWindow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useChat } from 'ai/react';
import { Route, Routes, Navigate } from 'react-router-dom';
import { getApiUrl } from './config';
Expand All @@ -10,21 +10,65 @@ import UserMessage from './components/UserMessage';
import Input from './components/Input';
import Tabs from './components/Tabs';
import MoreMenu from './components/MoreMenu';
import { BoxIcon } from './components/ui/icons';
import ReactMarkdown from 'react-markdown';

export interface Chat {
id: number;
title: string;
messages: Array<{ id: string; role: "function" | "system" | "user" | "assistant" | "data" | "tool"; content: string }>;
messages: Array<{
id: string;
role: 'function' | 'system' | 'user' | 'assistant' | 'data' | 'tool';
content: string;
}>;
}

function ChatContent({ chats, setChats, selectedChatId, setSelectedChatId }: {
chats: Chat[],
setChats: React.Dispatch<React.SetStateAction<Chat[]>>,
selectedChatId: number,
setSelectedChatId: React.Dispatch<React.SetStateAction<number>>
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);
}
}
};

const WingView: React.FC<{ onExpand: () => void; status: string }> = ({ onExpand, status }) => {
return (
<div
onClick={onExpand}
className="flex items-center justify-center w-full bg-gray-800 text-green-400 cursor-pointer rounded-lg p-4"
>
<div className="text-sm text-left font-mono bg-black bg-opacity-50 p-3 rounded-lg">
<span className="block">{status}</span>
</div>
</div>
);
};

function ChatContent({
chats,
setChats,
selectedChatId,
setSelectedChatId,
initialQuery,
setStatus,
}: {
chats: Chat[];
setChats: React.Dispatch<React.SetStateAction<Chat[]>>;
selectedChatId: number;
setSelectedChatId: React.Dispatch<React.SetStateAction<number>>;
initialQuery: string | null;
setStatus: React.Dispatch<React.SetStateAction<string>>;
}) {
const chat = chats.find((c: Chat) => c.id === selectedChatId);

window.electron.logInfo('chats' + JSON.stringify(chats, null, 2));

const {
messages,
input,
Expand All @@ -35,20 +79,42 @@ function ChatContent({ chats, setChats, selectedChatId, setSelectedChatId }: {
isLoading,
error
} = useChat({
api: getApiUrl("/reply"),
initialMessages: chat?.messages || []
api: getApiUrl('/reply'),
initialMessages: chat?.messages || [],
onToolCall: ({ toolCall }) => {
setStatus(`Executing tool: ${toolCall.toolName}`);
// Optionally handle tool call result here
},
onResponse: (response) => {
if (!response.ok) {
setStatus('An error occurred while receiving the response.');
} else {
setStatus('Receiving response...');
}
},
onFinish: (message, options) => {
setStatus('Goose is ready');
},
});

// Update chat messages when they change
useEffect(() => {
const updatedChats = chats.map(c =>
const updatedChats = chats.map((c) =>
c.id === selectedChatId ? { ...c, messages } : c
);
setChats(updatedChats);
}, [messages, selectedChatId]);

const initialQueryAppended = useRef(false);
useEffect(() => {
if (initialQuery && !initialQueryAppended.current) {
append({ role: 'user', content: initialQuery });
initialQueryAppended.current = true;
}
}, [initialQuery]);

return (
<div className="flex flex-col w-screen h-screen bg-window-gradient items-center justify-center p-[10px]">
<div className="chat-content flex flex-col w-screen h-screen bg-window-gradient items-center justify-center p-[10px]">
<div className="flex w-screen">
<div className="flex-1">
<Tabs
Expand All @@ -59,24 +125,23 @@ function ChatContent({ chats, setChats, selectedChatId, setSelectedChatId }: {
/>
</div>
<div className="flex">
<MoreMenu className="absolute top-2 right-2"
<MoreMenu
className="absolute top-2 right-2"
onStopGoose={() => {
stop()
stop();
}}
onClearContext={() => {
// TODO - Implement real behavior
append({
id: Date.now().toString(),
role: 'system',
content: 'Context cleared'
content: 'Context cleared',
});
}}
onRestartGoose={() => {
// TODO - Implement real behavior
append({
id: Date.now().toString(),
role: 'system',
content: 'Goose restarted'
content: 'Goose restarted',
});
}}
/>
Expand Down Expand Up @@ -128,44 +193,67 @@ 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<Chat[]>(() => {
const firstChat = {
id: 1,
title: initialQuery || 'Chat 1',
messages: initialHistory.length > 0 ? initialHistory :
(initialQuery ? [{
id: '0',
role: 'user' as const,
content: initialQuery
}] : [])
messages: initialHistory.length > 0 ? initialHistory : [],
};
return [firstChat];
});

const [selectedChatId, setSelectedChatId] = useState(1);

window.electron.logInfo("ChatWindow loaded");
const [mode, setMode] = useState<'expanded' | 'compact'>(
initialQuery ? 'compact' : 'expanded'
);

const [status, setStatus] = useState('Goose is ready');

const toggleMode = () => {
const newMode = mode === 'expanded' ? 'compact' : 'expanded';
console.log(`Toggle to ${newMode}`);
setMode(newMode);
handleResize(newMode);
};

window.electron.logInfo('ChatWindow loaded');

return (
<div className="relative w-screen h-screen overflow-hidden bg-transparent flex flex-col">
<Routes>
<Route
path="/chat/:id"
element={
<ChatContent
key={selectedChatId}
chats={chats}
setChats={setChats}
selectedChatId={selectedChatId}
setSelectedChatId={setSelectedChatId}
/>
}
/>
<Route path="*" element={<Navigate to="/chat/1" replace />} />
</Routes>


{/* Always render ChatContent but control its visibility */}
<div style={{ display: mode === 'expanded' ? 'block' : 'none' }}>
<Routes>
<Route
path="/chat/:id"
element={
<ChatContent
key={selectedChatId}
chats={chats}
setChats={setChats}
selectedChatId={selectedChatId}
setSelectedChatId={setSelectedChatId}
initialQuery={initialQuery}
setStatus={setStatus} // Pass setStatus to ChatContent
/>
}
/>
<Route path="*" element={<Navigate to="/chat/1" replace />} />
</Routes>
</div>

{/* Always render WingView but control its visibility */}
<div style={{ display: mode === 'expanded' ? 'none' : 'flex' }}>
<WingView onExpand={toggleMode} status={status} />
</div>
</div>
);
}
}
3 changes: 1 addition & 2 deletions ui/desktop/src/LauncherWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ declare global {
electron: {
hideWindow: () => void;
createChatWindow: (query: string) => void;
createWingToWingWindow: (query: string) => void;
};
}
}
Expand All @@ -18,7 +17,7 @@ export default function SpotlightWindow() {
e.preventDefault();
if (query.trim()) {
// Create a new chat window with the query
window.electron.createWingToWingWindow(query);
window.electron.createChatWindow(query);
setQuery('');
inputRef.current.blur()
}
Expand Down
Loading

0 comments on commit 0e1368a

Please sign in to comment.