Compare commits

...

2 Commits

Author SHA1 Message Date
pablonyx
285aba3c20 improved my docs 2025-04-13 16:01:09 -07:00
pablonyx
a4a399bc31 update 2025-04-11 15:11:35 -07:00
21 changed files with 362 additions and 328 deletions

View File

@@ -90,8 +90,25 @@ def get_folders(
db_session: Session = Depends(get_session),
) -> list[UserFolderSnapshot]:
user_id = user.id if user else None
folders = db_session.query(UserFolder).filter(UserFolder.user_id == user_id).all()
return [UserFolderSnapshot.from_model(folder) for folder in folders]
# Get folders that belong to the user or have the RECENT_DOCS_FOLDER_ID
folders = (
db_session.query(UserFolder)
.filter(
(UserFolder.user_id == user_id) | (UserFolder.id == RECENT_DOCS_FOLDER_ID)
)
.all()
)
# For each folder, filter files to only include those belonging to the current user
result = []
for folder in folders:
folder_snapshot = UserFolderSnapshot.from_model(folder)
folder_snapshot.files = [
file for file in folder_snapshot.files if file.user_id == user_id
]
result.append(folder_snapshot)
return result
@router.get("/user/folder/{folder_id}")
@@ -103,13 +120,25 @@ def get_folder(
user_id = user.id if user else None
folder = (
db_session.query(UserFolder)
.filter(UserFolder.id == folder_id, UserFolder.user_id == user_id)
.filter(
UserFolder.id == folder_id,
(
(UserFolder.user_id == user_id)
| (UserFolder.id == RECENT_DOCS_FOLDER_ID)
),
)
.first()
)
if not folder:
raise HTTPException(status_code=404, detail="Folder not found")
return UserFolderSnapshot.from_model(folder)
folder_snapshot = UserFolderSnapshot.from_model(folder)
# Filter files to only include those belonging to the current user
folder_snapshot.files = [
file for file in folder_snapshot.files if file.user_id == user_id
]
return folder_snapshot
RECENT_DOCS_FOLDER_ID = -1

View File

@@ -29,12 +29,11 @@ function LLMProviderUpdateModal({
llmProviderDescriptor?.name ||
"Custom LLM Provider";
const hasAdvancedOptions = llmProviderDescriptor?.name != "azure";
return (
<Modal
title={`${llmProviderDescriptor ? "Configure" : "Setup"} ${providerName}`}
onOutsideClick={() => onClose()}
hideOverflow={true}
>
<div className="max-h-[70vh] overflow-y-auto px-4">
{llmProviderDescriptor ? (
@@ -44,7 +43,6 @@ function LLMProviderUpdateModal({
existingLlmProvider={existingLlmProvider}
shouldMarkAsDefault={shouldMarkAsDefault}
setPopup={setPopup}
hasAdvancedOptions={hasAdvancedOptions}
/>
) : (
<CustomLLMProviderUpdateForm

View File

@@ -35,10 +35,12 @@ function LLMProviderUpdateModal({
existingLlmProvider?.name ||
"Custom LLM Provider";
const hasAdvancedOptions = llmProviderDescriptor?.name != "azure";
return (
<Modal title={`Setup ${providerName}`} onOutsideClick={() => onClose()}>
<Modal
title={`Setup ${providerName}`}
onOutsideClick={() => onClose()}
hideOverflow={true}
>
<div className="max-h-[70vh] overflow-y-auto px-4">
{llmProviderDescriptor ? (
<LLMProviderUpdateForm
@@ -47,7 +49,6 @@ function LLMProviderUpdateModal({
existingLlmProvider={existingLlmProvider}
shouldMarkAsDefault={shouldMarkAsDefault}
setPopup={setPopup}
hasAdvancedOptions={hasAdvancedOptions}
/>
) : (
<CustomLLMProviderUpdateForm

View File

@@ -29,7 +29,6 @@ export function LLMProviderUpdateForm({
setPopup,
hideSuccess,
firstTimeConfiguration = false,
hasAdvancedOptions = false,
}: {
llmProviderDescriptor: WellKnownLLMProviderDescriptor;
onClose: () => void;
@@ -40,7 +39,6 @@ export function LLMProviderUpdateForm({
// Set this when this is the first time the user is setting Onyx up.
firstTimeConfiguration?: boolean;
hasAdvancedOptions?: boolean;
}) {
const { mutate } = useSWRConfig();
@@ -302,7 +300,7 @@ export function LLMProviderUpdateForm({
}
})}
{hasAdvancedOptions && !firstTimeConfiguration && (
{!firstTimeConfiguration && (
<>
<Separator />
@@ -364,52 +362,49 @@ export function LLMProviderUpdateForm({
/>
))}
{hasAdvancedOptions && (
<>
<Separator />
<AdvancedOptionsToggle
showAdvancedOptions={showAdvancedOptions}
setShowAdvancedOptions={setShowAdvancedOptions}
/>
{showAdvancedOptions && (
<>
{llmProviderDescriptor.llm_names.length > 0 && (
<div className="w-full">
<MultiSelectField
selectedInitially={
formikProps.values.display_model_names
}
name="display_model_names"
label="Display Models"
subtext="Select the models to make available to users. Unselected models will not be available."
options={llmProviderDescriptor.llm_names.map(
(name) => ({
value: name,
// don't clean up names here to give admins descriptive names / handle duplicates
// like us.anthropic.claude-3-7-sonnet-20250219-v1:0 and anthropic.claude-3-7-sonnet-20250219-v1:0
label: name,
})
)}
onChange={(selected) =>
formikProps.setFieldValue(
"display_model_names",
selected
)
}
/>
</div>
)}
<IsPublicGroupSelector
formikProps={formikProps}
objectName="LLM Provider"
publicToWhom="all users"
enforceGroupSelection={true}
/>
</>
)}
</>
)}
<>
<Separator />
<AdvancedOptionsToggle
showAdvancedOptions={showAdvancedOptions}
setShowAdvancedOptions={setShowAdvancedOptions}
/>
{showAdvancedOptions && (
<>
{llmProviderDescriptor.llm_names.length > 0 && (
<div className="w-full">
<MultiSelectField
selectedInitially={
formikProps.values.display_model_names
}
name="display_model_names"
label="Display Models"
subtext="Select the models to make available to users. Unselected models will not be available."
options={llmProviderDescriptor.llm_names.map(
(name) => ({
value: name,
// don't clean up names here to give admins descriptive names / handle duplicates
// like us.anthropic.claude-3-7-sonnet-20250219-v1:0 and anthropic.claude-3-7-sonnet-20250219-v1:0
label: name,
})
)}
onChange={(selected) =>
formikProps.setFieldValue(
"display_model_names",
selected
)
}
/>
</div>
)}
<IsPublicGroupSelector
formikProps={formikProps}
objectName="LLM Provider"
publicToWhom="Users"
enforceGroupSelection={true}
/>
</>
)}
</>
</>
)}
@@ -417,7 +412,11 @@ export function LLMProviderUpdateForm({
{testError && <Text className="text-error mt-2">{testError}</Text>}
<div className="flex w-full mt-4">
<Button type="submit" variant="submit">
<Button
onClick={() => alert(JSON.stringify(formikProps.errors))}
type="submit"
variant="submit"
>
{isTesting ? (
<LoadingAnimation text="Testing" />
) : existingLlmProvider ? (

View File

@@ -40,10 +40,19 @@ const Page = async (props: {
// if user is already logged in, take them to the main app page
if (currentUser && currentUser.is_active && !currentUser.is_anonymous_user) {
console.log("Login page: User is logged in, redirecting to chat", {
userId: currentUser.id,
is_active: currentUser.is_active,
is_anonymous: currentUser.is_anonymous_user,
});
if (authTypeMetadata?.requiresVerification && !currentUser.is_verified) {
return redirect("/auth/waiting-on-verification");
}
return redirect("/chat");
// Add a query parameter to indicate this is a redirect from login
// This will help prevent redirect loops
return redirect("/chat?from=login");
}
// get where to send the user to authenticate

View File

@@ -18,6 +18,7 @@ export default async function Layout({
);
if ("redirect" in data) {
console.log("redirect", data.redirect);
redirect(data.redirect);
}

View File

@@ -160,6 +160,7 @@ export const DocumentsProvider: React.FC<DocumentsProviderProps> = ({
const refreshFolders = async () => {
try {
console.log("fetching folders");
const data = await documentsService.fetchFolders();
setFolders(data);
} catch (error) {

View File

@@ -1,6 +1,6 @@
"use client";
import React, { useMemo, useState, useTransition } from "react";
import React, { useEffect, useMemo, useState, useTransition } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import {
Plus,
@@ -79,9 +79,21 @@ export default function MyDocuments() {
);
const pageLimit = 10;
const searchParams = useSearchParams();
const router = useRouter();
const { popup, setPopup } = usePopup();
const [isCreateFolderOpen, setIsCreateFolderOpen] = useState(false);
useEffect(() => {
const createFolder = searchParams.get("createFolder");
if (createFolder) {
setIsCreateFolderOpen(true);
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.delete("createFolder");
router.replace(`?${newSearchParams.toString()}`);
}
}, [searchParams]);
const [isPending, startTransition] = useTransition();
const [hoveredColumn, setHoveredColumn] = useState<SortType | null>(null);

View File

@@ -599,29 +599,6 @@ export default function UserFolderContent({ folderId }: { folderId: number }) {
{/* Invalid file message */}
{/* Add a visual overlay when dragging files */}
{isDraggingOver && (
<div className="fixed inset-0 bg-neutral-950/10 backdrop-blur-sm z-50 pointer-events-none flex items-center justify-center transition-all duration-200 ease-in-out">
<div className="bg-white dark:bg-neutral-900 rounded-lg p-8 shadow-lg text-center border border-neutral-200 dark:border-neutral-800 max-w-md mx-auto">
<div className="bg-neutral-100 dark:bg-neutral-800 p-4 rounded-full w-20 h-20 mx-auto mb-5 flex items-center justify-center">
<Upload
className="w-10 h-10 text-neutral-600 dark:text-neutral-300"
strokeWidth={1.5}
/>
</div>
<h3 className="text-xl font-medium mb-2 text-neutral-900 dark:text-neutral-50">
Drop files to upload
</h3>
<p className="text-neutral-500 dark:text-neutral-400 text-sm">
Files will be uploaded to{" "}
<span className="font-medium text-neutral-900 dark:text-neutral-200">
{folderDetails?.name || "this folder"}
</span>
</p>
</div>
</div>
)}
<DeleteEntityModal
isOpen={isDeleteModalOpen}
onClose={() => setIsDeleteModalOpen(false)}
@@ -645,9 +622,9 @@ export default function UserFolderContent({ folderId }: { folderId: number }) {
<div className="flex -mt-[1px] flex-col w-full">
<div className="flex items-center mb-3">
<nav className="flex text-lg gap-x-1 items-center">
<nav className="flex text-base md:text-lg gap-x-1 items-center">
<span
className="font-medium leading-tight tracking-tight text-lg text-neutral-800 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-100 cursor-pointer flex items-center text-base"
className="font-medium leading-tight tracking-tight text-neutral-800 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-100 cursor-pointer flex items-center"
onClick={handleBack}
>
My Documents

View File

@@ -257,7 +257,7 @@ export const FileListItem: React.FC<FileListItemProps> = ({
<PopoverTrigger asChild>
<Button
variant="ghost"
className="group-hover:visible invisible h-8 w-8 p-0"
className="group-hover:visible mobile:visible invisible h-8 w-8 p-0"
>
<MoreHorizontal className="h-4 w-4" />
</Button>

View File

@@ -120,7 +120,7 @@ const DraggableItem: React.FC<{
<div className="w-6 flex items-center justify-center shrink-0">
<div
className={`${
isSelected ? "" : "opacity-0 group-hover:opacity-100"
isSelected ? "" : "desktop:opacity-0 group-hover:opacity-100"
} transition-opacity duration-150`}
onClick={(e) => {
e.stopPropagation();
@@ -215,7 +215,7 @@ const FilePickerFolderItem: React.FC<{
className={`transition-opacity duration-150 ${
isSelected || allFilesSelected
? "opacity-100"
: "opacity-0 group-hover:opacity-100"
: "desktop:opacity-0 group-hover:opacity-100"
}`}
onClick={(e) => {
e.preventDefault();
@@ -411,12 +411,6 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
}
}, [isOpen, selectedFiles, selectedFolders]);
useEffect(() => {
if (isOpen) {
refreshFolders();
}
}, [isOpen, refreshFolders]);
useEffect(() => {
if (currentFolder) {
if (currentFolder === -1) {
@@ -1103,7 +1097,7 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
}
>
<div className="h-[calc(70vh-5rem)] flex overflow-visible flex-col">
<div className="grid overflow-x-visible h-full overflow-y-hidden flex-1 w-full divide-x divide-neutral-200 dark:divide-neutral-700 grid-cols-2">
<div className="grid overflow-x-visible h-full overflow-y-hidden flex-1 w-full divide-x divide-neutral-200 dark:divide-neutral-700 desktop:grid-cols-2">
<div className="w-full h-full pb-4 overflow-hidden ">
<div className="px-6 sticky flex flex-col gap-y-2 z-[1000] top-0 mb-2 flex gap-x-2 w-full pr-4">
<div className="w-full relative">
@@ -1267,16 +1261,16 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
) : folders.length > 0 ? (
<div className="flex-grow overflow-y-auto px-4">
<p className="text-text-subtle dark:text-neutral-400">
No groups found
No folders found
</p>
</div>
) : (
<div className="flex-grow flex-col overflow-y-auto px-4 flex items-start justify-start gap-y-2">
<p className="text-sm text-muted-foreground dark:text-neutral-400">
No groups found
No folders found
</p>
<a
href="/chat/my-documents"
href="/chat/my-documents?createFolder=true"
className="inline-flex items-center text-sm justify-center text-neutral-600 dark:text-neutral-400 hover:underline"
>
<FolderIcon className="mr-2 h-4 w-4" />
@@ -1286,14 +1280,20 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
)}
</div>
<div
className={`w-full h-full flex flex-col ${
className={`mobile:hidden overflow-y-auto w-full h-full flex flex-col ${
isHoveringRight ? "bg-neutral-100 dark:bg-neutral-800/30" : ""
}`}
onDragEnter={() => setIsHoveringRight(true)}
onDragLeave={() => setIsHoveringRight(false)}
>
<div className="px-5 pb-5 flex-1 flex flex-col">
<div className="shrink default-scrollbar flex h-full overflow-y-auto mb-3">
<div className="px-5 h-full flex flex-col">
{/* Top section: scrollable, takes remaining space */}
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-neutral-800 dark:text-neutral-100">
Selected Items
</h3>
</div>
<div className="flex-1 min-h-0 overflow-y-auto">
<SelectedItemsList
uploadingFiles={uploadingFiles}
setPresentingDocument={setPresentingDocument}
@@ -1304,69 +1304,68 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
/>
</div>
<div className="flex flex-col space-y-3">
<div className="flex flex-col space-y-2">
<FileUploadSection
disabled={isUploadingFile || isCreatingFileFromLink}
onUpload={(files: File[]) => {
setIsUploadingFile(true);
setUploadStartTime(Date.now()); // Record start time
{/* Bottom section: fixed height, doesn't flex */}
<div className="flex-none py-2">
<FileUploadSection
disabled={isUploadingFile || isCreatingFileFromLink}
onUpload={(files: File[]) => {
setIsUploadingFile(true);
setUploadStartTime(Date.now()); // Record start time
// Add files to uploading files state
// Start the refresh interval to simulate progress
startRefreshInterval();
// Start the refresh interval to simulate progress
startRefreshInterval();
// Convert File[] to FileList for addUploadedFileToContext
const fileListArray = Array.from(files);
const fileList = new DataTransfer();
fileListArray.forEach((file) => fileList.items.add(file));
// Convert File[] to FileList for addUploadedFileToContext
const fileListArray = Array.from(files);
const fileList = new DataTransfer();
fileListArray.forEach((file) => fileList.items.add(file));
addUploadedFileToContext(fileList.files)
.then(() => refreshFolders())
.finally(() => {
setIsUploadingFile(false);
});
}}
onUrlUpload={async (url: string) => {
setIsCreatingFileFromLink(true);
setUploadStartTime(Date.now()); // Record start time
addUploadedFileToContext(fileList.files)
.then(() => refreshFolders())
.finally(() => {
setIsUploadingFile(false);
});
}}
onUrlUpload={async (url: string) => {
setIsCreatingFileFromLink(true);
setUploadStartTime(Date.now()); // Record start time
// Add URL to uploading files
setUploadingFiles((prev) => [
...prev,
{ name: url, progress: 0 },
]);
// Add URL to uploading files
setUploadingFiles((prev) => [
...prev,
{ name: url, progress: 0 },
]);
// Start the refresh interval to simulate progress
startRefreshInterval();
// Start the refresh interval to simulate progress
startRefreshInterval();
try {
const response: FileResponse[] = await createFileFromLink(
url,
-1
);
try {
const response: FileResponse[] =
await createFileFromLink(url, -1);
if (response.length > 0) {
// Extract domain from URL to help with detection
const urlObj = new URL(url);
if (response.length > 0) {
// Extract domain from URL to help with detection
const urlObj = new URL(url);
const createdFile: FileResponse = response[0];
addSelectedFile(createdFile);
// Make sure to remove the uploading file indicator when done
markFileComplete(url);
}
await refreshFolders();
} catch (e) {
console.error("Error creating file from link:", e);
// Also remove the uploading indicator on error
const createdFile: FileResponse = response[0];
addSelectedFile(createdFile);
// Make sure to remove the uploading file indicator when done
markFileComplete(url);
} finally {
setIsCreatingFileFromLink(false);
}
}}
isUploading={isUploadingFile || isCreatingFileFromLink}
/>
</div>
await refreshFolders();
} catch (e) {
console.error("Error creating file from link:", e);
// Also remove the uploading indicator on error
markFileComplete(url);
} finally {
setIsCreatingFileFromLink(false);
}
}}
isUploading={isUploadingFile || isCreatingFileFromLink}
/>
</div>
</div>
</div>

View File

@@ -33,7 +33,8 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
onRemoveFolder,
setPresentingDocument,
}) => {
const hasItems = folders.length > 0 || files.length > 0;
const hasItems =
folders.length > 0 || files.length > 0 || uploadingFiles.length > 0;
const openFile = (file: FileResponse) => {
if (file.link_url) {
window.open(file.link_url, "_blank");
@@ -47,89 +48,143 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
return (
<div className="h-full w-full flex flex-col">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-neutral-800 dark:text-neutral-100">
Selected Items
</h3>
</div>
<ScrollArea className="h-[200px] flex-grow pr-1">
<div className="space-y-2.5">
{folders.length > 0 && (
<div className="space-y-2.5">
{folders.map((folder: FolderResponse) => (
<div key={folder.id} className="group flex items-center gap-2">
<div
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-100/80 border-neutral-200 hover:bg-neutral-200/60",
"dark:bg-neutral-800/80 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150"
)}
>
<div className="flex items-center min-w-0 flex-1">
<FolderIcon className="h-5 w-5 mr-2 text-black dark:text-black shrink-0 fill-black dark:fill-black" />
<span className="text-sm font-medium truncate text-neutral-800 dark:text-neutral-100">
{truncateString(folder.name, 34)}
</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => onRemoveFolder(folder)}
className={cn(
"bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
"h-6 w-6 p-0 rounded-full shrink-0",
"hover:text-neutral-700",
"dark:text-neutral-300 dark:hover:text-neutral-100",
"dark:focus:ring-1 dark:focus:ring-neutral-500",
"dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out"
)}
aria-label={`Remove folder ${folder.name}`}
>
<X className="h-3 w-3 dark:text-neutral-200" />
</Button>
</div>
))}
</div>
)}
{files.length > 0 && (
<div className="space-y-2.5 ">
{files.map((file: FileResponse) => (
<div className="space-y-2.5 pb-2">
{folders.length > 0 && (
<div className="space-y-2.5">
{folders.map((folder: FolderResponse) => (
<div key={folder.id} className="group flex items-center gap-2">
<div
key={file.id}
className="group w-full flex items-center gap-2"
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-100/80 border-neutral-200 hover:bg-neutral-200/60",
"dark:bg-neutral-800/80 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150"
)}
>
<div
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-50 border-neutral-200 hover:bg-neutral-100",
"dark:bg-neutral-800/70 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150",
"cursor-pointer"
)}
onClick={() => openFile(file)}
>
<div className="flex items-center min-w-0 flex-1">
{getFileIconFromFileNameAndLink(file.name, file.link_url)}
<span className="text-sm truncate text-neutral-700 dark:text-neutral-200 ml-2.5">
{truncateString(file.name, 34)}
<div className="flex items-center min-w-0 flex-1">
<FolderIcon className="h-5 w-5 mr-2 text-black dark:text-black shrink-0 fill-black dark:fill-black" />
<span className="text-sm font-medium truncate text-neutral-800 dark:text-neutral-100">
{truncateString(folder.name, 34)}
</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => onRemoveFolder(folder)}
className={cn(
"bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
"h-6 w-6 p-0 rounded-full shrink-0",
"hover:text-neutral-700",
"dark:text-neutral-300 dark:hover:text-neutral-100",
"dark:focus:ring-1 dark:focus:ring-neutral-500",
"dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out"
)}
aria-label={`Remove folder ${folder.name}`}
>
<X className="h-3 w-3 dark:text-neutral-200" />
</Button>
</div>
))}
</div>
)}
{files.length > 0 && (
<div className="space-y-2.5 ">
{files.map((file: FileResponse) => (
<div
key={file.id}
className="group w-full flex items-center gap-2"
>
<div
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-50 border-neutral-200 hover:bg-neutral-100",
"dark:bg-neutral-800/70 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150",
"cursor-pointer"
)}
onClick={() => openFile(file)}
>
<div className="flex items-center min-w-0 flex-1">
{getFileIconFromFileNameAndLink(file.name, file.link_url)}
<span className="text-sm truncate text-neutral-700 dark:text-neutral-200 ml-2.5">
{truncateString(file.name, 34)}
</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => onRemoveFile(file)}
className={cn(
"bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
"h-6 w-6 p-0 rounded-full shrink-0",
"hover:text-neutral-700",
"dark:text-neutral-300 dark:hover:text-neutral-100",
"dark:focus:ring-1 dark:focus:ring-neutral-500",
"dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out"
)}
aria-label={`Remove file ${file.name}`}
>
<X className="h-3 w-3 dark:text-neutral-200" />
</Button>
</div>
))}
</div>
)}
<div className="max-w-full space-y-2.5">
{uploadingFiles
.filter(
(uploadingFile) =>
!files.map((file) => file.name).includes(uploadingFile.name)
)
.map((uploadingFile, index) => (
<div key={index} className="mr-8 flex items-center gap-2">
<div
key={`uploading-${index}`}
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-50 border-neutral-200 hover:bg-neutral-100",
"dark:bg-neutral-800/70 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150",
"cursor-pointer"
)}
>
<div className="flex items-center min-w-0 flex-1">
<div className="flex items-center gap-2 min-w-0">
{uploadingFile.name.startsWith("http") ? (
<Loader2 className="w-4 h-4 animate-spin text-blue-500" />
) : (
<CircularProgress
progress={uploadingFile.progress}
size={18}
showPercentage={false}
/>
)}
<span className="truncate text-sm text-text-dark dark:text-text-dark">
{uploadingFile.name.startsWith("http")
? `${uploadingFile.name.substring(0, 30)}${
uploadingFile.name.length > 30 ? "..." : ""
}`
: truncateString(uploadingFile.name, 34)}
</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => onRemoveFile(file)}
// onClick={() => onRemoveFile(file)}
className={cn(
"bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
"h-6 w-6 p-0 rounded-full shrink-0",
@@ -139,82 +194,20 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
"dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out"
)}
aria-label={`Remove file ${file.name}`}
// aria-label={`Remove file ${file.name}`}
>
<X className="h-3 w-3 dark:text-neutral-200" />
</Button>
</div>
))}
</div>
)}
<div className="max-w-full space-y-2.5">
{uploadingFiles
.filter(
(uploadingFile) =>
!files.map((file) => file.name).includes(uploadingFile.name)
)
.map((uploadingFile, index) => (
<div key={index} className="mr-8 flex items-center gap-2">
<div
key={`uploading-${index}`}
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-50 border-neutral-200 hover:bg-neutral-100",
"dark:bg-neutral-800/70 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150",
"cursor-pointer"
)}
>
<div className="flex items-center min-w-0 flex-1">
<div className="flex items-center gap-2 min-w-0">
{uploadingFile.name.startsWith("http") ? (
<Loader2 className="w-4 h-4 animate-spin text-blue-500" />
) : (
<CircularProgress
progress={uploadingFile.progress}
size={18}
showPercentage={false}
/>
)}
<span className="truncate text-sm text-text-dark dark:text-text-dark">
{uploadingFile.name.startsWith("http")
? `${uploadingFile.name.substring(0, 30)}${
uploadingFile.name.length > 30 ? "..." : ""
}`
: truncateString(uploadingFile.name, 34)}
</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
// onClick={() => onRemoveFile(file)}
className={cn(
"bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
"h-6 w-6 p-0 rounded-full shrink-0",
"hover:text-neutral-700",
"dark:text-neutral-300 dark:hover:text-neutral-100",
"dark:focus:ring-1 dark:focus:ring-neutral-500",
"dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out"
)}
// aria-label={`Remove file ${file.name}`}
>
<X className="h-3 w-3 dark:text-neutral-200" />
</Button>
</div>
</div>
))}
</div>
{!hasItems && (
<div className="flex items-center justify-center h-24 text-sm text-neutral-500 dark:text-neutral-400 italic bg-neutral-50/50 dark:bg-neutral-800/30 rounded-md border border-neutral-200/50 dark:border-neutral-700/50">
No items selected
</div>
)}
</div>
))}
</div>
</ScrollArea>
{!hasItems && (
<div className="flex items-center justify-center h-24 text-sm text-neutral-500 dark:text-neutral-400 italic bg-neutral-50/50 dark:bg-neutral-800/30 rounded-md border border-neutral-200/50 dark:border-neutral-700/50">
No items selected
</div>
)}
</div>
</div>
);
};

View File

@@ -101,7 +101,7 @@ export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
<PopoverTrigger asChild>
<Button
variant="ghost"
className={`group-hover:visible invisible h-8 w-8 p-0 ${
className={`group-hover:visible mobile:visible invisible h-8 w-8 p-0 ${
folder.id === -1 ? "!invisible pointer-events-none" : ""
}`}
>
@@ -110,14 +110,6 @@ export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
</PopoverTrigger>
<PopoverContent className="!p-0 w-40">
<div className="space-y-0">
{/* <Button variant="menu" onClick={onMove}>
<FiArrowDown className="h-4 w-4" />
Move
</Button>
<Button variant="menu" onClick={onRename}>
<FiEdit className="h-4 w-4" />
Rename
</Button> */}
<Button variant="menu" onClick={handleDeleteClick}>
<FiTrash className="h-4 w-4" />
Delete

View File

@@ -74,7 +74,6 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
return (
<div>
<Separator />
{isAdmin && (
<>
<BooleanFormField

View File

@@ -24,6 +24,7 @@ interface ModalProps {
removeBottomPadding?: boolean;
removePadding?: boolean;
increasedPadding?: boolean;
hideOverflow?: boolean;
}
export function Modal({
@@ -43,6 +44,7 @@ export function Modal({
removeBottomPadding,
removePadding,
increasedPadding,
hideOverflow,
}: ModalProps) {
const modalRef = useRef<HTMLDivElement>(null);
const [isMounted, setIsMounted] = useState(false);
@@ -92,7 +94,7 @@ export function Modal({
flex
flex-col
${heightOverride ? `h-${heightOverride}` : "max-h-[90vh]"}
overflow-auto
${hideOverflow ? "overflow-hidden" : "overflow-auto"}
`}
>
{onOutsideClick && !hideCloseButton && (

View File

@@ -27,9 +27,9 @@ export function TokenDisplay({
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center space-x-3 bg-neutral-100 dark:bg-neutral-800 rounded-full px-4 py-1.5">
<div className="relative w-36 h-2 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden">
<div className="hidden sm:block relative w-24 h-2 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden">
<div
className={`absolute top-0 left-0 h-full rounded-full ${
className={` absolute top-0 left-0 h-full rounded-full ${
tokenPercentage >= 100
? "bg-yellow-500 dark:bg-yellow-600"
: "bg-green-500 dark:bg-green-600"

View File

@@ -16,7 +16,6 @@ interface TextViewProps {
presentingDocument: MinimalOnyxDocument;
onClose: () => void;
}
export default function TextView({
presentingDocument,
onClose,
@@ -27,6 +26,13 @@ export default function TextView({
const [fileName, setFileName] = useState("");
const [isLoading, setIsLoading] = useState(true);
const [fileType, setFileType] = useState("application/octet-stream");
const [renderCount, setRenderCount] = useState(0);
// Log render count on each render
useEffect(() => {
setRenderCount((prevCount) => prevCount + 1);
console.log(`TextView component rendered ${renderCount + 1} times`);
}, []);
// Detect if a given MIME type is one of the recognized markdown formats
const isMarkdownFormat = (mimeType: string): boolean => {
@@ -63,6 +69,7 @@ export default function TextView({
};
const fetchFile = useCallback(async () => {
console.log("fetching file");
setIsLoading(true);
const fileId =
presentingDocument.document_id.split("__")[1] ||
@@ -107,13 +114,14 @@ export default function TextView({
// Keep the slight delay for a smoother loading experience
setTimeout(() => {
setIsLoading(false);
console.log("finished loading");
}, 1000);
}
}, [presentingDocument]);
useEffect(() => {
fetchFile();
}, [fetchFile]);
}, []);
const handleDownload = () => {
const link = document.createElement("a");

View File

@@ -45,7 +45,7 @@ export default function CreateEntityModal({
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{trigger}</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogContent className="max-w-[95%] sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader>

View File

@@ -174,7 +174,6 @@ export const CustomTooltip = ({
: {}
}
>
lll
{content}
</div>
</div>,

View File

@@ -113,7 +113,24 @@ export async function fetchChatData(searchParams: {
? `${fullUrl}?${searchParamsString}`
: fullUrl;
if (!NEXT_PUBLIC_ENABLE_CHROME_EXTENSION) {
// Check the referrer to prevent redirect loops
const referrer = headersList.get("referer") || "";
const isComingFromLogin = referrer.includes("/auth/login");
// Also check for the from=login query parameter
const isRedirectedFromLogin = searchParams["from"] === "login";
console.log(
`Auth check: authDisabled=${authDisabled}, user=${!!user}, referrer=${referrer}, fromLogin=${isRedirectedFromLogin}`
);
// Only redirect if we're not already coming from the login page
if (
!NEXT_PUBLIC_ENABLE_CHROME_EXTENSION &&
!isComingFromLogin &&
!isRedirectedFromLogin
) {
console.log("Redirecting to login from chat page");
return {
redirect: `/auth/login?next=${encodeURIComponent(redirectUrl)}`,
};

View File

@@ -131,12 +131,10 @@ export async function renameItem(
}
export async function downloadItem(documentId: string): Promise<Blob> {
const response = await fetch(
`/api/chat/file/${encodeURIComponent(documentId)}`,
{
method: "GET",
}
);
const fileId = documentId.split("__")[1] || documentId;
const response = await fetch(`/api/chat/file/${encodeURIComponent(fileId)}`, {
method: "GET",
});
if (!response.ok) {
throw new Error("Failed to fetch file");
}