Skip to content

Commit d3a91a6

Browse files
committed
feat: BrandLogo component, insight cards on home screen, theme token overrides
BrandLogo: new component that renders whitelabel logo (as img) or falls back to the Goose SVG. Replaces hardcoded <Goose /> in SessionsInsights, BaseChat watermark, ProviderGuard welcome, LocalModelSetup, and TelemetryOptOutModal. ProviderGuard now shows 'Welcome to {appName}'. InsightCards: new shared component for rich starter prompt cards inspired by the Pulse dashboard mockups. Cards show label, description, icon, and action link. Used on home screen (SessionsInsights) and in-chat (PopularChatTopics). Triggered when starterPrompts have descriptions. Home screen sections: new homeScreen config (showStats, showRecentChats, showInsightCards) controls which sections appear. Defaults preserve current behavior. Managerbot config hides stats, shows insight cards. Theme overrides: applyThemeTokens() now applies whitelabel theme overrides (light/dark/base) on top of base tokens. Enables custom colors and fonts without modifying theme-tokens.ts. Types: added WhiteLabelHomeScreen, WhiteLabelTheme, extended WhiteLabelStarterPrompt with description and action fields.
1 parent c798a7d commit d3a91a6

13 files changed

Lines changed: 364 additions & 191 deletions

ui/desktop/src/components/BaseChat.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import CreateRecipeFromSessionModal from './recipes/CreateRecipeFromSessionModal
4040
import { toastSuccess } from '../toasts';
4141
import { Recipe } from '../recipe';
4242
import { useAutoSubmit } from '../hooks/useAutoSubmit';
43-
import { Goose } from './icons';
43+
import BrandLogo from './BrandLogo';
4444
import EnvironmentBadge from './GooseSidebar/EnvironmentBadge';
4545

4646
const CurrentModelContext = createContext<{ model: string; mode: string } | null>(null);
@@ -399,10 +399,7 @@ export default function BaseChat({
399399
rel="noopener noreferrer"
400400
className="no-drag flex flex-row items-center gap-1 hover:opacity-80 transition-opacity"
401401
>
402-
<Goose className="size-5 goose-icon-animation" />
403-
<span className="text-sm leading-none text-text-secondary -translate-y-px">
404-
goose
405-
</span>
402+
<BrandLogo size="sm" />
406403
</a>
407404
<EnvironmentBadge className="translate-y-px" />
408405
</div>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Goose } from './icons/Goose';
2+
import { useWhiteLabel } from '../whitelabel/WhiteLabelContext';
3+
4+
interface BrandLogoProps {
5+
className?: string;
6+
size?: 'sm' | 'md' | 'lg';
7+
}
8+
9+
const SIZE_MAP = {
10+
sm: 'size-5',
11+
md: 'size-8',
12+
lg: 'size-10',
13+
};
14+
15+
export default function BrandLogo({ className, size = 'md' }: BrandLogoProps) {
16+
const { branding } = useWhiteLabel();
17+
const sizeClass = SIZE_MAP[size];
18+
19+
if (branding.logo) {
20+
return <img src={branding.logo} alt={branding.appName} className={className ?? sizeClass} />;
21+
}
22+
23+
return <Goose className={className ?? sizeClass} />;
24+
}

ui/desktop/src/components/Hub.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export default function Hub({
7575
return (
7676
<div className="flex flex-col h-full min-h-0 bg-background-secondary">
7777
<div className="flex-1 flex flex-col min-h-[45vh] overflow-hidden mb-0.5 relative">
78-
<SessionInsights />
78+
<SessionInsights onInsightClick={(prompt) => handleSubmit({ msg: prompt, images: [] })} />
7979
{isCreatingSession && (
8080
<div className="absolute bottom-1 left-4 z-20 pointer-events-none">
8181
<LoadingGoose chatState={ChatState.LoadingConversation} />

ui/desktop/src/components/LocalModelSetup.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '../api';
1111
import { toastService } from '../toasts';
1212
import { trackOnboardingSetupFailed } from '../utils/analytics';
13-
import { Goose } from './icons';
13+
import BrandLogo from './BrandLogo';
1414

1515
interface LocalModelSetupProps {
1616
onSuccess: () => void;
@@ -179,7 +179,7 @@ export function LocalModelSetup({ onSuccess, onCancel }: LocalModelSetupProps) {
179179
{/* Header */}
180180
<div className="text-left space-y-3">
181181
<div className="origin-bottom-left goose-icon-animation">
182-
<Goose className="size-6 sm:size-8" />
182+
<BrandLogo size="md" />
183183
</div>
184184
<h1 className="text-2xl sm:text-4xl font-light">Run Locally</h1>
185185
<p className="text-text-muted text-base sm:text-lg">
Lines changed: 33 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,50 @@
11
import React from 'react';
2-
import {
3-
FolderTree,
4-
MessageSquare,
5-
Code,
6-
BookOpen,
7-
Terminal,
8-
Zap,
9-
Search,
10-
FileText,
11-
LayoutDashboard,
12-
CalendarCheck,
13-
type LucideIcon,
14-
} from 'lucide-react';
2+
import { FolderTree, MessageSquare, Code } from 'lucide-react';
153
import { useWhiteLabel } from '../whitelabel/WhiteLabelContext';
4+
import { InsightCards } from './sessions/InsightCards';
165

176
interface PopularChatTopicsProps {
187
append: (text: string) => void;
198
}
209

21-
interface ChatTopic {
10+
interface DefaultTopic {
2211
id: string;
2312
icon: React.ReactNode;
24-
description: string;
13+
label: string;
2514
prompt: string;
2615
}
2716

28-
const ICON_MAP: Record<string, LucideIcon> = {
29-
'folder-tree': FolderTree,
30-
'message-square': MessageSquare,
31-
code: Code,
32-
'book-open': BookOpen,
33-
terminal: Terminal,
34-
zap: Zap,
35-
search: Search,
36-
'file-text': FileText,
37-
'layout-dashboard': LayoutDashboard,
38-
'calendar-check': CalendarCheck,
39-
};
40-
41-
const DEFAULT_TOPICS: ChatTopic[] = [
17+
const DEFAULT_TOPICS: DefaultTopic[] = [
4218
{
4319
id: 'organize-photos',
4420
icon: <FolderTree className="w-5 h-5" />,
45-
description: 'Organize the photos on my desktop into neat little folders by subject matter',
21+
label: 'Organize the photos on my desktop into neat little folders by subject matter',
4622
prompt: 'Organize the photos on my desktop into neat little folders by subject matter',
4723
},
4824
{
4925
id: 'government-forms',
5026
icon: <MessageSquare className="w-5 h-5" />,
51-
description:
27+
label:
5228
'Describe in detail how various forms of government works and rank each by units of geese',
5329
prompt:
5430
'Describe in detail how various forms of government works and rank each by units of geese',
5531
},
5632
{
5733
id: 'tamagotchi-game',
5834
icon: <Code className="w-5 h-5" />,
59-
description:
60-
'Develop a tamagotchi game that lives on my computer and follows a pixelated styling',
35+
label: 'Develop a tamagotchi game that lives on my computer and follows a pixelated styling',
6136
prompt: 'Develop a tamagotchi game that lives on my computer and follows a pixelated styling',
6237
},
6338
];
6439

65-
function useTopics(): ChatTopic[] {
66-
const { branding } = useWhiteLabel();
67-
68-
if (!branding.starterPrompts || branding.starterPrompts.length === 0) {
69-
return DEFAULT_TOPICS;
70-
}
71-
72-
return branding.starterPrompts.map((sp, i) => {
73-
const IconComponent = ICON_MAP[sp.icon] ?? Code;
74-
return {
75-
id: `starter-${i}`,
76-
icon: <IconComponent className="w-5 h-5" />,
77-
description: sp.label,
78-
prompt: sp.prompt,
79-
};
80-
});
81-
}
82-
83-
export default function PopularChatTopics({ append }: PopularChatTopicsProps) {
84-
const topics = useTopics();
85-
86-
const handleTopicClick = (prompt: string) => {
87-
append(prompt);
88-
};
89-
40+
/** Simple list layout — default goose starter prompts */
41+
function TopicList({
42+
topics,
43+
onSelect,
44+
}: {
45+
topics: DefaultTopic[];
46+
onSelect: (prompt: string) => void;
47+
}) {
9048
return (
9149
<div className="absolute bottom-0 left-0 p-6 max-w-md">
9250
<h3 className="text-text-secondary text-sm mb-1">Popular chat topics</h3>
@@ -95,20 +53,20 @@ export default function PopularChatTopics({ append }: PopularChatTopicsProps) {
9553
<div
9654
key={topic.id}
9755
className="flex items-center justify-between py-1.5 hover:bg-background-secondary rounded-md cursor-pointer transition-colors"
98-
onClick={() => handleTopicClick(topic.prompt)}
56+
onClick={() => onSelect(topic.prompt)}
9957
>
10058
<div className="flex items-center gap-3 flex-1 min-w-0">
10159
<div className="flex-shrink-0 text-text-secondary">{topic.icon}</div>
10260
<div className="flex-1 min-w-0">
103-
<p className="text-text-primary text-sm leading-tight">{topic.description}</p>
61+
<p className="text-text-primary text-sm leading-tight">{topic.label}</p>
10462
</div>
10563
</div>
10664
<div className="flex-shrink-0 ml-4">
10765
<button
10866
className="text-sm text-text-secondary hover:text-text-primary transition-colors cursor-pointer"
10967
onClick={(e) => {
11068
e.stopPropagation();
111-
handleTopicClick(topic.prompt);
69+
onSelect(topic.prompt);
11270
}}
11371
>
11472
Start
@@ -120,3 +78,17 @@ export default function PopularChatTopics({ append }: PopularChatTopicsProps) {
12078
</div>
12179
);
12280
}
81+
82+
export default function PopularChatTopics({ append }: PopularChatTopicsProps) {
83+
const { branding } = useWhiteLabel();
84+
85+
if (branding.starterPrompts && branding.starterPrompts.length > 0) {
86+
return (
87+
<div className="p-6">
88+
<InsightCards prompts={branding.starterPrompts} onSelect={append} heading="Topics" />
89+
</div>
90+
);
91+
}
92+
93+
return <TopicList topics={DEFAULT_TOPICS} onSelect={append} />;
94+
}

ui/desktop/src/components/ProviderGuard.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import {
2121
trackOnboardingSetupFailed,
2222
} from '../utils/analytics';
2323

24-
import { Goose, OpenRouter, Tetrate, ChatGPT } from './icons';
24+
import { OpenRouter, Tetrate, ChatGPT } from './icons';
25+
import BrandLogo from './BrandLogo';
26+
import { useWhiteLabel } from '../whitelabel/WhiteLabelContext';
2527

2628
interface ProviderGuardProps {
2729
didSelectProvider: boolean;
@@ -31,6 +33,7 @@ interface ProviderGuardProps {
3133
export default function ProviderGuard({ didSelectProvider, children }: ProviderGuardProps) {
3234
const { read, upsert } = useConfig();
3335
const navigate = useNavigate();
36+
const { branding } = useWhiteLabel();
3437
const [isChecking, setIsChecking] = useState(true);
3538
const [hasProvider, setHasProvider] = useState(false);
3639
const [showFirstTimeSetup, setShowFirstTimeSetup] = useState(false);
@@ -331,13 +334,15 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
331334
<div className="text-left mb-8 sm:mb-12">
332335
<div className="space-y-3 sm:space-y-4">
333336
<div className="origin-bottom-left goose-icon-animation">
334-
<Goose className="size-6 sm:size-8" />
337+
<BrandLogo size="md" />
335338
</div>
336-
<h1 className="text-2xl sm:text-4xl font-light text-left">Welcome to Goose</h1>
339+
<h1 className="text-2xl sm:text-4xl font-light text-left">
340+
Welcome to {branding.appName}
341+
</h1>
337342
</div>
338343
<p className="text-text-secondary text-base sm:text-lg mt-4 sm:mt-6">
339-
Since its your first time here, lets get you set up with an AI provider so goose
340-
can work its magic.
344+
Since it&apos;s your first time here, let&apos;s get you set up with an AI
345+
provider.
341346
</p>
342347
</div>
343348

ui/desktop/src/components/TelemetryOptOutModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useEffect } from 'react';
22
import { BaseModal } from './ui/BaseModal';
33
import { Button } from './ui/button';
4-
import { Goose } from './icons/Goose';
4+
import BrandLogo from './BrandLogo';
55
import { TELEMETRY_UI_ENABLED } from '../updates';
66
import { toastService } from '../toasts';
77
import { useConfig } from './ConfigContext';
@@ -103,7 +103,7 @@ export default function TelemetryOptOutModal(props: TelemetryOptOutModalProps) {
103103
>
104104
<div className="px-2 py-3">
105105
<div className="flex justify-center mb-4">
106-
<Goose className="size-10 text-text-primary" />
106+
<BrandLogo size="lg" />
107107
</div>
108108
<h2 className="text-2xl font-regular dark:text-white text-gray-900 text-center mb-3">
109109
Help improve goose
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React from 'react';
2+
import {
3+
Code,
4+
BarChart,
5+
Users,
6+
Package,
7+
MapPin,
8+
ShieldCheck,
9+
TrendingUp,
10+
Clock,
11+
AlertTriangle,
12+
FolderTree,
13+
MessageSquare,
14+
BookOpen,
15+
Terminal,
16+
Zap,
17+
Search,
18+
FileText,
19+
LayoutDashboard,
20+
CalendarCheck,
21+
type LucideIcon,
22+
} from 'lucide-react';
23+
import type { WhiteLabelStarterPrompt } from '../../whitelabel/types';
24+
25+
const ICON_MAP: Record<string, LucideIcon> = {
26+
'folder-tree': FolderTree,
27+
'message-square': MessageSquare,
28+
code: Code,
29+
'book-open': BookOpen,
30+
terminal: Terminal,
31+
zap: Zap,
32+
search: Search,
33+
'file-text': FileText,
34+
'layout-dashboard': LayoutDashboard,
35+
'calendar-check': CalendarCheck,
36+
'bar-chart': BarChart,
37+
users: Users,
38+
package: Package,
39+
'map-pin': MapPin,
40+
'shield-check': ShieldCheck,
41+
'trending-up': TrendingUp,
42+
clock: Clock,
43+
'alert-triangle': AlertTriangle,
44+
};
45+
46+
function getIcon(name: string, className = 'w-4 h-4'): React.ReactNode {
47+
const Icon = ICON_MAP[name] ?? Code;
48+
return <Icon className={className} />;
49+
}
50+
51+
interface InsightCardsProps {
52+
prompts: WhiteLabelStarterPrompt[];
53+
onSelect: (prompt: string) => void;
54+
heading?: string;
55+
}
56+
57+
export function InsightCards({ prompts, onSelect, heading = 'Insights' }: InsightCardsProps) {
58+
return (
59+
<div className="bg-background-primary rounded-2xl p-6">
60+
<h3 className="text-sm text-text-secondary mb-3">{heading}</h3>
61+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
62+
{prompts.map((sp, i) => (
63+
<button
64+
key={`insight-${i}`}
65+
className="flex flex-col gap-2 p-4 rounded-xl bg-background-secondary hover:bg-background-tertiary border border-border-primary transition-colors text-left cursor-pointer"
66+
onClick={() => onSelect(sp.prompt)}
67+
>
68+
<div className="flex items-center justify-between w-full">
69+
<span className="text-sm font-medium text-text-primary">{sp.label}</span>
70+
<span className="text-text-secondary flex-shrink-0 ml-2">
71+
{getIcon(sp.icon, 'w-4 h-4')}
72+
</span>
73+
</div>
74+
{sp.description && (
75+
<p className="text-xs text-text-secondary leading-relaxed line-clamp-2">
76+
{sp.description}
77+
</p>
78+
)}
79+
<span className="text-xs text-text-info hover:underline">
80+
{sp.action ?? 'Review →'}
81+
</span>
82+
</button>
83+
))}
84+
</div>
85+
</div>
86+
);
87+
}

0 commit comments

Comments
 (0)