Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 115 additions & 17 deletions chat/bun.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions chat/package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
{
"dependencies": {
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-tabs": "^1.1.11",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"jszip": "^3.10.1",
"lucide-react": "^0.511.0",
"next": "15.4.7",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.8",
"react-textarea-autosize": "^8.5.9",
"sonner": "^2.0.3",
"tailwind-merge": "^3.3.0"
Expand Down
54 changes: 54 additions & 0 deletions chat/src/components/chat-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,17 @@ type MessageType = "user" | "raw";

export type ServerStatus = "stable" | "running" | "offline" | "unknown";

export interface FileUploadResponse {
ok: boolean;
filePath?: string;
}

interface ChatContextValue {
messages: (Message | DraftMessage)[];
loading: boolean;
serverStatus: ServerStatus;
sendMessage: (message: string, type?: MessageType) => void;
uploadFiles: (formData: FormData) => Promise<FileUploadResponse>;
}

const ChatContext = createContext<ChatContextValue | undefined>(undefined);
Expand Down Expand Up @@ -268,13 +274,61 @@ export function ChatProvider({ children }: PropsWithChildren) {
}
};

// Upload files to workspace
const uploadFiles = async (formData: FormData): Promise<FileUploadResponse> => {
let result: FileUploadResponse = {ok: true};
try{
const response = await fetch(`${agentAPIUrl}/upload`, {
method: 'POST',
body: formData,
});

if (!response.ok) {
result.ok = false;
const errorData = await response.json();
console.error("Failed to send message:", errorData);
const detail = errorData.detail;
const messages =
"errors" in errorData
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
errorData.errors.map((e: any) => e.message).join(", ")
: "";

const fullDetail = `${detail}: ${messages}`;
toast.error(`Failed to upload files`, {
description: fullDetail,
});
} else {
result = (await response.json()) as FileUploadResponse;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
result.ok = false;
console.error("Error uploading files:", error);
const detail = error.detail;
const messages =
"errors" in error
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
error.errors.map((e: any) => e.message).join("\n")
: "";

const fullDetail = `${detail}: ${messages}`;

toast.error(`Error uploading files`, {
description: fullDetail,
});
}
return result;
}

return (
<ChatContext.Provider
value={{
messages,
loading,
sendMessage,
serverStatus,
uploadFiles,
}}
>
{children}
Expand Down
6 changes: 3 additions & 3 deletions chat/src/components/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
"use client";

import { useChat } from "./chat-provider";
import {useChat} from "./chat-provider";
import MessageInput from "./message-input";
import MessageList from "./message-list";

export function Chat() {
const { messages, loading, sendMessage, serverStatus } = useChat();
const {messages, loading, sendMessage, serverStatus} = useChat();

return (
<>
<MessageList messages={messages} />
<MessageList messages={messages}/>
<MessageInput
onSendMessage={sendMessage}
disabled={loading}
Expand Down
37 changes: 37 additions & 0 deletions chat/src/components/drag-drop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";
import { useDropzone } from "react-dropzone";

export interface DragDropProps {
onFilesAdded: (files: File[]) => void;
disabled?: boolean;
children: React.ReactNode;
className?: string;
}

export function DragDrop({ onFilesAdded, disabled = false, children, className = "" }: DragDropProps) {
const { getRootProps, getInputProps, isDragActive } = useDropzone({
noClick: true,
disabled,
onDropAccepted: (files: File[]) => {
onFilesAdded(files);
},
multiple: true,
});

return (
<div
{...getRootProps()}
className={`relative ${className} ${
isDragActive && !disabled ? 'border-primary border-2 border-dashed rounded-lg text-center transition-colors' : ''
}`}
>
<input {...getInputProps()} />
{isDragActive && !disabled && (
<div className="absolute inset-0 flex items-center justify-center bg-primary/20z-10">
<p className="text-sm text-primary font-medium">Drop the files here</p>
</div>
)}
{children}
</div>
);
}
Loading