Add light/dark theme toggle with CSS custom properties
- Wire next-themes ThemeProvider in layout with class strategy, defaultTheme=dark - Define all Gemini colors as CSS custom properties (--gem-*) in globals.css with light (:root) and dark (.dark) variants - Replace all hardcoded hex colors across 7 Gemini components with var() refs - Add Sun/Moon toggle button in GeminiTopbar (right side, before Settings) - Theme persists to localStorage via next-themes (key: gem-theme) - Dark theme renders identically to before (same hex values) - Light theme: white bg, light gray surfaces, same blue-purple accent gradient Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
3232739a32
commit
39aad16373
+96
-24
@@ -3,39 +3,111 @@
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
/* ── Light theme (default) ─────────────────────────────────────────────── */
|
||||
:root {
|
||||
/* Gemini Dark Theme — always dark */
|
||||
--background: #131314;
|
||||
--foreground: #e3e3e3;
|
||||
--card: #1e1e1e;
|
||||
--card-foreground: #e3e3e3;
|
||||
--popover: #2a2a2a;
|
||||
--popover-foreground: #e3e3e3;
|
||||
--gem-bg: #ffffff;
|
||||
--gem-surface: #f0f4f9;
|
||||
--gem-surface-hover: #e8ecf1;
|
||||
--gem-surface-2: #f5f5f5;
|
||||
--gem-surface-3: #eaeaea;
|
||||
--gem-border: #d9dce0;
|
||||
--gem-border-hover: #c0c4c8;
|
||||
--gem-text: #1f1f1f;
|
||||
--gem-text-secondary: #444746;
|
||||
--gem-text-muted: #5f6368;
|
||||
--gem-text-placeholder: #9aa0a6;
|
||||
--gem-blue: #4285f4;
|
||||
--gem-blue-hover: #3b78e7;
|
||||
--gem-purple: #a855f7;
|
||||
--gem-mid-gradient: #7c55f0;
|
||||
--gem-active-bg: #d3e3fd;
|
||||
--gem-ext-connected-bg: #e6f4e6;
|
||||
--gem-ext-connected-border: rgba(0, 128, 0, 0.2);
|
||||
--gem-scrollbar-thumb: #c0c4c8;
|
||||
--gem-scrollbar-thumb-hover: #a0a4a8;
|
||||
|
||||
/* shadcn vars — keep for UI lib compat */
|
||||
--background: var(--gem-bg);
|
||||
--foreground: var(--gem-text);
|
||||
--card: var(--gem-surface);
|
||||
--card-foreground: var(--gem-text);
|
||||
--popover: var(--gem-surface-2);
|
||||
--popover-foreground: var(--gem-text);
|
||||
--primary: #4285f4;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #2a2a2a;
|
||||
--secondary-foreground: #e3e3e3;
|
||||
--muted: #2a2a2a;
|
||||
--muted-foreground: #9aa0a6;
|
||||
--accent: #2a2a2a;
|
||||
--accent-foreground: #e3e3e3;
|
||||
--secondary: var(--gem-surface-2);
|
||||
--secondary-foreground: var(--gem-text);
|
||||
--muted: var(--gem-surface-2);
|
||||
--muted-foreground: var(--gem-text-muted);
|
||||
--accent: var(--gem-surface-2);
|
||||
--accent-foreground: var(--gem-text);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.985 0 0);
|
||||
--border: #3a3a3a;
|
||||
--input: #1e1e1e;
|
||||
--border: var(--gem-border);
|
||||
--input: var(--gem-surface);
|
||||
--ring: #4285f4;
|
||||
--radius: 0.75rem;
|
||||
--sidebar: #1e1e1e;
|
||||
--sidebar-foreground: #e3e3e3;
|
||||
--sidebar: var(--gem-surface);
|
||||
--sidebar-foreground: var(--gem-text);
|
||||
--sidebar-primary: #4285f4;
|
||||
--sidebar-primary-foreground: #ffffff;
|
||||
--sidebar-accent: #2a2a2a;
|
||||
--sidebar-accent-foreground: #e3e3e3;
|
||||
--sidebar-border: #3a3a3a;
|
||||
--sidebar-accent: var(--gem-surface-2);
|
||||
--sidebar-accent-foreground: var(--gem-text);
|
||||
--sidebar-border: var(--gem-border);
|
||||
--sidebar-ring: #4285f4;
|
||||
/* Gemini brand gradient stops */
|
||||
}
|
||||
|
||||
/* ── Dark theme ────────────────────────────────────────────────────────── */
|
||||
.dark {
|
||||
--gem-bg: #131314;
|
||||
--gem-surface: #1e1e1e;
|
||||
--gem-surface-hover: #252526;
|
||||
--gem-surface-2: #2a2a2a;
|
||||
--gem-surface-3: #333333;
|
||||
--gem-border: #3a3a3a;
|
||||
--gem-border-hover: #4a4a4a;
|
||||
--gem-text: #e3e3e3;
|
||||
--gem-text-secondary: #c4c7c5;
|
||||
--gem-text-muted: #9aa0a6;
|
||||
--gem-text-placeholder: #5f6368;
|
||||
--gem-blue: #4285f4;
|
||||
--gem-blue-hover: #3b78e7;
|
||||
--gem-purple: #a855f7;
|
||||
--gem-mid-gradient: #7c55f0;
|
||||
--gem-active-bg: #1a2a40;
|
||||
--gem-ext-connected-bg: #1a2a1a;
|
||||
--gem-ext-connected-border: rgba(0, 128, 0, 0.3);
|
||||
--gem-scrollbar-thumb: #3a3a3a;
|
||||
--gem-scrollbar-thumb-hover: #4a4a4a;
|
||||
|
||||
--background: var(--gem-bg);
|
||||
--foreground: var(--gem-text);
|
||||
--card: var(--gem-surface);
|
||||
--card-foreground: var(--gem-text);
|
||||
--popover: var(--gem-surface-2);
|
||||
--popover-foreground: var(--gem-text);
|
||||
--primary: #4285f4;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: var(--gem-surface-2);
|
||||
--secondary-foreground: var(--gem-text);
|
||||
--muted: var(--gem-surface-2);
|
||||
--muted-foreground: var(--gem-text-muted);
|
||||
--accent: var(--gem-surface-2);
|
||||
--accent-foreground: var(--gem-text);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--destructive-foreground: oklch(0.985 0 0);
|
||||
--border: var(--gem-border);
|
||||
--input: var(--gem-surface);
|
||||
--ring: #4285f4;
|
||||
--radius: 0.75rem;
|
||||
--sidebar: var(--gem-surface);
|
||||
--sidebar-foreground: var(--gem-text);
|
||||
--sidebar-primary: #4285f4;
|
||||
--sidebar-primary-foreground: #ffffff;
|
||||
--sidebar-accent: var(--gem-surface-2);
|
||||
--sidebar-accent-foreground: var(--gem-text);
|
||||
--sidebar-border: var(--gem-border);
|
||||
--sidebar-ring: #4285f4;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
@@ -80,7 +152,7 @@
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #3a3a3a transparent;
|
||||
scrollbar-color: var(--gem-scrollbar-thumb) transparent;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
@@ -94,10 +166,10 @@
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #3a3a3a;
|
||||
background: var(--gem-scrollbar-thumb);
|
||||
border-radius: 3px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #4a4a4a;
|
||||
background: var(--gem-scrollbar-thumb-hover);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Geist, Geist_Mono } from 'next/font/google'
|
||||
import { Analytics } from '@vercel/analytics/next'
|
||||
import { ThemeProvider } from '@/components/theme-provider'
|
||||
import './globals.css'
|
||||
|
||||
const _geist = Geist({ subsets: ["latin"] });
|
||||
@@ -35,9 +36,11 @@ export default function RootLayout({
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="zh-CN">
|
||||
<html lang="zh-CN" suppressHydrationWarning>
|
||||
<body className="font-sans antialiased">
|
||||
{children}
|
||||
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem={false} storageKey="gem-theme">
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
{process.env.NODE_ENV === 'production' && <Analytics />}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -43,11 +43,11 @@ export function ExtensionsPanel({
|
||||
|
||||
const handleConnect = async (ext: Extension) => {
|
||||
if (!tempKey.trim()) return;
|
||||
|
||||
|
||||
setConnecting(ext.id);
|
||||
// 模拟API验证
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
|
||||
|
||||
onUpdateExtension(ext.id, tempKey, true);
|
||||
setConnecting(null);
|
||||
setEditingId(null);
|
||||
@@ -79,13 +79,13 @@ export function ExtensionsPanel({
|
||||
/>
|
||||
|
||||
{/* Panel */}
|
||||
<div className="relative w-full max-w-lg bg-[#1e1e1e] border border-[#3a3a3a] rounded-2xl shadow-2xl overflow-hidden">
|
||||
<div className="relative w-full max-w-lg bg-[var(--gem-surface)] border border-[var(--gem-border)] rounded-2xl shadow-2xl overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-[#3a3a3a]">
|
||||
<h2 className="text-lg font-semibold text-[#e3e3e3]">扩展程序</h2>
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-[var(--gem-border)]">
|
||||
<h2 className="text-lg font-semibold text-[var(--gem-text)]">扩展程序</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1.5 rounded-full hover:bg-[#2a2a2a] text-[#9aa0a6] hover:text-[#e3e3e3] transition-colors"
|
||||
className="p-1.5 rounded-full hover:bg-[var(--gem-surface-2)] text-[var(--gem-text-muted)] hover:text-[var(--gem-text)] transition-colors"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
@@ -104,8 +104,8 @@ export function ExtensionsPanel({
|
||||
className={cn(
|
||||
"rounded-xl border transition-all duration-200",
|
||||
ext.connected
|
||||
? "bg-[#1a2a1a] border-green-800/50"
|
||||
: "bg-[#2a2a2a] border-[#3a3a3a]"
|
||||
? "bg-[var(--gem-ext-connected-bg)] border-green-800/50"
|
||||
: "bg-[var(--gem-surface-2)] border-[var(--gem-border)]"
|
||||
)}
|
||||
>
|
||||
{/* Extension header */}
|
||||
@@ -113,17 +113,17 @@ export function ExtensionsPanel({
|
||||
<div
|
||||
className={cn(
|
||||
"w-10 h-10 rounded-lg flex items-center justify-center",
|
||||
ext.connected ? "bg-green-900/50" : "bg-[#3a3a3a]"
|
||||
ext.connected ? "bg-green-900/50" : "bg-[var(--gem-border)]"
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
size={20}
|
||||
className={ext.connected ? "text-green-400" : "text-[#9aa0a6]"}
|
||||
className={ext.connected ? "text-green-400" : "text-[var(--gem-text-muted)]"}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-medium text-[#e3e3e3]">{ext.name}</h3>
|
||||
<h3 className="font-medium text-[var(--gem-text)]">{ext.name}</h3>
|
||||
{ext.connected && (
|
||||
<span className="flex items-center gap-1 text-xs text-green-400">
|
||||
<Check size={12} />
|
||||
@@ -131,7 +131,7 @@ export function ExtensionsPanel({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-[#9aa0a6] truncate">
|
||||
<p className="text-sm text-[var(--gem-text-muted)] truncate">
|
||||
{ext.description}
|
||||
</p>
|
||||
</div>
|
||||
@@ -144,7 +144,7 @@ export function ExtensionsPanel({
|
||||
"px-3 py-1.5 text-sm font-medium rounded-lg transition-colors",
|
||||
ext.connected
|
||||
? "text-red-400 hover:bg-red-900/30"
|
||||
: "text-[#4285f4] hover:bg-[#4285f4]/10"
|
||||
: "text-[var(--gem-blue)] hover:bg-[var(--gem-blue)]/10"
|
||||
)}
|
||||
>
|
||||
{ext.connected ? "断开" : "连接"}
|
||||
@@ -156,7 +156,7 @@ export function ExtensionsPanel({
|
||||
{isEditing && (
|
||||
<div className="px-4 pb-4 space-y-3">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs text-[#9aa0a6] flex items-center gap-1">
|
||||
<label className="text-xs text-[var(--gem-text-muted)] flex items-center gap-1">
|
||||
<AlertCircle size={12} />
|
||||
API Key(密钥将安全存储)
|
||||
</label>
|
||||
@@ -166,7 +166,7 @@ export function ExtensionsPanel({
|
||||
value={tempKey}
|
||||
onChange={(e) => setTempKey(e.target.value)}
|
||||
placeholder="请输入 API Key"
|
||||
className="w-full px-3 py-2 pr-10 bg-[#1e1e1e] border border-[#3a3a3a] rounded-lg text-[#e3e3e3] placeholder:text-[#5f6368] focus:outline-none focus:border-[#4285f4] text-sm"
|
||||
className="w-full px-3 py-2 pr-10 bg-[var(--gem-surface)] border border-[var(--gem-border)] rounded-lg text-[var(--gem-text)] placeholder:text-[var(--gem-text-placeholder)] focus:outline-none focus:border-[var(--gem-blue)] text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -176,7 +176,7 @@ export function ExtensionsPanel({
|
||||
[ext.id]: !prev[ext.id],
|
||||
}))
|
||||
}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-[#9aa0a6] hover:text-[#e3e3e3]"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-[var(--gem-text-muted)] hover:text-[var(--gem-text)]"
|
||||
>
|
||||
{showKey[ext.id] ? (
|
||||
<EyeOff size={16} />
|
||||
@@ -189,7 +189,7 @@ export function ExtensionsPanel({
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
className="flex-1 px-3 py-2 text-sm text-[#9aa0a6] hover:text-[#e3e3e3] hover:bg-[#3a3a3a] rounded-lg transition-colors"
|
||||
className="flex-1 px-3 py-2 text-sm text-[var(--gem-text-muted)] hover:text-[var(--gem-text)] hover:bg-[var(--gem-border)] rounded-lg transition-colors"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
@@ -199,8 +199,8 @@ export function ExtensionsPanel({
|
||||
className={cn(
|
||||
"flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors flex items-center justify-center gap-2",
|
||||
tempKey.trim() && !isConnecting
|
||||
? "bg-[#4285f4] text-white hover:bg-[#3b78e7]"
|
||||
: "bg-[#3a3a3a] text-[#5f6368] cursor-not-allowed"
|
||||
? "bg-[var(--gem-blue)] text-white hover:bg-[var(--gem-blue-hover)]"
|
||||
: "bg-[var(--gem-border)] text-[var(--gem-text-placeholder)] cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
{isConnecting ? (
|
||||
@@ -219,14 +219,14 @@ export function ExtensionsPanel({
|
||||
{/* Connected info */}
|
||||
{ext.connected && !isEditing && (
|
||||
<div className="px-4 pb-4">
|
||||
<div className="flex items-center gap-2 text-xs text-[#9aa0a6]">
|
||||
<div className="flex items-center gap-2 text-xs text-[var(--gem-text-muted)]">
|
||||
<span>API Key:</span>
|
||||
<code className="px-2 py-0.5 bg-[#2a2a2a] rounded text-[#9aa0a6]">
|
||||
<code className="px-2 py-0.5 bg-[var(--gem-surface-2)] rounded text-[var(--gem-text-muted)]">
|
||||
{ext.apiKey.slice(0, 8)}****{ext.apiKey.slice(-4)}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => handleEdit(ext)}
|
||||
className="text-[#4285f4] hover:underline ml-auto"
|
||||
className="text-[var(--gem-blue)] hover:underline ml-auto"
|
||||
>
|
||||
修改
|
||||
</button>
|
||||
@@ -239,8 +239,8 @@ export function ExtensionsPanel({
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="px-6 py-3 border-t border-[#3a3a3a] bg-[#1a1a1a]">
|
||||
<p className="text-xs text-[#5f6368] text-center">
|
||||
<div className="px-6 py-3 border-t border-[var(--gem-border)] bg-[var(--gem-bg)]">
|
||||
<p className="text-xs text-[var(--gem-text-placeholder)] text-center">
|
||||
连接扩展后,可在对话中查看相关系统数据摘要
|
||||
</p>
|
||||
</div>
|
||||
@@ -271,29 +271,29 @@ export function TicketSummary({ tickets }: TicketSummaryProps) {
|
||||
P0: "text-red-400",
|
||||
P1: "text-orange-400",
|
||||
P2: "text-yellow-400",
|
||||
P3: "text-[#9aa0a6]",
|
||||
P3: "text-[var(--gem-text-muted)]",
|
||||
};
|
||||
|
||||
if (tickets.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl px-4 mb-4">
|
||||
<div className="bg-[#1e1e1e] border border-[#3a3a3a] rounded-xl overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-2.5 border-b border-[#3a3a3a] bg-[#252525]">
|
||||
<Ticket size={16} className="text-[#4285f4]" />
|
||||
<span className="text-sm font-medium text-[#e3e3e3]">工单摘要</span>
|
||||
<span className="text-xs text-[#9aa0a6]">({tickets.length} 条待关注)</span>
|
||||
<div className="bg-[var(--gem-surface)] border border-[var(--gem-border)] rounded-xl overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-2.5 border-b border-[var(--gem-border)] bg-[var(--gem-surface-hover)]">
|
||||
<Ticket size={16} className="text-[var(--gem-blue)]" />
|
||||
<span className="text-sm font-medium text-[var(--gem-text)]">工单摘要</span>
|
||||
<span className="text-xs text-[var(--gem-text-muted)]">({tickets.length} 条待关注)</span>
|
||||
</div>
|
||||
<div className="divide-y divide-[#2a2a2a]">
|
||||
<div className="divide-y divide-[var(--gem-surface-2)]">
|
||||
{tickets.slice(0, 5).map((ticket) => (
|
||||
<div
|
||||
key={ticket.id}
|
||||
className="flex items-center gap-3 px-4 py-2.5 hover:bg-[#2a2a2a] transition-colors cursor-pointer"
|
||||
className="flex items-center gap-3 px-4 py-2.5 hover:bg-[var(--gem-surface-2)] transition-colors cursor-pointer"
|
||||
>
|
||||
<span className={cn("text-xs font-mono", priorityColors[ticket.priority])}>
|
||||
{ticket.priority}
|
||||
</span>
|
||||
<span className="flex-1 text-sm text-[#e3e3e3] truncate">
|
||||
<span className="flex-1 text-sm text-[var(--gem-text)] truncate">
|
||||
{ticket.title}
|
||||
</span>
|
||||
<span
|
||||
@@ -304,13 +304,13 @@ export function TicketSummary({ tickets }: TicketSummaryProps) {
|
||||
>
|
||||
{statusMap[ticket.status].label}
|
||||
</span>
|
||||
<span className="text-xs text-[#5f6368]">{ticket.createdAt}</span>
|
||||
<span className="text-xs text-[var(--gem-text-placeholder)]">{ticket.createdAt}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{tickets.length > 5 && (
|
||||
<div className="px-4 py-2 text-center border-t border-[#2a2a2a]">
|
||||
<button className="text-xs text-[#4285f4] hover:underline">
|
||||
<div className="px-4 py-2 text-center border-t border-[var(--gem-surface-2)]">
|
||||
<button className="text-xs text-[var(--gem-blue)] hover:underline">
|
||||
查看全部 {tickets.length} 条工单
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -317,7 +317,7 @@ export function GeminiChat() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-[#131314] overflow-hidden font-sans" role="main">
|
||||
<div className="flex h-screen bg-[var(--gem-bg)] overflow-hidden font-sans" role="main">
|
||||
{/* Sidebar */}
|
||||
<GeminiSidebar
|
||||
isOpen={sidebarOpen}
|
||||
|
||||
@@ -94,10 +94,10 @@ export function GeminiInput({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sticky bottom-0 left-0 right-0 pb-6 pt-4 bg-gradient-to-t from-[#131314] via-[#131314]/90 to-transparent pointer-events-none">
|
||||
<div className="sticky bottom-0 left-0 right-0 pb-6 pt-4 bg-gradient-to-t from-[var(--gem-bg)] via-[var(--gem-bg)]/90 to-transparent pointer-events-none">
|
||||
<div className="max-w-3xl mx-auto px-4 pointer-events-auto">
|
||||
{/* Input container */}
|
||||
<div className="bg-[#1e1e1e] border border-[#3a3a3a] rounded-3xl shadow-lg focus-within:border-[#4a4a4a] transition-colors duration-150">
|
||||
<div className="bg-[var(--gem-surface)] border border-[var(--gem-border)] rounded-3xl shadow-lg focus-within:border-[var(--gem-border-hover)] transition-colors duration-150">
|
||||
{/* Selected tools chips (auto show when has active tools) */}
|
||||
{activeTools.size > 0 && (
|
||||
<div className="flex items-center gap-2 px-4 pt-3 pb-1 flex-wrap">
|
||||
@@ -108,7 +108,7 @@ export function GeminiInput({
|
||||
return (
|
||||
<span
|
||||
key={tool.id}
|
||||
className="flex items-center gap-2 px-2.5 py-1.5 rounded-lg text-xs font-medium bg-[#4285f4] text-white border border-[#4285f4]"
|
||||
className="flex items-center gap-2 px-2.5 py-1.5 rounded-lg text-xs font-medium bg-[var(--gem-blue)] text-white border border-[var(--gem-blue)]"
|
||||
>
|
||||
<Icon size={14} />
|
||||
<span>{tool.label}</span>
|
||||
@@ -130,7 +130,7 @@ export function GeminiInput({
|
||||
{/* Left: File upload button */}
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="p-1.5 rounded-full text-[#9aa0a6] hover:text-[#e3e3e3] hover:bg-[#2a2a2a] transition-colors duration-150 flex-shrink-0 cursor-pointer mb-0.5"
|
||||
className="p-1.5 rounded-full text-[var(--gem-text-muted)] hover:text-[var(--gem-text)] hover:bg-[var(--gem-surface-2)] transition-colors duration-150 flex-shrink-0 cursor-pointer mb-0.5"
|
||||
aria-label="文件上传"
|
||||
title="上传文件"
|
||||
>
|
||||
@@ -151,8 +151,8 @@ export function GeminiInput({
|
||||
className={cn(
|
||||
"p-1.5 rounded-full transition-colors duration-150 flex-shrink-0 cursor-pointer mb-0.5",
|
||||
showTools
|
||||
? "bg-[#4285f4]/20 text-[#4285f4]"
|
||||
: "text-[#9aa0a6] hover:text-[#e3e3e3] hover:bg-[#2a2a2a]"
|
||||
? "bg-[var(--gem-blue)]/20 text-[var(--gem-blue)]"
|
||||
: "text-[var(--gem-text-muted)] hover:text-[var(--gem-text)] hover:bg-[var(--gem-surface-2)]"
|
||||
)}
|
||||
aria-label="工具菜单"
|
||||
title="工具"
|
||||
@@ -170,7 +170,7 @@ export function GeminiInput({
|
||||
placeholder="阐述你的图片"
|
||||
rows={1}
|
||||
className={cn(
|
||||
"flex-1 bg-transparent text-[#e3e3e3] text-sm placeholder:text-[#5f6368] resize-none outline-none",
|
||||
"flex-1 bg-transparent text-[var(--gem-text)] text-sm placeholder:text-[var(--gem-text-placeholder)] resize-none outline-none",
|
||||
"leading-relaxed py-0.5 min-h-[28px] max-h-[200px] overflow-y-auto scrollbar-thin"
|
||||
)}
|
||||
aria-label="消息输入"
|
||||
@@ -180,7 +180,7 @@ export function GeminiInput({
|
||||
{/* Right controls */}
|
||||
<div className="flex items-center gap-1 flex-shrink-0 mb-0.5">
|
||||
<button
|
||||
className="p-1.5 rounded-full text-[#9aa0a6] hover:text-[#e3e3e3] hover:bg-[#2a2a2a] transition-colors duration-150 cursor-pointer"
|
||||
className="p-1.5 rounded-full text-[var(--gem-text-muted)] hover:text-[var(--gem-text)] hover:bg-[var(--gem-surface-2)] transition-colors duration-150 cursor-pointer"
|
||||
aria-label="语音输入"
|
||||
>
|
||||
<Mic size={20} />
|
||||
@@ -192,8 +192,8 @@ export function GeminiInput({
|
||||
className={cn(
|
||||
"w-9 h-9 rounded-full flex items-center justify-center transition-all duration-150",
|
||||
canSend
|
||||
? "bg-[#4285f4] text-white hover:bg-[#3b78e7] cursor-pointer shadow-md"
|
||||
: "bg-[#2a2a2a] text-[#5f6368] cursor-not-allowed"
|
||||
? "bg-[var(--gem-blue)] text-white hover:bg-[var(--gem-blue-hover)] cursor-pointer shadow-md"
|
||||
: "bg-[var(--gem-surface-2)] text-[var(--gem-text-placeholder)] cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
<ArrowUp
|
||||
@@ -211,8 +211,8 @@ export function GeminiInput({
|
||||
className={cn(
|
||||
"px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-150 cursor-pointer",
|
||||
selectedModel === "flash"
|
||||
? "bg-[#4285f4] text-white"
|
||||
: "bg-[#2a2a2a] text-[#9aa0a6] hover:bg-[#333333] hover:text-[#e3e3e3]"
|
||||
? "bg-[var(--gem-blue)] text-white"
|
||||
: "bg-[var(--gem-surface-2)] text-[var(--gem-text-muted)] hover:bg-[var(--gem-surface-3)] hover:text-[var(--gem-text)]"
|
||||
)}
|
||||
aria-pressed={selectedModel === "flash"}
|
||||
>
|
||||
@@ -223,8 +223,8 @@ export function GeminiInput({
|
||||
className={cn(
|
||||
"px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-150 cursor-pointer",
|
||||
selectedModel === "pro"
|
||||
? "bg-[#a855f7] text-white"
|
||||
: "bg-[#2a2a2a] text-[#9aa0a6] hover:bg-[#333333] hover:text-[#e3e3e3]"
|
||||
? "bg-[var(--gem-purple)] text-white"
|
||||
: "bg-[var(--gem-surface-2)] text-[var(--gem-text-muted)] hover:bg-[var(--gem-surface-3)] hover:text-[var(--gem-text)]"
|
||||
)}
|
||||
aria-pressed={selectedModel === "pro"}
|
||||
>
|
||||
@@ -234,7 +234,7 @@ export function GeminiInput({
|
||||
|
||||
{/* Tools panel (collapsible, below input) */}
|
||||
{showTools && (
|
||||
<div className="px-4 pb-3 pt-2 border-t border-[#2a2a2a]">
|
||||
<div className="px-4 pb-3 pt-2 border-t border-[var(--gem-surface-2)]">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{TOOLS.map((tool) => {
|
||||
const Icon = tool.icon;
|
||||
@@ -246,8 +246,8 @@ export function GeminiInput({
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 cursor-pointer whitespace-nowrap",
|
||||
isActive
|
||||
? "bg-[#4285f4] text-white border border-[#4285f4]"
|
||||
: "bg-[#2a2a2a] text-[#9aa0a6] border border-transparent hover:bg-[#333333] hover:text-[#e3e3e3]"
|
||||
? "bg-[var(--gem-blue)] text-white border border-[var(--gem-blue)]"
|
||||
: "bg-[var(--gem-surface-2)] text-[var(--gem-text-muted)] border border-transparent hover:bg-[var(--gem-surface-3)] hover:text-[var(--gem-text)]"
|
||||
)}
|
||||
aria-pressed={isActive}
|
||||
>
|
||||
@@ -262,9 +262,9 @@ export function GeminiInput({
|
||||
</div>
|
||||
|
||||
{/* Disclaimer */}
|
||||
<p className="text-center text-[11px] text-[#5f6368] mt-2.5">
|
||||
<p className="text-center text-[11px] text-[var(--gem-text-placeholder)] mt-2.5">
|
||||
运营大脑可能会出错,请仔细检查其回复。{" "}
|
||||
<button className="underline hover:text-[#9aa0a6] transition-colors cursor-pointer">
|
||||
<button className="underline hover:text-[var(--gem-text-muted)] transition-colors cursor-pointer">
|
||||
你的隐私与运营大脑应用
|
||||
</button>
|
||||
</p>
|
||||
|
||||
@@ -37,10 +37,10 @@ function renderContent(content: string) {
|
||||
elements.push(
|
||||
<pre
|
||||
key={i}
|
||||
className="bg-[#1e1e1e] border border-[#3a3a3a] rounded-xl p-4 overflow-x-auto my-3 text-sm font-mono text-[#c4c7c5]"
|
||||
className="bg-[var(--gem-surface)] border border-[var(--gem-border)] rounded-xl p-4 overflow-x-auto my-3 text-sm font-mono text-[var(--gem-text-secondary)]"
|
||||
>
|
||||
{lang && (
|
||||
<div className="text-xs text-[#9aa0a6] mb-2 uppercase tracking-wide">{lang}</div>
|
||||
<div className="text-xs text-[var(--gem-text-muted)] mb-2 uppercase tracking-wide">{lang}</div>
|
||||
)}
|
||||
<code>{codeLines.join("\n")}</code>
|
||||
</pre>
|
||||
@@ -49,7 +49,7 @@ function renderContent(content: string) {
|
||||
// H2
|
||||
else if (line.startsWith("## ")) {
|
||||
elements.push(
|
||||
<h2 key={i} className="text-lg font-semibold text-[#e3e3e3] mt-4 mb-2">
|
||||
<h2 key={i} className="text-lg font-semibold text-[var(--gem-text)] mt-4 mb-2">
|
||||
{line.slice(3)}
|
||||
</h2>
|
||||
);
|
||||
@@ -57,7 +57,7 @@ function renderContent(content: string) {
|
||||
// H3
|
||||
else if (line.startsWith("### ")) {
|
||||
elements.push(
|
||||
<h3 key={i} className="text-base font-semibold text-[#e3e3e3] mt-3 mb-1.5">
|
||||
<h3 key={i} className="text-base font-semibold text-[var(--gem-text)] mt-3 mb-1.5">
|
||||
{line.slice(4)}
|
||||
</h3>
|
||||
);
|
||||
@@ -70,7 +70,7 @@ function renderContent(content: string) {
|
||||
i++;
|
||||
}
|
||||
elements.push(
|
||||
<ul key={i} className="list-disc list-inside space-y-1 my-2 text-[#c4c7c5] text-sm leading-relaxed">
|
||||
<ul key={i} className="list-disc list-inside space-y-1 my-2 text-[var(--gem-text-secondary)] text-sm leading-relaxed">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx} dangerouslySetInnerHTML={{ __html: inlineFormat(item) }} />
|
||||
))}
|
||||
@@ -86,7 +86,7 @@ function renderContent(content: string) {
|
||||
i++;
|
||||
}
|
||||
elements.push(
|
||||
<ol key={i} className="list-decimal list-inside space-y-1 my-2 text-[#c4c7c5] text-sm leading-relaxed">
|
||||
<ol key={i} className="list-decimal list-inside space-y-1 my-2 text-[var(--gem-text-secondary)] text-sm leading-relaxed">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx} dangerouslySetInnerHTML={{ __html: inlineFormat(item) }} />
|
||||
))}
|
||||
@@ -103,7 +103,7 @@ function renderContent(content: string) {
|
||||
elements.push(
|
||||
<p
|
||||
key={i}
|
||||
className="text-[#c4c7c5] text-sm leading-relaxed"
|
||||
className="text-[var(--gem-text-secondary)] text-sm leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: inlineFormat(line) }}
|
||||
/>
|
||||
);
|
||||
@@ -116,9 +116,9 @@ function renderContent(content: string) {
|
||||
|
||||
function inlineFormat(text: string): string {
|
||||
return text
|
||||
.replace(/\*\*(.+?)\*\*/g, '<strong class="text-[#e3e3e3] font-semibold">$1</strong>')
|
||||
.replace(/\*\*(.+?)\*\*/g, '<strong style="color:var(--gem-text);font-weight:600">$1</strong>')
|
||||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||
.replace(/`(.+?)`/g, '<code class="bg-[#2a2a2a] px-1.5 py-0.5 rounded text-xs font-mono text-[#a8c4f5]">$1</code>');
|
||||
.replace(/`(.+?)`/g, '<code style="background:var(--gem-surface-2);color:#a8c4f5;padding:2px 6px;border-radius:4px;font-size:0.75rem;font-family:monospace">$1</code>');
|
||||
}
|
||||
|
||||
const GemIcon = ({ size = 18 }: { size?: number }) => (
|
||||
@@ -149,7 +149,7 @@ export function GeminiMessage({ message, onRegenerate }: GeminiMessageProps) {
|
||||
if (message.role === "user") {
|
||||
return (
|
||||
<div className="flex justify-end mb-4" role="article" aria-label="Your message">
|
||||
<div className="max-w-[70%] bg-[#2a2a2a] rounded-2xl px-5 py-3.5 text-[#e3e3e3] text-sm leading-relaxed whitespace-pre-wrap">
|
||||
<div className="max-w-[70%] bg-[var(--gem-surface-2)] rounded-2xl px-5 py-3.5 text-[var(--gem-text)] text-sm leading-relaxed whitespace-pre-wrap">
|
||||
{message.content}
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,8 +221,8 @@ function ActionButton({
|
||||
className={cn(
|
||||
"p-1.5 rounded-lg transition-colors duration-150 cursor-pointer",
|
||||
active
|
||||
? "text-[#4285f4] bg-[#1a2a40]"
|
||||
: "text-[#9aa0a6] hover:text-[#e3e3e3] hover:bg-[#2a2a2a]"
|
||||
? "text-[var(--gem-blue)] bg-[var(--gem-active-bg)]"
|
||||
: "text-[var(--gem-text-muted)] hover:text-[var(--gem-text)] hover:bg-[var(--gem-surface-2)]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -54,7 +54,7 @@ export function GeminiSidebar({
|
||||
<aside
|
||||
className={cn(
|
||||
"fixed left-0 top-0 h-full z-20 flex flex-col transition-all duration-300 ease-in-out",
|
||||
"bg-[#1e1e1e] border-r border-[#3a3a3a]",
|
||||
"bg-[var(--gem-surface)] border-r border-[var(--gem-border)]",
|
||||
isOpen ? "w-[260px] opacity-100" : "w-0 opacity-0 overflow-hidden pointer-events-none"
|
||||
)}
|
||||
aria-label="Sidebar navigation"
|
||||
@@ -62,7 +62,7 @@ export function GeminiSidebar({
|
||||
{/* Logo */}
|
||||
<div className="flex items-center gap-2.5 px-4 pt-5 pb-4">
|
||||
<GemIcon />
|
||||
<span className="text-xl font-semibold bg-gradient-to-r from-[#4285f4] to-[#a855f7] bg-clip-text text-transparent select-none">
|
||||
<span className="text-xl font-semibold bg-gradient-to-r from-[var(--gem-blue)] to-[var(--gem-purple)] bg-clip-text text-transparent select-none">
|
||||
运营大脑
|
||||
</span>
|
||||
</div>
|
||||
@@ -71,17 +71,17 @@ export function GeminiSidebar({
|
||||
<div className="px-3 mb-4">
|
||||
<button
|
||||
onClick={onNewChat}
|
||||
className="w-full flex items-center gap-2.5 px-4 py-2.5 rounded-full border border-[#3a3a3a] text-[#e3e3e3] text-sm font-medium hover:bg-[#2a2a2a] transition-colors duration-150 cursor-pointer"
|
||||
className="w-full flex items-center gap-2.5 px-4 py-2.5 rounded-full border border-[var(--gem-border)] text-[var(--gem-text)] text-sm font-medium hover:bg-[var(--gem-surface-2)] transition-colors duration-150 cursor-pointer"
|
||||
aria-label="新建对话"
|
||||
>
|
||||
<PencilLine size={16} className="text-[#9aa0a6]" />
|
||||
<PencilLine size={16} className="text-[var(--gem-text-muted)]" />
|
||||
<span>新建对话</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Recent conversations */}
|
||||
<div className="flex-1 overflow-y-auto px-2 scrollbar-thin">
|
||||
<p className="text-xs font-medium text-[#9aa0a6] px-3 mb-2 uppercase tracking-wide">
|
||||
<p className="text-xs font-medium text-[var(--gem-text-muted)] px-3 mb-2 uppercase tracking-wide">
|
||||
最近的对话
|
||||
</p>
|
||||
<nav aria-label="Recent conversations">
|
||||
@@ -95,8 +95,8 @@ export function GeminiSidebar({
|
||||
className={cn(
|
||||
"w-full flex items-center justify-between px-3 py-2.5 rounded-xl text-sm text-left transition-colors duration-150 cursor-pointer group",
|
||||
activeConversationId === conv.id
|
||||
? "bg-[#2a2a2a] text-[#e3e3e3]"
|
||||
: "text-[#c4c7c5] hover:bg-[#2a2a2a] hover:text-[#e3e3e3]"
|
||||
? "bg-[var(--gem-surface-2)] text-[var(--gem-text)]"
|
||||
: "text-[var(--gem-text-secondary)] hover:bg-[var(--gem-surface-2)] hover:text-[var(--gem-text)]"
|
||||
)}
|
||||
aria-current={activeConversationId === conv.id ? "page" : undefined}
|
||||
>
|
||||
@@ -104,7 +104,7 @@ export function GeminiSidebar({
|
||||
{hoveredId === conv.id && (
|
||||
<MoreHorizontal
|
||||
size={16}
|
||||
className="flex-shrink-0 ml-1 text-[#9aa0a6] hover:text-[#e3e3e3]"
|
||||
className="flex-shrink-0 ml-1 text-[var(--gem-text-muted)] hover:text-[var(--gem-text)]"
|
||||
aria-label="More options"
|
||||
/>
|
||||
)}
|
||||
@@ -116,7 +116,7 @@ export function GeminiSidebar({
|
||||
</div>
|
||||
|
||||
{/* Bottom nav */}
|
||||
<div className="border-t border-[#3a3a3a] pt-2 pb-4 px-2">
|
||||
<div className="border-t border-[var(--gem-border)] pt-2 pb-4 px-2">
|
||||
<nav aria-label="App navigation">
|
||||
<ul className="space-y-0.5">
|
||||
{[
|
||||
@@ -126,12 +126,12 @@ export function GeminiSidebar({
|
||||
<li key={label}>
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-[#9aa0a6] hover:bg-[#2a2a2a] hover:text-[#e3e3e3] transition-colors duration-150 cursor-pointer"
|
||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-[var(--gem-text-muted)] hover:bg-[var(--gem-surface-2)] hover:text-[var(--gem-text)] transition-colors duration-150 cursor-pointer"
|
||||
>
|
||||
<Icon size={18} />
|
||||
<span>{label}</span>
|
||||
{label === "扩展程序" && connectedExtensionsCount > 0 && (
|
||||
<span className="ml-auto text-xs bg-[#4285f4] text-white px-1.5 py-0.5 rounded-full">
|
||||
<span className="ml-auto text-xs bg-[var(--gem-blue)] text-white px-1.5 py-0.5 rounded-full">
|
||||
{connectedExtensionsCount}
|
||||
</span>
|
||||
)}
|
||||
@@ -142,16 +142,16 @@ export function GeminiSidebar({
|
||||
</nav>
|
||||
|
||||
{/* User avatar */}
|
||||
<div className="flex items-center gap-3 px-3 pt-3 mt-1 border-t border-[#3a3a3a]">
|
||||
<div className="flex items-center gap-3 px-3 pt-3 mt-1 border-t border-[var(--gem-border)]">
|
||||
<div
|
||||
className="w-8 h-8 rounded-full bg-gradient-to-br from-[#4285f4] to-[#a855f7] flex items-center justify-center text-white text-sm font-semibold flex-shrink-0"
|
||||
className="w-8 h-8 rounded-full bg-gradient-to-br from-[var(--gem-blue)] to-[var(--gem-purple)] flex items-center justify-center text-white text-sm font-semibold flex-shrink-0"
|
||||
aria-label="用户头像"
|
||||
>
|
||||
U
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm text-[#e3e3e3] font-medium truncate">用户</p>
|
||||
<p className="text-xs text-[#9aa0a6] truncate">user@gmail.com</p>
|
||||
<p className="text-sm text-[var(--gem-text)] font-medium truncate">用户</p>
|
||||
<p className="text-xs text-[var(--gem-text-muted)] truncate">user@gmail.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Menu, ChevronDown, Settings } from "lucide-react";
|
||||
import { Menu, ChevronDown, Settings, Sun, Moon } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface GeminiTopbarProps {
|
||||
@@ -19,12 +20,13 @@ const models = [
|
||||
export function GeminiTopbar({ sidebarOpen, onToggleSidebar }: GeminiTopbarProps) {
|
||||
const [selectedModel, setSelectedModel] = useState("Gemini 2.0 Flash");
|
||||
const [modelDropdownOpen, setModelDropdownOpen] = useState(false);
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
"fixed top-0 right-0 z-10 flex items-center justify-between px-4 h-14",
|
||||
"bg-[#131314]/80 backdrop-blur-md border-b border-[#3a3a3a]/50",
|
||||
"bg-[var(--gem-bg)]/80 backdrop-blur-md border-b border-[var(--gem-border)]/50",
|
||||
"transition-all duration-300",
|
||||
sidebarOpen ? "left-[260px]" : "left-0"
|
||||
)}
|
||||
@@ -33,7 +35,7 @@ export function GeminiTopbar({ sidebarOpen, onToggleSidebar }: GeminiTopbarProps
|
||||
{/* Left: hamburger */}
|
||||
<button
|
||||
onClick={onToggleSidebar}
|
||||
className="p-2 rounded-full hover:bg-[#2a2a2a] text-[#9aa0a6] hover:text-[#e3e3e3] transition-colors duration-150 cursor-pointer"
|
||||
className="p-2 rounded-full hover:bg-[var(--gem-surface-2)] text-[var(--gem-text-muted)] hover:text-[var(--gem-text)] transition-colors duration-150 cursor-pointer"
|
||||
aria-label={sidebarOpen ? "关闭侧边栏" : "打开侧边栏"}
|
||||
aria-expanded={sidebarOpen}
|
||||
>
|
||||
@@ -44,7 +46,7 @@ export function GeminiTopbar({ sidebarOpen, onToggleSidebar }: GeminiTopbarProps
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setModelDropdownOpen((prev) => !prev)}
|
||||
className="flex items-center gap-1.5 px-4 py-1.5 rounded-full border border-[#3a3a3a] text-[#e3e3e3] text-sm font-medium hover:bg-[#2a2a2a] transition-colors duration-150 cursor-pointer"
|
||||
className="flex items-center gap-1.5 px-4 py-1.5 rounded-full border border-[var(--gem-border)] text-[var(--gem-text)] text-sm font-medium hover:bg-[var(--gem-surface-2)] transition-colors duration-150 cursor-pointer"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={modelDropdownOpen}
|
||||
aria-label="选择模型"
|
||||
@@ -53,7 +55,7 @@ export function GeminiTopbar({ sidebarOpen, onToggleSidebar }: GeminiTopbarProps
|
||||
<ChevronDown
|
||||
size={14}
|
||||
className={cn(
|
||||
"text-[#9aa0a6] transition-transform duration-200",
|
||||
"text-[var(--gem-text-muted)] transition-transform duration-200",
|
||||
modelDropdownOpen && "rotate-180"
|
||||
)}
|
||||
/>
|
||||
@@ -69,7 +71,7 @@ export function GeminiTopbar({ sidebarOpen, onToggleSidebar }: GeminiTopbarProps
|
||||
/>
|
||||
<ul
|
||||
role="listbox"
|
||||
className="absolute top-full left-1/2 -translate-x-1/2 mt-2 w-52 bg-[#2a2a2a] border border-[#3a3a3a] rounded-2xl shadow-2xl overflow-hidden z-20 py-1.5"
|
||||
className="absolute top-full left-1/2 -translate-x-1/2 mt-2 w-52 bg-[var(--gem-surface-2)] border border-[var(--gem-border)] rounded-2xl shadow-2xl overflow-hidden z-20 py-1.5"
|
||||
aria-label="可用的模型"
|
||||
>
|
||||
{models.map((model) => (
|
||||
@@ -82,8 +84,8 @@ export function GeminiTopbar({ sidebarOpen, onToggleSidebar }: GeminiTopbarProps
|
||||
className={cn(
|
||||
"w-full text-left px-4 py-2.5 text-sm transition-colors duration-150 cursor-pointer",
|
||||
selectedModel === model
|
||||
? "text-[#4285f4] bg-[#1e1e1e]"
|
||||
: "text-[#e3e3e3] hover:bg-[#333333]"
|
||||
? "text-[var(--gem-blue)] bg-[var(--gem-surface)]"
|
||||
: "text-[var(--gem-text)] hover:bg-[var(--gem-surface-3)]"
|
||||
)}
|
||||
>
|
||||
{model}
|
||||
@@ -95,16 +97,23 @@ export function GeminiTopbar({ sidebarOpen, onToggleSidebar }: GeminiTopbarProps
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right: settings + avatar */}
|
||||
{/* Right: theme toggle + settings + avatar */}
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
className="p-2 rounded-full hover:bg-[#2a2a2a] text-[#9aa0a6] hover:text-[#e3e3e3] transition-colors duration-150 cursor-pointer"
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
className="p-2 rounded-full hover:bg-[var(--gem-surface-2)] text-[var(--gem-text-muted)] hover:text-[var(--gem-text)] transition-colors duration-150 cursor-pointer"
|
||||
aria-label={theme === "dark" ? "切换亮色主题" : "切换暗色主题"}
|
||||
>
|
||||
{theme === "dark" ? <Sun size={20} /> : <Moon size={20} />}
|
||||
</button>
|
||||
<button
|
||||
className="p-2 rounded-full hover:bg-[var(--gem-surface-2)] text-[var(--gem-text-muted)] hover:text-[var(--gem-text)] transition-colors duration-150 cursor-pointer"
|
||||
aria-label="设置"
|
||||
>
|
||||
<Settings size={20} />
|
||||
</button>
|
||||
<button
|
||||
className="w-8 h-8 rounded-full bg-gradient-to-br from-[#4285f4] to-[#a855f7] flex items-center justify-center text-white text-sm font-semibold ml-1 cursor-pointer hover:opacity-90 transition-opacity"
|
||||
className="w-8 h-8 rounded-full bg-gradient-to-br from-[var(--gem-blue)] to-[var(--gem-purple)] flex items-center justify-center text-white text-sm font-semibold ml-1 cursor-pointer hover:opacity-90 transition-opacity"
|
||||
aria-label="用户资料"
|
||||
>
|
||||
U
|
||||
|
||||
@@ -44,12 +44,12 @@ export function GeminiWelcome({ onSuggestionClick }: GeminiWelcomeProps) {
|
||||
{/* Greeting */}
|
||||
<div className="text-center mb-10">
|
||||
<h1 className="text-4xl font-semibold mb-3 leading-tight text-balance">
|
||||
<span className="bg-gradient-to-r from-[#4285f4] via-[#7c55f0] to-[#a855f7] bg-clip-text text-transparent">
|
||||
<span className="bg-gradient-to-r from-[var(--gem-blue)] via-[var(--gem-mid-gradient)] to-[var(--gem-purple)] bg-clip-text text-transparent">
|
||||
{greeting},
|
||||
</span>{" "}
|
||||
<span className="text-[#e3e3e3]">用户</span>
|
||||
<span className="text-[var(--gem-text)]">用户</span>
|
||||
</h1>
|
||||
<p className="text-[#9aa0a6] text-xl">我今天可以帮你做什么运营工作?</p>
|
||||
<p className="text-[var(--gem-text-muted)] text-xl">我今天可以帮你做什么运营工作?</p>
|
||||
</div>
|
||||
|
||||
{/* Suggestion cards */}
|
||||
@@ -58,13 +58,13 @@ export function GeminiWelcome({ onSuggestionClick }: GeminiWelcomeProps) {
|
||||
<button
|
||||
key={short}
|
||||
onClick={() => onSuggestionClick(text)}
|
||||
className="flex flex-col gap-2 p-4 rounded-2xl border border-[#3a3a3a] bg-[#1e1e1e] hover:bg-[#252526] hover:border-[#4a4a4a] transition-all duration-150 text-left cursor-pointer group"
|
||||
className="flex flex-col gap-2 p-4 rounded-2xl border border-[var(--gem-border)] bg-[var(--gem-surface)] hover:bg-[var(--gem-surface-hover)] hover:border-[var(--gem-border-hover)] transition-all duration-150 text-left cursor-pointer group"
|
||||
aria-label={`Suggestion: ${text}`}
|
||||
>
|
||||
<div className="w-8 h-8 rounded-full bg-[#2a2a2a] flex items-center justify-center group-hover:bg-[#333333] transition-colors">
|
||||
<Icon size={16} className="text-[#9aa0a6]" />
|
||||
<div className="w-8 h-8 rounded-full bg-[var(--gem-surface-2)] flex items-center justify-center group-hover:bg-[var(--gem-surface-3)] transition-colors">
|
||||
<Icon size={16} className="text-[var(--gem-text-muted)]" />
|
||||
</div>
|
||||
<span className="text-[#c4c7c5] text-sm leading-snug line-clamp-2">{text}</span>
|
||||
<span className="text-[var(--gem-text-secondary)] text-sm leading-snug line-clamp-2">{text}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user