diff --git a/src/components/openclaw/OpenClawAssistantPane.tsx b/src/components/openclaw/OpenClawAssistantPane.tsx index 6f9a2fb..8960ad0 100644 --- a/src/components/openclaw/OpenClawAssistantPane.tsx +++ b/src/components/openclaw/OpenClawAssistantPane.tsx @@ -55,6 +55,7 @@ type OpenClawAssistantPaneProps = { variant?: "page" | "sidebar"; showConversation?: boolean; emptyConversationHint?: string; + integrationsHref?: string; onStateChange?: (state: OpenClawAssistantViewState) => void; }; @@ -401,6 +402,7 @@ export function OpenClawAssistantPane({ variant = "page", showConversation = true, emptyConversationHint, + integrationsHref = "/panel/api", onStateChange, }: OpenClawAssistantPaneProps) { const router = useRouter(); @@ -1292,7 +1294,7 @@ export function OpenClawAssistantPane({ ); } function DesktopChip({ label, + icon: Icon, active = false, -}: { - label: string; - active?: boolean; -}) { +}: DesktopChipProps): ReactNode { return (
- {label} + {Icon ? : null} + {label}
); } -function ToolbarChip({ - label, - active = false, -}: { - label: string; - active?: boolean; -}) { +function CounterBadge({ label, value }: CounterBadgeProps): ReactNode { return ( - +
+ {label} + {value} +
); } -function DetailCard({ title, description, meta }: DetailCardProps) { +function XWorkmateIconRail({ + navigation, + activeSection, + onSelect, + sidebarExpanded, + onToggleSidebar, +}: { + navigation: ReturnType; + activeSection: WorkspaceDestination; + onSelect: (section: WorkspaceDestination) => void; + sidebarExpanded: boolean; + onToggleSidebar: () => void; +}): ReactNode { + const groups = [ + navigation.primaryItems, + navigation.workspaceItems, + navigation.toolItems, + ]; + return ( -
-
- {meta} +
+ +
+ {groups.map((group, index) => ( +
+ {group.map((item) => ( + onSelect(item.key)} + /> + ))} + {index < groups.length - 1 ? ( +
+ ) : null} +
+ ))} +
+ +
+ {navigation.footerItems.map((item) => ( + onSelect(item.key)} + /> + ))} + +
+ ); } -function SectionOverview({ +function XWorkmateSessionSidebar({ isChinese, - section, -}: { - isChinese: boolean; - section: SectionDefinition; -}) { + searchValue, + onSearchChange, + onRefresh, + onCreateTask, + runningCount, + currentCount, + skillCount, + taskTitle, + taskPreview, + taskUpdatedLabel, + taskCount, + hasVisibleTask, + profileBadge, +}: SessionSidebarProps): ReactNode { return ( - <> -
-
-
-
- - - -
-

- {section.label} -

-

- {section.description} -

-
- {section.tabs.map((tab, index) => ( - - ))} -
+ + ); +} + +function XWorkmateWorkspaceHeader({ + isChinese, + title, + statusLabel, + sessionLabel, + connectionLabel, +}: WorkspaceHeaderProps): ReactNode { + return ( +
+
+

+ {title} +

+
+ + + + +
+
+ +
+ + +
+
+ ); +} + +function XWorkmateGatewayEmptyState({ + isChinese, + canManageIntegrations, + onPrimaryAction, + onSecondaryAction, + primaryActionLabel, + secondaryActionLabel, +}: GatewayEmptyStateProps): ReactNode { + const description = canManageIntegrations + ? pickCopy( + isChinese, + "请先在 Settings -> AI Gateway 中配置地址、API Key 和默认模型,然后继续当前任务。", + "Set the endpoint, API key, and default model in Settings -> AI Gateway before continuing this task.", + ) + : pickCopy( + isChinese, + "当前工作台使用共享连接配置。请联系管理员完成 AI Gateway 配置,然后回到当前任务继续。", + "This workspace uses a shared integration profile. Ask an administrator to finish the AI Gateway setup, then continue here.", + ); + + return ( +
+
+
+ +
+ +

+ {pickCopy( + isChinese, + "先配置 AI Gateway", + "Configure AI Gateway first", + )} +

+

+ {description} +

+ +
+ + +
+
+
+ ); +} + +function XWorkmateEmptyComposer({ + isChinese, + actionLabel, + onAction, +}: EmptyComposerProps): ReactNode { + return ( +
+
+
+ + +
+ {pickCopy( + isChinese, + "输入后 XWorkmate 会沿用当前任务上下文持续处理。", + "XWorkmate will continue from the current task context after the gateway is configured.", )}
+ +
+
+ + {pickCopy(isChinese, "技能", "Skills")} +
+
+ + {pickCopy(isChinese, "高", "High")} +
+ + +
-
- {(section.cards ?? []).map((card) => ( - - ))} +
+ ); +} + +function XWorkmatePlaceholderPanel({ + isChinese, + sectionLabel, + onReturnHome, +}: PlaceholderPanelProps): ReactNode { + return ( +
+
+
+ +
+

+ {sectionLabel} +

+

+ {pickCopy( + isChinese, + "本次在线版优先复刻桌面端 assistant 首页,这个入口先保留导航位,不在当前改造范围内。", + "This pass focuses on the desktop-aligned assistant home. This destination stays as a navigation stub for now.", + )} +

+
- +
); } @@ -571,12 +726,18 @@ export function XWorkmateWorkspacePage({ const [activeSection, setActiveSection] = useState("assistant"); const [sidebarExpanded, setSidebarExpanded] = useState(true); + const [searchValue, setSearchValue] = useState(""); + const [assistantState, setAssistantState] = + useState(null); const setScope = useOpenClawConsoleStore((state) => state.setScope); const applyDefaults = useOpenClawConsoleStore((state) => state.applyDefaults); const setSelectedSessionKey = useOpenClawConsoleStore( (state) => state.setSelectedSessionKey, ); + const selectedSessionKey = useOpenClawConsoleStore( + (state) => state.selectedSessionKey, + ); const openclawUrl = useOpenClawConsoleStore((state) => state.openclawUrl); const vaultUrl = useOpenClawConsoleStore((state) => state.vaultUrl); const apisixUrl = useOpenClawConsoleStore((state) => state.apisixUrl); @@ -590,215 +751,216 @@ export function XWorkmateWorkspacePage({ if (!initialSessionKey.trim()) { return; } - setSelectedSessionKey(initialSessionKey); + + setSelectedSessionKey(initialSessionKey.trim()); }, [initialSessionKey, setSelectedSessionKey]); - const sections = useMemo(() => createSections(isChinese), [isChinese]); - const activeDefinition = - sections.find((section) => section.key === activeSection) ?? sections[0]; + const navigation = useMemo(() => buildNavigation(isChinese), [isChinese]); + const activeItem = useMemo(() => { + const items = [ + ...navigation.primaryItems, + ...navigation.workspaceItems, + ...navigation.toolItems, + ...navigation.footerItems, + ]; + return items.find((item) => item.key === activeSection) ?? items[0]; + }, [activeSection, navigation]); + const openclawEndpoint = openclawUrl || defaults.openclawUrl; + const connected = Boolean(openclawEndpoint.trim()); const endpointLabel = formatEndpoint( openclawEndpoint, - pickCopy(isChinese, "未连接目标", "No target"), + pickCopy(isChinese, "AI Gateway 未配置", "AI Gateway not configured"), ); - const connected = Boolean(openclawEndpoint.trim()); const configuredCount = [ openclawEndpoint, vaultUrl || defaults.vaultUrl, apisixUrl || defaults.apisixUrl, ].filter((item) => item.trim().length > 0).length; - const primarySections = sections.filter((section) => - ["assistant", "tasks", "skills"].includes(section.key), - ); - const workspaceSections = sections.filter((section) => - ["nodes", "agents"].includes(section.key), - ); - const toolSections = sections.filter((section) => - ["mcpServer", "clawHub", "secrets", "aiGateway"].includes(section.key), - ); - const footerSections = sections.filter((section) => - ["settings", "account"].includes(section.key), - ); const integrationRoute = profile?.profileScope === "tenant-shared" ? "/xworkmate/admin" : "/xworkmate/integrations"; - const canEditIntegrations = Boolean(profile?.canEditIntegrations); - const profileModeLabel = - profile?.profileScope === "tenant-shared" - ? pickCopy(isChinese, "共享配置", "Shared Profile") - : pickCopy(isChinese, "个人配置", "Personal Profile"); - const connectionHint = profile - ? profile.profileScope === "tenant-shared" && !profile.canEditIntegrations - ? pickCopy( - isChinese, - "当前是共享版工作台。只有管理员能修改连接配置,普通成员可直接使用已发布能力。", - "This is the shared workspace. Only administrators can change integrations, while members can use the published workspace.", - ) - : profile.profileScope === "tenant-shared" - ? pickCopy( - isChinese, - "你正在维护共享版连接配置,保存后会影响 svc.plus/xworkmate 的共享工作台。", - "You are editing the shared integrations profile for svc.plus/xworkmate.", - ) - : pickCopy( - isChinese, - "你正在使用租户独享工作台,连接配置只对当前用户生效。", - "You are using a tenant-private workspace, and the profile only affects the current member.", - ) - : pickCopy( - isChinese, - "未检测到租户配置,当前仍会回退到浏览器会话内的默认连接。", - "No tenant profile was resolved yet, so the workspace falls back to browser-session defaults.", - ); - const primaryActionLabel = canEditIntegrations - ? pickCopy(isChinese, "打开配置页", "Open Config") - : pickCopy(isChinese, "查看状态", "View Status"); - const secondaryActionLabel = canEditIntegrations - ? pickCopy(isChinese, "管理连接", "Manage Integrations") - : pickCopy(isChinese, "等待管理员配置", "Await Admin Setup"); + const canManageIntegrations = profile + ? Boolean(profile.canEditIntegrations) + : true; + const profileBadge = profile + ? [ + profile.edition === "shared_public" + ? pickCopy(isChinese, "共享版", "Shared edition") + : pickCopy(isChinese, "租户独享版", "Tenant edition"), + profile.membershipRole, + requestHost ?? "", + ] + .filter(Boolean) + .join(" · ") + : undefined; - const openConnections = () => { - if (!canEditIntegrations) { - return; - } + const taskTitle = resolveTaskTitle( + assistantState?.selectedSessionLabel, + selectedSessionKey || initialSessionKey || "main", + isChinese, + ); + const lastMessage = + assistantState && assistantState.messages.length > 0 + ? assistantState.messages[assistantState.messages.length - 1] + : undefined; + const taskPreview = + lastMessage?.text?.trim() || + pickCopy( + isChinese, + "连接配置完成后,当前任务会在这里持续同步状态与结果。", + "Once the gateway is configured, the current task will keep syncing progress and results here.", + ); + const taskUpdatedLabel = formatRelativeTime( + lastMessage?.timestampMs, + isChinese, + ); + const runningCount = + assistantState?.connectionState === "connecting" || + Boolean(assistantState?.streamingText.trim()) + ? 1 + : 0; + const currentCount = + (assistantState?.messages.length ?? 0) > 0 || + Boolean(assistantState?.streamingText.trim()) + ? 1 + : 0; + const taskCount = searchValue.trim() + ? taskTitle.toLowerCase().includes(searchValue.trim().toLowerCase()) + ? 1 + : 0 + : 1; + const hasVisibleTask = taskCount > 0; + const statusLabel = connected + ? runningCount > 0 + ? pickCopy(isChinese, "运行中", "Running") + : pickCopy(isChinese, "当前任务", "Current task") + : pickCopy(isChinese, "排队中", "Queued"); + const connectionLabel = connected + ? `${pickCopy(isChinese, "仅 AI Gateway", "AI Gateway only")} · ${endpointLabel}` + : `${pickCopy(isChinese, "仅 AI Gateway", "AI Gateway only")} · ${pickCopy( + isChinese, + "AI Gateway 未配置", + "AI Gateway not configured", + )}`; + const primaryActionLabel = canManageIntegrations + ? pickCopy(isChinese, "配置 AI Gateway", "Configure AI Gateway") + : pickCopy(isChinese, "查看 AI Gateway", "View AI Gateway"); + const secondaryActionLabel = canManageIntegrations + ? pickCopy(isChinese, "打开 AI Gateway", "Open AI Gateway") + : pickCopy(isChinese, "等待管理员配置", "Await admin setup"); + + const openIntegrations = () => { router.push(integrationRoute); }; + const resetToDefaultTask = () => { + setActiveSection("assistant"); + setSelectedSessionKey(""); + }; + return ( -
-
+
+
+ setSidebarExpanded((current) => !current)} + /> + {sidebarExpanded ? ( - - ) : ( -
- -
- )} - -
-
-
- {profile ? ( -
- - - {profile.edition === "shared_public" - ? pickCopy(isChinese, "共享版", "Shared Edition") - : pickCopy(isChinese, "租户独享版", "Tenant Edition")} - - · - {profile.tenant.name} - · - {profile.membershipRole} - · - {profileModeLabel} - {requestHost ? ( - <> - · - {requestHost} - - ) : null} + {activeSection === "assistant" ? ( + <> +
+ {connected ? ( +
+ +
+ ) : ( + + )}
- ) : null} - {activeSection === "assistant" ? ( - - ) : ( - + ) : null} + + ) : ( +
+ - )} -
+
+ )}
-
- - {connected - ? `${pickCopy(isChinese, "在线网关", "Gateway Online")} · ${configuredCount}/3` - : `${pickCopy(isChinese, "集成概况", "Integrations")} · ${configuredCount}/3`} +
+ + + {connected + ? `${pickCopy(isChinese, "网关在线", "Gateway online")} · ${configuredCount}/3` + : `${pickCopy(isChinese, "集成概况", "Integrations")} · ${configuredCount}/3`} +
);