fix(Nextjs): Header import issue in setting & document preview page
This commit is contained in:
parent
339a378e7f
commit
d9a46fdff3
3 changed files with 111 additions and 74 deletions
|
|
@ -1,13 +1,13 @@
|
|||
/**
|
||||
* DocumentPreviewPage Component
|
||||
*
|
||||
*
|
||||
* A component that displays and manages document previews for presentation generation.
|
||||
* Features:
|
||||
* - Document content preview with markdown support
|
||||
* - Sidebar navigation for documents
|
||||
* - Document content editing and saving
|
||||
* - Presentation generation workflow
|
||||
*
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ import MarkdownRenderer from "./MarkdownRenderer";
|
|||
import { getIconFromFile } from "../../utils/others";
|
||||
import { ChevronRight, PanelRightOpen, X } from "lucide-react";
|
||||
import ToolTip from "@/components/ToolTip";
|
||||
import Header from "@/app/dashboard/components/Header";
|
||||
import Header from "@/components/Header";
|
||||
|
||||
// Types
|
||||
interface LoadingState {
|
||||
|
|
@ -60,7 +60,9 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
// Local state
|
||||
const [textContents, setTextContents] = useState<TextContents>({});
|
||||
const [selectedDocument, setSelectedDocument] = useState<string | null>(null);
|
||||
const [downloadingDocuments, setDownloadingDocuments] = useState<string[]>([]);
|
||||
const [downloadingDocuments, setDownloadingDocuments] = useState<string[]>(
|
||||
[]
|
||||
);
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const [showLoading, setShowLoading] = useState<LoadingState>({
|
||||
message: "",
|
||||
|
|
@ -72,17 +74,19 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
// Memoized computed values
|
||||
const fileItems: FileItem[] = useMemo(() => {
|
||||
if (!files || !Array.isArray(files) || files.length === 0) return [];
|
||||
return files.flat().filter((item: any) => item && item.name && item.file_path);
|
||||
return files
|
||||
.flat()
|
||||
.filter((item: any) => item && item.name && item.file_path);
|
||||
}, [files]);
|
||||
|
||||
const documentKeys = useMemo(() => {
|
||||
return fileItems.map(file => file.name);
|
||||
return fileItems.map((file) => file.name);
|
||||
}, [fileItems]);
|
||||
|
||||
const updateSelectedDocument = (value: string) => {
|
||||
setSelectedDocument(value);
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.value = textContents[value] || '';
|
||||
textareaRef.current.value = textContents[value] || "";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -92,7 +96,7 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
body: JSON.stringify({ filePath }),
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
};
|
||||
|
||||
const maintainDocumentTexts = async () => {
|
||||
const newDocuments: string[] = [];
|
||||
|
|
@ -102,7 +106,7 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
documentKeys.forEach((key: string) => {
|
||||
if (!(key in textContents)) {
|
||||
newDocuments.push(key);
|
||||
const fileItem = fileItems.find(item => item.name === key);
|
||||
const fileItem = fileItems.find((item) => item.name === key);
|
||||
if (fileItem) {
|
||||
promises.push(readFile(fileItem.file_path));
|
||||
}
|
||||
|
|
@ -113,7 +117,7 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
setDownloadingDocuments(newDocuments);
|
||||
try {
|
||||
const results = await Promise.all(promises);
|
||||
setTextContents(prev => {
|
||||
setTextContents((prev) => {
|
||||
const newContents = { ...prev };
|
||||
newDocuments.forEach((key, index) => {
|
||||
newContents[key] = results[index].content || "";
|
||||
|
|
@ -121,7 +125,7 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
return newContents;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error reading files:', error);
|
||||
console.error("Error reading files:", error);
|
||||
toast.error("Failed to read document content");
|
||||
}
|
||||
setDownloadingDocuments([]);
|
||||
|
|
@ -130,7 +134,6 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
|
||||
const handleCreatePresentation = async () => {
|
||||
try {
|
||||
|
||||
setShowLoading({
|
||||
message: "Generating presentation outline...",
|
||||
show: true,
|
||||
|
|
@ -138,20 +141,23 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
progress: true,
|
||||
});
|
||||
|
||||
const documentPaths = fileItems.map((fileItem: FileItem) => fileItem.file_path);
|
||||
const createResponse = await PresentationGenerationApi.createPresentation({
|
||||
prompt: config?.prompt ?? "",
|
||||
n_slides: config?.slides ? parseInt(config.slides) : null,
|
||||
file_paths: documentPaths,
|
||||
language: config?.language ?? "",
|
||||
|
||||
});
|
||||
const documentPaths = fileItems.map(
|
||||
(fileItem: FileItem) => fileItem.file_path
|
||||
);
|
||||
const createResponse = await PresentationGenerationApi.createPresentation(
|
||||
{
|
||||
prompt: config?.prompt ?? "",
|
||||
n_slides: config?.slides ? parseInt(config.slides) : null,
|
||||
file_paths: documentPaths,
|
||||
language: config?.language ?? "",
|
||||
}
|
||||
);
|
||||
|
||||
dispatch(setPresentationId(createResponse.id));
|
||||
router.push("/outline");
|
||||
} catch (error: any) {
|
||||
console.error("Error in radar presentation creation:", error);
|
||||
toast.error('Error', {
|
||||
toast.error("Error", {
|
||||
description: error.message || "Error in radar presentation creation.",
|
||||
});
|
||||
setShowLoading({
|
||||
|
|
@ -194,7 +200,9 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
{downloadingDocuments.includes(selectedDocument) ? (
|
||||
<Skeleton className="w-full h-full" />
|
||||
) : (
|
||||
<MarkdownRenderer content={textContents[selectedDocument] || ""} />
|
||||
<MarkdownRenderer
|
||||
content={textContents[selectedDocument] || ""}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -206,8 +214,10 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className={`border-r border-gray-200 fixed xl:relative w-full z-50 xl:z-auto
|
||||
transition-all duration-300 ease-in-out max-w-[200px] md:max-w-[300px] h-[85vh] rounded-md p-5`}>
|
||||
<div
|
||||
className={`border-r border-gray-200 fixed xl:relative w-full z-50 xl:z-auto
|
||||
transition-all duration-300 ease-in-out max-w-[200px] md:max-w-[300px] h-[85vh] rounded-md p-5`}
|
||||
>
|
||||
<X
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="text-black mb-4 ml-auto mr-0 cursor-pointer hover:text-gray-600"
|
||||
|
|
@ -222,8 +232,9 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
<div
|
||||
key={key}
|
||||
onClick={() => updateSelectedDocument(key)}
|
||||
className={`${selectedDocument === key ? 'border border-blue-500' : ""
|
||||
} flex p-2 rounded-sm gap-2 items-center cursor-pointer`}
|
||||
className={`${
|
||||
selectedDocument === key ? "border border-blue-500" : ""
|
||||
} flex p-2 rounded-sm gap-2 items-center cursor-pointer`}
|
||||
>
|
||||
<img
|
||||
className="h-6 w-6 border border-gray-200"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
import Header from '@/app/dashboard/components/Header'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import React from 'react'
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import React from "react";
|
||||
|
||||
const loading = () => {
|
||||
return (
|
||||
<div>
|
||||
<Skeleton className="h-20 w-full mx-auto" />
|
||||
<div className=' flex gap-14 pb-10 h-screen py-6'>
|
||||
<Skeleton className="h-full w-[30%] mx-auto" />
|
||||
<Skeleton className="h-full w-[70%] " />
|
||||
return (
|
||||
<div>
|
||||
<Skeleton className="h-20 w-full mx-auto" />
|
||||
<div className=" flex gap-14 pb-10 h-screen py-6">
|
||||
<Skeleton className="h-full w-[30%] mx-auto" />
|
||||
<Skeleton className="h-full w-[70%] " />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default loading
|
||||
export default loading;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Header from "../dashboard/components/Header";
|
||||
import { Loader2, Download, CheckCircle } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { RootState } from "@/store/store";
|
||||
|
|
@ -9,10 +8,11 @@ import { handleSaveLLMConfig } from "@/utils/storeHelpers";
|
|||
import {
|
||||
checkIfSelectedOllamaModelIsPulled,
|
||||
pullOllamaModel,
|
||||
LLMConfig
|
||||
LLMConfig,
|
||||
} from "@/utils/providerUtils";
|
||||
import { useRouter } from "next/navigation";
|
||||
import LLMProviderSelection from "@/components/LLMSelection";
|
||||
import Header from "@/components/Header";
|
||||
|
||||
// Button state interface
|
||||
interface ButtonState {
|
||||
|
|
@ -27,14 +27,16 @@ interface ButtonState {
|
|||
const SettingsPage = () => {
|
||||
const router = useRouter();
|
||||
const userConfigState = useSelector((state: RootState) => state.userConfig);
|
||||
const [llmConfig, setLlmConfig] = useState<LLMConfig>(userConfigState.llm_config);
|
||||
const [llmConfig, setLlmConfig] = useState<LLMConfig>(
|
||||
userConfigState.llm_config
|
||||
);
|
||||
const canChangeKeys = userConfigState.can_change_keys;
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [buttonState, setButtonState] = useState<ButtonState>({
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
text: "Save Configuration",
|
||||
showProgress: false
|
||||
showProgress: false,
|
||||
});
|
||||
|
||||
const [downloadingModel, setDownloadingModel] = useState<{
|
||||
|
|
@ -47,8 +49,14 @@ const SettingsPage = () => {
|
|||
const [showDownloadModal, setShowDownloadModal] = useState<boolean>(false);
|
||||
|
||||
const downloadProgress = React.useMemo(() => {
|
||||
if (downloadingModel && downloadingModel.downloaded !== null && downloadingModel.size !== null) {
|
||||
return Math.round((downloadingModel.downloaded / downloadingModel.size) * 100);
|
||||
if (
|
||||
downloadingModel &&
|
||||
downloadingModel.downloaded !== null &&
|
||||
downloadingModel.size !== null
|
||||
) {
|
||||
return Math.round(
|
||||
(downloadingModel.downloaded / downloadingModel.size) * 100
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}, [downloadingModel?.downloaded, downloadingModel?.size]);
|
||||
|
|
@ -56,17 +64,19 @@ const SettingsPage = () => {
|
|||
const handleSaveConfig = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setButtonState(prev => ({
|
||||
setButtonState((prev) => ({
|
||||
...prev,
|
||||
isLoading: true,
|
||||
isDisabled: true,
|
||||
text: "Saving Configuration..."
|
||||
text: "Saving Configuration...",
|
||||
}));
|
||||
|
||||
await handleSaveLLMConfig(llmConfig);
|
||||
|
||||
if (llmConfig.LLM === "ollama" && llmConfig.OLLAMA_MODEL) {
|
||||
const isPulled = await checkIfSelectedOllamaModelIsPulled(llmConfig.OLLAMA_MODEL);
|
||||
const isPulled = await checkIfSelectedOllamaModelIsPulled(
|
||||
llmConfig.OLLAMA_MODEL
|
||||
);
|
||||
if (!isPulled) {
|
||||
setShowDownloadModal(true);
|
||||
await handleModelDownload();
|
||||
|
|
@ -75,26 +85,24 @@ const SettingsPage = () => {
|
|||
|
||||
toast.info("Configuration saved successfully");
|
||||
setIsLoading(false);
|
||||
setButtonState(prev => ({
|
||||
setButtonState((prev) => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
text: "Save Configuration"
|
||||
text: "Save Configuration",
|
||||
}));
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
toast.info(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to save configuration"
|
||||
error instanceof Error ? error.message : "Failed to save configuration"
|
||||
);
|
||||
setIsLoading(false);
|
||||
setButtonState(prev => ({
|
||||
setButtonState((prev) => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
isDisabled: false,
|
||||
text: "Save Configuration"
|
||||
text: "Save Configuration",
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
|
@ -110,15 +118,21 @@ const SettingsPage = () => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (downloadingModel && downloadingModel.downloaded !== null && downloadingModel.size !== null) {
|
||||
const percentage = Math.round(((downloadingModel.downloaded / downloadingModel.size) * 100));
|
||||
if (
|
||||
downloadingModel &&
|
||||
downloadingModel.downloaded !== null &&
|
||||
downloadingModel.size !== null
|
||||
) {
|
||||
const percentage = Math.round(
|
||||
(downloadingModel.downloaded / downloadingModel.size) * 100
|
||||
);
|
||||
setButtonState({
|
||||
isLoading: true,
|
||||
isDisabled: true,
|
||||
text: `Downloading Model (${percentage}%)`,
|
||||
showProgress: true,
|
||||
progressPercentage: percentage,
|
||||
status: downloadingModel.status
|
||||
status: downloadingModel.status,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -162,10 +176,11 @@ const SettingsPage = () => {
|
|||
<button
|
||||
onClick={handleSaveConfig}
|
||||
disabled={buttonState.isDisabled}
|
||||
className={`w-full font-semibold py-3 px-4 rounded-lg transition-all duration-500 ${buttonState.isDisabled
|
||||
? "bg-gray-400 cursor-not-allowed"
|
||||
: "bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 focus:ring-4 focus:ring-blue-200"
|
||||
} text-white`}
|
||||
className={`w-full font-semibold py-3 px-4 rounded-lg transition-all duration-500 ${
|
||||
buttonState.isDisabled
|
||||
? "bg-gray-400 cursor-not-allowed"
|
||||
: "bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 focus:ring-4 focus:ring-blue-200"
|
||||
} text-white`}
|
||||
>
|
||||
{buttonState.isLoading ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
|
|
@ -196,7 +211,9 @@ const SettingsPage = () => {
|
|||
|
||||
{/* Title */}
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||
{downloadingModel.done ? "Download Complete!" : "Downloading Model"}
|
||||
{downloadingModel.done
|
||||
? "Download Complete!"
|
||||
: "Downloading Model"}
|
||||
</h3>
|
||||
|
||||
{/* Model Name */}
|
||||
|
|
@ -230,20 +247,31 @@ const SettingsPage = () => {
|
|||
)}
|
||||
|
||||
{/* Status Message */}
|
||||
{downloadingModel.status && downloadingModel.status !== "pulled" && (
|
||||
<div className="text-xs text-gray-500">
|
||||
{downloadingModel.status === "downloading" && "Downloading model files..."}
|
||||
{downloadingModel.status === "verifying" && "Verifying model integrity..."}
|
||||
{downloadingModel.status === "pulling" && "Pulling model from registry..."}
|
||||
</div>
|
||||
)}
|
||||
{downloadingModel.status &&
|
||||
downloadingModel.status !== "pulled" && (
|
||||
<div className="text-xs text-gray-500">
|
||||
{downloadingModel.status === "downloading" &&
|
||||
"Downloading model files..."}
|
||||
{downloadingModel.status === "verifying" &&
|
||||
"Verifying model integrity..."}
|
||||
{downloadingModel.status === "pulling" &&
|
||||
"Pulling model from registry..."}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Download Info */}
|
||||
{downloadingModel.downloaded && downloadingModel.size && (
|
||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg">
|
||||
<div className="flex justify-between text-xs text-gray-600">
|
||||
<span>Downloaded: {(downloadingModel.downloaded / 1024 / 1024).toFixed(1)} MB</span>
|
||||
<span>Total: {(downloadingModel.size / 1024 / 1024).toFixed(1)} MB</span>
|
||||
<span>
|
||||
Downloaded:{" "}
|
||||
{(downloadingModel.downloaded / 1024 / 1024).toFixed(1)}{" "}
|
||||
MB
|
||||
</span>
|
||||
<span>
|
||||
Total: {(downloadingModel.size / 1024 / 1024).toFixed(1)}{" "}
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue