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
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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user