Spaces:
Sleeping
Sleeping
Update web/src/components/LeftSidebar.tsx
Browse files
web/src/components/LeftSidebar.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import React, { useMemo, useState } from "react";
|
| 2 |
import { Button } from "./ui/button";
|
| 3 |
import { Input } from "./ui/input";
|
|
@@ -5,7 +6,15 @@ import { Separator } from "./ui/separator";
|
|
| 5 |
import { Bookmark, Trash2, Edit2, Check, X as XIcon } from "lucide-react";
|
| 6 |
|
| 7 |
import type { SavedChat, Workspace } from "../App";
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
type Props = {
|
| 11 |
savedChats: SavedChat[];
|
|
@@ -19,8 +28,8 @@ type Props = {
|
|
| 19 |
// ✅ 当前选中的课程(My Space 用它)
|
| 20 |
selectedCourse: string;
|
| 21 |
|
| 22 |
-
// ✅
|
| 23 |
-
availableCourses:
|
| 24 |
};
|
| 25 |
|
| 26 |
function formatSub(ts: any) {
|
|
@@ -53,36 +62,34 @@ export function LeftSidebar({
|
|
| 53 |
const [editingId, setEditingId] = useState<string | null>(null);
|
| 54 |
const [draftTitle, setDraftTitle] = useState("");
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
// ✅ individual(My Space): 用 selectedCourse 去 availableCourses 找
|
| 61 |
-
const courseInfo: CourseDirectoryItem | null = useMemo(() => {
|
| 62 |
-
const wsCourse = (currentWorkspace as any)?.courseInfo as
|
| 63 |
-
| { id?: string; name?: string; instructor?: any; teachingAssistant?: any }
|
| 64 |
-
| undefined;
|
| 65 |
-
|
| 66 |
-
// 1) workspace 自带 courseInfo(group 课 workspace)
|
| 67 |
-
const wsId = wsCourse?.id?.trim();
|
| 68 |
-
if (wsId) {
|
| 69 |
-
const hit = availableCourses.find((c) => c.id === wsId);
|
| 70 |
-
return hit ?? (wsCourse as any);
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
const wsName = wsCourse?.name?.trim();
|
| 74 |
-
if (wsName) {
|
| 75 |
-
const hit = availableCourses.find((c) => c.name === wsName);
|
| 76 |
-
return hit ?? (wsCourse as any);
|
| 77 |
-
}
|
| 78 |
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
const selId = (selectedCourse || "").trim();
|
| 81 |
if (selId) {
|
| 82 |
-
const hit = availableCourses.find((c) => c.id === selId);
|
| 83 |
if (hit) return hit;
|
| 84 |
}
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
return null;
|
| 87 |
}, [availableCourses, currentWorkspace, selectedCourse]);
|
| 88 |
|
|
@@ -118,7 +125,7 @@ export function LeftSidebar({
|
|
| 118 |
return (
|
| 119 |
<div className="h-full w-full flex flex-col min-h-0">
|
| 120 |
{/* ================= Course Info(不滚动) ================= */}
|
| 121 |
-
{courseInfo
|
| 122 |
<div className="px-4 pt-4 pb-3 flex-shrink-0 space-y-2">
|
| 123 |
<div className="font-semibold text-base">{courseInfo.name}</div>
|
| 124 |
|
|
@@ -162,7 +169,7 @@ export function LeftSidebar({
|
|
| 162 |
)}
|
| 163 |
</div>
|
| 164 |
</div>
|
| 165 |
-
)}
|
| 166 |
|
| 167 |
{/* ✅ Info / Saved Chat 分割线:固定 #ECECF1 */}
|
| 168 |
<Separator className="flex-shrink-0 bg-[#ECECF1]" />
|
|
|
|
| 1 |
+
// web/src/components/LeftSidebar.tsx
|
| 2 |
import React, { useMemo, useState } from "react";
|
| 3 |
import { Button } from "./ui/button";
|
| 4 |
import { Input } from "./ui/input";
|
|
|
|
| 6 |
import { Bookmark, Trash2, Edit2, Check, X as XIcon } from "lucide-react";
|
| 7 |
|
| 8 |
import type { SavedChat, Workspace } from "../App";
|
| 9 |
+
|
| 10 |
+
// 你项目里可能有 courseDirectory,也可能直接从 App 传 availableCourses(CourseInfo[])。
|
| 11 |
+
// 所以这里不强依赖 CourseDirectoryItem 结构,做宽松兼容。
|
| 12 |
+
type AnyCourse = {
|
| 13 |
+
id: string;
|
| 14 |
+
name: string;
|
| 15 |
+
instructor?: { name: string; email: string };
|
| 16 |
+
teachingAssistant?: { name: string; email: string };
|
| 17 |
+
};
|
| 18 |
|
| 19 |
type Props = {
|
| 20 |
savedChats: SavedChat[];
|
|
|
|
| 28 |
// ✅ 当前选中的课程(My Space 用它)
|
| 29 |
selectedCourse: string;
|
| 30 |
|
| 31 |
+
// ✅ 允许两种来源:CourseInfo[] 或 CourseDirectoryItem[]
|
| 32 |
+
availableCourses: AnyCourse[];
|
| 33 |
};
|
| 34 |
|
| 35 |
function formatSub(ts: any) {
|
|
|
|
| 62 |
const [editingId, setEditingId] = useState<string | null>(null);
|
| 63 |
const [draftTitle, setDraftTitle] = useState("");
|
| 64 |
|
| 65 |
+
const currentWorkspace = useMemo(
|
| 66 |
+
() => workspaces.find((w) => w.id === currentWorkspaceId),
|
| 67 |
+
[workspaces, currentWorkspaceId]
|
| 68 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
+
/**
|
| 71 |
+
* ✅ 最关键:courseInfo 一定要能在 My Space 下拿到
|
| 72 |
+
* 优先使用 selectedCourse(你 App 里 currentCourseId),这才是 My Space 的 source of truth
|
| 73 |
+
* group workspace 再用 workspace.courseInfo 兜底
|
| 74 |
+
*/
|
| 75 |
+
const courseInfo: AnyCourse | null = useMemo(() => {
|
| 76 |
const selId = (selectedCourse || "").trim();
|
| 77 |
if (selId) {
|
| 78 |
+
const hit = availableCourses.find((c) => String(c.id) === selId);
|
| 79 |
if (hit) return hit;
|
| 80 |
}
|
| 81 |
|
| 82 |
+
const wsCourse = (currentWorkspace as any)?.courseInfo as AnyCourse | undefined;
|
| 83 |
+
if (wsCourse?.id) {
|
| 84 |
+
const hit = availableCourses.find((c) => String(c.id) === String(wsCourse.id));
|
| 85 |
+
return hit ?? wsCourse;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
if (wsCourse?.name) {
|
| 89 |
+
const hit = availableCourses.find((c) => c.name === wsCourse.name);
|
| 90 |
+
return hit ?? wsCourse;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
return null;
|
| 94 |
}, [availableCourses, currentWorkspace, selectedCourse]);
|
| 95 |
|
|
|
|
| 125 |
return (
|
| 126 |
<div className="h-full w-full flex flex-col min-h-0">
|
| 127 |
{/* ================= Course Info(不滚动) ================= */}
|
| 128 |
+
{courseInfo ? (
|
| 129 |
<div className="px-4 pt-4 pb-3 flex-shrink-0 space-y-2">
|
| 130 |
<div className="font-semibold text-base">{courseInfo.name}</div>
|
| 131 |
|
|
|
|
| 169 |
)}
|
| 170 |
</div>
|
| 171 |
</div>
|
| 172 |
+
) : null}
|
| 173 |
|
| 174 |
{/* ✅ Info / Saved Chat 分割线:固定 #ECECF1 */}
|
| 175 |
<Separator className="flex-shrink-0 bg-[#ECECF1]" />
|