feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s

- Sidebar: dynamic brand-accent colors, brainstorm section restyled
- AI chat general: popup panel with expand/collapse, hides when contextual AI open
- AI chat contextual: tabs reordered (Actions first), X close button, height fix
- Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.)
- Global color cleanup: emerald/orange hardcoded → brand-accent dynamic
- Brainstorm page: orange → brand-accent throughout
- PageEntry animation component added to key pages
- Floating AI button: bg-brand-accent instead of hardcoded black
- i18n: all 15 locales updated with new AI/billing keys
- Billing: freemium quota tracking, BYOK, stripe subscription scaffolding
- Admin: integrated into new design
- AGENTS.md + CLAUDE.md project rules added
This commit is contained in:
Antigravity
2026-05-16 12:59:30 +00:00
parent 1fcea6ed7d
commit bd495be965
2284 changed files with 395285 additions and 2327 deletions

View File

@@ -174,6 +174,10 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
const [selectedEmbeddingModel, setSelectedEmbeddingModel] = useState<string>(config.AI_MODEL_EMBEDDING || '')
const [selectedChatModel, setSelectedChatModel] = useState<string>(config.AI_MODEL_CHAT || '')
const [tagsFallbackProvider, setTagsFallbackProvider] = useState<string>(config.AI_PROVIDER_TAGS_FALLBACK || '')
const [embedFallbackProvider, setEmbedFallbackProvider] = useState<string>(config.AI_PROVIDER_EMBEDDING_FALLBACK || '')
const [chatFallbackProvider, setChatFallbackProvider] = useState<string>(config.AI_PROVIDER_CHAT_FALLBACK || '')
// Dynamic Models State (for local providers with /api/tags or /v1/models endpoints)
const [dynamicModels, setDynamicModels] = useState<Record<ModelPurpose, string[]>>({
tags: [],
@@ -309,6 +313,11 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
const tagsModel = formData.get('AI_MODEL_TAGS') as string
if (tagsModel) data.AI_MODEL_TAGS = tagsModel
const tagsFallbackProv = formData.get('AI_PROVIDER_TAGS_FALLBACK') as string
data.AI_PROVIDER_TAGS_FALLBACK = tagsFallbackProv?.trim() ?? ''
const tagsFallbackModel = formData.get('AI_MODEL_TAGS_FALLBACK') as string
if (tagsFallbackModel) data.AI_MODEL_TAGS_FALLBACK = tagsFallbackModel
// Save provider-specific config for tags
if (tagsProv === 'ollama') {
const ollamaUrl = formData.get('BASE_URL_ollama_tags') as string
@@ -337,6 +346,11 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
const embedModel = formData.get('AI_MODEL_EMBEDDING') as string
if (embedModel) data.AI_MODEL_EMBEDDING = embedModel
const embedFallbackProv = formData.get('AI_PROVIDER_EMBEDDING_FALLBACK') as string
data.AI_PROVIDER_EMBEDDING_FALLBACK = embedFallbackProv?.trim() ?? ''
const embedFallbackModel = formData.get('AI_MODEL_EMBEDDING_FALLBACK') as string
if (embedFallbackModel) data.AI_MODEL_EMBEDDING_FALLBACK = embedFallbackModel
// Save provider-specific config for embeddings
if (embedProv === 'ollama') {
const ollamaUrl = formData.get('BASE_URL_ollama_embeddings') as string
@@ -365,6 +379,11 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
const chatModel = formData.get('AI_MODEL_CHAT') as string
if (chatModel) data.AI_MODEL_CHAT = chatModel
const chatFallbackProv = formData.get('AI_PROVIDER_CHAT_FALLBACK') as string
data.AI_PROVIDER_CHAT_FALLBACK = chatFallbackProv?.trim() ?? ''
const chatFallbackModel = formData.get('AI_MODEL_CHAT_FALLBACK') as string
if (chatFallbackModel) data.AI_MODEL_CHAT_FALLBACK = chatFallbackModel
// Save provider-specific config for chat
if (chatProv === 'ollama') {
const ollamaUrl = formData.get('BASE_URL_ollama_chat') as string
@@ -717,6 +736,33 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<input type="hidden" name="AI_MODEL_TAGS" value={selectedTagsModel} />
{renderProviderConfig(tagsProvider, 'tags', selectedTagsModel, setSelectedTagsModel)}
<div className="space-y-2 pt-3 border-t border-border/40">
<p className="text-xs font-medium text-muted-foreground">{t('admin.ai.fallbackSectionTitle')}</p>
<p className="text-xs text-muted-foreground">{t('admin.ai.fallbackSectionDescription')}</p>
<Label htmlFor="AI_PROVIDER_TAGS_FALLBACK">{t('admin.ai.fallbackProvider')}</Label>
<select
id="AI_PROVIDER_TAGS_FALLBACK"
name="AI_PROVIDER_TAGS_FALLBACK"
value={tagsFallbackProvider}
onChange={(e) => setTagsFallbackProvider(e.target.value)}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
>
<option value="">{t('admin.ai.fallbackNone')}</option>
{providerOptions.map((opt) => (
<option key={`fb-tags-${opt.value}`} value={opt.value}>
{opt.label}
</option>
))}
</select>
<Label htmlFor="AI_MODEL_TAGS_FALLBACK">{t('admin.ai.fallbackModel')}</Label>
<Input
id="AI_MODEL_TAGS_FALLBACK"
name="AI_MODEL_TAGS_FALLBACK"
defaultValue={config.AI_MODEL_TAGS_FALLBACK || ''}
placeholder={t('admin.ai.fallbackModelPlaceholder')}
/>
</div>
</div>
{/* Embeddings Provider */}
@@ -752,6 +798,33 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<input type="hidden" name="AI_MODEL_EMBEDDING" value={selectedEmbeddingModel} />
{renderProviderConfig(embeddingsProvider, 'embeddings', selectedEmbeddingModel, setSelectedEmbeddingModel)}
<div className="space-y-2 pt-3 border-t border-border/40">
<p className="text-xs font-medium text-muted-foreground">{t('admin.ai.fallbackSectionTitle')}</p>
<p className="text-xs text-muted-foreground">{t('admin.ai.fallbackSectionDescription')}</p>
<Label htmlFor="AI_PROVIDER_EMBEDDING_FALLBACK">{t('admin.ai.fallbackProvider')}</Label>
<select
id="AI_PROVIDER_EMBEDDING_FALLBACK"
name="AI_PROVIDER_EMBEDDING_FALLBACK"
value={embedFallbackProvider}
onChange={(e) => setEmbedFallbackProvider(e.target.value)}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
>
<option value="">{t('admin.ai.fallbackNone')}</option>
{embeddingsProviderOptions.map((opt) => (
<option key={`fb-embed-${opt.value}`} value={opt.value}>
{opt.label}
</option>
))}
</select>
<Label htmlFor="AI_MODEL_EMBEDDING_FALLBACK">{t('admin.ai.fallbackModel')}</Label>
<Input
id="AI_MODEL_EMBEDDING_FALLBACK"
name="AI_MODEL_EMBEDDING_FALLBACK"
defaultValue={config.AI_MODEL_EMBEDDING_FALLBACK || ''}
placeholder={t('admin.ai.fallbackModelPlaceholder')}
/>
</div>
</div>
{/* Chat Provider */}
@@ -782,6 +855,33 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
<input type="hidden" name="AI_MODEL_CHAT" value={selectedChatModel} />
{renderProviderConfig(chatProvider, 'chat', selectedChatModel, setSelectedChatModel)}
<div className="space-y-2 pt-3 border-t border-border/40">
<p className="text-xs font-medium text-muted-foreground">{t('admin.ai.fallbackSectionTitle')}</p>
<p className="text-xs text-muted-foreground">{t('admin.ai.fallbackSectionDescription')}</p>
<Label htmlFor="AI_PROVIDER_CHAT_FALLBACK">{t('admin.ai.fallbackProvider')}</Label>
<select
id="AI_PROVIDER_CHAT_FALLBACK"
name="AI_PROVIDER_CHAT_FALLBACK"
value={chatFallbackProvider}
onChange={(e) => setChatFallbackProvider(e.target.value)}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
>
<option value="">{t('admin.ai.fallbackNone')}</option>
{providerOptions.map((opt) => (
<option key={`fb-chat-${opt.value}`} value={opt.value}>
{opt.label}
</option>
))}
</select>
<Label htmlFor="AI_MODEL_CHAT_FALLBACK">{t('admin.ai.fallbackModel')}</Label>
<Input
id="AI_MODEL_CHAT_FALLBACK"
name="AI_MODEL_CHAT_FALLBACK"
defaultValue={config.AI_MODEL_CHAT_FALLBACK || ''}
placeholder={t('admin.ai.fallbackModelPlaceholder')}
/>
</div>
</div>
</div>
<div className="px-6 pb-6 flex flex-col sm:flex-row gap-3 sm:justify-between pt-6">