+
+
+ {/* Drag overlay */}
+ {isDragActive && (
+
+ )}
+
+ {/* Uploaded files display */}
+ {uploadedFiles.length > 0 && (
+
+ {uploadedFiles.map((file) => (
+
+
+ {file.name}
+
+
+ ))}
+
+ )}
+
{/* Input container styled to look like one input */}
inputRef.current?.focus()}
className={cn(
- "relative flex items-center gap-0 min-h-[56px] w-full",
- "bg-background/80 backdrop-blur-xl",
- "border border-white/10 rounded-xl",
- "shadow-[0_0_60px_-15px_rgba(255,255,255,0.1)]",
- "focus-within:shadow-[0_0_80px_-10px_rgba(255,255,255,0.15)]",
- "focus-within:border-white/60",
- "transition-all duration-300",
+ "relative w-full min-h-[96px]",
+ isExpanded ? "rounded-[24px]" : "rounded-[32px]",
+ "border border-white/25",
+ "bg-sidebar backdrop-blur-xl",
+ "px-4 py-2 md:px-6 md:py-3",
+ "shadow-[0_24px_90px_-40px_rgba(0,0,0,0.85)]",
+ "focus-within:border-white/40",
+ "transition-[border-radius,height] duration-300 ease-in-out",
"cursor-text"
)}
>
- {/* Prefix label */}
-
- {baseText}
-
+
+ {/* Static Prefix - positioned absolutely but matched with text-indent */}
+ {/* Static Prefix - positioned absolutely but matched with text-indent */}
+
+ Create a workspace on
+
- {/* Input field */}
-
-
{
if (e.key === "Enter" && !e.shiftKey) {
@@ -167,21 +401,19 @@ export function HomePromptInput({ shouldFocus }: HomePromptInputProps) {
handleSubmit(e);
}
}}
+ onScroll={handleScroll}
/>
{/* Typing placeholder - dimmer for contrast with white prefix */}
{!value && (
)}
+
- {/* Submit arrow */}
-
+
+
+
+
+
+
+ open()}>
+
+ Upload PDF
+
+ setIsUrlDialogOpen(true)}>
+
+ Add URL
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/hooks/workspace/use-create-workspace.ts b/src/hooks/workspace/use-create-workspace.ts
index 4f81cbd6..a0ffdbe9 100644
--- a/src/hooks/workspace/use-create-workspace.ts
+++ b/src/hooks/workspace/use-create-workspace.ts
@@ -140,6 +140,7 @@ export function useCreateWorkspaceFromPrompt() {
prompt: string,
options?: {
template?: WorkspaceTemplate;
+ initialState?: AgentState;
onSuccess?: (workspace: CreateWorkspaceResponse["workspace"]) => void;
onError?: (error: Error) => void;
}
@@ -154,6 +155,7 @@ export function useCreateWorkspaceFromPrompt() {
icon: icon || null,
color: (color as CardColor) || null,
template: options?.template || "getting_started",
+ initialState: options?.initialState,
});
options?.onSuccess?.(result.workspace);
diff --git a/src/hooks/workspace/use-pdf-upload.ts b/src/hooks/workspace/use-pdf-upload.ts
new file mode 100644
index 00000000..244a8ba8
--- /dev/null
+++ b/src/hooks/workspace/use-pdf-upload.ts
@@ -0,0 +1,88 @@
+import { useState, useCallback } from "react";
+import type { PdfData } from "@/lib/workspace-state/types";
+
+export interface UploadedPdfMetadata {
+ fileUrl: string;
+ filename: string;
+ fileSize: number;
+ name: string;
+}
+
+export interface PdfUploadState {
+ isUploading: boolean;
+ error: Error | null;
+ uploadedFiles: UploadedPdfMetadata[];
+}
+
+/**
+ * Hook for uploading PDF files to Supabase storage
+ * Consolidates upload logic used across thread.tsx and WorkspaceSection.tsx
+ */
+export function usePdfUpload() {
+ const [state, setState] = useState
({
+ isUploading: false,
+ error: null,
+ uploadedFiles: [],
+ });
+
+ const uploadFiles = useCallback(async (files: File[]): Promise => {
+ setState((prev) => ({ ...prev, isUploading: true, error: null }));
+
+ try {
+ const uploadPromises = files.map(async (file) => {
+ const formData = new FormData();
+ formData.append("file", file);
+
+ const uploadResponse = await fetch("/api/upload-file", {
+ method: "POST",
+ body: formData,
+ });
+
+ if (!uploadResponse.ok) {
+ throw new Error(`Failed to upload PDF: ${uploadResponse.statusText}`);
+ }
+
+ const { url: fileUrl, filename } = await uploadResponse.json();
+
+ return {
+ fileUrl,
+ filename: filename || file.name,
+ fileSize: file.size,
+ name: file.name.replace(/\.pdf$/i, ""),
+ };
+ });
+
+ const uploadResults = await Promise.all(uploadPromises);
+
+ setState((prev) => ({
+ ...prev,
+ isUploading: false,
+ uploadedFiles: [...prev.uploadedFiles, ...uploadResults],
+ }));
+
+ return uploadResults;
+ } catch (error) {
+ const err = error instanceof Error ? error : new Error("Failed to upload PDFs");
+ setState((prev) => ({ ...prev, isUploading: false, error: err }));
+ throw err;
+ }
+ }, []);
+
+ const clearFiles = useCallback(() => {
+ setState({ isUploading: false, error: null, uploadedFiles: [] });
+ }, []);
+
+ const removeFile = useCallback((fileUrl: string) => {
+ setState((prev) => ({
+ ...prev,
+ uploadedFiles: prev.uploadedFiles.filter((f) => f.fileUrl !== fileUrl),
+ }));
+ }, []);
+
+ return {
+ ...state,
+ uploadFiles,
+ clearFiles,
+ removeFile,
+ };
+}
diff --git a/src/lib/ai/tools/web-search.ts b/src/lib/ai/tools/web-search.ts
index abf70660..2ed1e964 100644
--- a/src/lib/ai/tools/web-search.ts
+++ b/src/lib/ai/tools/web-search.ts
@@ -75,7 +75,7 @@ export function createWebSearchTool() {
execute: async ({ query }) => {
// Use a lightweight model for the internal search loop
const { text, providerMetadata } = await generateText({
- model: google('gemini-2.5-flash-lite'),
+ model: google('gemini-flash-lite-latest'),
tools: {
googleSearch: google.tools.googleSearch({}),
},
diff --git a/src/lib/ai/workers/quiz-worker.ts b/src/lib/ai/workers/quiz-worker.ts
index 56ae251b..f7e77aeb 100644
--- a/src/lib/ai/workers/quiz-worker.ts
+++ b/src/lib/ai/workers/quiz-worker.ts
@@ -5,7 +5,7 @@ import { logger } from "@/lib/utils/logger";
import { QuizQuestion, QuestionType } from "@/lib/workspace-state/types";
import { generateItemId } from "@/lib/workspace-state/item-helpers";
-const DEFAULT_CHAT_MODEL_ID = "gemini-2.5-flash-lite";
+const DEFAULT_CHAT_MODEL_ID = "gemini-flash-lite-latest";
export type QuizWorkerParams = {
topic?: string; // Used only if no context provided