Files
codex-app-server/cloud/console-react/src/sections/MembersPanel.tsx
T
host ebc5624893 console: React 生产级重写(左列表 + 中对话 + 右详情)
旧单页 console.html 换成 Vite + React + TypeScript 单文件构建产物
(309525 字节,sha 90345ec8…,重建字节一致),源码在
cloud/console-react/,改页面只改 src 再 npm run build:console,
产物不手改。布局:左侧列表(模块导航 + 任务列表)、中间标准对话
(实时链路时间线 + 继续会话聊天框,共用 Chat 组件)、右侧详情
(元数据、操作、评审、审批、协调子任务、子代理、线程)。
API 与后端语义未改,key 只存浏览器 localStorage。
6 个 console 标记单测随实现改名更新(意图不变:壳确实调了
这些接口、turn 信息仍在详情里、旧 goal 输入仍不存在);
P1 标记单测未动且通过。
2026-10-29 06:32:38 +08:00

49 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { useConsole } from "../context";
import { Card, Bar, DataListRaw } from "../components/ui";
import { ROLES } from "../api/types";
// Right-panel context for the Projects view. Previously embedded in the
// projects card; moved here so the centre stays the project list/create
// and the right stays the people for the selected project.
export default function MembersPanel() {
const ctx = useConsole();
const [inviteUser, setInviteUser] = useState("");
const [inviteRole, setInviteRole] = useState("contributor");
const proj = ctx.projects.find((p) => p.id === ctx.projectId);
return (
<Card title="项目成员">
{proj ? <div className="muted">{proj.name} | {proj.id} | 仓库 {proj.repo_url || "未绑定"} | 我的角色 {proj.my_role || "未知"}</div> : <div className="muted">先选项目</div>}
<Bar>
<input type="text" placeholder="成员 user_id 或用户名" value={inviteUser} onChange={(e) => setInviteUser(e.target.value)} />
<select aria-label="邀请角色" value={inviteRole} onChange={(e) => setInviteRole(e.target.value)}>
<option value="viewer">viewer(只读)</option>
<option value="contributor">contributor(可建任务)</option>
<option value="maintainer">maintainer(可批合并)</option>
<option value="owner">owner(可管项目)</option>
</select>
<button onClick={async () => { await ctx.inviteMember(inviteUser, inviteRole); setInviteUser(""); }}>邀请成员</button>
<button onClick={ctx.refreshMembers}>刷新成员</button>
</Bar>
{ctx.memberError ? <div className="muted">{ctx.memberError}</div>
: ctx.members.length === 0 ? <div className="muted">先选项目</div>
: (
<DataListRaw>
{ctx.members.map((m) => {
const label = (m.username || "") + (m.username ? "(" + String(m.user_id).slice(0, 8) + ")" : String(m.user_id).slice(0, 8));
return (
<li key={m.user_id}>
<span className="row-main">{label}</span>
<select aria-label="成员角色" value={m.role || "viewer"} onChange={(e) => void ctx.changeMemberRole(m.user_id, e.target.value)}>
{ROLES.map((r) => <option key={r} value={r}>{r}</option>)}
</select>
</li>
);
})}
</DataListRaw>
)}
<span className="muted">{ctx.memberMsg}</span>
</Card>
);
}