Skip to content
Closed
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
16 changes: 16 additions & 0 deletions .changeset/ui-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"think-app": minor
"think-extension": minor
---

Improve UI styling and accessibility across the application:

- Replace Work Sans font with Inter for better readability
- Update dark mode color scheme for better contrast and accessibility
- Improve dropdown styling with proper background colors
- Add max-width constraint (640px) to chat messages for better readability
- Update selected chat item styling in light mode
- Reduce sidebar navigation text size by 2pt
- Fix orphaned words in setup completion modal
- Update logo to use dark mode variant when appropriate

74 changes: 74 additions & 0 deletions app/electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,38 @@ app.whenReady().then(async () => {
// Set app name (for dev mode - production uses productName from package.json)
app.setName('Think');

// Register protocol handler for think:// URLs
if (!app.isDefaultProtocolClient('think')) {
app.setAsDefaultProtocolClient('think');
}

// Handle think:// protocol URLs
app.on('open-url', (event, url) => {
event.preventDefault();
handleProtocolUrl(url);
});

// Handle think:// protocol URLs on Windows/Linux (second-instance)
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
return;
}

app.on('second-instance', (event, commandLine) => {
// Someone tried to run a second instance, focus our window instead
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}

// Check for protocol URL in command line arguments
const protocolUrl = commandLine.find(arg => arg.startsWith('think://'));
if (protocolUrl) {
handleProtocolUrl(protocolUrl);
}
});

// Set dock icon on macOS (for dev mode)
if (process.platform === 'darwin' && !app.isPackaged) {
const iconPath = path.join(__dirname, '../public/icons/think-os-agent.png');
Expand Down Expand Up @@ -280,9 +312,51 @@ app.on('activate', () => {
mainWindow.webContents.send('backend-ready', { token: APP_TOKEN });
});
}
} else {
// Bring window to front if it exists
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
}
});

/**
* Handle think:// protocol URLs
*/
function handleProtocolUrl(url) {
// Ensure window is created and visible
if (!mainWindow) {
createWindow();
} else {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}

// Parse URL and send to renderer
// Format: think://memories/123 or think://chats/456
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.once('did-finish-load', () => {
mainWindow.webContents.send('protocol-url', { url });
});
// If already loaded, send immediately
if (mainWindow.webContents.isLoading() === false) {
mainWindow.webContents.send('protocol-url', { url });
}
}
}

// Handle protocol URLs passed as command line arguments (Windows/Linux)
if (process.platform !== 'darwin') {
const protocolUrl = process.argv.find(arg => arg.startsWith('think://'));
if (protocolUrl) {
// Will be handled after app.whenReady()
app.once('ready', () => {
handleProtocolUrl(protocolUrl);
});
}
}

// IPC handlers
ipcMain.handle('check-ollama', async () => {
// Check if API is responding (running)
Expand Down
1,439 changes: 1,439 additions & 0 deletions app/public/fonts/Inter-VariableFont_slnt,wght.ttf

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions app/public/icons/think-os-agent-dark-mode-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions app/public/icons/think-os-agent-dark-mode.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions app/src/LockScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,16 @@ export default function LockScreen({ needsSetup, onUnlock }: Props) {

return (
<div className={cn(
"relative flex items-center justify-center min-h-screen bg-background p-4 transition-opacity duration-500",
"relative flex items-center justify-center min-h-screen p-4 transition-opacity duration-500",
success && "opacity-0"
)}>
{/* Animated gradient background orbs */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -left-40 w-80 h-80 bg-primary/20 rounded-full blur-3xl animate-pulse" />
<div className="absolute -bottom-40 -right-40 w-80 h-80 bg-primary/15 rounded-full blur-3xl animate-pulse" style={{ animationDelay: '1s' }} />
<div className="absolute -top-40 -left-40 w-80 h-80 rounded-full blur-3xl animate-pulse bg-orb-primary" />
<div className="absolute -bottom-40 -right-40 w-80 h-80 rounded-full blur-3xl animate-pulse bg-orb-secondary" style={{ animationDelay: '1s' }} />
</div>

<div className="relative w-full max-w-[320px] space-y-8">
<div className="relative w-full max-w-[400px] space-y-8">
{/* Logo */}
<div className="flex justify-center">
<img
Expand Down
13 changes: 7 additions & 6 deletions app/src/SetupWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ declare global {

function WizardLayout({ title, children }: { title?: string; children: React.ReactNode }) {
return (
<div className="relative flex items-center justify-center min-h-screen bg-background p-4">
<div className="relative flex items-center justify-center min-h-screen p-4">
{/* Animated gradient background orbs */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -left-40 w-80 h-80 bg-primary/20 rounded-full blur-3xl animate-pulse" />
<div className="absolute -bottom-40 -right-40 w-80 h-80 bg-primary/15 rounded-full blur-3xl animate-pulse" style={{ animationDelay: '1s' }} />
<div className="absolute -top-40 -left-40 w-80 h-80 rounded-full blur-3xl animate-pulse bg-orb-primary" />
<div className="absolute -bottom-40 -right-40 w-80 h-80 rounded-full blur-3xl animate-pulse bg-orb-secondary" style={{ animationDelay: '1s' }} />
</div>

<div className="relative w-full max-w-[320px] space-y-8">
<div className="relative w-full max-w-[400px] space-y-8">
<div className="flex justify-center">
<img src="./branding/Think_OS_Full_Word_Mark-lightmode.svg" alt="Think" className="h-8 dark:hidden" />
<img src="./branding/Think_OS_Full_Word_Mark.svg" alt="Think" className="h-8 hidden dark:block" />
Expand Down Expand Up @@ -263,8 +263,9 @@ export default function SetupWizard({ onComplete }: Props) {
</svg>
</div>
<p className="text-muted-foreground">
Ollama is running and models are ready. <br/>
You can now use Think with local AI.
Ollama is running and models are ready.
<br/>
You can now use Think with local&nbsp;AI.
</p>
<Button className="w-full" onClick={onComplete}>
Get Started
Expand Down
42 changes: 22 additions & 20 deletions app/src/components/ChatMessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,28 +115,30 @@ export function ChatMessageList({
}

return (
<div ref={scrollRef} className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((message) => (
<ChatMessage key={message.id} message={message} />
))}
{showLoading && (
<div className="flex justify-start animate-slide-up">
<div className="bg-muted p-3 rounded-lg">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Search className="h-3 w-3" />
<span>Searching memories...</span>
<Loader2 className="h-3 w-3 animate-spin" />
<div ref={scrollRef} className="flex-1 overflow-y-auto p-4">
<div className="max-w-[640px] mx-auto space-y-4">
{messages.map((message) => (
<ChatMessage key={message.id} message={message} />
))}
{showLoading && (
<div className="flex justify-start animate-slide-up">
<div className="bg-muted p-3 rounded-lg">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Search className="h-3 w-3" />
<span>Searching memories...</span>
<Loader2 className="h-3 w-3 animate-spin" />
</div>
</div>
</div>
</div>
)}
{showFollowups && onSendMessage && (
<PromptChips
prompts={followupSuggestions}
onSelect={onSendMessage}
className="animate-slide-up"
/>
)}
)}
{showFollowups && onSendMessage && (
<PromptChips
prompts={followupSuggestions}
onSelect={onSendMessage}
className="animate-slide-up"
/>
)}
</div>
</div>
);
}
9 changes: 7 additions & 2 deletions app/src/components/ChatSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,16 @@ export function ChatSidebar({ onNewChat }: ChatSidebarProps) {
className={cn(
"group flex items-center gap-2 px-2 py-2 rounded-md text-sm cursor-pointer transition-colors",
currentConversationId === conversation.id
? "bg-primary/10 text-foreground"
? "bg-primary/5 dark:bg-secondary text-foreground"
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
)}
>
<MessageSquare className="h-4 w-4 shrink-0" />
<MessageSquare
className={cn(
"h-4 w-4 shrink-0",
currentConversationId === conversation.id && "text-primary"
)}
/>
<span className="flex-1 truncate text-xs">
{conversation.title || "New conversation"}
</span>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/ModelSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export function ModelSelector({ type = "chat", provider, selectedModel, onModelC
key={model.name}
className={cn(
"flex items-center justify-between px-3 py-2.5 hover:bg-muted/50 cursor-pointer",
model.name === displayModel && "bg-muted"
model.name === displayModel && "bg-muted dark:bg-secondary text-foreground"
)}
onClick={() => model.is_downloaded && selectModel(model.name)}
>
Expand Down
Loading