Commit 906063ee authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

feat(team): add AI workforce team management hub, gradient circle avatars, and...

feat(team): add AI workforce team management hub, gradient circle avatars, and collaboration flow DAG
parent 458d41da
Pipeline #3907 failed with stage
......@@ -19,12 +19,23 @@ export interface ChannelItem {
export interface AgentMember {
id: string;
name: string;
shortName: string;
initial: string;
role: "Founder" | "PM" | "Architect" | "Coder" | "QA";
avatar: string;
title: string;
department: string;
avatarGradient: string;
avatarColor: string;
model: string;
skills: string[];
status: "online" | "thinking" | "busy";
activeTask?: string;
stats?: {
tasksCompleted: number;
testsPassed: string;
avgSpeed: string;
approvalRate: string;
};
}
export interface ModelItem {
......@@ -123,53 +134,108 @@ export const MOCK_CHANNELS: Record<string, ChannelItem[]> = {
export const MOCK_AGENTS: AgentMember[] = [
{
id: "agent-founder",
name: "Thomas Shelby (Founder)",
name: "Thomas Shelby",
shortName: "Thomas",
initial: "T",
role: "Founder",
avatar: "👑",
title: "CEO & Founder",
department: "Executive HQ",
avatarGradient: "from-amber-500 to-orange-600",
avatarColor: "#F59E0B",
model: "claude-3-7-sonnet",
skills: ["/goal", "/export-doc", "/teamwork-preview", "/approve"],
status: "online",
activeTask: "Reviewing company-wide pipeline gates",
stats: {
tasksCompleted: 48,
testsPassed: "100%",
avgSpeed: "1.1s",
approvalRate: "96%",
},
},
{
id: "agent-pm",
name: "PM Strategist Bot",
name: "Minh Quang",
shortName: "Minh",
initial: "M",
role: "PM",
avatar: "📊",
title: "Lead Product Manager",
department: "Product & Spec",
avatarGradient: "from-indigo-500 to-purple-600",
avatarColor: "#6366F1",
model: "claude-3-7-sonnet",
skills: ["/breakdown", "/spec", "/prd-generator"],
status: "online",
activeTask: "Synthesizing User Stories from Founder vision",
stats: {
tasksCompleted: 64,
testsPassed: "100%",
avgSpeed: "1.4s",
approvalRate: "94%",
},
},
{
id: "agent-architect",
name: "System Architect Bot",
name: "An Nguyen",
shortName: "An",
initial: "A",
role: "Architect",
avatar: "🏛️",
title: "Principal System Architect",
department: "System Architecture",
avatarGradient: "from-cyan-500 to-teal-600",
avatarColor: "#06B6D4",
model: "o1",
skills: ["/schema-design", "/api-contract", "/dag-planner"],
status: "online",
activeTask: "Designing idempotent webhook pipeline & schemas",
stats: {
tasksCompleted: 52,
testsPassed: "100%",
avgSpeed: "2.1s",
approvalRate: "98%",
},
},
{
id: "agent-coder",
name: "Lead Coder Bot",
name: "Đức Anh",
shortName: "Đức",
initial: "Đ",
role: "Coder",
avatar: "💻",
title: "Lead Software Engineer",
department: "Engineering",
avatarGradient: "from-emerald-500 to-green-600",
avatarColor: "#10B981",
model: "deepseek-reasoner",
skills: ["/code-gen", "/refactor", "/unit-test-builder"],
status: "online",
activeTask: "Generating test-driven implementation and pull request diffs",
stats: {
tasksCompleted: 112,
testsPassed: "100%",
avgSpeed: "1.8s",
approvalRate: "95%",
},
},
{
id: "agent-qa",
name: "QA & Security Auditor",
name: "Thu Hà",
shortName: "Hà",
initial: "H",
role: "QA",
avatar: "🛡️",
title: "Lead Security Auditor",
department: "Security & Compliance",
avatarGradient: "from-rose-500 to-pink-600",
avatarColor: "#F43F5E",
model: "claude-3-7-sonnet",
skills: ["/security-audit", "/stress-test", "/adversarial-suite"],
status: "online",
activeTask: "Conducting zero-trust gate verification",
stats: {
tasksCompleted: 86,
testsPassed: "100%",
avgSpeed: "1.3s",
approvalRate: "99%",
},
},
];
......
import { TeamManagementPage } from "../../team/team-management-page";
/** @jsxImportSource react */
import type { CSSProperties } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
......@@ -339,6 +340,17 @@ export function SessionPage(props: SessionPageProps) {
));
const sessionPanelState = useSessionPanelState(sidePanelSessionKey);
const activePanelTab = useActivePanelTab(sidePanelSessionKey);
const [teamViewOpen, setTeamViewOpen] = useState(false);
useEffect(() => {
const handleToggle = () => setTeamViewOpen((prev) => !prev);
const handleClose = () => setTeamViewOpen(false);
window.addEventListener("openwork-open-team", handleToggle);
window.addEventListener("openwork-close-team", handleClose);
return () => {
window.removeEventListener("openwork-open-team", handleToggle);
window.removeEventListener("openwork-close-team", handleClose);
};
}, []);
const [hiddenTargetRevision, setHiddenTargetRevision] = useState(0);
const [, setExtensionStateVersion] = useState(0);
const hiddenAccessibleTargetIds = useMemo(
......@@ -1287,6 +1299,14 @@ export function SessionPage(props: SessionPageProps) {
<ResizablePanelGroup orientation="vertical" className="min-h-0 flex-1 overflow-hidden">
<ResizablePanel minSize="180px" className="min-h-0">
<div className="relative h-full min-w-0 overflow-hidden bg-dls-surface mac:bg-dls-surface/85 mac:backdrop-blur-2xl mac:backdrop-saturate-150">
{teamViewOpen ? (
<div className="absolute inset-0 z-30 overflow-y-auto bg-background animate-in fade-in duration-200">
<TeamManagementPage
workspaceName={props.selectedWorkspaceName || "AI Software Studio"}
onOpenChat={() => setTeamViewOpen(false)}
/>
</div>
) : null}
{props.primarySlot ? (
<div className="h-full overflow-y-auto" data-workspace-primary-slot>
{props.primarySlot}
......
......@@ -12,6 +12,7 @@ import {
Code2,
Layers,
ShieldCheck,
Settings,
} from "lucide-react";
import { cn } from "@/lib/utils";
import type { AgentMember } from "@/app/lib/mock-data";
......@@ -156,6 +157,17 @@ export function WorkforceList({
</button>
}
/>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
window.dispatchEvent(new CustomEvent("openwork-open-team"));
}}
title="Quản lý tổ chức AI (Team Hub)"
className="ml-1 flex size-5 cursor-pointer items-center justify-center rounded text-muted-foreground/60 transition hover:bg-sidebar-accent hover:text-foreground"
>
<Settings className="size-3" />
</button>
</div>
<CollapsibleContent className="mt-1">
......@@ -163,8 +175,9 @@ export function WorkforceList({
{agents.map((agent) => {
const statusMeta = getStatusBadge(agent.status);
const isSelected = activeAgentId === agent.id;
const iconMeta = getAgentIcon(agent);
const IconComponent = iconMeta.Icon;
const initial = agent.initial || agent.name.charAt(0);
const shortName = agent.shortName || agent.name.split(" ")[0];
const gradientClass = agent.avatarGradient || "from-blue-500 to-indigo-600";
return (
<SidebarMenuItem key={agent.id}>
......@@ -177,14 +190,19 @@ export function WorkforceList({
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground shadow-xs"
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground",
)}
title={`${agent.name} (${agent.role}) - ${agent.department}\nModel: ${agent.model}\nStatus: ${statusMeta.label}`}
title={`${agent.name} (${agent.title || agent.role}) - ${agent.department}\nModel: ${agent.model}\nStatus: ${statusMeta.label}`}
>
<SidebarGlyphSlot>
<span className={cn("relative flex size-4 items-center justify-center rounded", iconMeta.bg)}>
<IconComponent className={cn("size-3.5", iconMeta.color)} />
<span
className={cn(
"absolute -bottom-0.5 -right-0.5 size-1.5 rounded-full border border-sidebar-background",
"relative flex size-5 shrink-0 items-center justify-center rounded-full font-semibold text-[10px] text-white shadow-xs bg-gradient-to-tr",
gradientClass,
)}
>
{initial}
<span
className={cn(
"absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-sidebar-background ring-1 ring-background/50",
statusMeta.className,
)}
title={statusMeta.label}
......@@ -192,13 +210,15 @@ export function WorkforceList({
</span>
</SidebarGlyphSlot>
<div className="flex min-w-0 flex-1 items-center justify-between gap-1">
<span className="truncate text-foreground/90 font-medium">
{agent.name.split(" ")[0]}
<span className="ml-1 text-[11px] font-normal text-muted-foreground/70">
{agent.role}
<div className="flex min-w-0 flex-1 items-center justify-between gap-1.5">
<div className="flex min-w-0 items-center gap-1 truncate">
<span className="font-medium text-foreground">
{shortName}
</span>
<span className="text-[11px] text-muted-foreground/70 truncate">
· {agent.role}
</span>
</div>
<span
className="shrink-0 rounded border border-border/40 bg-muted/40 px-1 py-0.2 font-mono text-[9px] text-muted-foreground/80"
......
/** @jsxImportSource react */
import * as React from "react";
import {
Users,
Plus,
Settings,
Sparkles,
Bot,
Brain,
Code2,
Layers,
ShieldCheck,
Kanban,
Crown,
Zap,
Activity,
CheckCircle2,
ArrowRight,
Filter,
Search,
ChevronRight,
TrendingUp,
Cpu,
MessageSquare,
Sliders,
ShieldAlert,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { MOCK_AGENTS, type AgentMember } from "@/app/lib/mock-data";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
export interface TeamManagementPageProps {
workspaceName?: string;
onOpenChat?: (agentId?: string) => void;
}
export function TeamManagementPage({
workspaceName = "AI Software Studio",
onOpenChat,
}: TeamManagementPageProps) {
const [agents, setAgents] = React.useState<AgentMember[]>(MOCK_AGENTS);
const [selectedDept, setSelectedDept] = React.useState<string>("all");
const [searchQuery, setSearchQuery] = React.useState<string>("");
const [addDialogOpen, setAddDialogOpen] = React.useState(false);
const [newAgentName, setNewAgentName] = React.useState("");
const [newAgentRole, setNewAgentRole] = React.useState<AgentMember["role"]>("Coder");
const [newAgentModel, setNewAgentModel] = React.useState("deepseek-reasoner");
const departments = [
{ id: "all", label: "Tất cả phòng ban" },
{ id: "Executive HQ", label: "Ban Điều Hành" },
{ id: "Product & Spec", label: "Sản phẩm & PRD" },
{ id: "System Architecture", label: "Kiến trúc hệ thống" },
{ id: "Engineering", label: "Kỹ thuật phần mềm" },
{ id: "Security & Compliance", label: "Bảo mật & Kiểm toán" },
];
const filteredAgents = agents.filter((a) => {
const matchDept = selectedDept === "all" || a.department === selectedDept;
const matchSearch =
a.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
a.role.toLowerCase().includes(searchQuery.toLowerCase()) ||
(a.title && a.title.toLowerCase().includes(searchQuery.toLowerCase()));
return matchDept && matchSearch;
});
const handleAddAgent = () => {
if (!newAgentName.trim()) return;
const initial = newAgentName.trim().charAt(0).toUpperCase();
const newAgent: AgentMember = {
id: `agent-custom-${Date.now()}`,
name: newAgentName,
shortName: newAgentName.split(" ")[0],
initial,
role: newAgentRole,
title: `Senior AI ${newAgentRole}`,
department:
newAgentRole === "Coder"
? "Engineering"
: newAgentRole === "QA"
? "Security & Compliance"
: newAgentRole === "PM"
? "Product & Spec"
: newAgentRole === "Architect"
? "System Architecture"
: "Executive HQ",
avatarGradient:
newAgentRole === "Coder"
? "from-emerald-500 to-green-600"
: newAgentRole === "QA"
? "from-rose-500 to-pink-600"
: newAgentRole === "PM"
? "from-indigo-500 to-purple-600"
: newAgentRole === "Architect"
? "from-cyan-500 to-teal-600"
: "from-amber-500 to-orange-600",
avatarColor: "#10B981",
model: newAgentModel,
skills: ["/goal", "/code-gen", "/verify"],
status: "online",
activeTask: "Ready for assignment",
stats: {
tasksCompleted: 1,
testsPassed: "100%",
avgSpeed: "1.2s",
approvalRate: "100%",
},
};
setAgents((prev) => [...prev, newAgent]);
setNewAgentName("");
setAddDialogOpen(false);
};
const totalTasks = agents.reduce((acc, a) => acc + (a.stats?.tasksCompleted || 0), 0);
return (
<div className="flex h-full flex-1 flex-col overflow-y-auto bg-background p-6 md:p-8">
{/* Header Banner */}
<div className="flex flex-col justify-between gap-4 border-b border-border/60 pb-6 md:flex-row md:items-center">
<div>
<div className="flex items-center gap-2.5">
<span className="flex size-9 items-center justify-center rounded-xl bg-primary/10 text-primary">
<Users className="size-5" />
</span>
<div>
<h1 className="text-2xl font-bold tracking-tight text-foreground">
Tổ Chức Đội Ngũ AI ({workspaceName})
</h1>
<p className="text-sm text-muted-foreground">
Quản lý các Agent tự vận hành, phân quyền vai trò, mô hình LLM và luồng phối hợp liên phòng ban.
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2.5">
<Button
onClick={() => setAddDialogOpen(true)}
className="flex items-center gap-2 rounded-xl bg-primary px-4 py-2 font-medium text-primary-foreground shadow-sm transition hover:bg-primary/90"
>
<Plus className="size-4" />
Thêm Thành Viên AI
</Button>
</div>
</div>
{/* KPI Stats Strip */}
<div className="my-6 grid grid-cols-2 gap-4 md:grid-cols-4">
<div className="rounded-2xl border border-border/60 bg-card p-4 shadow-xs">
<div className="flex items-center justify-between text-muted-foreground">
<span className="text-xs font-medium uppercase tracking-wider">Tổng Nhân Sự AI</span>
<Bot className="size-4 text-primary" />
</div>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-2xl font-bold text-foreground">{agents.length}</span>
<span className="text-xs text-emerald-500 font-medium">100% Online</span>
</div>
</div>
<div className="rounded-2xl border border-border/60 bg-card p-4 shadow-xs">
<div className="flex items-center justify-between text-muted-foreground">
<span className="text-xs font-medium uppercase tracking-wider">Tác Vụ Hoàn Thành</span>
<Zap className="size-4 text-amber-500" />
</div>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-2xl font-bold text-foreground">{totalTasks}</span>
<span className="text-xs text-muted-foreground">tuần này</span>
</div>
</div>
<div className="rounded-2xl border border-border/60 bg-card p-4 shadow-xs">
<div className="flex items-center justify-between text-muted-foreground">
<span className="text-xs font-medium uppercase tracking-wider">Tỷ Lệ Pass Test</span>
<CheckCircle2 className="size-4 text-emerald-500" />
</div>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-2xl font-bold text-foreground">100%</span>
<span className="text-xs text-emerald-500 font-medium">0 hồi quy</span>
</div>
</div>
<div className="rounded-2xl border border-border/60 bg-card p-4 shadow-xs">
<div className="flex items-center justify-between text-muted-foreground">
<span className="text-xs font-medium uppercase tracking-wider">Thời Gian Phản Hồi</span>
<TrendingUp className="size-4 text-cyan-500" />
</div>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-2xl font-bold text-foreground">1.4s</span>
<span className="text-xs text-muted-foreground">trung bình</span>
</div>
</div>
</div>
{/* Collaboration Flow DAG Banner */}
<div className="mb-6 rounded-2xl border border-border/60 bg-card/60 p-4 shadow-xs">
<div className="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
<Activity className="size-3.5 text-primary" />
Quy Trình Tự Động Phối Hợp (Collaboration Pipeline DAG)
</div>
<div className="flex flex-wrap items-center gap-2 text-xs">
<span className="flex items-center gap-1.5 rounded-lg bg-amber-500/10 px-2.5 py-1.5 font-medium text-amber-500 border border-amber-500/20">
<Crown className="size-3.5" /> Founder (Chiến lược)
</span>
<ArrowRight className="size-3.5 text-muted-foreground/40" />
<span className="flex items-center gap-1.5 rounded-lg bg-indigo-500/10 px-2.5 py-1.5 font-medium text-indigo-500 border border-indigo-500/20">
<Kanban className="size-3.5" /> PM (PRD & Stories)
</span>
<ArrowRight className="size-3.5 text-muted-foreground/40" />
<span className="flex items-center gap-1.5 rounded-lg bg-cyan-500/10 px-2.5 py-1.5 font-medium text-cyan-500 border border-cyan-500/20">
<Layers className="size-3.5" /> Architect (Schema Contract)
</span>
<ArrowRight className="size-3.5 text-muted-foreground/40" />
<span className="flex items-center gap-1.5 rounded-lg bg-emerald-500/10 px-2.5 py-1.5 font-medium text-emerald-500 border border-emerald-500/20">
<Code2 className="size-3.5" /> Coder (Code & Unit Test)
</span>
<ArrowRight className="size-3.5 text-muted-foreground/40" />
<span className="flex items-center gap-1.5 rounded-lg bg-rose-500/10 px-2.5 py-1.5 font-medium text-rose-500 border border-rose-500/20">
<ShieldCheck className="size-3.5" /> QA (Security Gate)
</span>
<ArrowRight className="size-3.5 text-muted-foreground/40" />
<span className="flex items-center gap-1.5 rounded-lg bg-primary/10 px-2.5 py-1.5 font-medium text-primary border border-primary/20">
<CheckCircle2 className="size-3.5" /> Release Gate
</span>
</div>
</div>
{/* Filter & Search Bar */}
<div className="mb-6 flex flex-col justify-between gap-3 sm:flex-row sm:items-center">
<div className="flex flex-wrap items-center gap-1.5">
{departments.map((d) => (
<button
key={d.id}
onClick={() => setSelectedDept(d.id)}
className={cn(
"rounded-xl px-3 py-1.5 text-xs font-medium transition",
selectedDept === d.id
? "bg-primary text-primary-foreground shadow-xs"
: "bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground",
)}
>
{d.label}
</button>
))}
</div>
<div className="relative w-full sm:w-64">
<Search className="absolute left-3 top-2.5 size-4 text-muted-foreground" />
<Input
placeholder="Tìm theo tên hoặc vai trò..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="rounded-xl pl-9 text-xs"
/>
</div>
</div>
{/* Agent Cards Grid */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{filteredAgents.map((agent) => {
const initial = agent.initial || agent.name.charAt(0);
const shortName = agent.shortName || agent.name.split(" ")[0];
const gradientClass = agent.avatarGradient || "from-blue-500 to-indigo-600";
return (
<div
key={agent.id}
className="flex flex-col justify-between rounded-2xl border border-border/60 bg-card p-5 shadow-xs transition hover:border-border hover:shadow-md"
>
<div>
{/* Card Header */}
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-3">
<span
className={cn(
"relative flex size-12 shrink-0 items-center justify-center rounded-2xl font-bold text-base text-white shadow-md bg-gradient-to-tr",
gradientClass,
)}
>
{initial}
<span className="absolute -bottom-0.5 -right-0.5 size-3 rounded-full border-2 border-card bg-emerald-500" />
</span>
<div>
<h3 className="font-semibold text-foreground">{agent.name}</h3>
<p className="text-xs text-muted-foreground">{agent.title}</p>
</div>
</div>
<span className="rounded-lg border border-border/40 bg-muted/40 px-2 py-0.5 font-mono text-[10px] text-muted-foreground">
{agent.model}
</span>
</div>
{/* Active Task */}
<div className="mt-4 rounded-xl border border-border/40 bg-muted/20 p-2.5 text-xs">
<span className="text-muted-foreground font-medium">Đang phụ trách: </span>
<span className="text-foreground">{agent.activeTask || "Sẵn sàng nhận việc"}</span>
</div>
{/* Skills tags */}
<div className="mt-3 flex flex-wrap gap-1.5">
{agent.skills.map((skill) => (
<span
key={skill}
className="rounded-md border border-border/40 bg-card px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground"
>
{skill}
</span>
))}
</div>
</div>
{/* Card Footer & Micro Stats */}
<div className="mt-5 border-t border-border/40 pt-4">
<div className="grid grid-cols-3 gap-2 text-center text-xs text-muted-foreground">
<div>
<div className="font-bold text-foreground">{agent.stats?.tasksCompleted || 0}</div>
<div className="text-[10px]">Tác vụ</div>
</div>
<div>
<div className="font-bold text-emerald-500">{agent.stats?.testsPassed || "100%"}</div>
<div className="text-[10px]">Pass Test</div>
</div>
<div>
<div className="font-bold text-foreground">{agent.stats?.avgSpeed || "1.2s"}</div>
<div className="text-[10px]">Tốc độ</div>
</div>
</div>
<div className="mt-4 flex items-center gap-2">
<Button
variant="outline"
size="sm"
className="flex-1 rounded-xl text-xs"
onClick={() => onOpenChat?.(agent.id)}
>
<MessageSquare className="mr-1 size-3.5" />
Chat Riêng
</Button>
<Button
variant="default"
size="sm"
className="flex-1 rounded-xl text-xs bg-primary text-primary-foreground"
onClick={() => onOpenChat?.(agent.id)}
>
<Zap className="mr-1 size-3.5" />
Giao Việc
</Button>
</div>
</div>
</div>
);
})}
</div>
{/* Add Agent Modal Dialog */}
<Dialog open={addDialogOpen} onOpenChange={setAddDialogOpen}>
<DialogContent className="rounded-2xl sm:max-w-md">
<DialogHeader>
<DialogTitle>Thêm Thành Viên AI Mới</DialogTitle>
<DialogDescription>
Tạo một Agent AI mới cho {workspaceName}, gán mô hình LLM và vai trò cụ thể.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-2">
<div>
<label className="text-xs font-medium text-muted-foreground">Tên Thành Viên AI</label>
<Input
placeholder="Ví dụ: Hoàng Long, Lan Chi..."
value={newAgentName}
onChange={(e) => setNewAgentName(e.target.value)}
className="mt-1 rounded-xl"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-xs font-medium text-muted-foreground">Vai Trò Chuyên Môn</label>
<select
value={newAgentRole}
onChange={(e) => setNewAgentRole(e.target.value as AgentMember["role"])}
className="mt-1 flex h-9 w-full rounded-xl border border-input bg-background px-3 py-1 text-sm shadow-xs focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
>
<option value="Coder">Coder (Kỹ sư phần mềm)</option>
<option value="QA">QA & Security (Kiểm toán bảo mật)</option>
<option value="PM">PM (Quản lý sản phẩm)</option>
<option value="Architect">Architect (Kiến trúc sư)</option>
<option value="Founder">Founder (Ban điều hành)</option>
</select>
</div>
<div>
<label className="text-xs font-medium text-muted-foreground">Mô Hình LLM</label>
<select
value={newAgentModel}
onChange={(e) => setNewAgentModel(e.target.value)}
className="mt-1 flex h-9 w-full rounded-xl border border-input bg-background px-3 py-1 text-sm shadow-xs focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
>
<option value="deepseek-reasoner">DeepSeek R1 (Lập trình & Lý luận)</option>
<option value="claude-3-7-sonnet">Claude 3.7 Sonnet (Hybrid Reasoning)</option>
<option value="o1">OpenAI o1 (Logic phức tạp)</option>
<option value="gpt-4o">GPT-4o (Đa năng & Tốc độ)</option>
</select>
</div>
</div>
</div>
<DialogFooter className="gap-2 sm:gap-0">
<Button variant="outline" onClick={() => setAddDialogOpen(false)} className="rounded-xl">
Hủy
</Button>
<Button onClick={handleAddAgent} className="rounded-xl bg-primary text-primary-foreground">
Thêm Thành Viên
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}
import { TeamManagementPage } from "../domains/team/team-management-page";
/** @jsxImportSource react */
import {
useCallback,
......@@ -1258,6 +1259,18 @@ export function SessionRoute() {
}, [navigate, selectedSessionId, sidebarActiveWorkspaceId]);
const extensionsMainOpen = /^\/(?:workspace\/[^/]+\/)?extensions(?:\/|$)/.test(location.pathname);
const teamMainOpen = /^\/(?:workspace\/[^/]+\/)?team(?:\/|$)/.test(location.pathname);
const [showTeamView, setShowTeamView] = useState(false);
useEffect(() => {
const handleOpenTeam = () => setShowTeamView(true);
const handleCloseTeam = () => setShowTeamView(false);
window.addEventListener("openwork-open-team", handleOpenTeam);
window.addEventListener("openwork-close-team", handleCloseTeam);
return () => {
window.removeEventListener("openwork-open-team", handleOpenTeam);
window.removeEventListener("openwork-close-team", handleCloseTeam);
};
}, []);
const surfaceProps = useMemo(() => {
if (!selectedWorkspaceId || !selectedSessionId) {
......@@ -2905,7 +2918,16 @@ export function SessionRoute() {
}}
notFoundMessage={gatedRouteNotFoundMessage}
mainContentTakeover={
extensionsMainOpen ? (
(showTeamView || teamMainOpen) ? (
<TeamManagementPage
workspaceName={selectedWorkspace?.displayName || selectedWorkspace?.name || "AI Software Studio"}
onOpenChat={() => {
if (selectedWorkspaceId && selectedSessionId) {
navigateToWorkspaceSession(selectedWorkspaceId, selectedSessionId);
}
}}
/>
) : extensionsMainOpen ? (
<SettingsSurface
standaloneExtensions
workspaceId={selectedWorkspaceId || undefined}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment