Attempt to fix note resizing with React keys and Muuri sync
This commit is contained in:
@@ -177,7 +177,7 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' }) {
|
||||
{type === 'tags' && result.success && result.tags && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Info className="h-4 w-4 text-blue-600" />
|
||||
<Info className="h-4 w-4 text-primary" />
|
||||
<span className="text-sm font-medium">Generated Tags:</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -247,7 +247,7 @@ export function AI_TESTER({ type }: { type: 'tags' | 'embeddings' }) {
|
||||
{/* Loading State */}
|
||||
{isLoading && (
|
||||
<div className="text-center py-4">
|
||||
<Loader2 className="h-8 w-8 animate-spin mx-auto text-blue-600" />
|
||||
<Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" />
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
Testing {type === 'tags' ? 'tags generation' : 'embeddings'}...
|
||||
</p>
|
||||
|
||||
@@ -36,8 +36,8 @@ export default async function AITestPage() {
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{/* Tags Provider Test */}
|
||||
<Card className="border-blue-200 dark:border-blue-900">
|
||||
<CardHeader className="bg-blue-50/50 dark:bg-blue-950/20">
|
||||
<Card className="border-primary/20 dark:border-primary/30">
|
||||
<CardHeader className="bg-primary/5 dark:bg-primary/10">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<span className="text-2xl">🏷️</span>
|
||||
Tags Generation Test
|
||||
|
||||
@@ -21,7 +21,7 @@ export default async function AdminAIPage() {
|
||||
title: 'Avg Response Time',
|
||||
value: '1.2s',
|
||||
trend: { value: 5, isPositive: true },
|
||||
icon: <Activity className="h-5 w-5 text-blue-600 dark:text-blue-400" />,
|
||||
icon: <Activity className="h-5 w-5 text-primary dark:text-primary-foreground" />,
|
||||
},
|
||||
{
|
||||
title: 'Active Features',
|
||||
@@ -103,7 +103,7 @@ export default async function AdminAIPage() {
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
provider.status === 'Connected'
|
||||
? 'text-green-700 dark:text-green-400 bg-green-100 dark:bg-green-900'
|
||||
: 'text-blue-700 dark:text-blue-400 bg-blue-100 dark:bg-blue-900'
|
||||
: 'text-primary dark:text-primary-foreground bg-primary/10 dark:bg-primary/20'
|
||||
}`}
|
||||
>
|
||||
{provider.status}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default async function AdminPage() {
|
||||
title: 'Total Users',
|
||||
value: users.length,
|
||||
trend: { value: 12, isPositive: true },
|
||||
icon: <Users className="h-5 w-5 text-blue-600 dark:text-blue-400" />,
|
||||
icon: <Users className="h-5 w-5 text-primary dark:text-primary-foreground" />,
|
||||
},
|
||||
{
|
||||
title: 'Active Sessions',
|
||||
|
||||
@@ -213,9 +213,9 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
<form action={handleSaveAI}>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Tags Generation Section */}
|
||||
<div className="space-y-4 p-4 border rounded-lg bg-blue-50/50 dark:bg-blue-950/20">
|
||||
<div className="space-y-4 p-4 border rounded-lg bg-primary/5 dark:bg-primary/10">
|
||||
<h3 className="text-base font-semibold flex items-center gap-2">
|
||||
<span className="text-blue-600">🏷️</span> Tags Generation Provider
|
||||
<span className="text-primary">🏷️</span> Tags Generation Provider
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">AI provider for automatic tag suggestions. Recommended: Ollama (free, local).</p>
|
||||
|
||||
@@ -264,7 +264,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="OPENAI_API_KEY">API Key</Label>
|
||||
<Input id="OPENAI_API_KEY" name="OPENAI_API_KEY" type="password" defaultValue={config.OPENAI_API_KEY || ''} placeholder="sk-..." />
|
||||
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">platform.openai.com</a></p>
|
||||
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">platform.openai.com</a></p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="AI_MODEL_TAGS_OPENAI">Model</Label>
|
||||
@@ -278,7 +278,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
<option key={model} value={model}>{model}</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-muted-foreground"><strong className="text-green-600">gpt-4o-mini</strong> = Best value • <strong className="text-blue-600">gpt-4o</strong> = Best quality</p>
|
||||
<p className="text-xs text-muted-foreground"><strong className="text-green-600">gpt-4o-mini</strong> = Best value • <strong className="text-primary">gpt-4o</strong> = Best quality</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -372,7 +372,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="OPENAI_API_KEY">API Key</Label>
|
||||
<Input id="OPENAI_API_KEY" name="OPENAI_API_KEY" type="password" defaultValue={config.OPENAI_API_KEY || ''} placeholder="sk-..." />
|
||||
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">platform.openai.com</a></p>
|
||||
<p className="text-xs text-muted-foreground">Your OpenAI API key from <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">platform.openai.com</a></p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="AI_MODEL_EMBEDDING_OPENAI">Model</Label>
|
||||
@@ -386,7 +386,7 @@ export function AdminSettingsForm({ config }: { config: Record<string, string> }
|
||||
<option key={model} value={model}>{model}</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-muted-foreground"><strong className="text-green-600">text-embedding-3-small</strong> = Best value • <strong className="text-blue-600">text-embedding-3-large</strong> = Best quality</p>
|
||||
<p className="text-xs text-muted-foreground"><strong className="text-green-600">text-embedding-3-small</strong> = Best value • <strong className="text-primary">text-embedding-3-large</strong> = Best quality</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@ import { cn } from '@/lib/utils'
|
||||
import { LabelFilter } from '@/components/label-filter'
|
||||
|
||||
export default function HomePage() {
|
||||
console.log('[HomePage] Component rendering')
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
// Force re-render when search params change (for filtering)
|
||||
@@ -58,7 +58,7 @@ export default function HomePage() {
|
||||
|
||||
// Callback for NoteInput to trigger notebook suggestion and update UI
|
||||
const handleNoteCreated = useCallback((note: Note) => {
|
||||
console.log('[NotebookSuggestion] Note created:', { id: note.id, notebookId: note.notebookId, contentLength: note.content?.length })
|
||||
|
||||
|
||||
// Update UI immediately by adding the note to the list if it matches current filters
|
||||
setNotes((prevNotes) => {
|
||||
@@ -120,19 +120,19 @@ export default function HomePage() {
|
||||
// Only suggest if note has no notebook and has 20+ words
|
||||
if (!note.notebookId) {
|
||||
const wordCount = (note.content || '').trim().split(/\s+/).filter(w => w.length > 0).length
|
||||
console.log('[NotebookSuggestion] Word count:', wordCount)
|
||||
|
||||
|
||||
if (wordCount >= 20) {
|
||||
console.log('[NotebookSuggestion] Triggering suggestion for note:', note.id)
|
||||
|
||||
setNotebookSuggestion({
|
||||
noteId: note.id,
|
||||
content: note.content || ''
|
||||
})
|
||||
} else {
|
||||
console.log('[NotebookSuggestion] Not enough words, need 20+')
|
||||
|
||||
}
|
||||
} else {
|
||||
console.log('[NotebookSuggestion] Note has notebook, skipping')
|
||||
|
||||
}
|
||||
|
||||
// Refresh in background to ensure data consistency (non-blocking)
|
||||
@@ -265,10 +265,10 @@ export default function HomePage() {
|
||||
|
||||
// Helper for Breadcrumbs
|
||||
const Breadcrumbs = ({ notebookName }: { notebookName: string }) => (
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 mb-1">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-1">
|
||||
<span>Notebooks</span>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
<span className="font-medium text-blue-600">{notebookName}</span>
|
||||
<span className="font-medium text-primary">{notebookName}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -283,12 +283,12 @@ export default function HomePage() {
|
||||
<div className="flex items-start justify-between">
|
||||
{/* Title Section */}
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-xl">
|
||||
<div className="p-3 bg-primary/10 dark:bg-primary/20 rounded-xl">
|
||||
{(() => {
|
||||
const Icon = getNotebookIcon(currentNotebook.icon || 'folder')
|
||||
return (
|
||||
<Icon
|
||||
className={cn("w-8 h-8", !currentNotebook.color && "text-blue-600 dark:text-blue-400")}
|
||||
className={cn("w-8 h-8", !currentNotebook.color && "text-primary dark:text-primary-foreground")}
|
||||
style={currentNotebook.color ? { color: currentNotebook.color } : undefined}
|
||||
/>
|
||||
)
|
||||
@@ -311,7 +311,7 @@ export default function HomePage() {
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setShowNoteInput(!showNoteInput)}
|
||||
className="h-10 px-6 rounded-full bg-blue-600 hover:bg-blue-700 text-white font-medium shadow-sm gap-2 transition-all"
|
||||
className="h-10 px-6 rounded-full bg-primary hover:bg-primary/90 text-primary-foreground font-medium shadow-sm gap-2 transition-all"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
Add Note
|
||||
@@ -329,7 +329,7 @@ export default function HomePage() {
|
||||
{/* Title Section */}
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="p-3 bg-white border border-gray-100 dark:bg-gray-800 dark:border-gray-700 rounded-xl shadow-sm">
|
||||
<FileText className="w-8 h-8 text-blue-600" />
|
||||
<FileText className="w-8 h-8 text-primary" />
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold text-gray-900 dark:text-white tracking-tight">Notes</h1>
|
||||
</div>
|
||||
@@ -362,7 +362,7 @@ export default function HomePage() {
|
||||
|
||||
<Button
|
||||
onClick={() => setShowNoteInput(!showNoteInput)}
|
||||
className="h-10 px-6 rounded-full bg-blue-600 hover:bg-blue-700 text-white font-medium shadow-sm gap-2 transition-all"
|
||||
className="h-10 px-6 rounded-full bg-primary hover:bg-primary/90 text-primary-foreground font-medium shadow-sm gap-2 transition-all"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
Add Note
|
||||
|
||||
@@ -30,13 +30,15 @@ export function AppearanceSettingsForm({ initialTheme, initialFontSize }: Appear
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) root.classList.add('dark')
|
||||
} else if (value === 'dark') {
|
||||
root.classList.add('dark')
|
||||
} else if (value === 'light') {
|
||||
root.setAttribute('data-theme', 'light')
|
||||
} else {
|
||||
root.setAttribute('data-theme', value)
|
||||
if (['midnight'].includes(value)) root.classList.add('dark')
|
||||
if (['midnight', 'blue', 'sepia'].includes(value)) root.classList.add('dark')
|
||||
}
|
||||
|
||||
// Save to DB (no need for router.refresh - localStorage handles immediate visuals)
|
||||
await updateUser({ theme: value as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' })
|
||||
await updateUser({ theme: value as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue' })
|
||||
}
|
||||
|
||||
const handleFontSizeChange = async (value: string) => {
|
||||
@@ -71,10 +73,11 @@ export function AppearanceSettingsForm({ initialTheme, initialFontSize }: Appear
|
||||
description="Select app's visual theme"
|
||||
value={theme}
|
||||
options={[
|
||||
{ value: 'light', label: 'Light' },
|
||||
{ value: 'slate', label: 'Light' },
|
||||
{ value: 'dark', label: 'Dark' },
|
||||
{ value: 'sepia', label: 'Sepia' },
|
||||
{ value: 'midnight', label: 'Midnight' },
|
||||
{ value: 'blue', label: 'Blue' },
|
||||
{ value: 'auto', label: 'Auto (system)' },
|
||||
]}
|
||||
onChange={handleThemeChange}
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function SettingsPage() {
|
||||
</Link>
|
||||
<Link href="/settings/profile">
|
||||
<div className="p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer">
|
||||
<RefreshCw className="h-6 w-6 text-blue-500 mb-2" />
|
||||
<RefreshCw className="h-6 w-6 text-primary mb-2" />
|
||||
<h3 className="font-semibold">Profile Settings</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Manage your account and preferences
|
||||
|
||||
Reference in New Issue
Block a user