feat: organize project conversations and add coding teacher side chat

This commit is contained in:
brother7 committed 2026-09-22 12:30:26 +08:00
1 parent 2f82b9f79c
commit 9242a40d87
45 files changed
+2987 -1137

No files matched your search

+8 -21
View File
@@ -1,3 +1,4 @@
import { CodingProjectConversations } from '@/pages/Chat/CodingProjectConversations';
import { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
@@ -21,7 +22,6 @@ import { Button } from '@/components/ui/button';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { DisclosureContent } from '@/components/ui/disclosure';
import { getAgentAvatarSrc } from '@/lib/agent-avatars';
import { getAiHardwareOverview, type AiHardwareOverview } from '@/lib/ai-hardware';
import {
AI_HARDWARE_NAVIGATION_EVENT,
@@ -216,7 +216,6 @@ export function Sidebar({
const setActiveProject = useCodingWorkspaceStore((state) => state.setActiveProject);
const removeProject = useCodingWorkspaceStore((state) => state.removeProject);
const createProject = useCodingWorkspaceStore((state) => state.createProject);
const projectConfigs = useProjectConfigStore((state) => state.configsByProjectId);
const loadProjectConfig = useProjectConfigStore((state) => state.load);
const removeProjectConfig = useProjectConfigStore((state) => state.remove);
const authUser = useAuthStore((state) => state.user);
@@ -251,6 +250,7 @@ export function Sidebar({
const accountButtonRef = useRef<HTMLButtonElement | null>(null);
const accountMenuRef = useRef<HTMLDivElement | null>(null);
const tokenPointRequestIdRef = useRef(0);
const projectEntryIntentRef = useRef(0);
const resetCardRequestIdRef = useRef(0);
const navigate = useNavigate();
const location = useLocation();
@@ -523,13 +523,15 @@ export function Sidebar({
}, []);
const enterProject = useCallback(async (project: CodingProjectSummary) => {
const intent = ++projectEntryIntentRef.current;
const result = await loadProjectConfig(project.id);
if (intent !== projectEntryIntentRef.current) return;
if (result.status !== 'valid' || !result.config) {
showProjectEntryError(project, result.status === 'missing' ? '项目缺少必要的配置文件' : result.error ?? '项目配置无效');
return;
}
await setActiveProject(project.id);
navigate('/chat');
if (intent === projectEntryIntentRef.current) navigate('/chat');
}, [loadProjectConfig, navigate, setActiveProject, showProjectEntryError]);
const readAndEnterProject = useCallback(async (project: CodingProjectSummary) => {
@@ -740,12 +742,10 @@ export function Sidebar({
) : null}
{visibleProjects.map((project) => {
const projectConfig = projectConfigs[project.id];
const active = activeProject?.id === project.id;
return (
<button
<div key={project.id}><button
type="button"
key={project.id}
data-testid={`sidebar-course-project-${project.id}`}
aria-label={`进入项目 ${project.name}`}
aria-current={active ? 'page' : undefined}
@@ -758,23 +758,10 @@ export function Sidebar({
)}
>
<span className="min-w-0 flex-1 truncate text-sm font-semibold">{project.name}</span>
<div className="flex shrink-0 -space-x-1.5" aria-label="项目智能体">
{projectConfig?.agents.map((agent) => (
<span
key={agent.id}
title={agent.name.trim() || agent.roleName}
className="h-5 w-5 overflow-hidden rounded-full border border-background bg-white"
>
<img
src={getAgentAvatarSrc(agent.avatarId, agent.avatarDataUrl)}
alt=""
className="h-full w-full object-cover [image-rendering:pixelated]"
/>
</span>
))}
</div>
<ChevronRight aria-hidden="true" className="h-3.5 w-3.5 shrink-0 -translate-x-0.5 text-muted-foreground opacity-50 transition-[opacity,transform,color] duration-200 ease-out group-hover/project:translate-x-0 group-hover/project:text-foreground group-hover/project:opacity-100 group-focus-visible/project:translate-x-0 group-focus-visible/project:text-foreground group-focus-visible/project:opacity-100" />
</button>
{active && <CodingProjectConversations key={project.id} projectId={project.id} />}
</div>
);
})}