slice 4: accent palette refinement — blue → teal
Replace the four accent-family hex codes site-wide with the teal palette (keeps the dark theme; only swaps the accent family, not the dark base): #3b82f6 (accent primary CTA) → #5B7C8D (teal) [839×] #2563eb (accent-dark / hover) → #4A6473 (darker teal) [44×] #60a5fa (info link, lighter) → #8FB0C3 (mid teal) [68×] #1e3a5f (accent-muted bg) → #2F4F5F (navy) [44×] tailwind.config.js tokens updated to match (accent/accent-dark/accent-muted). Sweep also covers tailwind.css. 108 files touched. The dark base (#0d0d0d, #111, #161616, #1a1a1a etc.) is intentionally untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -214,7 +214,7 @@ function renderMarkdown(text: string): string {
|
||||
.replace(/^# (.+)$/gm, '<h1 class="text-xl font-bold text-white mt-5 mb-2">$1</h1>')
|
||||
.replace(/\*\*(.+?)\*\*/g, '<strong class="text-white font-semibold">$1</strong>')
|
||||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||
.replace(/`([^`]+)`/g, '<code class="bg-[#1a2535] text-[#3b82f6] px-1 py-0.5 rounded text-sm font-mono">$1</code>')
|
||||
.replace(/`([^`]+)`/g, '<code class="bg-[#1a2535] text-[#5B7C8D] px-1 py-0.5 rounded text-sm font-mono">$1</code>')
|
||||
.replace(/^- (.+)$/gm, '<li class="ml-4 list-disc text-[#aaa]">$1</li>')
|
||||
.replace(/^\d+\. (.+)$/gm, '<li class="ml-4 list-decimal text-[#aaa]">$1</li>')
|
||||
.replace(/\n\n/g, '</p><p class="mb-3 text-[#ccc] leading-relaxed">')
|
||||
@@ -294,7 +294,7 @@ function DraftPreviewModal({ content, title, penName, siteId, onClose, onSave }:
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onSave(true)}
|
||||
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
|
||||
className="bg-[#5B7C8D] hover:bg-[#4A6473] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
|
||||
>
|
||||
Open in Editor →
|
||||
</button>
|
||||
@@ -353,7 +353,7 @@ function RegenerateModal({ onClose, onRegenerate }: RegenerateModalProps) {
|
||||
onClick={() => { setSelectedVariant(v.prompt); setCustomPrompt(''); }}
|
||||
className={`text-left text-xs px-3 py-2 rounded border transition-colors ${
|
||||
selectedVariant === v.prompt
|
||||
? 'border-[#3b82f6] bg-[#1e3a5f] text-white'
|
||||
? 'border-[#5B7C8D] bg-[#2F4F5F] text-white'
|
||||
: 'border-[#1e2a3a] text-[#aaa] hover:border-[#2a3a50] hover:text-white hover:bg-[#161f2e]'
|
||||
}`}
|
||||
>
|
||||
@@ -370,7 +370,7 @@ function RegenerateModal({ onClose, onRegenerate }: RegenerateModalProps) {
|
||||
onChange={e => { setCustomPrompt(e.target.value); setSelectedVariant(null); }}
|
||||
placeholder="e.g. Rewrite this focusing on the European market impact..."
|
||||
rows={3}
|
||||
className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#333] resize-none"
|
||||
className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#5B7C8D] placeholder-[#333] resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -386,7 +386,7 @@ function RegenerateModal({ onClose, onRegenerate }: RegenerateModalProps) {
|
||||
<button onClick={onClose} className="text-[#555] hover:text-[#aaa] text-sm transition-colors">Cancel</button>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
|
||||
className="bg-[#5B7C8D] hover:bg-[#4A6473] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
|
||||
>
|
||||
Regenerate ↺
|
||||
</button>
|
||||
@@ -418,7 +418,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
|
||||
const found = PEN_NAME_PROFILES.find(p => p.name === e.target.value);
|
||||
if (found) side === 'left' ? setLeftPen(found) : setRightPen(found);
|
||||
}}
|
||||
className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded px-2 py-1.5 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded px-2 py-1.5 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
{PEN_NAME_PROFILES.map(p => (
|
||||
<option key={p.name} value={p.name}>{p.name} ({p.site})</option>
|
||||
@@ -434,7 +434,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
|
||||
</span>
|
||||
<span className="text-white font-semibold text-sm">{profile.name}</span>
|
||||
</div>
|
||||
<p className="text-[#3b82f6] text-xs">{profile.title}</p>
|
||||
<p className="text-[#5B7C8D] text-xs">{profile.title}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -458,7 +458,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
|
||||
|
||||
<div>
|
||||
<p className="text-[#555] text-[10px] uppercase tracking-wider font-bold mb-1">Example Lead</p>
|
||||
<p className="text-[#aaa] text-xs italic leading-relaxed border-l-2 border-[#3b82f6]/40 pl-3">"{profile.exampleLead}"</p>
|
||||
<p className="text-[#aaa] text-xs italic leading-relaxed border-l-2 border-[#5B7C8D]/40 pl-3">"{profile.exampleLead}"</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -485,7 +485,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
|
||||
<button
|
||||
key={f}
|
||||
onClick={() => setFilterSite(f)}
|
||||
className={`text-xs px-2.5 py-1 rounded transition-colors ${filterSite === f ? 'bg-[#3b82f6] text-white' : 'bg-[#1e2a3a] text-[#aaa] hover:text-white'}`}
|
||||
className={`text-xs px-2.5 py-1 rounded transition-colors ${filterSite === f ? 'bg-[#5B7C8D] text-white' : 'bg-[#1e2a3a] text-[#aaa] hover:text-white'}`}
|
||||
>
|
||||
{f === 'ALL' ? 'All Writers' : f === 'BB' ? 'Broadcast Beat' : 'AV Beat'}
|
||||
</button>
|
||||
@@ -514,7 +514,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className={`text-[9px] font-bold px-1 rounded ${p.site === 'AV' ? 'bg-orange-900/40 text-orange-400' : 'bg-blue-900/40 text-blue-400'}`}>{p.site}</span>
|
||||
<span className="text-white text-xs font-medium group-hover:text-[#3b82f6] transition-colors">{p.name}</span>
|
||||
<span className="text-white text-xs font-medium group-hover:text-[#5B7C8D] transition-colors">{p.name}</span>
|
||||
</div>
|
||||
<p className="text-[#555] text-[10px]">{p.title}</p>
|
||||
</button>
|
||||
@@ -560,14 +560,14 @@ function ResponseHistorySidebar({ messages, onJumpTo, onClose }: ResponseHistory
|
||||
className="w-full text-left p-3 rounded border border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e] transition-colors group"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-[10px] text-[#3b82f6] font-bold">Response #{idx + 1}</span>
|
||||
<span className="text-[10px] text-[#5B7C8D] font-bold">Response #{idx + 1}</span>
|
||||
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${msg.provider === 'ollama' ? 'bg-green-500' : 'bg-yellow-500'}`} />
|
||||
</div>
|
||||
<p className="text-[#aaa] text-xs line-clamp-3 leading-relaxed">{preview}…</p>
|
||||
<div className="flex items-center gap-3 mt-2">
|
||||
<span className="text-[10px] text-[#444]">{msg.timestamp.toLocaleTimeString()}</span>
|
||||
<span className="text-[10px] text-[#444]">{wordCount} words</span>
|
||||
<span className="text-[10px] text-[#3b82f6] opacity-0 group-hover:opacity-100 transition-opacity">Jump ↑</span>
|
||||
<span className="text-[10px] text-[#5B7C8D] opacity-0 group-hover:opacity-100 transition-opacity">Jump ↑</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -927,7 +927,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
{/* Header */}
|
||||
<div className="bg-[#111827] border-b border-[#1e2a3a] px-4 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="/admin" className="text-[#555] hover:text-[#3b82f6] text-sm transition-colors">← Dashboard</Link>
|
||||
<Link href="/admin" className="text-[#555] hover:text-[#5B7C8D] text-sm transition-colors">← Dashboard</Link>
|
||||
<span className="text-[#333]">/</span>
|
||||
<h1 className="text-white font-bold text-sm tracking-wide">AI Console</h1>
|
||||
</div>
|
||||
@@ -943,18 +943,18 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
onClick={() => setShowHistorySidebar(v => !v)}
|
||||
className={`text-xs px-2.5 py-1.5 rounded border transition-colors ${
|
||||
showHistorySidebar
|
||||
? 'bg-[#1e3a5f] border-[#3b82f6]/40 text-[#3b82f6]'
|
||||
? 'bg-[#2F4F5F] border-[#5B7C8D]/40 text-[#5B7C8D]'
|
||||
: 'bg-[#1e2a3a] border-[#2a3a50] text-[#555] hover:text-[#aaa]'
|
||||
}`}
|
||||
title="Toggle response history sidebar"
|
||||
>
|
||||
History
|
||||
{assistantMessageCount > 0 && (
|
||||
<span className="ml-1.5 bg-[#3b82f6] text-white text-[9px] font-bold px-1 py-0.5 rounded-full">{assistantMessageCount}</span>
|
||||
<span className="ml-1.5 bg-[#5B7C8D] text-white text-[9px] font-bold px-1 py-0.5 rounded-full">{assistantMessageCount}</span>
|
||||
)}
|
||||
</button>
|
||||
<Link href="/dashboard/ai-console/health" className="text-[#555] hover:text-[#3b82f6] text-xs transition-colors">Health</Link>
|
||||
<Link href="/dashboard/ai-console/settings" className="text-[#555] hover:text-[#3b82f6] text-xs transition-colors">Settings</Link>
|
||||
<Link href="/dashboard/ai-console/health" className="text-[#555] hover:text-[#5B7C8D] text-xs transition-colors">Health</Link>
|
||||
<Link href="/dashboard/ai-console/settings" className="text-[#555] hover:text-[#5B7C8D] text-xs transition-colors">Settings</Link>
|
||||
<button onClick={handleNewSession} className="text-xs bg-[#1e2a3a] hover:bg-[#253347] text-[#aaa] px-3 py-1.5 rounded transition-colors">
|
||||
+ New Session
|
||||
</button>
|
||||
@@ -1002,7 +1002,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
key={session.id}
|
||||
className={`group p-2.5 rounded border cursor-pointer transition-colors ${
|
||||
currentSessionId === session.id
|
||||
? 'border-[#3b82f6]/40 bg-[#1e2a3a]'
|
||||
? 'border-[#5B7C8D]/40 bg-[#1e2a3a]'
|
||||
: 'border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e]'
|
||||
}`}
|
||||
>
|
||||
@@ -1046,11 +1046,11 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
onClick={() => {
|
||||
fetch('/api/ai/console?action=ping').then(r => r.json()).then(setAiStatus).catch(() => {});
|
||||
}}
|
||||
className="text-[10px] text-[#555] hover:text-[#3b82f6] transition-colors"
|
||||
className="text-[10px] text-[#555] hover:text-[#5B7C8D] transition-colors"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
<Link href="/dashboard/ai-console/health" className="text-[10px] text-[#555] hover:text-[#3b82f6] transition-colors">
|
||||
<Link href="/dashboard/ai-console/health" className="text-[10px] text-[#555] hover:text-[#5B7C8D] transition-colors">
|
||||
Health Dashboard →
|
||||
</Link>
|
||||
</div>
|
||||
@@ -1066,7 +1066,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
<select
|
||||
value={selectedSite}
|
||||
onChange={e => setSelectedSite(Number(e.target.value) as 1 | 2)}
|
||||
className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
<option value={1}>Broadcast Beat</option>
|
||||
<option value={2}>AV Beat</option>
|
||||
@@ -1077,7 +1077,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
<select
|
||||
value={selectedPenName}
|
||||
onChange={e => setSelectedPenName(e.target.value)}
|
||||
className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
{penNames.map(name => (
|
||||
<option key={name} value={name}>{name}</option>
|
||||
@@ -1086,7 +1086,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowStyleGuides(true)}
|
||||
className="text-[10px] text-[#3b82f6] hover:text-[#60a5fa] transition-colors ml-auto"
|
||||
className="text-[10px] text-[#5B7C8D] hover:text-[#8FB0C3] transition-colors ml-auto"
|
||||
>
|
||||
View style guide →
|
||||
</button>
|
||||
@@ -1123,7 +1123,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
</div>
|
||||
)}
|
||||
<div className={`rounded-lg px-4 py-3 ${
|
||||
msg.role === 'user' ?'bg-[#1e3a5f] text-white text-sm' :'bg-[#111827] border border-[#1e2a3a] text-[#ccc] text-sm'
|
||||
msg.role === 'user' ?'bg-[#2F4F5F] text-white text-sm' :'bg-[#111827] border border-[#1e2a3a] text-[#ccc] text-sm'
|
||||
}`}>
|
||||
{msg.role === 'user' ? (
|
||||
<p className="whitespace-pre-wrap">{msg.content}</p>
|
||||
@@ -1151,7 +1151,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowDraftPreview(true)}
|
||||
className="text-[10px] text-[#3b82f6] hover:text-[#60a5fa] bg-[#111827] border border-[#3b82f6]/30 px-2 py-1 rounded transition-colors font-medium"
|
||||
className="text-[10px] text-[#5B7C8D] hover:text-[#8FB0C3] bg-[#111827] border border-[#5B7C8D]/30 px-2 py-1 rounded transition-colors font-medium"
|
||||
>
|
||||
Preview Draft
|
||||
</button>
|
||||
@@ -1195,9 +1195,9 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
<div className="flex justify-start">
|
||||
<div className="bg-[#111827] border border-[#1e2a3a] rounded-lg px-4 py-3">
|
||||
<div className="flex gap-1">
|
||||
<div className="w-1.5 h-1.5 bg-[#3b82f6] rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
|
||||
<div className="w-1.5 h-1.5 bg-[#3b82f6] rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
|
||||
<div className="w-1.5 h-1.5 bg-[#3b82f6] rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
|
||||
<div className="w-1.5 h-1.5 bg-[#5B7C8D] rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
|
||||
<div className="w-1.5 h-1.5 bg-[#5B7C8D] rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
|
||||
<div className="w-1.5 h-1.5 bg-[#5B7C8D] rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1225,13 +1225,13 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
onChange={e => setUrlInput(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && handleFetchUrl()}
|
||||
placeholder="https://example.com/press-release"
|
||||
className="flex-1 bg-[#1e2a3a] border border-[#2a3a50] text-white text-sm rounded px-3 py-2 focus:outline-none focus:border-[#3b82f6] placeholder-[#444]"
|
||||
className="flex-1 bg-[#1e2a3a] border border-[#2a3a50] text-white text-sm rounded px-3 py-2 focus:outline-none focus:border-[#5B7C8D] placeholder-[#444]"
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
onClick={handleFetchUrl}
|
||||
disabled={fetchingUrl}
|
||||
className="bg-[#3b82f6] hover:bg-[#2563eb] text-white text-xs px-3 py-2 rounded transition-colors disabled:opacity-50"
|
||||
className="bg-[#5B7C8D] hover:bg-[#4A6473] text-white text-xs px-3 py-2 rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{fetchingUrl ? 'Fetching...' : 'Add'}
|
||||
</button>
|
||||
@@ -1244,19 +1244,19 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
<div className="flex gap-2 mb-2">
|
||||
<button
|
||||
onClick={() => setShowUrlInput(!showUrlInput)}
|
||||
className="text-xs text-[#555] hover:text-[#3b82f6] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
|
||||
className="text-xs text-[#555] hover:text-[#5B7C8D] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
|
||||
>
|
||||
+ Add URL
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setInput(prev => prev + '\n\n[Paste story content here for reference]')}
|
||||
className="text-xs text-[#555] hover:text-[#3b82f6] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
|
||||
className="text-xs text-[#555] hover:text-[#5B7C8D] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
|
||||
>
|
||||
+ Add Story
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setInput(prev => prev + `\n\nWrite in the style of ${selectedPenName}.`)}
|
||||
className="text-xs text-[#555] hover:text-[#3b82f6] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
|
||||
className="text-xs text-[#555] hover:text-[#5B7C8D] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
|
||||
>
|
||||
+ Add Style
|
||||
</button>
|
||||
@@ -1274,13 +1274,13 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
|
||||
}}
|
||||
placeholder="Ask the AI to write a story, generate headlines, translate content..."
|
||||
rows={3}
|
||||
className="flex-1 bg-[#111827] border border-[#1e2a3a] text-white text-sm rounded-lg px-4 py-3 focus:outline-none focus:border-[#3b82f6] placeholder-[#333] resize-none"
|
||||
className="flex-1 bg-[#111827] border border-[#1e2a3a] text-white text-sm rounded-lg px-4 py-3 focus:outline-none focus:border-[#5B7C8D] placeholder-[#333] resize-none"
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
onClick={() => handleSend()}
|
||||
disabled={isStreaming || !input.trim()}
|
||||
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium flex-1"
|
||||
className="bg-[#5B7C8D] hover:bg-[#4A6473] disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium flex-1"
|
||||
>
|
||||
{isStreaming ? (
|
||||
<span className="flex items-center gap-1">
|
||||
|
||||
@@ -117,7 +117,7 @@ export default function AIConsoleSettingsPage() {
|
||||
if (loading || loadingSettings) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center">
|
||||
<div className="w-6 h-6 border-2 border-[#3b82f6]/30 border-t-[#3b82f6] rounded-full animate-spin" />
|
||||
<div className="w-6 h-6 border-2 border-[#5B7C8D]/30 border-t-[#5B7C8D] rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -127,14 +127,14 @@ export default function AIConsoleSettingsPage() {
|
||||
{/* Header */}
|
||||
<div className="bg-[#111827] border-b border-[#1e2a3a] px-6 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="/dashboard/ai-console" className="text-[#555] hover:text-[#3b82f6] text-sm transition-colors">← AI Console</Link>
|
||||
<Link href="/dashboard/ai-console" className="text-[#555] hover:text-[#5B7C8D] text-sm transition-colors">← AI Console</Link>
|
||||
<span className="text-[#333]">/</span>
|
||||
<h1 className="text-white font-bold text-sm">AI Console Settings</h1>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
|
||||
className="bg-[#5B7C8D] hover:bg-[#4A6473] disabled:opacity-50 text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save Settings'}
|
||||
</button>
|
||||
@@ -177,7 +177,7 @@ export default function AIConsoleSettingsPage() {
|
||||
value={settings.ollama_endpoint}
|
||||
onChange={e => update('ollama_endpoint', e.target.value)}
|
||||
placeholder="http://192.168.1.x:11434 or https://your-tunnel.ngrok.io"
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6] placeholder-[#333]"
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#5B7C8D] placeholder-[#333]"
|
||||
/>
|
||||
<p className="text-[#444] text-[10px] mt-1">
|
||||
Use direct IP if on the same network, or a tunnel URL (ngrok, Cloudflare Tunnel, Tailscale) for remote access.
|
||||
@@ -212,7 +212,7 @@ export default function AIConsoleSettingsPage() {
|
||||
<select
|
||||
value={settings.ollama_model_default}
|
||||
onChange={e => update('ollama_model_default', e.target.value)}
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
<option value="">Auto (largest available)</option>
|
||||
{connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)}
|
||||
@@ -223,7 +223,7 @@ export default function AIConsoleSettingsPage() {
|
||||
<select
|
||||
value={settings.ollama_model_translation}
|
||||
onChange={e => update('ollama_model_translation', e.target.value)}
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
<option value="">Same as default</option>
|
||||
{connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)}
|
||||
@@ -234,7 +234,7 @@ export default function AIConsoleSettingsPage() {
|
||||
<select
|
||||
value={settings.ollama_model_background}
|
||||
onChange={e => update('ollama_model_background', e.target.value)}
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
<option value="">Same as default</option>
|
||||
{connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)}
|
||||
@@ -250,7 +250,7 @@ export default function AIConsoleSettingsPage() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => update('auto_failover', !settings.auto_failover)}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.auto_failover ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.auto_failover ? 'bg-[#5B7C8D]' : 'bg-[#2a3a50]'}`}
|
||||
>
|
||||
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.auto_failover ? 'translate-x-5' : 'translate-x-0.5'}`} />
|
||||
</button>
|
||||
@@ -263,7 +263,7 @@ export default function AIConsoleSettingsPage() {
|
||||
value={settings.failover_timeout_seconds}
|
||||
onChange={e => update('failover_timeout_seconds', Number(e.target.value))}
|
||||
min={3} max={30}
|
||||
className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#5B7C8D]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -301,7 +301,7 @@ export default function AIConsoleSettingsPage() {
|
||||
<select
|
||||
value={settings.default_site_id}
|
||||
onChange={e => update('default_site_id', Number(e.target.value))}
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
<option value={1}>Broadcast Beat</option>
|
||||
<option value={2}>AV Beat</option>
|
||||
@@ -312,7 +312,7 @@ export default function AIConsoleSettingsPage() {
|
||||
<select
|
||||
value={settings.default_pen_name_bb}
|
||||
onChange={e => update('default_pen_name_bb', e.target.value)}
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
{BB_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)}
|
||||
</select>
|
||||
@@ -322,7 +322,7 @@ export default function AIConsoleSettingsPage() {
|
||||
<select
|
||||
value={settings.default_pen_name_av}
|
||||
onChange={e => update('default_pen_name_av', e.target.value)}
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
{AV_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)}
|
||||
</select>
|
||||
@@ -335,7 +335,7 @@ export default function AIConsoleSettingsPage() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => update('save_session_history', !settings.save_session_history)}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.save_session_history ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.save_session_history ? 'bg-[#5B7C8D]' : 'bg-[#2a3a50]'}`}
|
||||
>
|
||||
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.save_session_history ? 'translate-x-5' : 'translate-x-0.5'}`} />
|
||||
</button>
|
||||
@@ -346,7 +346,7 @@ export default function AIConsoleSettingsPage() {
|
||||
<select
|
||||
value={settings.history_retention_days}
|
||||
onChange={e => update('history_retention_days', Number(e.target.value))}
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#5B7C8D]"
|
||||
>
|
||||
<option value={30}>30 days</option>
|
||||
<option value={60}>60 days</option>
|
||||
@@ -361,7 +361,7 @@ export default function AIConsoleSettingsPage() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => update('streaming_enabled', !settings.streaming_enabled)}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.streaming_enabled ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.streaming_enabled ? 'bg-[#5B7C8D]' : 'bg-[#2a3a50]'}`}
|
||||
>
|
||||
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.streaming_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} />
|
||||
</button>
|
||||
@@ -380,7 +380,7 @@ export default function AIConsoleSettingsPage() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => update('background_use_local', !settings.background_use_local)}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.background_use_local ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.background_use_local ? 'bg-[#5B7C8D]' : 'bg-[#2a3a50]'}`}
|
||||
>
|
||||
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.background_use_local ? 'translate-x-5' : 'translate-x-0.5'}`} />
|
||||
</button>
|
||||
@@ -393,7 +393,7 @@ export default function AIConsoleSettingsPage() {
|
||||
value={settings.background_timeout_seconds}
|
||||
onChange={e => update('background_timeout_seconds', Number(e.target.value))}
|
||||
min={30} max={300}
|
||||
className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#3b82f6]"
|
||||
className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#5B7C8D]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -404,7 +404,7 @@ export default function AIConsoleSettingsPage() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => update('priority_queue_enabled', !settings.priority_queue_enabled)}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.priority_queue_enabled ? 'bg-[#3b82f6]' : 'bg-[#2a3a50]'}`}
|
||||
className={`w-10 h-5 rounded-full transition-colors relative ${settings.priority_queue_enabled ? 'bg-[#5B7C8D]' : 'bg-[#2a3a50]'}`}
|
||||
>
|
||||
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.priority_queue_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} />
|
||||
</button>
|
||||
@@ -422,7 +422,7 @@ export default function AIConsoleSettingsPage() {
|
||||
'Classification and extraction tasks',
|
||||
].map((item, i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-xs text-[#555]">
|
||||
<span className="w-4 h-4 rounded-full bg-[#1e2a3a] text-[#3b82f6] text-[10px] flex items-center justify-center font-bold flex-shrink-0">{i + 1}</span>
|
||||
<span className="w-4 h-4 rounded-full bg-[#1e2a3a] text-[#5B7C8D] text-[10px] flex items-center justify-center font-bold flex-shrink-0">{i + 1}</span>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -130,9 +130,9 @@ export default function OverviewTab({ companyId, selectedCompany }: Props) {
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="revenue"
|
||||
stroke={selectedCompany === 'rmp' ? '#3b82f6' : '#a855f7'}
|
||||
stroke={selectedCompany === 'rmp' ? '#5B7C8D' : '#a855f7'}
|
||||
strokeWidth={2}
|
||||
dot={{ fill: selectedCompany === 'rmp' ? '#3b82f6' : '#a855f7', r: 3 }}
|
||||
dot={{ fill: selectedCompany === 'rmp' ? '#5B7C8D' : '#a855f7', r: 3 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
@@ -269,13 +269,13 @@ export default function EditorialDashboard() {
|
||||
|
||||
function SortIcon({ col }: { col: typeof sortBy }) {
|
||||
if (sortBy !== col) return <span className="text-[#444] ml-1">↕</span>;
|
||||
return <span className="text-[#3b82f6] ml-1">{sortDir === 'desc' ? '↓' : '↑'}</span>;
|
||||
return <span className="text-[#5B7C8D] ml-1">{sortDir === 'desc' ? '↓' : '↑'}</span>;
|
||||
}
|
||||
|
||||
if (loading || !authChecked) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
|
||||
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-[#5B7C8D] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -294,7 +294,7 @@ export default function EditorialDashboard() {
|
||||
<div className="flex items-center gap-3">
|
||||
<Link
|
||||
href="/admin/articles"
|
||||
className="px-3 py-1.5 text-xs font-semibold bg-[#3b82f6] hover:bg-blue-500 text-white rounded transition-colors"
|
||||
className="px-3 py-1.5 text-xs font-semibold bg-[#5B7C8D] hover:bg-blue-500 text-white rounded transition-colors"
|
||||
>
|
||||
+ New Article
|
||||
</Link>
|
||||
@@ -344,7 +344,7 @@ export default function EditorialDashboard() {
|
||||
onClick={() => setStatusFilter(s)}
|
||||
className={`px-3 py-1.5 text-xs font-semibold capitalize transition-colors ${
|
||||
statusFilter === s
|
||||
? 'bg-[#3b82f6] text-white'
|
||||
? 'bg-[#5B7C8D] text-white'
|
||||
: 'text-[#666] hover:text-[#aaa]'
|
||||
}`}
|
||||
>
|
||||
@@ -360,7 +360,7 @@ export default function EditorialDashboard() {
|
||||
placeholder="Search articles, clients, campaigns..."
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
className="w-full bg-[#111] border border-[#1e1e1e] rounded-lg px-3 py-1.5 text-xs text-[#ccc] placeholder-[#444] focus:outline-none focus:border-[#3b82f6] transition-colors"
|
||||
className="w-full bg-[#111] border border-[#1e1e1e] rounded-lg px-3 py-1.5 text-xs text-[#ccc] placeholder-[#444] focus:outline-none focus:border-[#5B7C8D] transition-colors"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
@@ -379,7 +379,7 @@ export default function EditorialDashboard() {
|
||||
<div className="bg-[#0d0d0d] border border-[#1e1e1e] rounded-xl overflow-hidden">
|
||||
{loadingData ? (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="w-6 h-6 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-6 h-6 border-2 border-[#5B7C8D] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
) : filtered.length === 0 ? (
|
||||
<div className="text-center py-20 text-[#444] text-sm">No articles found</div>
|
||||
@@ -437,7 +437,7 @@ export default function EditorialDashboard() {
|
||||
<span className="text-[#555]">{article.author_name}</span>
|
||||
)}
|
||||
{article.category && (
|
||||
<span className="text-[#3b82f6]/70 bg-[#3b82f6]/10 px-1.5 py-0.5 rounded text-[10px]">
|
||||
<span className="text-[#5B7C8D]/70 bg-[#5B7C8D]/10 px-1.5 py-0.5 rounded text-[10px]">
|
||||
{article.category}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -256,7 +256,7 @@ export default function ShowCalendarPage() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
|
||||
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-[#5B7C8D] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -294,7 +294,7 @@ export default function ShowCalendarPage() {
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => { setEditingEvent({ site_id: 1, year: new Date().getFullYear(), seo_tier: 3, event_type: 'trade_show' }); setShowEventModal(true); }}
|
||||
className="px-3 py-1.5 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors"
|
||||
className="px-3 py-1.5 bg-[#5B7C8D] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors"
|
||||
>
|
||||
+ Add Event
|
||||
</button>
|
||||
@@ -316,7 +316,7 @@ export default function ShowCalendarPage() {
|
||||
onClick={() => setSiteFilter(s)}
|
||||
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
|
||||
siteFilter === s
|
||||
? 'bg-[#3b82f6] text-white'
|
||||
? 'bg-[#5B7C8D] text-white'
|
||||
: 'bg-[#1a1a1a] text-[#888] hover:text-white border border-[#333]'
|
||||
}`}
|
||||
>
|
||||
@@ -333,7 +333,7 @@ export default function ShowCalendarPage() {
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
|
||||
activeTab === tab.id
|
||||
? 'border-[#3b82f6] text-white'
|
||||
? 'border-[#5B7C8D] text-white'
|
||||
: 'border-transparent text-[#666] hover:text-white'
|
||||
}`}
|
||||
>
|
||||
@@ -347,7 +347,7 @@ export default function ShowCalendarPage() {
|
||||
<div className="max-w-7xl mx-auto px-4 py-6">
|
||||
{loadingData ? (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="w-8 h-8 border-2 border-[#3b82f6] border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-[#5B7C8D] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -383,7 +383,7 @@ export default function ShowCalendarPage() {
|
||||
<span>Engine: {formatDate(event.story_engine_start_date)} – {formatDate(event.story_engine_end_date)}</span>
|
||||
{event.official_site && (
|
||||
<a href={`https://${event.official_site}`} target="_blank" rel="noopener noreferrer"
|
||||
className="text-[#3b82f6] hover:underline">{event.official_site}</a>
|
||||
className="text-[#5B7C8D] hover:underline">{event.official_site}</a>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-2 text-xs text-[#555]">
|
||||
@@ -440,7 +440,7 @@ export default function ShowCalendarPage() {
|
||||
onClick={() => setQueueStatus(s)}
|
||||
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
|
||||
queueStatus === s
|
||||
? 'bg-[#3b82f6] text-white'
|
||||
? 'bg-[#5B7C8D] text-white'
|
||||
: 'bg-[#1a1a1a] text-[#888] hover:text-white border border-[#333]'
|
||||
}`}
|
||||
>
|
||||
@@ -779,7 +779,7 @@ export default function ShowCalendarPage() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={actionLoading === 'save-event'}
|
||||
className="flex-1 py-2 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50"
|
||||
className="flex-1 py-2 bg-[#5B7C8D] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
{actionLoading === 'save-event' ? 'Saving...' : 'Save Event'}
|
||||
</button>
|
||||
@@ -857,7 +857,7 @@ export default function ShowCalendarPage() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={actionLoading === 'generate'}
|
||||
className="flex-1 py-2 bg-[#3b82f6] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50"
|
||||
className="flex-1 py-2 bg-[#5B7C8D] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
{actionLoading === 'generate' ? 'Generating...' : 'Generate Story'}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user