@yuangc/paseo-plugin-workspace-overview 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/client/agent-preview/PreviewActions.tsx +56 -0
  2. package/client/agent-preview/useAutoCommit.ts +91 -0
  3. package/client/components/AgentCommandMenu.tsx +51 -0
  4. package/client/components/AgentConversationPreview.tsx +620 -0
  5. package/client/components/AgentConversationTimeline.tsx +177 -0
  6. package/client/components/AgentCreatorPanel.tsx +152 -0
  7. package/client/components/AgentQuestionCard.tsx +244 -0
  8. package/client/components/AgentRow.tsx +108 -0
  9. package/client/components/BadgeWithTooltip.tsx +119 -0
  10. package/client/components/BranchRow.tsx +121 -0
  11. package/client/components/ControlGlyph.tsx +18 -0
  12. package/client/components/HeatmapCalendar.tsx +165 -0
  13. package/client/components/ModelBrowserMenu.tsx +135 -0
  14. package/client/components/PaseoAssistantMessage.tsx +216 -0
  15. package/client/components/PaseoShellDetail.tsx +128 -0
  16. package/client/components/PaseoStreamBadge.tsx +94 -0
  17. package/client/components/PaseoTimelineItem.tsx +147 -0
  18. package/client/components/PaseoToolCallGroup.tsx +52 -0
  19. package/client/components/PaseoToolDetail.tsx +55 -0
  20. package/client/components/ProjectCard.tsx +196 -0
  21. package/client/components/ProjectIcon.tsx +49 -0
  22. package/client/components/ProviderBrandIcon.tsx +52 -0
  23. package/client/components/ResizeHandle.tsx +91 -0
  24. package/client/components/SharedComposerInput.tsx +383 -0
  25. package/client/components/Tooltip.tsx +49 -0
  26. package/client/components/WorkspaceAgentRow.tsx +93 -0
  27. package/client/components/WorkspaceBadge.tsx +39 -0
  28. package/client/components/WorkspaceCreateComposer.tsx +170 -0
  29. package/client/components/WorkspaceCreatorPanel.tsx +256 -0
  30. package/client/components/WorkspaceLaunchMenu.tsx +76 -0
  31. package/client/components/WorkspaceMenu.tsx +116 -0
  32. package/client/components/WorkspaceSelectControl.tsx +52 -0
  33. package/client/components/agent-conversation-types.ts +7 -0
  34. package/client/components/workspace-creator-styles.ts +322 -0
  35. package/client/hooks/useAgentActivityHeatmap.ts +178 -0
  36. package/client/hooks/useFilter.ts +107 -0
  37. package/client/hooks/useKeyboardInset.ts +37 -0
  38. package/client/hooks/useResizableWidth.ts +70 -0
  39. package/client/hooks/useWorkspaces.ts +128 -0
  40. package/client/overview/useProjectOptions.ts +73 -0
  41. package/client/web.ts +160 -0
  42. package/client/workspace-agents.tsx +71 -0
  43. package/client/workspace-creator/badge-styles.ts +13 -0
  44. package/client/workspace-creator/constants.ts +14 -0
  45. package/client/workspace-creator/control-styles.ts +12 -0
  46. package/client/workspace-creator/provider-selection.ts +51 -0
  47. package/client/workspace-creator/submit-workspace.ts +67 -0
  48. package/client/workspace-creator/types.ts +40 -0
  49. package/client/workspace-creator/ui-utils.ts +15 -0
  50. package/client/workspace-creator/useProviderCatalog.ts +89 -0
  51. package/client/workspace-overview.tsx +533 -0
  52. package/index.client.tsx +36 -0
  53. package/index.server.ts +33 -0
  54. package/package.json +28 -4
  55. package/paseo-plugin.json +4 -0
  56. package/server/agent-config-impl.ts +57 -0
  57. package/server/auto-commit-impl.ts +221 -0
  58. package/server/git-branches-impl.ts +81 -0
  59. package/server/project-icon-impl.ts +102 -0
  60. package/server/terminal-launch-impl.ts +56 -0
  61. package/shared/agent-activity.ts +16 -0
  62. package/shared/agent-config.ts +30 -0
  63. package/shared/auto-commit.ts +38 -0
  64. package/shared/git-branches.ts +18 -0
  65. package/shared/overview-types.ts +87 -0
  66. package/shared/project-icon.ts +13 -0
  67. package/shared/terminal-launch.ts +20 -0
  68. package/README.md +0 -3
@@ -0,0 +1,56 @@
1
+ import React, { useCallback, useRef } from "react";
2
+ import { Pressable, Text, View, type ViewStyle } from "react-native";
3
+ import { Icon } from "@getpaseo/plugin/client/react-native";
4
+
5
+ import { useTooltip } from "../components/Tooltip";
6
+
7
+ export function QuickActionButton({ icon, color, disabled, tooltip, accessibilityLabel, onPress, style }: {
8
+ icon: string; color: string; disabled?: boolean; tooltip: Array<{ key: string; value: string }>;
9
+ accessibilityLabel: string; onPress: () => void; style: ViewStyle;
10
+ }) {
11
+ const tooltipContext = useTooltip();
12
+ const buttonRef = useRef<View | null>(null);
13
+ const showTooltip = useCallback(() => {
14
+ if (!tooltipContext || !buttonRef.current) return;
15
+ buttonRef.current.measureInWindow((x, y, width, height) => {
16
+ tooltipContext.show({ x: x + width, y: y + height, lines: tooltip });
17
+ });
18
+ }, [tooltip, tooltipContext]);
19
+ const hideTooltip = useCallback(() => tooltipContext?.hide(), [tooltipContext]);
20
+
21
+ return (
22
+ <Pressable
23
+ ref={buttonRef}
24
+ accessibilityLabel={accessibilityLabel}
25
+ onPress={onPress}
26
+ disabled={disabled}
27
+ style={[style, disabled && { opacity: 0.55 }]}
28
+ {...({ onMouseEnter: showTooltip, onMouseLeave: hideTooltip } as Record<string, unknown>)}
29
+ >
30
+ <Icon name={icon} size={14} color={color} />
31
+ </Pressable>
32
+ );
33
+ }
34
+
35
+ export function AutoCommitErrorToast({ message, copied, colors, containerStyle, copyButtonStyle, onDismiss, onCopy }: {
36
+ message: string; copied: boolean;
37
+ colors: { statusDanger: string; foregroundMuted: string; foreground: string };
38
+ containerStyle: ViewStyle; copyButtonStyle: ViewStyle; onDismiss: () => void; onCopy: () => void;
39
+ }) {
40
+ return (
41
+ <View style={containerStyle}>
42
+ <View style={{ flexDirection: "row", alignItems: "flex-start" }}>
43
+ <Icon name="TriangleAlert" size={14} color={colors.statusDanger} />
44
+ <Text style={{ color: colors.statusDanger, fontSize: 12, fontWeight: "600", marginLeft: 7, flex: 1 }}>一键提交失败</Text>
45
+ <Pressable onPress={onDismiss} style={{ padding: 2 }}><Icon name="X" size={13} color={colors.foregroundMuted} /></Pressable>
46
+ </View>
47
+ <Text selectable numberOfLines={4} style={{ color: colors.foreground, fontSize: 12, lineHeight: 17, marginTop: 6 }}>{message}</Text>
48
+ <View style={{ flexDirection: "row", justifyContent: "flex-end", marginTop: 8 }}>
49
+ <Pressable onPress={onCopy} style={copyButtonStyle}>
50
+ <Icon name={copied ? "Check" : "Copy"} size={12} color={colors.foregroundMuted} />
51
+ <Text style={{ color: colors.foregroundMuted, fontSize: 11 }}>{copied ? "已复制" : "复制错误"}</Text>
52
+ </Pressable>
53
+ </View>
54
+ </View>
55
+ );
56
+ }
@@ -0,0 +1,91 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useRpc } from "@getpaseo/plugin/client";
3
+ import { copyText } from "@getpaseo/plugin/client/react-native";
4
+
5
+ import { autoCommitStartRpc, autoCommitStatusRpc } from "../../shared/auto-commit";
6
+
7
+ export type AutoCommitState =
8
+ | { kind: "idle" }
9
+ | { kind: "preparing"; message: string }
10
+ | { kind: "running"; taskId: string; message: string }
11
+ | { kind: "done"; taskId: string; message: string }
12
+ | { kind: "error"; message: string; taskId?: string };
13
+
14
+ export function useAutoCommit(cwd: string | undefined, onDone: () => void) {
15
+ const startTask = useRpc(autoCommitStartRpc);
16
+ const getTaskStatus = useRpc(autoCommitStatusRpc);
17
+ const [pending, setPending] = useState(false);
18
+ const [state, setState] = useState<AutoCommitState>({ kind: "idle" });
19
+ const [copied, setCopied] = useState(false);
20
+
21
+ const runRef = useRef<{ cancelled: boolean; cancelWait?: () => void } | null>(null);
22
+
23
+ useEffect(() => {
24
+ setPending(false);
25
+ setState({ kind: "idle" });
26
+ setCopied(false);
27
+ return () => {
28
+ const run = runRef.current;
29
+ if (run) {
30
+ run.cancelled = true;
31
+ run.cancelWait?.();
32
+ }
33
+ runRef.current = null;
34
+ };
35
+ }, [cwd]);
36
+
37
+ const start = useCallback(async () => {
38
+ if (runRef.current) return;
39
+ if (!cwd) {
40
+ setState({ kind: "error", message: "当前 agent 没有可用工作目录" });
41
+ return;
42
+ }
43
+ const run = { cancelled: false } as { cancelled: boolean; cancelWait?: () => void };
44
+ runRef.current = run;
45
+ setPending(true);
46
+ setCopied(false);
47
+ setState({ kind: "preparing", message: "正在启动 Pi 独立进程" });
48
+ try {
49
+ const { taskId } = await startTask({ cwd });
50
+ if (run.cancelled) return;
51
+ setState({ kind: "running", taskId, message: "Pi 已启动,正在提交" });
52
+ for (;;) {
53
+ await new Promise<void>((resolve) => {
54
+ const timer = setTimeout(resolve, 1_200);
55
+ run.cancelWait = () => { clearTimeout(timer); resolve(); };
56
+ });
57
+ run.cancelWait = undefined;
58
+ if (run.cancelled) return;
59
+ const status = await getTaskStatus({ taskId });
60
+ if (run.cancelled) return;
61
+ const message = status.output.trim().split("\n").filter(Boolean).slice(-1)[0] ?? "Pi 正在运行";
62
+ if (status.status === "running") {
63
+ setState({ kind: "running", taskId, message });
64
+ continue;
65
+ }
66
+ if (status.status === "done") {
67
+ setState({ kind: "done", taskId, message: message || "Pi 已完成提交" });
68
+ onDone();
69
+ return;
70
+ }
71
+ setState({ kind: "error", taskId, message: [status.error, status.output].filter(Boolean).join("\n").trim() || "Pi 提交失败" });
72
+ return;
73
+ }
74
+ } catch (error) {
75
+ if (run.cancelled) return;
76
+ setState({ kind: "error", message: error instanceof Error ? error.message : String(error) });
77
+ } finally {
78
+ if (runRef.current === run) {
79
+ runRef.current = null;
80
+ setPending(false);
81
+ }
82
+ }
83
+ }, [cwd, getTaskStatus, onDone, startTask]);
84
+
85
+ const copyError = useCallback(() => {
86
+ if (state.kind !== "error") return;
87
+ void copyText(state.message).then(() => setCopied(true)).catch(() => {});
88
+ }, [state]);
89
+
90
+ return { state, pending, copied, start, copyError, dismiss: () => setState({ kind: "idle" }) };
91
+ }
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+ import { ActivityIndicator, Pressable, ScrollView, Text, View } from "react-native";
3
+ import { Icon } from "@getpaseo/plugin/client/react-native";
4
+ import type { PluginSurfaceProps } from "@getpaseo/plugin/client";
5
+
6
+ export interface AgentSlashCommand {
7
+ name: string;
8
+ description: string;
9
+ argumentHint: string;
10
+ kind?: "command" | "skill";
11
+ }
12
+
13
+ export function filterAgentCommands(commands: AgentSlashCommand[], query: string) {
14
+ const normalized = query.trim().toLowerCase();
15
+ return commands.filter((command) => command.name.toLowerCase().includes(normalized)).slice(0, 12);
16
+ }
17
+
18
+ export function AgentCommandMenu({ commands, query, loading, error, theme, activeIndex, onSelect }: {
19
+ commands: AgentSlashCommand[];
20
+ query: string;
21
+ loading: boolean;
22
+ error: string | null;
23
+ theme: PluginSurfaceProps["theme"];
24
+ activeIndex: number;
25
+ onSelect: (command: AgentSlashCommand) => void;
26
+ }) {
27
+ const matches = filterAgentCommands(commands, query);
28
+ return <View style={{ maxHeight: 260, marginBottom: 8, borderWidth: 1, borderColor: theme.colors.border, borderRadius: 8, backgroundColor: theme.colors.surface1, overflow: "hidden" }}>
29
+ {loading ? <View style={{ minHeight: 52, alignItems: "center", justifyContent: "center" }}><ActivityIndicator size="small" color={theme.colors.foregroundMuted} /></View> : null}
30
+ {!loading && error ? <Text style={{ padding: 12, color: theme.colors.statusDanger, fontSize: 12 }}>{error}</Text> : null}
31
+ {!loading && !error && matches.length === 0 ? <Text style={{ padding: 12, color: theme.colors.foregroundMuted, fontSize: 12 }}>没有匹配的命令</Text> : null}
32
+ {!loading && !error ? <ScrollView keyboardShouldPersistTaps="handled">
33
+ {matches.map((command, index) => <Pressable
34
+ key={`${command.kind ?? "command"}:${command.name}`}
35
+ accessibilityRole="button"
36
+ accessibilityLabel={`选择命令 /${command.name}`}
37
+ onPress={() => onSelect(command)}
38
+ style={({ hovered, pressed }: { hovered?: boolean; pressed?: boolean }) => ({ paddingHorizontal: 12, paddingVertical: 9, flexDirection: "row", gap: 10, backgroundColor: index === activeIndex || hovered || pressed ? theme.colors.surface2 : theme.colors.surface1 })}
39
+ >
40
+ <Icon name={command.kind === "skill" ? "Sparkles" : "Terminal"} size={14} color={theme.colors.foregroundMuted} />
41
+ <View style={{ flex: 1, minWidth: 0 }}>
42
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
43
+ <Text style={{ color: theme.colors.foreground, fontSize: 13, fontWeight: "600" }}>/{command.name}</Text>
44
+ {command.argumentHint ? <Text style={{ color: theme.colors.foregroundMuted, fontSize: 12 }}>{command.argumentHint}</Text> : null}
45
+ </View>
46
+ {command.description ? <Text numberOfLines={2} style={{ color: theme.colors.foregroundMuted, fontSize: 12, marginTop: 2 }}>{command.description}</Text> : null}
47
+ </View>
48
+ </Pressable>)}
49
+ </ScrollView> : null}
50
+ </View>;
51
+ }