forked from bai/curriculum-project-hub
feat: redesign Feishu project console
This commit is contained in:
@@ -5,12 +5,27 @@ export type ProjectOnboardingView =
|
||||
| { readonly mode: "browse"; readonly result: ProjectFolderPage };
|
||||
|
||||
export interface ProjectOnboardingActionValue {
|
||||
readonly action: "create_project_from_chat" | "bind_project" | "browse_folder" | "search_page" | "rename_project";
|
||||
readonly action:
|
||||
| "create_project_from_chat"
|
||||
| "bind_project"
|
||||
| "browse_folder"
|
||||
| "search_page"
|
||||
| "rename_project"
|
||||
| "select_agent_role"
|
||||
| "new_agent_session"
|
||||
| "show_session_history"
|
||||
| "resume_agent_session"
|
||||
| "compact_agent_session"
|
||||
| "browse_move_destination"
|
||||
| "move_project"
|
||||
| "create_folder";
|
||||
readonly organization_id: string;
|
||||
readonly project_id?: string | undefined;
|
||||
readonly folder_id?: string | undefined;
|
||||
readonly search_query?: string | undefined;
|
||||
readonly page?: number | undefined;
|
||||
readonly agent_role_id?: string | undefined;
|
||||
readonly session_id?: string | undefined;
|
||||
}
|
||||
|
||||
export function buildUnboundChatOnboardingCard(params: {
|
||||
@@ -62,49 +77,165 @@ export function buildProjectManagementCard(params: {
|
||||
readonly projectId: string;
|
||||
readonly projectName: string;
|
||||
readonly title?: string | undefined;
|
||||
readonly breadcrumb?: string | undefined;
|
||||
readonly selectedRole?: { readonly id: string; readonly roleId: string; readonly label: string } | undefined;
|
||||
readonly roles?: readonly { readonly id: string; readonly roleId: string; readonly label: string }[] | undefined;
|
||||
readonly currentSession?: {
|
||||
readonly id: string;
|
||||
readonly title: string | null;
|
||||
readonly updatedAt: Date;
|
||||
readonly runCount: number;
|
||||
readonly sdkSessionReady: boolean;
|
||||
} | null | undefined;
|
||||
readonly canManageProject?: boolean | undefined;
|
||||
readonly canCreateFolder?: boolean | undefined;
|
||||
}): Record<string, unknown> {
|
||||
const elements: unknown[] = [{
|
||||
tag: "markdown",
|
||||
content: [
|
||||
`当前项目: **${escapeMarkdown(params.projectName)}**`,
|
||||
`所在目录: **${escapeMarkdown(params.breadcrumb ?? "根目录")}**`,
|
||||
params.selectedRole === undefined
|
||||
? "当前角色: **未配置**"
|
||||
: `当前角色: **${escapeMarkdown(params.selectedRole.label)}**`,
|
||||
sessionSummary(params.currentSession),
|
||||
].join("\n"),
|
||||
}];
|
||||
const roles = params.roles ?? [];
|
||||
if (roles.length > 0) {
|
||||
elements.push({ tag: "markdown", content: "**切换角色**" });
|
||||
for (let index = 0; index < roles.length; index += 5) {
|
||||
elements.push({
|
||||
tag: "action",
|
||||
actions: roles.slice(index, index + 5).map((role) => actionButton(
|
||||
role.id === params.selectedRole?.id ? `✓ ${role.label}` : role.label,
|
||||
{
|
||||
action: "select_agent_role",
|
||||
organization_id: params.organizationId,
|
||||
project_id: params.projectId,
|
||||
agent_role_id: role.id,
|
||||
},
|
||||
role.id === params.selectedRole?.id ? "primary" : "default",
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
const sessionActions = [
|
||||
actionButton("新开会话", {
|
||||
action: "new_agent_session", organization_id: params.organizationId, project_id: params.projectId,
|
||||
}),
|
||||
actionButton("历史会话", {
|
||||
action: "show_session_history", organization_id: params.organizationId, project_id: params.projectId,
|
||||
}),
|
||||
];
|
||||
if (params.currentSession?.sdkSessionReady === true) {
|
||||
sessionActions.push(actionButton("压缩上下文", {
|
||||
action: "compact_agent_session", organization_id: params.organizationId, project_id: params.projectId,
|
||||
}));
|
||||
}
|
||||
elements.push(
|
||||
{ tag: "markdown", content: "**当前角色会话**" },
|
||||
{ tag: "action", actions: sessionActions },
|
||||
);
|
||||
if (params.canManageProject === true) {
|
||||
elements.push(
|
||||
{ tag: "markdown", content: "**项目管理**" },
|
||||
{ tag: "action", actions: [actionButton("移动项目", {
|
||||
action: "browse_move_destination",
|
||||
organization_id: params.organizationId,
|
||||
project_id: params.projectId,
|
||||
page: 1,
|
||||
})] },
|
||||
renameProjectForm(params),
|
||||
);
|
||||
}
|
||||
if (params.canCreateFolder === true) elements.push(createFolderForm(params));
|
||||
return {
|
||||
config: { wide_screen_mode: true },
|
||||
header: {
|
||||
title: { tag: "plain_text", content: params.title ?? "项目管理" },
|
||||
template: "green",
|
||||
},
|
||||
elements: [
|
||||
{ tag: "markdown", content: `当前项目: **${escapeMarkdown(params.projectName)}**` },
|
||||
elements,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSessionHistoryCard(params: {
|
||||
readonly organizationId: string;
|
||||
readonly projectId: string;
|
||||
readonly roleLabel: string;
|
||||
readonly sessions: readonly {
|
||||
readonly id: string;
|
||||
readonly title: string | null;
|
||||
readonly updatedAt: Date;
|
||||
readonly runCount: number;
|
||||
}[];
|
||||
}): Record<string, unknown> {
|
||||
const elements: unknown[] = [{ tag: "markdown", content: `角色: **${escapeMarkdown(params.roleLabel)}**` }];
|
||||
if (params.sessions.length === 0) elements.push({ tag: "markdown", content: "没有可恢复的历史会话。" });
|
||||
for (const session of params.sessions) {
|
||||
elements.push(
|
||||
{
|
||||
tag: "form",
|
||||
name: "project_rename_form",
|
||||
fallback: {
|
||||
tag: "fallback_text",
|
||||
text: { tag: "plain_text", content: "请升级飞书客户端后修改项目名称。" },
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
tag: "input",
|
||||
name: "project_name",
|
||||
required: true,
|
||||
max_length: 100,
|
||||
default_value: params.projectName,
|
||||
placeholder: { tag: "plain_text", content: "请输入项目名称" },
|
||||
},
|
||||
{
|
||||
tag: "button",
|
||||
name: "project_rename_submit",
|
||||
text: { tag: "plain_text", content: "保存项目名称" },
|
||||
type: "primary",
|
||||
complex_interaction: true,
|
||||
action_type: "form_submit",
|
||||
value: {
|
||||
project_onboarding: {
|
||||
action: "rename_project",
|
||||
organization_id: params.organizationId,
|
||||
project_id: params.projectId,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
tag: "markdown",
|
||||
content: `**${escapeMarkdown(session.title ?? "未命名会话")}**\n${session.runCount} runs · ${formatDate(session.updatedAt)}`,
|
||||
},
|
||||
],
|
||||
{ tag: "action", actions: [actionButton("恢复此会话", {
|
||||
action: "resume_agent_session",
|
||||
organization_id: params.organizationId,
|
||||
project_id: params.projectId,
|
||||
session_id: session.id,
|
||||
}, "primary")] },
|
||||
);
|
||||
}
|
||||
return {
|
||||
config: { wide_screen_mode: true },
|
||||
header: { title: { tag: "plain_text", content: "历史会话" }, template: "blue" },
|
||||
elements,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMoveProjectCard(params: {
|
||||
readonly organizationId: string;
|
||||
readonly projectId: string;
|
||||
readonly projectName: string;
|
||||
readonly folderId: string | null;
|
||||
readonly parentFolderId: string | null;
|
||||
readonly breadcrumb: string;
|
||||
readonly childFolders: readonly { readonly id: string; readonly name: string }[];
|
||||
}): Record<string, unknown> {
|
||||
const navigation: unknown[] = [];
|
||||
if (params.folderId !== null) {
|
||||
navigation.push(actionButton("⬆ 上一级", {
|
||||
action: "browse_move_destination",
|
||||
organization_id: params.organizationId,
|
||||
project_id: params.projectId,
|
||||
...(params.parentFolderId !== null ? { folder_id: params.parentFolderId } : {}),
|
||||
}));
|
||||
}
|
||||
for (const folder of params.childFolders) {
|
||||
navigation.push(actionButton(`📁 ${buttonLabel(folder.name, 22)}`, {
|
||||
action: "browse_move_destination",
|
||||
organization_id: params.organizationId,
|
||||
project_id: params.projectId,
|
||||
folder_id: folder.id,
|
||||
}));
|
||||
}
|
||||
const elements: unknown[] = [
|
||||
{ tag: "markdown", content: `移动 **${escapeMarkdown(params.projectName)}**\n当前位置: **${escapeMarkdown(params.breadcrumb)}**` },
|
||||
];
|
||||
for (let index = 0; index < navigation.length; index += 5) {
|
||||
elements.push({ tag: "action", actions: navigation.slice(index, index + 5) });
|
||||
}
|
||||
elements.push({ tag: "action", actions: [actionButton("移动到这里", {
|
||||
action: "move_project",
|
||||
organization_id: params.organizationId,
|
||||
project_id: params.projectId,
|
||||
...(params.folderId !== null ? { folder_id: params.folderId } : {}),
|
||||
}, "primary")] });
|
||||
return {
|
||||
config: { wide_screen_mode: true },
|
||||
header: { title: { tag: "plain_text", content: "移动项目" }, template: "blue" },
|
||||
elements,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -120,11 +251,15 @@ export function projectOnboardingActionFromValue(value: unknown): ProjectOnboard
|
||||
const folderId = fields.folder_id;
|
||||
const searchQuery = fields.search_query;
|
||||
const page = fields.page;
|
||||
const agentRoleId = fields.agent_role_id;
|
||||
const sessionId = fields.session_id;
|
||||
if (!isAction(rawAction) || typeof organizationId !== "string" || organizationId === "") return null;
|
||||
if ((rawAction === "bind_project" || rawAction === "rename_project") && (typeof projectId !== "string" || projectId === "")) return null;
|
||||
if (rawAction === "search_page" && (typeof searchQuery !== "string" || !validPage(page))) return null;
|
||||
if (folderId !== undefined && (typeof folderId !== "string" || folderId === "")) return null;
|
||||
if (page !== undefined && !validPage(page)) return null;
|
||||
if (rawAction === "select_agent_role" && (typeof agentRoleId !== "string" || agentRoleId === "")) return null;
|
||||
if (rawAction === "resume_agent_session" && (typeof sessionId !== "string" || sessionId === "")) return null;
|
||||
return {
|
||||
action: rawAction,
|
||||
organization_id: organizationId,
|
||||
@@ -132,6 +267,8 @@ export function projectOnboardingActionFromValue(value: unknown): ProjectOnboard
|
||||
...(typeof folderId === "string" && folderId !== "" ? { folder_id: folderId } : {}),
|
||||
...(typeof searchQuery === "string" ? { search_query: searchQuery.slice(0, 100) } : {}),
|
||||
...(typeof page === "number" ? { page } : {}),
|
||||
...(typeof agentRoleId === "string" && agentRoleId !== "" ? { agent_role_id: agentRoleId } : {}),
|
||||
...(typeof sessionId === "string" && sessionId !== "" ? { session_id: sessionId } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -258,6 +395,93 @@ function projectPage(view: ProjectOnboardingView): ProjectDiscoveryPage {
|
||||
};
|
||||
}
|
||||
|
||||
function renameProjectForm(params: {
|
||||
readonly organizationId: string;
|
||||
readonly projectId: string;
|
||||
readonly projectName: string;
|
||||
}): unknown {
|
||||
return {
|
||||
tag: "form",
|
||||
name: "project_rename_form",
|
||||
fallback: {
|
||||
tag: "fallback_text",
|
||||
text: { tag: "plain_text", content: "请升级飞书客户端后修改项目名称。" },
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
tag: "input",
|
||||
name: "project_name",
|
||||
required: true,
|
||||
max_length: 100,
|
||||
default_value: params.projectName,
|
||||
placeholder: { tag: "plain_text", content: "请输入项目名称" },
|
||||
},
|
||||
{
|
||||
tag: "button",
|
||||
name: "project_rename_submit",
|
||||
text: { tag: "plain_text", content: "保存项目名称" },
|
||||
type: "primary",
|
||||
complex_interaction: true,
|
||||
action_type: "form_submit",
|
||||
value: { project_onboarding: {
|
||||
action: "rename_project",
|
||||
organization_id: params.organizationId,
|
||||
project_id: params.projectId,
|
||||
} },
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function createFolderForm(params: { readonly organizationId: string; readonly projectId: string }): unknown {
|
||||
return {
|
||||
tag: "form",
|
||||
name: "folder_create_form",
|
||||
elements: [
|
||||
{
|
||||
tag: "input",
|
||||
name: "folder_name",
|
||||
required: true,
|
||||
max_length: 100,
|
||||
placeholder: { tag: "plain_text", content: "在当前目录下新建 Folder" },
|
||||
},
|
||||
{
|
||||
tag: "button",
|
||||
name: "folder_create_submit",
|
||||
text: { tag: "plain_text", content: "新建目录" },
|
||||
type: "default",
|
||||
complex_interaction: true,
|
||||
action_type: "form_submit",
|
||||
value: { project_onboarding: {
|
||||
action: "create_folder",
|
||||
organization_id: params.organizationId,
|
||||
project_id: params.projectId,
|
||||
} },
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function sessionSummary(session: {
|
||||
readonly title: string | null;
|
||||
readonly updatedAt: Date;
|
||||
readonly runCount: number;
|
||||
} | null | undefined): string {
|
||||
if (session === null || session === undefined) return "当前会话: **尚未开始**";
|
||||
return `当前会话: **${escapeMarkdown(session.title ?? "未命名会话")}** · ${session.runCount} runs · ${formatDate(session.updatedAt)}`;
|
||||
}
|
||||
|
||||
function formatDate(value: Date): string {
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
timeZone: "Asia/Shanghai",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
function actionButton(
|
||||
label: string,
|
||||
value: ProjectOnboardingActionValue,
|
||||
@@ -273,7 +497,11 @@ function actionButton(
|
||||
|
||||
function isAction(value: unknown): value is ProjectOnboardingActionValue["action"] {
|
||||
return value === "create_project_from_chat" || value === "bind_project"
|
||||
|| value === "browse_folder" || value === "search_page" || value === "rename_project";
|
||||
|| value === "browse_folder" || value === "search_page" || value === "rename_project"
|
||||
|| value === "select_agent_role" || value === "new_agent_session"
|
||||
|| value === "show_session_history" || value === "resume_agent_session"
|
||||
|| value === "compact_agent_session" || value === "browse_move_destination"
|
||||
|| value === "move_project" || value === "create_folder";
|
||||
}
|
||||
|
||||
function validPage(value: unknown): value is number {
|
||||
|
||||
Reference in New Issue
Block a user