diff options
| -rw-r--r-- | apps/web/components/views/add-memory/index.tsx | 625 | ||||
| -rw-r--r-- | packages/ai-sdk/src/index.ts | 2 | ||||
| -rw-r--r-- | packages/validation/schemas.ts | 92 |
3 files changed, 378 insertions, 341 deletions
diff --git a/apps/web/components/views/add-memory/index.tsx b/apps/web/components/views/add-memory/index.tsx index 8bd6a0f0..5116e952 100644 --- a/apps/web/components/views/add-memory/index.tsx +++ b/apps/web/components/views/add-memory/index.tsx @@ -1,9 +1,9 @@ -import { $fetch } from "@lib/api" +import { $fetch } from "@lib/api"; import { fetchConsumerProProduct, fetchMemoriesFeature, -} from "@repo/lib/queries" -import { Button } from "@repo/ui/components/button" +} from "@repo/lib/queries"; +import { Button } from "@repo/ui/components/button"; import { Dialog, DialogContent, @@ -11,18 +11,18 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@repo/ui/components/dialog" -import { Input } from "@repo/ui/components/input" -import { Label } from "@repo/ui/components/label" -import { Textarea } from "@repo/ui/components/textarea" -import { useForm } from "@tanstack/react-form" -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +} from "@repo/ui/components/dialog"; +import { Input } from "@repo/ui/components/input"; +import { Label } from "@repo/ui/components/label"; +import { Textarea } from "@repo/ui/components/textarea"; +import { useForm } from "@tanstack/react-form"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Dropzone, DropzoneContent, DropzoneEmptyState, -} from "@ui/components/shadcn-io/dropzone" -import { useCustomer } from "autumn-js/react" +} from "@ui/components/shadcn-io/dropzone"; +import { useCustomer } from "autumn-js/react"; import { Brain, FileIcon, @@ -31,19 +31,19 @@ import { PlugIcon, Plus, UploadIcon, -} from "lucide-react" -import { AnimatePresence, motion } from "motion/react" -import dynamic from "next/dynamic" -import { useEffect, useState } from "react" -import { toast } from "sonner" -import { z } from "zod" -import { analytics } from "@/lib/analytics" -import { useProject } from "@/stores" -import { ConnectionsTabContent } from "../connections-tab-content" -import { ActionButtons } from "./action-buttons" -import { MemoryUsageRing } from "./memory-usage-ring" -import { ProjectSelection } from "./project-selection" -import { TabButton } from "./tab-button" +} from "lucide-react"; +import { AnimatePresence, motion } from "motion/react"; +import dynamic from "next/dynamic"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { z } from "zod"; +import { analytics } from "@/lib/analytics"; +import { useProject } from "@/stores"; +import { ConnectionsTabContent } from "../connections-tab-content"; +import { ActionButtons } from "./action-buttons"; +import { MemoryUsageRing } from "./memory-usage-ring"; +import { ProjectSelection } from "./project-selection"; +import { TabButton } from "./tab-button"; const TextEditor = dynamic( () => import("./text-editor").then((mod) => ({ default: mod.TextEditor })), @@ -64,102 +64,91 @@ const TextEditor = dynamic( ), ssr: false, }, -) - -// // Processing status component -// function ProcessingStatus({ status }: { status: string }) { -// const statusConfig = { -// queued: { color: "text-yellow-400", label: "Queued", icon: "⏳" }, -// extracting: { color: "text-blue-400", label: "Extracting", icon: "📤" }, -// chunking: { color: "text-indigo-400", label: "Chunking", icon: "✂️" }, -// embedding: { color: "text-purple-400", label: "Embedding", icon: "🧠" }, -// indexing: { color: "text-pink-400", label: "Indexing", icon: "📝" }, -// unknown: { color: "text-gray-400", label: "Processing", icon: "⚙️" }, -// } - -// const config = -// statusConfig[status as keyof typeof statusConfig] || statusConfig.unknown - -// return ( -// <div className={`flex items-center gap-1 text-xs ${config.color}`}> -// <span>{config.icon}</span> -// <span>{config.label}</span> -// </div> -// ) -// } +); + +// Simple function to extract plain text title from HTML content +const getPlainTextTitle = (htmlContent: string) => { + const temp = document.createElement("div"); + temp.innerHTML = htmlContent; + const plainText = temp.textContent || temp.innerText || htmlContent; + const firstLine = plainText.split("\n")[0].trim(); + return firstLine.length > 0 + ? firstLine.substring(0, 100) + : plainText.substring(0, 100); +}; export function AddMemoryView({ onClose, initialTab = "note", }: { - onClose?: () => void - initialTab?: "note" | "link" | "file" | "connect" + onClose?: () => void; + initialTab?: "note" | "link" | "file" | "connect"; }) { - const queryClient = useQueryClient() - const { selectedProject, setSelectedProject } = useProject() - const [showAddDialog, setShowAddDialog] = useState(true) - const [selectedFiles, setSelectedFiles] = useState<File[]>([]) + const queryClient = useQueryClient(); + const { selectedProject, setSelectedProject } = useProject(); + const [showAddDialog, setShowAddDialog] = useState(true); + const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [activeTab, setActiveTab] = useState< "note" | "link" | "file" | "connect" - >(initialTab) - const autumn = useCustomer() - const [showCreateProjectDialog, setShowCreateProjectDialog] = useState(false) - const [newProjectName, setNewProjectName] = useState("") + >(initialTab); + const autumn = useCustomer(); + const [showCreateProjectDialog, setShowCreateProjectDialog] = useState(false); + const [newProjectName, setNewProjectName] = useState(""); // Check memory limits - const { data: memoriesCheck } = fetchMemoriesFeature(autumn) + const { data: memoriesCheck } = fetchMemoriesFeature(autumn); - const memoriesUsed = memoriesCheck?.usage ?? 0 - const memoriesLimit = memoriesCheck?.included_usage ?? 0 + const memoriesUsed = memoriesCheck?.usage ?? 0; + const memoriesLimit = memoriesCheck?.included_usage ?? 0; // Fetch projects for the dropdown const { data: projects = [], isLoading: isLoadingProjects } = useQuery({ queryKey: ["projects"], queryFn: async () => { - const response = await $fetch("@get/projects") + const response = await $fetch("@get/projects"); if (response.error) { - throw new Error(response.error?.message || "Failed to load projects") + throw new Error(response.error?.message || "Failed to load projects"); } - return response.data?.projects || [] + return response.data?.projects || []; }, staleTime: 30 * 1000, - }) + }); // Create project mutation const createProjectMutation = useMutation({ mutationFn: async (name: string) => { const response = await $fetch("@post/projects", { body: { name }, - }) + }); if (response.error) { - throw new Error(response.error?.message || "Failed to create project") + throw new Error(response.error?.message || "Failed to create project"); } - return response.data + return response.data; }, onSuccess: (data) => { - analytics.projectCreated() - toast.success("Project created successfully!") - setShowCreateProjectDialog(false) - setNewProjectName("") - queryClient.invalidateQueries({ queryKey: ["projects"] }) + analytics.projectCreated(); + toast.success("Project created successfully!"); + setShowCreateProjectDialog(false); + setNewProjectName(""); + queryClient.invalidateQueries({ queryKey: ["projects"] }); // Set the newly created project as selected if (data?.containerTag) { - setSelectedProject(data.containerTag) + setSelectedProject(data.containerTag); // Update form values - addContentForm.setFieldValue("project", data.containerTag) - fileUploadForm.setFieldValue("project", data.containerTag) + addContentForm.setFieldValue("project", data.containerTag); + fileUploadForm.setFieldValue("project", data.containerTag); } }, onError: (error) => { toast.error("Failed to create project", { description: error instanceof Error ? error.message : "Unknown error", - }) + }); }, - }) + }); const addContentForm = useForm({ defaultValues: { @@ -171,8 +160,8 @@ export function AddMemoryView({ content: value.content, project: value.project, contentType: activeTab as "note" | "link", - }) - formApi.reset() + }); + formApi.reset(); }, validators: { onChange: z.object({ @@ -180,19 +169,19 @@ export function AddMemoryView({ project: z.string(), }), }, - }) + }); // Re-validate content field when tab changes between note/link // biome-ignore lint/correctness/useExhaustiveDependencies: It is what it is useEffect(() => { // Trigger validation of the content field when switching between note/link if (activeTab === "note" || activeTab === "link") { - const currentValue = addContentForm.getFieldValue("content") + const currentValue = addContentForm.getFieldValue("content"); if (currentValue) { - addContentForm.validateField("content", "change") + addContentForm.validateField("content", "change"); } } - }, [activeTab]) + }, [activeTab]); // Form for file upload metadata const fileUploadForm = useForm({ @@ -203,8 +192,8 @@ export function AddMemoryView({ }, onSubmit: async ({ value, formApi }) => { if (selectedFiles.length === 0) { - toast.error("Please select a file to upload") - return + toast.error("Please select a file to upload"); + return; } for (const file of selectedFiles) { @@ -213,13 +202,13 @@ export function AddMemoryView({ title: value.title || undefined, description: value.description || undefined, project: value.project, - }) + }); } - formApi.reset() - setSelectedFiles([]) + formApi.reset(); + setSelectedFiles([]); }, - }) + }); const addContentMutation = useMutation({ mutationFn: async ({ @@ -227,191 +216,212 @@ export function AddMemoryView({ project, contentType, }: { - content: string - project: string - contentType: "note" | "link" + content: string; + project: string; + contentType: "note" | "link"; }) => { - // close the modal - onClose?.() - - const processingPromise = (async () => { - // First, create the memory - const response = await $fetch("@post/documents", { - body: { - content: content, - containerTags: [project], - metadata: { - sm_source: "consumer", // Use "consumer" source to bypass limits - }, + console.log("📤 Creating memory..."); + + const response = await $fetch("@post/documents", { + body: { + content: content, + containerTags: [project], + metadata: { + sm_source: "consumer", }, - }) - - if (response.error) { - throw new Error( - response.error?.message || `Failed to add ${contentType}`, - ) - } - - const memoryId = response.data.id - - // Polling function to check status - const pollForCompletion = async (): Promise<any> => { - let attempts = 0 - const maxAttempts = 60 // Maximum 5 minutes (60 attempts * 5 seconds) - - while (attempts < maxAttempts) { - try { - const memory = await $fetch<{ status: string; content: string }>( - `@get/documents/${memoryId}`, - ) - - if (memory.error) { - throw new Error( - memory.error?.message || "Failed to fetch memory status", - ) - } - - // Check if processing is complete - // Adjust this condition based on your API response structure - if ( - memory.data?.status === "done" || - // Sometimes the memory might be ready when it has content and no processing status - memory.data?.content - ) { - return memory.data - } - - // If still processing, wait and try again - await new Promise((resolve) => setTimeout(resolve, 5000)) // Wait 5 seconds - attempts++ - } catch (error) { - console.error("Error polling memory status:", error) - // Don't throw immediately, retry a few times - if (attempts >= 3) { - throw new Error("Failed to check processing status") - } - await new Promise((resolve) => setTimeout(resolve, 5000)) - attempts++ - } - } - - // If we've exceeded max attempts, throw an error - throw new Error( - "Memory processing timed out. Please check back later.", - ) - } - - // Wait for completion - const completedMemory = await pollForCompletion() - return completedMemory - })() + }, + }); - toast.promise(processingPromise, { - loading: "Processing...", - success: `${contentType === "link" ? "Link" : "Note"} created successfully!`, - error: (err) => - `Failed to add ${contentType}: ${err instanceof Error ? err.message : "Unknown error"}`, - }) + if (response.error) { + throw new Error( + response.error?.message || `Failed to add ${contentType}`, + ); + } - return processingPromise + console.log("✅ Memory created:", response.data); + return response.data; }, onMutate: async ({ content, project, contentType }) => { - console.log("🚀 onMutate starting...") + console.log("🚀 OPTIMISTIC UPDATE: Starting for", contentType); - // Cancel any outgoing refetches + // Cancel queries to prevent conflicts await queryClient.cancelQueries({ queryKey: ["documents-with-memories", project], - }) - console.log("✅ Cancelled queries") + }); + console.log("📞 QUERIES CANCELLED for project:", project); - // Snapshot the previous value + // Get previous data for rollback const previousMemories = queryClient.getQueryData([ "documents-with-memories", project, - ]) - console.log("📸 Previous memories:", previousMemories) + ]); - // Create optimistic memory + // Create optimistic memory with proper title + const tempId = `temp-${Date.now()}`; const optimisticMemory = { - id: `temp-${Date.now()}`, + id: tempId, content: contentType === "link" ? "" : content, url: contentType === "link" ? content : null, title: - contentType === "link" ? "Processing..." : content.substring(0, 100), - description: - contentType === "link" - ? "Extracting content..." - : "Processing content...", + contentType === "link" ? "Processing..." : getPlainTextTitle(content), + description: "Processing...", containerTags: [project], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), - status: "queued", - type: contentType, - metadata: { - processingStage: "queued", - processingMessage: "Added to processing queue", - }, memoryEntries: [], isOptimistic: true, - } - console.log("🎯 Created optimistic memory:", optimisticMemory) + }; - // Optimistically update to include the new memory + console.log("🎯 Adding optimistic memory:", optimisticMemory); + + // Add to cache optimistically queryClient.setQueryData( ["documents-with-memories", project], (old: any) => { - console.log("🔄 Old data:", old) - const newData = old + if (old?.pages) { + // Infinite query structure + const newPages = [...old.pages]; + if (newPages.length > 0) { + newPages[0] = { + ...newPages[0], + documents: [optimisticMemory, ...(newPages[0].documents || [])], + }; + } else { + newPages.push({ + documents: [optimisticMemory], + pagination: { currentPage: 1, totalPages: 1, totalCount: 1 }, + }); + } + return { ...old, pages: newPages }; + } + + // Regular query structure + return old ? { ...old, documents: [optimisticMemory, ...(old.documents || [])], totalCount: (old.totalCount || 0) + 1, } - : { documents: [optimisticMemory], totalCount: 1 } - console.log("✨ New data:", newData) - return newData + : { documents: [optimisticMemory], totalCount: 1 }; }, - ) + ); - console.log("✅ onMutate completed") - return { previousMemories, optimisticId: optimisticMemory.id } + return { previousMemories, optimisticId: tempId }; }, - // If the mutation fails, roll back to the previous value onError: (error, variables, context) => { + console.log("❌ Mutation failed, rolling back"); if (context?.previousMemories) { queryClient.setQueryData( ["documents-with-memories", variables.project], context.previousMemories, - ) + ); } + toast.error(`Failed to add ${variables.contentType}`); }, - onSuccess: (_data, variables) => { + onSuccess: (data, variables, context) => { + console.log( + "✅ Mutation succeeded, starting simple polling for memory:", + data.id, + ); analytics.memoryAdded({ type: variables.contentType === "link" ? "link" : "note", project_id: variables.project, content_length: variables.content.length, - }) - - queryClient.invalidateQueries({ - queryKey: ["documents-with-memories", variables.project], - }) - - setTimeout(() => { - queryClient.invalidateQueries({ - queryKey: ["documents-with-memories", variables.project], - }) - }, 30000) // 30 seconds - - setTimeout(() => { - queryClient.invalidateQueries({ - queryKey: ["documents-with-memories", variables.project], - }) - }, 120000) // 2 minutes - - setShowAddDialog(false) - onClose?.() + }); + + toast.success( + `${variables.contentType === "link" ? "Link" : "Note"} added successfully!`, + ); + setShowAddDialog(false); + onClose?.(); + + // Simple polling to replace optimistic update when ready + const pollMemory = async () => { + try { + const memory = await $fetch(`@get/documents/${data.id}`); + console.log("🔍 Polling memory:", memory.data); + + if (memory.data && !memory.error) { + // Check if memory has been processed (has memory entries, substantial content, and NOT untitled/processing) + const hasRealTitle = + memory.data.title && + !memory.data.title.toLowerCase().includes("untitled") && + !memory.data.title.toLowerCase().includes("processing") && + memory.data.title.length > 3; + + const isReady = + memory.data.memoryEntries?.length > 0 || + (memory.data.content && + memory.data.content.length > 10 && + hasRealTitle); + + console.log("📊 Memory ready check:", { + isReady, + hasMemoryEntries: memory.data.memoryEntries?.length, + hasContent: memory.data.content?.length, + title: memory.data.title, + hasRealTitle, + titleNotUntitled: + memory.data.title && + !memory.data.title.toLowerCase().includes("untitled"), + titleNotProcessing: + memory.data.title && + !memory.data.title.toLowerCase().includes("processing"), + }); + + if (isReady) { + console.log("✅ Memory ready, replacing optimistic update"); + // Replace optimistic memory with real data + queryClient.setQueryData( + ["documents-with-memories", variables.project], + (old: any) => { + if (old?.pages) { + const newPages = old.pages.map((page: any) => ({ + ...page, + documents: page.documents.map((doc: any) => { + if (doc.isOptimistic && doc.id.startsWith("temp-")) { + return { ...memory.data, isOptimistic: false }; + } + return doc; + }), + })); + return { ...old, pages: newPages }; + } + return old; + }, + ); + return true; // Stop polling + } + } + return false; // Continue polling + } catch (error) { + console.error("❌ Error polling memory:", error); + return false; + } + }; + + // Poll every 3 seconds for up to 30 attempts (90 seconds) + let attempts = 0; + const maxAttempts = 30; + + const poll = async () => { + if (attempts >= maxAttempts) { + console.log("⚠️ Polling stopped after max attempts"); + return; + } + + const isDone = await pollMemory(); + attempts++; + + if (!isDone && attempts < maxAttempts) { + setTimeout(poll, 3000); + } + }; + + // Start polling after 2 seconds + setTimeout(poll, 2000); }, - }) + }); const fileUploadMutation = useMutation({ mutationFn: async ({ @@ -420,10 +430,10 @@ export function AddMemoryView({ description, project, }: { - file: File - title?: string - description?: string - project: string + file: File; + title?: string; + description?: string; + project: string; }) => { // TEMPORARILY DISABLED: Limit check disabled // Check if user can add more memories @@ -433,9 +443,9 @@ export function AddMemoryView({ // ); // } - const formData = new FormData() - formData.append("file", file) - formData.append("containerTags", JSON.stringify([project])) + const formData = new FormData(); + formData.append("file", file); + formData.append("containerTags", JSON.stringify([project])); const response = await fetch( `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/documents/file`, @@ -444,14 +454,14 @@ export function AddMemoryView({ body: formData, credentials: "include", }, - ) + ); if (!response.ok) { - const error = await response.json() - throw new Error(error.error || "Failed to upload file") + const error = await response.json(); + throw new Error(error.error || "Failed to upload file"); } - const data = await response.json() + const data = await response.json(); // If we have metadata, we can update the document after creation if (title || description) { @@ -463,23 +473,23 @@ export function AddMemoryView({ sm_source: "consumer", // Use "consumer" source to bypass limits }, }, - }) + }); } - return data + return data; }, // Optimistic update onMutate: async ({ file, title, description, project }) => { // Cancel any outgoing refetches await queryClient.cancelQueries({ queryKey: ["documents-with-memories", project], - }) + }); // Snapshot the previous value const previousMemories = queryClient.getQueryData([ "documents-with-memories", project, - ]) + ]); // Create optimistic memory for the file const optimisticMemory = { @@ -499,23 +509,50 @@ export function AddMemoryView({ mimeType: file.type, }, memoryEntries: [], - } + }; // Optimistically update to include the new memory queryClient.setQueryData( ["documents-with-memories", project], (old: any) => { - if (!old) return { documents: [optimisticMemory], totalCount: 1 } + // Handle infinite query structure + if (old?.pages) { + // This is an infinite query - add to the first page + const newPages = [...old.pages]; + if (newPages.length > 0) { + // Add to the first page + const firstPage = { ...newPages[0] }; + firstPage.documents = [ + optimisticMemory, + ...(firstPage.documents || []), + ]; + newPages[0] = firstPage; + } else { + // No pages yet, create the first page + newPages.push({ + documents: [optimisticMemory], + pagination: { currentPage: 1, totalPages: 1, totalCount: 1 }, + totalCount: 1, + }); + } + + return { + ...old, + pages: newPages, + }; + } + // Fallback for regular query structure + if (!old) return { documents: [optimisticMemory], totalCount: 1 }; return { ...old, documents: [optimisticMemory, ...(old.documents || [])], totalCount: (old.totalCount || 0) + 1, - } + }; }, - ) + ); // Return a context object with the snapshotted value - return { previousMemories } + return { previousMemories }; }, // If the mutation fails, roll back to the previous value onError: (error, variables, context) => { @@ -523,11 +560,11 @@ export function AddMemoryView({ queryClient.setQueryData( ["documents-with-memories", variables.project], context.previousMemories, - ) + ); } toast.error("Failed to upload file", { description: error instanceof Error ? error.message : "Unknown error", - }) + }); }, onSuccess: (_data, variables) => { analytics.memoryAdded({ @@ -535,18 +572,18 @@ export function AddMemoryView({ project_id: variables.project, file_size: variables.file.size, file_type: variables.file.type, - }) + }); toast.success("File uploaded successfully!", { description: "Your file is being processed", - }) - setShowAddDialog(false) - onClose?.() - }, - // Always refetch after error or success - onSettled: () => { - queryClient.invalidateQueries({ queryKey: ["documents-with-memories"] }) + }); + setShowAddDialog(false); + onClose?.(); }, - }) + // Don't invalidate queries immediately - let optimistic updates work + // onSettled: () => { + // queryClient.invalidateQueries({ queryKey: ["documents-with-memories"] }) + // }, + }); return ( <AnimatePresence mode="wait"> @@ -554,8 +591,8 @@ export function AddMemoryView({ <Dialog key="add-memory-dialog" onOpenChange={(open) => { - setShowAddDialog(open) - if (!open) onClose?.() + setShowAddDialog(open); + if (!open) onClose?.(); }} open={showAddDialog} > @@ -614,9 +651,9 @@ export function AddMemoryView({ <div className="space-y-4"> <form onSubmit={(e) => { - e.preventDefault() - e.stopPropagation() - addContentForm.handleSubmit() + e.preventDefault(); + e.stopPropagation(); + addContentForm.handleSubmit(); }} > <div className="grid gap-4"> @@ -632,9 +669,9 @@ export function AddMemoryView({ validators={{ onChange: ({ value }) => { if (!value || value.trim() === "") { - return "Note is required" + return "Note is required"; } - return undefined + return undefined; }, }} > @@ -716,9 +753,9 @@ export function AddMemoryView({ isSubmitDisabled={!addContentForm.state.canSubmit} isSubmitting={addContentMutation.isPending} onCancel={() => { - setShowAddDialog(false) - onClose?.() - addContentForm.reset() + setShowAddDialog(false); + onClose?.(); + addContentForm.reset(); }} submitIcon={Plus} submitText="Add Note" @@ -732,9 +769,9 @@ export function AddMemoryView({ <div className="space-y-4"> <form onSubmit={(e) => { - e.preventDefault() - e.stopPropagation() - addContentForm.handleSubmit() + e.preventDefault(); + e.stopPropagation(); + addContentForm.handleSubmit(); }} > <div className="grid gap-4"> @@ -756,13 +793,13 @@ export function AddMemoryView({ validators={{ onChange: ({ value }) => { if (!value || value.trim() === "") { - return "Link is required" + return "Link is required"; } try { - new URL(value) - return undefined + new URL(value); + return undefined; } catch { - return "Please enter a valid link" + return "Please enter a valid link"; } }, }} @@ -842,9 +879,9 @@ export function AddMemoryView({ isSubmitDisabled={!addContentForm.state.canSubmit} isSubmitting={addContentMutation.isPending} onCancel={() => { - setShowAddDialog(false) - onClose?.() - addContentForm.reset() + setShowAddDialog(false); + onClose?.(); + addContentForm.reset(); }} submitIcon={Plus} submitText="Add Link" @@ -858,9 +895,9 @@ export function AddMemoryView({ <div className="space-y-4"> <form onSubmit={(e) => { - e.preventDefault() - e.stopPropagation() - fileUploadForm.handleSubmit() + e.preventDefault(); + e.stopPropagation(); + fileUploadForm.handleSubmit(); }} > <div className="grid gap-4"> @@ -994,10 +1031,10 @@ export function AddMemoryView({ isSubmitDisabled={selectedFiles.length === 0} isSubmitting={fileUploadMutation.isPending} onCancel={() => { - setShowAddDialog(false) - onClose?.() - fileUploadForm.reset() - setSelectedFiles([]) + setShowAddDialog(false); + onClose?.(); + fileUploadForm.reset(); + setSelectedFiles([]); }} submitIcon={UploadIcon} submitText="Upload File" @@ -1065,8 +1102,8 @@ export function AddMemoryView({ <Button className="bg-white/5 hover:bg-white/10 border-white/10 text-white w-full sm:w-auto" onClick={() => { - setShowCreateProjectDialog(false) - setNewProjectName("") + setShowCreateProjectDialog(false); + setNewProjectName(""); }} type="button" variant="outline" @@ -1103,19 +1140,19 @@ export function AddMemoryView({ </Dialog> )} </AnimatePresence> - ) + ); } export function AddMemoryExpandedView() { - const [showDialog, setShowDialog] = useState(false) + const [showDialog, setShowDialog] = useState(false); const [selectedTab, setSelectedTab] = useState< "note" | "link" | "file" | "connect" - >("note") + >("note"); const handleOpenDialog = (tab: "note" | "link" | "file" | "connect") => { - setSelectedTab(tab) - setShowDialog(true) - } + setSelectedTab(tab); + setShowDialog(true); + }; return ( <> @@ -1186,5 +1223,5 @@ export function AddMemoryExpandedView() { /> )} </> - ) + ); } diff --git a/packages/ai-sdk/src/index.ts b/packages/ai-sdk/src/index.ts index e419075c..2eed2172 100644 --- a/packages/ai-sdk/src/index.ts +++ b/packages/ai-sdk/src/index.ts @@ -1 +1 @@ -export * from "./tools" +export * from "./tools"; diff --git a/packages/validation/schemas.ts b/packages/validation/schemas.ts index 92524164..a704b755 100644 --- a/packages/validation/schemas.ts +++ b/packages/validation/schemas.ts @@ -1,12 +1,12 @@ -import { z } from "zod" +import { z } from "zod"; export const MetadataSchema = z.record( z.union([z.string(), z.number(), z.boolean()]), -) -export type Metadata = z.infer<typeof MetadataSchema> +); +export type Metadata = z.infer<typeof MetadataSchema>; -export const VisibilityEnum = z.enum(["public", "private", "unlisted"]) -export type Visibility = z.infer<typeof VisibilityEnum> +export const VisibilityEnum = z.enum(["public", "private", "unlisted"]); +export type Visibility = z.infer<typeof VisibilityEnum>; export const DocumentTypeEnum = z.enum([ "text", @@ -20,8 +20,8 @@ export const DocumentTypeEnum = z.enum([ "notion_doc", "webpage", "onedrive", -]) -export type DocumentType = z.infer<typeof DocumentTypeEnum> +]); +export type DocumentType = z.infer<typeof DocumentTypeEnum>; export const DocumentStatusEnum = z.enum([ "unknown", @@ -32,8 +32,8 @@ export const DocumentStatusEnum = z.enum([ "indexing", "done", "failed", -]) -export type DocumentStatus = z.infer<typeof DocumentStatusEnum> +]); +export type DocumentStatus = z.infer<typeof DocumentStatusEnum>; export const ProcessingStepSchema = z.object({ name: z.string(), @@ -43,8 +43,8 @@ export const ProcessingStepSchema = z.object({ error: z.string().optional(), metadata: z.record(z.unknown()).optional(), finalStatus: z.enum(["done", "failed"]).optional(), -}) -export type ProcessingStep = z.infer<typeof ProcessingStepSchema> +}); +export type ProcessingStep = z.infer<typeof ProcessingStepSchema>; export const ProcessingMetadataSchema = z.object({ startTime: z.number(), @@ -55,8 +55,8 @@ export const ProcessingMetadataSchema = z.object({ chunkingStrategy: z.string().optional(), tokenCount: z.number().optional(), steps: z.array(ProcessingStepSchema), -}) -export type ProcessingMetadata = z.infer<typeof ProcessingMetadataSchema> +}); +export type ProcessingMetadata = z.infer<typeof ProcessingMetadataSchema>; export const DocumentSchema = z.object({ id: z.string(), @@ -97,11 +97,11 @@ export const DocumentSchema = z.object({ // Timestamps createdAt: z.coerce.date(), updatedAt: z.coerce.date(), -}) -export type Document = z.infer<typeof DocumentSchema> +}); +export type Document = z.infer<typeof DocumentSchema>; -export const ChunkTypeEnum = z.enum(["text", "image"]) -export type ChunkType = z.infer<typeof ChunkTypeEnum> +export const ChunkTypeEnum = z.enum(["text", "image"]); +export type ChunkType = z.infer<typeof ChunkTypeEnum>; export const ChunkSchema = z.object({ id: z.string(), @@ -120,15 +120,15 @@ export const ChunkSchema = z.object({ matryokshaEmbeddingModel: z.string().nullable().optional(), createdAt: z.coerce.date(), -}) -export type Chunk = z.infer<typeof ChunkSchema> +}); +export type Chunk = z.infer<typeof ChunkSchema>; export const ConnectionProviderEnum = z.enum([ "notion", "google-drive", "onedrive", -]) -export type ConnectionProvider = z.infer<typeof ConnectionProviderEnum> +]); +export type ConnectionProvider = z.infer<typeof ConnectionProviderEnum>; export const ConnectionStateSchema = z.object({ stateToken: z.string(), @@ -142,8 +142,8 @@ export const ConnectionStateSchema = z.object({ containerTags: z.array(z.string()).nullable().optional(), createdAt: z.coerce.date(), expiresAt: z.coerce.date().nullable().optional(), -}) -export type ConnectionState = z.infer<typeof ConnectionStateSchema> +}); +export type ConnectionState = z.infer<typeof ConnectionStateSchema>; export const ConnectionSchema = z.object({ id: z.string(), @@ -163,8 +163,8 @@ export const ConnectionSchema = z.object({ metadata: z.record(z.unknown()), createdAt: z.coerce.date(), -}) -export type Connection = z.infer<typeof ConnectionSchema> +}); +export type Connection = z.infer<typeof ConnectionSchema>; export const RequestTypeEnum = z.enum([ "add", @@ -175,8 +175,8 @@ export const RequestTypeEnum = z.enum([ "delete", "chat", "search_v4", -]) -export type RequestType = z.infer<typeof RequestTypeEnum> +]); +export type RequestType = z.infer<typeof RequestTypeEnum>; export const ApiRequestSchema = z.object({ id: z.string(), @@ -212,8 +212,8 @@ export const ApiRequestSchema = z.object({ origin: z.string().default("api"), createdAt: z.coerce.date(), -}) -export type ApiRequest = z.infer<typeof ApiRequestSchema> +}); +export type ApiRequest = z.infer<typeof ApiRequestSchema>; export const SpaceSchema = z.object({ id: z.string(), @@ -233,11 +233,11 @@ export const SpaceSchema = z.object({ createdAt: z.coerce.date(), updatedAt: z.coerce.date(), -}) -export type Space = z.infer<typeof SpaceSchema> +}); +export type Space = z.infer<typeof SpaceSchema>; -export const MemoryRelationEnum = z.enum(["updates", "extends", "derives"]) -export type MemoryRelation = z.infer<typeof MemoryRelationEnum> +export const MemoryRelationEnum = z.enum(["updates", "extends", "derives"]); +export type MemoryRelation = z.infer<typeof MemoryRelationEnum>; export const MemoryEntrySchema = z.object({ id: z.string(), @@ -274,14 +274,14 @@ export const MemoryEntrySchema = z.object({ createdAt: z.coerce.date(), updatedAt: z.coerce.date(), -}) -export type MemoryEntry = z.infer<typeof MemoryEntrySchema> +}); +export type MemoryEntry = z.infer<typeof MemoryEntrySchema>; export const DocumentsToSpacesSchema = z.object({ documentId: z.string(), spaceId: z.string(), -}) -export type DocumentsToSpaces = z.infer<typeof DocumentsToSpacesSchema> +}); +export type DocumentsToSpaces = z.infer<typeof DocumentsToSpacesSchema>; export const MemoryDocumentSourceSchema = z.object({ memoryEntryId: z.string(), @@ -289,11 +289,11 @@ export const MemoryDocumentSourceSchema = z.object({ relevanceScore: z.number().default(100), metadata: z.record(z.unknown()).nullable().optional(), addedAt: z.coerce.date(), -}) -export type MemoryDocumentSource = z.infer<typeof MemoryDocumentSourceSchema> +}); +export type MemoryDocumentSource = z.infer<typeof MemoryDocumentSourceSchema>; -export const SpaceRoleEnum = z.enum(["owner", "admin", "editor", "viewer"]) -export type SpaceRole = z.infer<typeof SpaceRoleEnum> +export const SpaceRoleEnum = z.enum(["owner", "admin", "editor", "viewer"]); +export type SpaceRole = z.infer<typeof SpaceRoleEnum>; export const SpacesToMembersSchema = z.object({ spaceId: z.string(), @@ -302,8 +302,8 @@ export const SpacesToMembersSchema = z.object({ metadata: MetadataSchema.nullable().optional(), createdAt: z.coerce.date(), updatedAt: z.coerce.date(), -}) -export type SpacesToMembers = z.infer<typeof SpacesToMembersSchema> +}); +export type SpacesToMembers = z.infer<typeof SpacesToMembersSchema>; export const OrganizationSettingsSchema = z.object({ id: z.string(), @@ -331,8 +331,8 @@ export const OrganizationSettingsSchema = z.object({ onedriveClientSecret: z.string().nullable().optional(), updatedAt: z.coerce.date(), -}) -export type OrganizationSettings = z.infer<typeof OrganizationSettingsSchema> +}); +export type OrganizationSettings = z.infer<typeof OrganizationSettingsSchema>; export const schemas = { // Base types @@ -368,4 +368,4 @@ export const schemas = { // Auth OrganizationSettingsSchema, -} as const +} as const; |