First batch of smart stuff

This commit is contained in:
2025-12-25 23:53:12 +00:00
parent 4cb398869d
commit 58de25e5bc
34 changed files with 2974 additions and 74 deletions

View File

@@ -8,6 +8,7 @@ import { Edit, Calendar, Pin } from "lucide-react";
import { getNoteColor } from "@/lib/constants";
import clsx from "clsx";
import remarkGfm from "remark-gfm";
import { RelatedNotes } from "./related-notes";
interface NoteViewDialogProps {
@@ -15,9 +16,10 @@ interface NoteViewDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onEdit: () => void;
onSelectNote?: (id: string) => void;
}
export function NoteViewDialog({ note, open, onOpenChange, onEdit }: NoteViewDialogProps) {
export function NoteViewDialog({ note, open, onOpenChange, onEdit, onSelectNote }: NoteViewDialogProps) {
const colorClass = getNoteColor(note.color);
return (
@@ -42,6 +44,17 @@ export function NoteViewDialog({ note, open, onOpenChange, onEdit }: NoteViewDia
<div className="prose dark:prose-invert max-w-none text-base leading-relaxed break-words pb-6">
<ReactMarkdown remarkPlugins={[remarkGfm]}>{note.content}</ReactMarkdown>
</div>
{/* Smart Features: Related Notes */}
<div className="pb-4">
<RelatedNotes
noteId={note.id}
onSelectNote={onSelectNote ? (id) => {
onOpenChange(false);
setTimeout(() => onSelectNote(id), 100); // Small delay to allow dialog close animation?
} : undefined}
/>
</div>
</div>
<DialogFooter className="pt-4 mt-2 border-t border-black/5 dark:border-white/5 flex sm:justify-between items-center gap-4 shrink-0">

View File

@@ -0,0 +1,62 @@
import { useRelatedNotes } from "@/hooks/use-related-notes";
import { useNotes } from "@/hooks/use-notes";
import { Skeleton } from "@/components/ui/skeleton";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Link2 } from "lucide-react";
interface RelatedNotesProps {
noteId: string;
onSelectNote?: (id: string) => void;
}
export function RelatedNotes({ noteId, onSelectNote }: RelatedNotesProps) {
const { relatedLinks, isRelatedLoading } = useRelatedNotes(noteId);
const { data: notes } = useNotes(); // We need to look up note titles from source_id
if (isRelatedLoading) {
return (
<div className="space-y-2 mt-4">
<h3 className="text-sm font-medium">Related Notes</h3>
<div className="flex flex-wrap gap-2">
<Skeleton className="h-8 w-24" />
<Skeleton className="h-8 w-32" />
</div>
</div>
);
}
if (!relatedLinks || relatedLinks.length === 0) {
return null;
}
return (
<div className="space-y-2 mt-6 border-t pt-4">
<h3 className="text-sm font-medium flex items-center gap-2">
<Link2 className="w-4 h-4" />
Related Notes
</h3>
<div className="flex flex-wrap gap-2">
{relatedLinks.map((link) => {
const targetNote = notes?.find((n: any) => n.id === link.target_note_id);
if (!targetNote) return null;
return (
<Button
key={link.target_note_id}
variant="outline"
size="sm"
className="h-8 text-xs max-w-[200px] justify-start"
onClick={() => onSelectNote?.(link.target_note_id)}
>
<span className="truncate">{targetNote.title || "Untitled"}</span>
<Badge variant="secondary" className="ml-2 text-[10px] h-5 px-1">
{Math.round(link.score * 100)}%
</Badge>
</Button>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,23 @@
import { useQuery } from "@tanstack/react-query";
import { api } from "@/lib/api";
export interface NoteLink {
source_note_id: string;
target_note_id: string;
score: number;
created_at: string;
}
export function useRelatedNotes(noteId: string | undefined) {
const { data, error, isLoading } = useQuery({
queryKey: ["notes", noteId, "related"],
queryFn: () => api.get(`/notes/${noteId}/related`),
enabled: !!noteId,
});
return {
relatedLinks: data as NoteLink[] | undefined,
isRelatedLoading: isLoading,
relatedError: error,
};
}

View File

@@ -1,12 +1,12 @@
const NOTE_COLORS = [
{ name: "DEFAULT", value: "bg-background border-border", label: "Default" },
{ name: "RED", value: "bg-red-50 border-red-200 dark:bg-red-950/20 dark:border-red-900", label: "Red" },
{ name: "ORANGE", value: "bg-orange-50 border-orange-200 dark:bg-orange-950/20 dark:border-orange-900", label: "Orange" },
{ name: "YELLOW", value: "bg-yellow-50 border-yellow-200 dark:bg-yellow-950/20 dark:border-yellow-900", label: "Yellow" },
{ name: "GREEN", value: "bg-green-50 border-green-200 dark:bg-green-950/20 dark:border-green-900", label: "Green" },
{ name: "TEAL", value: "bg-teal-50 border-teal-200 dark:bg-teal-950/20 dark:border-teal-900", label: "Teal" },
{ name: "BLUE", value: "bg-blue-50 border-blue-200 dark:bg-blue-950/20 dark:border-blue-900", label: "Blue" },
{ name: "INDIGO", value: "bg-indigo-50 border-indigo-200 dark:bg-indigo-950/20 dark:border-indigo-900", label: "Indigo" },
{ name: "RED", value: "bg-red-50 border-red-200 dark:bg-red-950 dark:border-red-900", label: "Red" },
{ name: "ORANGE", value: "bg-orange-50 border-orange-200 dark:bg-orange-950 dark:border-orange-900", label: "Orange" },
{ name: "YELLOW", value: "bg-yellow-50 border-yellow-200 dark:bg-yellow-950 dark:border-yellow-900", label: "Yellow" },
{ name: "GREEN", value: "bg-green-50 border-green-200 dark:bg-green-950 dark:border-green-900", label: "Green" },
{ name: "TEAL", value: "bg-teal-50 border-teal-200 dark:bg-teal-950 dark:border-teal-900", label: "Teal" },
{ name: "BLUE", value: "bg-blue-50 border-blue-200 dark:bg-blue-950 dark:border-blue-900", label: "Blue" },
{ name: "INDIGO", value: "bg-indigo-50 border-indigo-200 dark:bg-indigo-950 dark:border-indigo-900", label: "Indigo" },
];
export function getNoteColor(colorName: string | undefined): string {