From c27d796c8ecedfab915894216495df7f366b82d1 Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:45:07 -0700 Subject: [PATCH] Improve settings, rewards, chat, and skills UI polish (#328) * feat: add Rewards page and enhance Settings navigation - Introduced a new Rewards page, accessible via a protected route. - Updated the Settings component to include a new SettingsSectionPage for better organization of account and automation settings. - Refactored the SettingsHome component to group menu items and improve navigation. - Enhanced the useSettingsNavigation hook to support new routes for account and automation settings. * feat: implement app-dotted-canvas styling across multiple pages - Added a new CSS class `app-dotted-canvas` to create a dotted background effect for various components. - Updated the App, Agents, Conversations, Home, Intelligence, Invites, Mnemonic, Rewards, Settings, Skills, Webhooks, and Welcome pages to utilize the new styling, enhancing visual consistency across the application. - Refactored the BottomTabBar component's SVG path for improved icon representation. * refactor: update BottomTabBar styling for improved UI consistency - Modified the BottomTabBar component's navigation and button styles to enhance visual appeal and user experience. - Adjusted class names for better alignment with design standards, including changes to padding, border, and background colors. - Improved active and inactive button states for clearer user interaction feedback. * refactor: enhance layout and styling in App and Settings components - Adjusted padding in the App component to improve layout consistency. - Updated BottomTabBar component to enhance styling and user interaction. - Removed deprecated delete data functionality from SettingsHome and refined related menu items for clarity. - Modified Settings page layout for better responsiveness and visual appeal. * refactor: improve layout and styling in Settings and Skills components - Simplified the rendering of grouped and destructive settings in SettingsHome for better readability. - Adjusted padding and margin in SettingsMenuItem for a more consistent button appearance. - Introduced BuiltInSkillCard component in Skills page to enhance the display of built-in skills with improved navigation. - Updated button styles in SkillActionButton for better visual consistency and user experience. * style: align third-party skill card typography * style: refine built-in and connected skills layout * style: update Conversations component for improved UI consistency - Changed background color of message container to `bg-stone-50` for better visual contrast. - Adjusted input field layout to enhance user experience, including centering elements and refining focus styles. - Updated class names for input field to ensure consistent focus behavior and visual feedback. * feat: enhance settings navigation with fallback functionality - Introduced a new `goBackWithFallback` function to improve navigation behavior when the user attempts to go back in the settings. - Updated the `navigateBack` and `closeSettings` functions to utilize the new fallback mechanism, ensuring a smoother user experience when navigating through settings routes. - Removed redundant route handling logic to streamline the navigation process. * style: remove app-dotted-canvas class for consistent layout across pages - Eliminated the `app-dotted-canvas` class from multiple components to streamline styling and ensure a uniform layout. - Updated the Agents, Conversations, Home, Intelligence, Invites, Mnemonic, Rewards, Settings, Skills, Webhooks, and Welcome pages for improved visual consistency. * style: improve code formatting and readability in Rewards and Skills components - Reformatted code in the Rewards component for better readability by adjusting indentation and line breaks. - Enhanced the Skills component by updating the description formatting for clarity and consistency. - Ensured consistent styling practices across both components to maintain a clean codebase. --- app/src/App.tsx | 4 +- app/src/AppRoutes.tsx | 10 + app/src/components/BottomTabBar.tsx | 12 +- app/src/components/settings/SettingsHome.tsx | 347 +++--------------- .../settings/SettingsSectionPage.tsx | 51 +++ .../settings/components/SettingsMenuItem.tsx | 2 +- .../settings/hooks/useSettingsNavigation.ts | 57 +-- app/src/components/skills/shared.tsx | 8 +- app/src/index.css | 7 + app/src/pages/Agents.tsx | 2 +- app/src/pages/Conversations.tsx | 152 ++------ app/src/pages/Home.tsx | 2 +- app/src/pages/Intelligence.tsx | 2 +- app/src/pages/Invites.tsx | 2 +- app/src/pages/Mnemonic.tsx | 2 +- app/src/pages/Rewards.tsx | 235 ++++++++++++ app/src/pages/Settings.tsx | 236 +++++++++++- app/src/pages/Skills.tsx | 142 ++++++- app/src/pages/Webhooks.tsx | 4 +- app/src/pages/Welcome.tsx | 2 +- 20 files changed, 803 insertions(+), 476 deletions(-) create mode 100644 app/src/components/settings/SettingsSectionPage.tsx create mode 100644 app/src/pages/Rewards.tsx diff --git a/app/src/App.tsx b/app/src/App.tsx index c32dec815..b4b993fdf 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -29,8 +29,8 @@ function App() { -
-
+
+
diff --git a/app/src/AppRoutes.tsx b/app/src/AppRoutes.tsx index 3d6c1b6c9..6d98de762 100644 --- a/app/src/AppRoutes.tsx +++ b/app/src/AppRoutes.tsx @@ -9,6 +9,7 @@ import Conversations from './pages/Conversations'; import Home from './pages/Home'; import Intelligence from './pages/Intelligence'; import Invites from './pages/Invites'; +import Rewards from './pages/Rewards'; import Settings from './pages/Settings'; import Skills from './pages/Skills'; import Webhooks from './pages/Webhooks'; @@ -90,6 +91,15 @@ const AppRoutes = () => { } /> + + + + } + /> + ), @@ -142,8 +142,8 @@ const BottomTabBar = () => { }; return ( -
-

- This will sign you out and permanently delete ALL data including: • App settings and - conversations • Email data from Gmail • Chat history from Telegram • Cached files - from Notion • All other skills data + This will sign you out and permanently delete local app data including: • App + settings and conversations • Email data from Gmail • Chat history from Telegram • + Cached files from Notion • All other skills data

This action cannot be undone and may take a few moments to complete. @@ -516,7 +267,7 @@ const SettingsHome = () => {

diff --git a/app/src/components/settings/SettingsSectionPage.tsx b/app/src/components/settings/SettingsSectionPage.tsx new file mode 100644 index 000000000..af90c1709 --- /dev/null +++ b/app/src/components/settings/SettingsSectionPage.tsx @@ -0,0 +1,51 @@ +import type { ReactNode } from 'react'; + +import SettingsHeader from './components/SettingsHeader'; +import SettingsMenuItem from './components/SettingsMenuItem'; +import { useSettingsNavigation } from './hooks/useSettingsNavigation'; + +export interface SettingsSectionItem { + id: string; + title: string; + description?: string; + icon: ReactNode; + route: string; +} + +interface SettingsSectionPageProps { + title: string; + description?: string; + items: SettingsSectionItem[]; +} + +const SettingsSectionPage = ({ title, description, items }: SettingsSectionPageProps) => { + const { navigateBack, navigateToSettings } = useSettingsNavigation(); + + return ( +
+ + +
+
+ {description &&

{description}

} + +
+ {items.map((item, index) => ( + navigateToSettings(item.route)} + isFirst={index === 0} + isLast={index === items.length - 1} + /> + ))} +
+
+
+
+ ); +}; + +export default SettingsSectionPage; diff --git a/app/src/components/settings/components/SettingsMenuItem.tsx b/app/src/components/settings/components/SettingsMenuItem.tsx index a83d547e4..dc3ed2dcc 100644 --- a/app/src/components/settings/components/SettingsMenuItem.tsx +++ b/app/src/components/settings/components/SettingsMenuItem.tsx @@ -31,7 +31,7 @@ const SettingsMenuItem = ({ return ( ); @@ -146,7 +146,7 @@ export function SkillActionButton({ e.stopPropagation(); onOpenModal(); }} - className="px-4 py-1.5 text-xs font-medium text-primary-300 bg-primary-500/10 border border-primary-500/30 rounded-lg hover:bg-primary-500/20 transition-colors flex-shrink-0 ml-3"> + className="ml-3 flex-shrink-0 rounded-lg border border-primary-200 bg-primary-50 px-4 py-1.5 text-xs font-medium text-primary-700 transition-colors hover:bg-primary-100"> Setup ); @@ -156,7 +156,7 @@ export function SkillActionButton({ return ( ); @@ -168,7 +168,7 @@ export function SkillActionButton({ e.stopPropagation(); onOpenModal(); }} - className="px-4 py-1.5 text-xs font-medium text-primary-300 bg-primary-500/10 border border-primary-500/30 rounded-lg hover:bg-primary-500/20 transition-colors flex-shrink-0 ml-3"> + className="ml-3 flex-shrink-0 rounded-lg border border-primary-200 bg-primary-50 px-4 py-1.5 text-xs font-medium text-primary-700 transition-colors hover:bg-primary-100"> Configure ); diff --git a/app/src/index.css b/app/src/index.css index 93e483e44..eb08ae786 100644 --- a/app/src/index.css +++ b/app/src/index.css @@ -152,6 +152,13 @@ @apply card hover:shadow-medium; } + .app-dotted-canvas { + background-color: #f8f7f2; + background-image: radial-gradient(circle at center, #cdcdcd 1px, transparent 1px); + background-size: 18px 18px; + background-position: 0 0; + } + /* Input components with consistent styling */ .input-primary { @apply w-full px-4 py-3 rounded-lg; diff --git a/app/src/pages/Agents.tsx b/app/src/pages/Agents.tsx index 6afc36eb4..7fca6f0d8 100644 --- a/app/src/pages/Agents.tsx +++ b/app/src/pages/Agents.tsx @@ -1,6 +1,6 @@ const Agents = () => { return ( -
+
diff --git a/app/src/pages/Conversations.tsx b/app/src/pages/Conversations.tsx index a9797c136..7fc33efc9 100644 --- a/app/src/pages/Conversations.tsx +++ b/app/src/pages/Conversations.tsx @@ -6,7 +6,6 @@ import { useNavigate } from 'react-router-dom'; import { type ChatSendError, chatSendError } from '../chat/chatSendError'; import { useLocalModelStatus } from '../hooks/useLocalModelStatus'; import { creditsApi, type TeamUsage } from '../services/api/creditsApi'; -import { inferenceApi, type ModelInfo } from '../services/api/inferenceApi'; import { chatCancel, chatSend, @@ -44,6 +43,7 @@ import { const DEFAULT_THREAD_ID = 'default-thread'; const DEFAULT_THREAD_TITLE = 'Conversation'; +const AGENTIC_MODEL_ID = 'agentic-v1'; type ToolTimelineEntryStatus = 'running' | 'success' | 'error'; type InputMode = 'text' | 'voice'; type ReplyMode = 'text' | 'voice'; @@ -134,9 +134,6 @@ const Conversations = () => { const [isPlayingReply, setIsPlayingReply] = useState(false); const [inlineSuggestionValue, setInlineSuggestionValue] = useState(''); - const [availableModels, setAvailableModels] = useState([]); - const [selectedModel, setSelectedModel] = useState('agentic-v1'); - const [isLoadingModels, setIsLoadingModels] = useState(false); const [isSending, setIsSending] = useState(false); const [sendError, setSendError] = useState(null); const socketStatus = useAppSelector(selectSocketStatus); @@ -210,23 +207,6 @@ const Conversations = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [dispatch]); - useEffect(() => { - setIsLoadingModels(true); - inferenceApi - .listModels() - .then(data => { - if (data.data.length > 0) { - setAvailableModels(data.data); - const preferred = data.data.find(m => m.id === 'agentic-v1'); - setSelectedModel(preferred ? preferred.id : data.data[0].id); - } - }) - .catch(() => { - // Keep default model on failure - }) - .finally(() => setIsLoadingModels(false)); - }, []); - useEffect(() => { setIsLoadingBudget(true); creditsApi @@ -339,6 +319,14 @@ const Conversations = () => { } }, [inputMode, isRecording]); + useEffect(() => { + if (inputMode === 'voice') { + setReplyMode('voice'); + } else if (replyMode === 'voice') { + setReplyMode('text'); + } + }, [inputMode, replyMode]); + // Proactively check voice binary availability when switching to voice mode useEffect(() => { if (inputMode !== 'voice' || !rustChat) return; @@ -701,7 +689,7 @@ const Conversations = () => { // ── Cloud socket path (unchanged) ──────────────────────────────────────── try { - await chatSend({ threadId: sendingThreadId, message: trimmed, model: selectedModel }); + await chatSend({ threadId: sendingThreadId, message: trimmed, model: AGENTIC_MODEL_ID }); // setIsSending(false) and setActiveThread(null) happen in the onDone/onError event handlers } catch (err) { @@ -922,36 +910,15 @@ const Conversations = () => { } }; - const selectedThread = threads.find(t => t.id === selectedThreadId); const selectedThreadToolTimeline = selectedThreadId ? (toolTimelineByThread[selectedThreadId] ?? []) : []; const inlineCompletionSuffix = getInlineCompletionSuffix(inputValue, inlineSuggestionValue); return ( -
+
-
-
-
-

- {selectedThread?.title || DEFAULT_THREAD_TITLE} -

- {selectedThread?.isActive && ( - - Active - - )} -
- {selectedThread?.createdAt && ( -

- Created {formatRelativeTime(selectedThread.createdAt)} -

- )} -
-
- -
+
{isLoadingMessages ? (
{Array.from({ length: 4 }).map((_, i) => ( @@ -1219,81 +1186,7 @@ const Conversations = () => {
)} -
- {isLoadingModels ? ( - Loading models… - ) : ( - <> - Model - - - )} -
-
- Input - - -
-
- Reply - - -
+
{(isLoadingBudget || teamUsage) && (
{teamUsage ? ( @@ -1371,7 +1264,7 @@ const Conversations = () => { {inputMode === 'text' ? (
-
+
@@ -1386,7 +1279,7 @@ const Conversations = () => { placeholder="Type a message..." rows={1} disabled={isSending || !rustChat} - className="relative z-10 w-full resize-none border-0 bg-transparent pl-4 pr-10 py-2.5 text-sm leading-normal whitespace-pre-wrap break-words font-sans text-stone-900 placeholder:text-stone-400 focus:outline-none focus:ring-0 max-h-32 disabled:opacity-50 disabled:cursor-not-allowed" + className="relative z-10 w-full resize-none border-0 bg-transparent pl-4 pr-10 py-2.5 text-sm leading-normal whitespace-pre-wrap break-words font-sans text-stone-900 placeholder:text-stone-400 outline-none focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0 max-h-32 disabled:opacity-50 disabled:cursor-not-allowed" /> {/* Mic icon inside input */}
) : (
+ + +
+
+
+ +
+
+
+ Progress +
+
+ {unlockedCount}/{rewardRoles.length} +
+

+ Roles unlocked from your current app activity. +

+ +
+
+
+ +
+
+ Discord linked + + {hasDiscordConnection ? 'Yes' : 'No'} + +
+
+ Total messages + {totalMessages} +
+
+ Plan + {user?.subscription?.plan ?? 'FREE'} +
+
+
+ +
+ {rewardRoles.map(role => ( +
+
+
+
+

{role.title}

+ + {role.unlocked ? 'Unlocked' : 'Locked'} + +
+

{role.description}

+
+ +
+ {role.unlocked ? ( + + + + ) : ( + + + + )} +
+
+ +
+
+
+ Unlock Action +
+
{role.actionLabel}
+
+
{role.progressLabel}
+
+
+ ))} +
+
+
+
+ ); +}; + +export default Rewards; diff --git a/app/src/pages/Settings.tsx b/app/src/pages/Settings.tsx index e15009042..2fc85389e 100644 --- a/app/src/pages/Settings.tsx +++ b/app/src/pages/Settings.tsx @@ -24,15 +24,247 @@ import TeamMembersPanel from '../components/settings/panels/TeamMembersPanel'; import TeamPanel from '../components/settings/panels/TeamPanel'; import WebhooksDebugPanel from '../components/settings/panels/WebhooksDebugPanel'; import SettingsHome from '../components/settings/SettingsHome'; +import SettingsSectionPage from '../components/settings/SettingsSectionPage'; + +const accountSettingsItems = [ + { + id: 'billing', + title: 'Billing & Usage', + description: 'Manage your subscription, credits, and payment methods', + route: 'billing', + icon: ( + + + + ), + }, + { + id: 'recovery-phrase', + title: 'Recovery Phrase', + description: 'Manage your BIP39 recovery phrase for encryption and wallet access', + route: 'recovery-phrase', + icon: ( + + + + ), + }, + { + id: 'team', + title: 'Team', + description: 'Manage your team, members, and invites', + route: 'team', + icon: ( + + + + ), + }, + { + id: 'connections', + title: 'Connections', + description: 'Review and manage linked account connections', + route: 'connections', + icon: ( + + + + ), + }, +]; + +const automationSettingsItems = [ + { + id: 'accessibility', + title: 'Accessibility Automation', + description: 'Desktop permissions, assisted controls, and safety-bound sessions', + route: 'accessibility', + icon: ( + + + + ), + }, + { + id: 'screen-intelligence', + title: 'Screen Intelligence', + description: 'Window capture policy, vision summaries, and memory ingestion', + route: 'screen-intelligence', + icon: ( + + + + ), + }, + { + id: 'autocomplete', + title: 'Inline Autocomplete', + description: 'Manage predictive text style, app filters, and live completion controls', + route: 'autocomplete', + icon: ( + + + + ), + }, + { + id: 'messaging', + title: 'Messaging Channels', + description: 'Configure Telegram/Discord auth modes and default channel routing', + route: 'messaging', + icon: ( + + + + ), + }, + { + id: 'cron-jobs', + title: 'Cron Jobs', + description: 'View and configure scheduled jobs for runtime skills', + route: 'cron-jobs', + icon: ( + + + + ), + }, +]; + +const aiSettingsItems = [ + { + id: 'local-model', + title: 'Local AI Model', + description: 'Choose model tier by device capability and manage downloads', + route: 'local-model', + icon: ( + + + + ), + }, + { + id: 'ai', + title: 'AI Configuration', + description: 'Configure persona, prompting behavior, and AI runtime settings', + route: 'ai', + icon: ( + + + + ), + }, + { + id: 'skills', + title: 'Skills', + description: 'Configure browser access, skill behavior, and installed skill capabilities', + route: 'skills', + icon: ( + + + + ), + }, +]; const Settings = () => { return ( -
+
} /> + + } + /> + + } + /> + + } + /> } /> } /> } /> diff --git a/app/src/pages/Skills.tsx b/app/src/pages/Skills.tsx index 508a8ca18..b38fd4968 100644 --- a/app/src/pages/Skills.tsx +++ b/app/src/pages/Skills.tsx @@ -1,4 +1,5 @@ import { useMemo, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { DefaultIcon, @@ -46,6 +47,46 @@ interface SkillCardProps { onSetup: () => void; } +interface BuiltInSkillCardProps { + title: string; + description: string; + route: string; + icon: React.ReactNode; + ctaLabel?: string; +} + +function BuiltInSkillCard({ + title, + description, + route, + icon, + ctaLabel = 'Open settings', +}: BuiltInSkillCardProps) { + const navigate = useNavigate(); + + return ( + + ); +} + function SkillCard({ skill, onSetup }: SkillCardProps) { const connectionStatus = useSkillConnectionStatus(skill.id); const statusDisplay = STATUS_DISPLAY[connectionStatus] || STATUS_DISPLAY.offline; @@ -106,7 +147,7 @@ function SkillCard({ skill, onSetup }: SkillCardProps) { {/* Info */}
- {skill.name} + {skill.name}
@@ -114,8 +155,13 @@ function SkillCard({ skill, onSetup }: SkillCardProps) { {statusDisplay.text}
+ {skill.description && ( +

+ {skill.description} +

+ )} {syncSummaryText && ( -

{syncSummaryText}

+

{syncSummaryText}

)} {isSyncing && (
@@ -225,6 +271,7 @@ function SkillCard({ skill, onSetup }: SkillCardProps) { // ─── Main Skills Page ─────────────────────────────────────────────────────── export default function Skills() { + const navigate = useNavigate(); // Skills from registry via RPC const { skills: availableSkills, loading: skillsLoading } = useAvailableSkills(); @@ -277,16 +324,101 @@ export default function Skills() { setSetupModalOpen(true); }; + const builtInSkills = [ + { + id: 'screen-intelligence', + title: 'Screen Intelligence', + description: + 'Capture windows, summarize what is on screen, and feed useful context into memory.', + route: '/settings/screen-intelligence', + icon: ( + + + + ), + }, + { + id: 'text-autocomplete', + title: 'Text Auto-Complete', + description: + 'Suggest inline completions while you type and control where autocomplete is active.', + route: '/settings/autocomplete', + icon: ( + + + + ), + }, + { + id: 'voice-stt', + title: 'Voice Speech To Text', + description: + 'Use the microphone for dictation and voice-driven chat with local speech recognition.', + route: '/settings/local-model', + icon: ( + + + + ), + }, + ]; + return ( -
+
+
+
+

Built-in Skills

+

+ Core desktop capabilities configured from settings. +

+
+
+ {builtInSkills.map(skill => ( + + ))} +
+
+ {/* Main card */}
{/* Header */} -
-

Skills

+
+
+

Connected Skills

+

+ Third-party integrations and external data sources. +

+
+
{/* Skills list */} diff --git a/app/src/pages/Webhooks.tsx b/app/src/pages/Webhooks.tsx index 64787851f..e9fb27ba8 100644 --- a/app/src/pages/Webhooks.tsx +++ b/app/src/pages/Webhooks.tsx @@ -19,7 +19,7 @@ export default function Webhooks() { if (loading && tunnels.length === 0) { return ( -
+
Loading webhooks… @@ -29,7 +29,7 @@ export default function Webhooks() { } return ( -
+
{/* Connection status */}
diff --git a/app/src/pages/Welcome.tsx b/app/src/pages/Welcome.tsx index e7d5cab98..8c8190690 100644 --- a/app/src/pages/Welcome.tsx +++ b/app/src/pages/Welcome.tsx @@ -4,7 +4,7 @@ import TypewriterGreeting from '../components/TypewriterGreeting'; const Welcome = () => { return ( -
+