Spaces:
Sleeping
Sleeping
Update web/src/App.tsx
Browse files- web/src/App.tsx +175 -168
web/src/App.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import { Button } from "./components/ui/button";
|
|
| 12 |
import { Toaster } from "./components/ui/sonner";
|
| 13 |
import { toast } from "sonner";
|
| 14 |
|
| 15 |
-
// ✅
|
| 16 |
import { apiChat, apiUpload, apiMemoryline } from "./lib/api";
|
| 17 |
|
| 18 |
export interface Message {
|
|
@@ -848,186 +848,193 @@ function App() {
|
|
| 848 |
if (showOnboarding && user) return <Onboarding user={user} onComplete={handleOnboardingComplete} onSkip={handleOnboardingSkip} />;
|
| 849 |
|
| 850 |
return (
|
| 851 |
-
<div className="
|
| 852 |
<Toaster />
|
| 853 |
|
| 854 |
-
{/*
|
| 855 |
-
<div className="flex-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
|
|
|
|
|
|
| 873 |
|
| 874 |
-
|
| 875 |
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
)}
|
| 882 |
-
|
| 883 |
-
{/* Main area: NO page scroll, only inner panes scroll */}
|
| 884 |
-
<div className="flex-1 min-h-0 flex overflow-hidden relative" style={{ overscrollBehavior: "none" }}>
|
| 885 |
-
{!leftPanelVisible && (
|
| 886 |
-
<Button
|
| 887 |
-
variant="secondary"
|
| 888 |
-
size="icon"
|
| 889 |
-
onClick={() => setLeftPanelVisible(true)}
|
| 890 |
-
className="hidden lg:flex absolute z-[100] h-8 w-5 shadow-lg rounded-full bg-card border border-border transition-all duration-200 ease-in-out hover:translate-x-[10px]"
|
| 891 |
-
style={{ left: "-5px", top: "1rem" }}
|
| 892 |
-
title="Open panel"
|
| 893 |
-
>
|
| 894 |
-
<ChevronRight className="h-3 w-3" />
|
| 895 |
-
</Button>
|
| 896 |
)}
|
| 897 |
|
| 898 |
-
{
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
{leftPanelVisible ? (
|
| 902 |
-
<aside className="hidden lg:flex w-80 bg-card border-r border-border flex-col min-h-0 overflow-hidden relative">
|
| 903 |
<Button
|
| 904 |
variant="secondary"
|
| 905 |
size="icon"
|
| 906 |
-
onClick={() => setLeftPanelVisible(
|
| 907 |
-
className="absolute z-[
|
| 908 |
-
style={{
|
| 909 |
-
title="
|
| 910 |
>
|
| 911 |
-
<
|
| 912 |
</Button>
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 940 |
</aside>
|
| 941 |
-
) : null}
|
| 942 |
-
|
| 943 |
-
{/* Mobile left drawer */}
|
| 944 |
-
<aside
|
| 945 |
-
className={`
|
| 946 |
-
fixed lg:hidden inset-y-0 left-0 z-50
|
| 947 |
-
w-80 bg-card border-r border-border
|
| 948 |
-
transform transition-transform duration-300 ease-in-out
|
| 949 |
-
${leftSidebarOpen ? "translate-x-0" : "-translate-x-full"}
|
| 950 |
-
flex flex-col
|
| 951 |
-
mt-16
|
| 952 |
-
h-[calc(100vh-4rem)]
|
| 953 |
-
min-h-0
|
| 954 |
-
overflow-hidden
|
| 955 |
-
`}
|
| 956 |
-
>
|
| 957 |
-
<div className="p-4 border-b border-border flex justify-between items-center flex-shrink-0">
|
| 958 |
-
<h3>Settings & Guide</h3>
|
| 959 |
-
<Button variant="ghost" size="icon" onClick={() => setLeftSidebarOpen(false)}>
|
| 960 |
-
<X className="h-5 w-5" />
|
| 961 |
-
</Button>
|
| 962 |
-
</div>
|
| 963 |
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
|
| 983 |
-
|
| 984 |
-
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
| 994 |
-
|
| 995 |
-
|
| 996 |
-
|
| 997 |
-
|
| 998 |
-
|
| 999 |
-
|
| 1000 |
-
|
| 1001 |
-
|
| 1002 |
-
|
| 1003 |
-
|
| 1004 |
-
|
| 1005 |
-
|
| 1006 |
-
|
| 1007 |
-
|
| 1008 |
-
onNextQuestion={handleNextQuestion}
|
| 1009 |
-
onStartQuiz={handleStartQuiz}
|
| 1010 |
-
quizState={quizState}
|
| 1011 |
-
isTyping={isTyping}
|
| 1012 |
-
showClearDialog={showClearDialog}
|
| 1013 |
-
onConfirmClear={(shouldSave) => {
|
| 1014 |
-
handleClearConversation(shouldSave);
|
| 1015 |
-
setShowClearDialog(false);
|
| 1016 |
-
}}
|
| 1017 |
-
onCancelClear={() => setShowClearDialog(false)}
|
| 1018 |
-
savedChats={savedChats}
|
| 1019 |
-
workspaces={workspaces}
|
| 1020 |
-
currentWorkspaceId={currentWorkspaceId}
|
| 1021 |
-
onSaveFile={(content, type, _format, targetWorkspaceId) =>
|
| 1022 |
-
handleSave(content, type, false, (_format ?? "text") as "pdf" | "text", targetWorkspaceId)
|
| 1023 |
-
}
|
| 1024 |
-
leftPanelVisible={leftPanelVisible}
|
| 1025 |
-
currentCourseId={currentCourseId}
|
| 1026 |
-
onCourseChange={setCurrentCourseId}
|
| 1027 |
-
availableCourses={availableCourses}
|
| 1028 |
-
showReviewBanner={showReviewBanner}
|
| 1029 |
-
/>
|
| 1030 |
-
</main>
|
| 1031 |
</div>
|
| 1032 |
</div>
|
| 1033 |
);
|
|
|
|
| 12 |
import { Toaster } from "./components/ui/sonner";
|
| 13 |
import { toast } from "sonner";
|
| 14 |
|
| 15 |
+
// ✅ backend API bindings
|
| 16 |
import { apiChat, apiUpload, apiMemoryline } from "./lib/api";
|
| 17 |
|
| 18 |
export interface Message {
|
|
|
|
| 848 |
if (showOnboarding && user) return <Onboarding user={user} onComplete={handleOnboardingComplete} onSkip={handleOnboardingSkip} />;
|
| 849 |
|
| 850 |
return (
|
| 851 |
+
<div className="appShell bg-background">
|
| 852 |
<Toaster />
|
| 853 |
|
| 854 |
+
{/* APP: column layout (header + main) */}
|
| 855 |
+
<div className="col flex-1 min-w-0">
|
| 856 |
+
{/* Header fixed */}
|
| 857 |
+
<div className="colFixed flex-shrink-0">
|
| 858 |
+
<Header
|
| 859 |
+
user={user}
|
| 860 |
+
onMenuClick={() => setLeftSidebarOpen(!leftSidebarOpen)}
|
| 861 |
+
onUserClick={() => {}}
|
| 862 |
+
isDarkMode={isDarkMode}
|
| 863 |
+
onToggleDarkMode={() => setIsDarkMode(!isDarkMode)}
|
| 864 |
+
language={language}
|
| 865 |
+
onLanguageChange={setLanguage}
|
| 866 |
+
workspaces={workspaces}
|
| 867 |
+
currentWorkspace={currentWorkspace}
|
| 868 |
+
onWorkspaceChange={setCurrentWorkspaceId}
|
| 869 |
+
onCreateWorkspace={handleCreateWorkspace}
|
| 870 |
+
onLogout={() => setUser(null)}
|
| 871 |
+
availableCourses={availableCourses}
|
| 872 |
+
onUserUpdate={setUser}
|
| 873 |
+
/>
|
| 874 |
+
</div>
|
| 875 |
|
| 876 |
+
{showProfileEditor && user && <ProfileEditor user={user} onSave={setUser} onClose={() => setShowProfileEditor(false)} />}
|
| 877 |
|
| 878 |
+
{/* Review banner fixed */}
|
| 879 |
+
{showReviewBanner && (
|
| 880 |
+
<div className="colFixed flex-shrink-0 w-full bg-background border-b border-border relative z-50">
|
| 881 |
+
<ReviewBanner onReview={handleReviewClick} onDismiss={handleDismissReviewBanner} />
|
| 882 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 883 |
)}
|
| 884 |
|
| 885 |
+
{/* Main area: NO page scroll, only inner panes scroll */}
|
| 886 |
+
<div className="colFlex flex min-h-0 overflow-hidden relative" style={{ overscrollBehavior: "none" }}>
|
| 887 |
+
{!leftPanelVisible && (
|
|
|
|
|
|
|
| 888 |
<Button
|
| 889 |
variant="secondary"
|
| 890 |
size="icon"
|
| 891 |
+
onClick={() => setLeftPanelVisible(true)}
|
| 892 |
+
className="hidden lg:flex absolute z-[100] h-8 w-5 shadow-lg rounded-full bg-card border border-border transition-all duration-200 ease-in-out hover:translate-x-[10px]"
|
| 893 |
+
style={{ left: "-5px", top: "1rem" }}
|
| 894 |
+
title="Open panel"
|
| 895 |
>
|
| 896 |
+
<ChevronRight className="h-3 w-3" />
|
| 897 |
</Button>
|
| 898 |
+
)}
|
| 899 |
+
|
| 900 |
+
{leftSidebarOpen && <div className="fixed inset-0 bg-black/50 z-40 lg:hidden" onClick={() => setLeftSidebarOpen(false)} />}
|
| 901 |
+
|
| 902 |
+
{/* Desktop left panel */}
|
| 903 |
+
{leftPanelVisible ? (
|
| 904 |
+
<aside className="hidden lg:flex w-80 bg-card border-r border-border col min-h-0 overflow-hidden relative">
|
| 905 |
+
<Button
|
| 906 |
+
variant="secondary"
|
| 907 |
+
size="icon"
|
| 908 |
+
onClick={() => setLeftPanelVisible(false)}
|
| 909 |
+
className="absolute z-[70] h-8 w-5 shadow-lg rounded-full bg-card border border-border"
|
| 910 |
+
style={{ right: "-10px", top: "1rem" }}
|
| 911 |
+
title="Close panel"
|
| 912 |
+
>
|
| 913 |
+
<ChevronLeft className="h-3 w-3" />
|
| 914 |
+
</Button>
|
| 915 |
+
|
| 916 |
+
{/* LeftSidebar 内部自己控制滚动(Saved list),外层绝不滚 */}
|
| 917 |
+
<LeftSidebar
|
| 918 |
+
learningMode={learningMode}
|
| 919 |
+
language={language}
|
| 920 |
+
onLearningModeChange={setLearningMode}
|
| 921 |
+
onLanguageChange={setLanguage}
|
| 922 |
+
spaceType={spaceType}
|
| 923 |
+
groupMembers={groupMembers}
|
| 924 |
+
user={user}
|
| 925 |
+
onLogin={setUser}
|
| 926 |
+
onLogout={() => setUser(null)}
|
| 927 |
+
isLoggedIn={!!user}
|
| 928 |
+
onEditProfile={() => setShowProfileEditor(true)}
|
| 929 |
+
savedItems={savedItems}
|
| 930 |
+
recentlySavedId={recentlySavedId}
|
| 931 |
+
onUnsave={handleUnsave}
|
| 932 |
+
onSave={handleSave}
|
| 933 |
+
savedChats={savedChats}
|
| 934 |
+
onLoadChat={handleLoadChat}
|
| 935 |
+
onDeleteSavedChat={handleDeleteSavedChat}
|
| 936 |
+
onRenameSavedChat={handleRenameSavedChat}
|
| 937 |
+
currentWorkspaceId={currentWorkspaceId}
|
| 938 |
+
workspaces={workspaces}
|
| 939 |
+
selectedCourse={currentCourseId}
|
| 940 |
+
availableCourses={availableCourses}
|
| 941 |
+
/>
|
| 942 |
+
</aside>
|
| 943 |
+
) : null}
|
| 944 |
+
|
| 945 |
+
{/* Mobile left drawer */}
|
| 946 |
+
<aside
|
| 947 |
+
className={`
|
| 948 |
+
fixed lg:hidden inset-y-0 left-0 z-50
|
| 949 |
+
w-80 bg-card border-r border-border
|
| 950 |
+
transform transition-transform duration-300 ease-in-out
|
| 951 |
+
${leftSidebarOpen ? "translate-x-0" : "-translate-x-full"}
|
| 952 |
+
col
|
| 953 |
+
mt-16
|
| 954 |
+
h-[calc(100vh-4rem)]
|
| 955 |
+
min-h-0
|
| 956 |
+
overflow-hidden
|
| 957 |
+
`}
|
| 958 |
+
>
|
| 959 |
+
<div className="colFixed p-4 border-b border-border flex justify-between items-center flex-shrink-0">
|
| 960 |
+
<h3>Settings & Guide</h3>
|
| 961 |
+
<Button variant="ghost" size="icon" onClick={() => setLeftSidebarOpen(false)}>
|
| 962 |
+
<X className="h-5 w-5" />
|
| 963 |
+
</Button>
|
| 964 |
+
</div>
|
| 965 |
+
|
| 966 |
+
<div className="colFlex min-h-0 overflow-hidden">
|
| 967 |
+
<LeftSidebar
|
| 968 |
+
learningMode={learningMode}
|
| 969 |
+
language={language}
|
| 970 |
+
onLearningModeChange={setLearningMode}
|
| 971 |
+
onLanguageChange={setLanguage}
|
| 972 |
+
spaceType={spaceType}
|
| 973 |
+
groupMembers={groupMembers}
|
| 974 |
+
user={user}
|
| 975 |
+
onLogin={setUser}
|
| 976 |
+
onLogout={() => setUser(null)}
|
| 977 |
+
isLoggedIn={!!user}
|
| 978 |
+
onEditProfile={() => setShowProfileEditor(true)}
|
| 979 |
+
savedItems={savedItems}
|
| 980 |
+
recentlySavedId={recentlySavedId}
|
| 981 |
+
onUnsave={handleUnsave}
|
| 982 |
+
onSave={handleSave}
|
| 983 |
+
savedChats={savedChats}
|
| 984 |
+
onLoadChat={handleLoadChat}
|
| 985 |
+
onDeleteSavedChat={handleDeleteSavedChat}
|
| 986 |
+
currentWorkspaceId={currentWorkspaceId}
|
| 987 |
+
workspaces={workspaces}
|
| 988 |
+
selectedCourse={currentCourseId}
|
| 989 |
+
availableCourses={availableCourses}
|
| 990 |
+
/>
|
| 991 |
+
</div>
|
| 992 |
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 993 |
|
| 994 |
+
{/* Chat column: outer never scroll, ChatArea controls internal scroll */}
|
| 995 |
+
<main className="flex-1 min-w-0 col min-h-0 overflow-hidden">
|
| 996 |
+
<div className="colFlex min-h-0 overflow-hidden">
|
| 997 |
+
<ChatArea
|
| 998 |
+
messages={messages}
|
| 999 |
+
onSendMessage={handleSendMessage}
|
| 1000 |
+
uploadedFiles={uploadedFiles}
|
| 1001 |
+
onFileUpload={handleFileUpload}
|
| 1002 |
+
onRemoveFile={handleRemoveFile}
|
| 1003 |
+
onFileTypeChange={handleFileTypeChange}
|
| 1004 |
+
memoryProgress={memoryProgress}
|
| 1005 |
+
isLoggedIn={!!user}
|
| 1006 |
+
learningMode={learningMode}
|
| 1007 |
+
onClearConversation={() => setShowClearDialog(true)}
|
| 1008 |
+
onSaveChat={handleSaveChat}
|
| 1009 |
+
onLearningModeChange={setLearningMode}
|
| 1010 |
+
spaceType={spaceType}
|
| 1011 |
+
chatMode={chatMode}
|
| 1012 |
+
onChatModeChange={setChatMode}
|
| 1013 |
+
onNextQuestion={handleNextQuestion}
|
| 1014 |
+
onStartQuiz={handleStartQuiz}
|
| 1015 |
+
quizState={quizState}
|
| 1016 |
+
isTyping={isTyping}
|
| 1017 |
+
showClearDialog={showClearDialog}
|
| 1018 |
+
onConfirmClear={(shouldSave) => {
|
| 1019 |
+
handleClearConversation(shouldSave);
|
| 1020 |
+
setShowClearDialog(false);
|
| 1021 |
+
}}
|
| 1022 |
+
onCancelClear={() => setShowClearDialog(false)}
|
| 1023 |
+
savedChats={savedChats}
|
| 1024 |
+
workspaces={workspaces}
|
| 1025 |
+
currentWorkspaceId={currentWorkspaceId}
|
| 1026 |
+
onSaveFile={(content, type, _format, targetWorkspaceId) =>
|
| 1027 |
+
handleSave(content, type, false, (_format ?? "text") as "pdf" | "text", targetWorkspaceId)
|
| 1028 |
+
}
|
| 1029 |
+
leftPanelVisible={leftPanelVisible}
|
| 1030 |
+
currentCourseId={currentCourseId}
|
| 1031 |
+
onCourseChange={setCurrentCourseId}
|
| 1032 |
+
availableCourses={availableCourses}
|
| 1033 |
+
showReviewBanner={showReviewBanner}
|
| 1034 |
+
/>
|
| 1035 |
+
</div>
|
| 1036 |
+
</main>
|
| 1037 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1038 |
</div>
|
| 1039 |
</div>
|
| 1040 |
);
|