@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.
- package/client/agent-preview/PreviewActions.tsx +56 -0
- package/client/agent-preview/useAutoCommit.ts +91 -0
- package/client/components/AgentCommandMenu.tsx +51 -0
- package/client/components/AgentConversationPreview.tsx +620 -0
- package/client/components/AgentConversationTimeline.tsx +177 -0
- package/client/components/AgentCreatorPanel.tsx +152 -0
- package/client/components/AgentQuestionCard.tsx +244 -0
- package/client/components/AgentRow.tsx +108 -0
- package/client/components/BadgeWithTooltip.tsx +119 -0
- package/client/components/BranchRow.tsx +121 -0
- package/client/components/ControlGlyph.tsx +18 -0
- package/client/components/HeatmapCalendar.tsx +165 -0
- package/client/components/ModelBrowserMenu.tsx +135 -0
- package/client/components/PaseoAssistantMessage.tsx +216 -0
- package/client/components/PaseoShellDetail.tsx +128 -0
- package/client/components/PaseoStreamBadge.tsx +94 -0
- package/client/components/PaseoTimelineItem.tsx +147 -0
- package/client/components/PaseoToolCallGroup.tsx +52 -0
- package/client/components/PaseoToolDetail.tsx +55 -0
- package/client/components/ProjectCard.tsx +196 -0
- package/client/components/ProjectIcon.tsx +49 -0
- package/client/components/ProviderBrandIcon.tsx +52 -0
- package/client/components/ResizeHandle.tsx +91 -0
- package/client/components/SharedComposerInput.tsx +383 -0
- package/client/components/Tooltip.tsx +49 -0
- package/client/components/WorkspaceAgentRow.tsx +93 -0
- package/client/components/WorkspaceBadge.tsx +39 -0
- package/client/components/WorkspaceCreateComposer.tsx +170 -0
- package/client/components/WorkspaceCreatorPanel.tsx +256 -0
- package/client/components/WorkspaceLaunchMenu.tsx +76 -0
- package/client/components/WorkspaceMenu.tsx +116 -0
- package/client/components/WorkspaceSelectControl.tsx +52 -0
- package/client/components/agent-conversation-types.ts +7 -0
- package/client/components/workspace-creator-styles.ts +322 -0
- package/client/hooks/useAgentActivityHeatmap.ts +178 -0
- package/client/hooks/useFilter.ts +107 -0
- package/client/hooks/useKeyboardInset.ts +37 -0
- package/client/hooks/useResizableWidth.ts +70 -0
- package/client/hooks/useWorkspaces.ts +128 -0
- package/client/overview/useProjectOptions.ts +73 -0
- package/client/web.ts +160 -0
- package/client/workspace-agents.tsx +71 -0
- package/client/workspace-creator/badge-styles.ts +13 -0
- package/client/workspace-creator/constants.ts +14 -0
- package/client/workspace-creator/control-styles.ts +12 -0
- package/client/workspace-creator/provider-selection.ts +51 -0
- package/client/workspace-creator/submit-workspace.ts +67 -0
- package/client/workspace-creator/types.ts +40 -0
- package/client/workspace-creator/ui-utils.ts +15 -0
- package/client/workspace-creator/useProviderCatalog.ts +89 -0
- package/client/workspace-overview.tsx +533 -0
- package/index.client.tsx +36 -0
- package/index.server.ts +33 -0
- package/package.json +28 -4
- package/paseo-plugin.json +4 -0
- package/server/agent-config-impl.ts +57 -0
- package/server/auto-commit-impl.ts +221 -0
- package/server/git-branches-impl.ts +81 -0
- package/server/project-icon-impl.ts +102 -0
- package/server/terminal-launch-impl.ts +56 -0
- package/shared/agent-activity.ts +16 -0
- package/shared/agent-config.ts +30 -0
- package/shared/auto-commit.ts +38 -0
- package/shared/git-branches.ts +18 -0
- package/shared/overview-types.ts +87 -0
- package/shared/project-icon.ts +13 -0
- package/shared/terminal-launch.ts +20 -0
- package/README.md +0 -3
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { ScrollView, Text, View, type TextStyle, type ViewStyle } from "react-native";
|
|
3
|
+
import type { PluginSurfaceProps } from "@getpaseo/plugin/client";
|
|
4
|
+
import { PaseoAssistantMessage } from "./PaseoAssistantMessage";
|
|
5
|
+
|
|
6
|
+
type ToolDetail = Record<string, any> & { type?: string };
|
|
7
|
+
|
|
8
|
+
function text(value: unknown): string {
|
|
9
|
+
if (typeof value === "string") return value;
|
|
10
|
+
if (value == null) return "";
|
|
11
|
+
try { return JSON.stringify(value, null, 2); } catch { return String(value); }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function CodePanel({ value, theme, diff = false }: { value: string; theme: PluginSurfaceProps["theme"]; diff?: boolean }) {
|
|
15
|
+
return <ScrollView style={{ maxHeight: 420, backgroundColor: theme.colors.surface1 }} nestedScrollEnabled>
|
|
16
|
+
<ScrollView horizontal nestedScrollEnabled contentContainerStyle={{ minWidth: "100%" }}>
|
|
17
|
+
<View style={{ minWidth: "100%", paddingHorizontal: 12, paddingVertical: 10 }}>
|
|
18
|
+
{value.split("\n").map((line, index) => {
|
|
19
|
+
const added = diff && line.startsWith("+") && !line.startsWith("+++");
|
|
20
|
+
const removed = diff && line.startsWith("-") && !line.startsWith("---");
|
|
21
|
+
return <Text key={index} selectable style={{ color: added ? theme.colors.statusSuccess : removed ? theme.colors.statusDanger : theme.colors.foreground, backgroundColor: added ? theme.colors.statusSuccess + "18" : removed ? theme.colors.statusDanger + "18" : "transparent", fontFamily: "monospace", fontSize: 12, lineHeight: 18 }}>{line || " "}</Text>;
|
|
22
|
+
})}
|
|
23
|
+
</View>
|
|
24
|
+
</ScrollView>
|
|
25
|
+
</ScrollView>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function PaseoToolDetail({ detail, theme }: { detail: ToolDetail; theme: PluginSurfaceProps["theme"] }) {
|
|
29
|
+
const styles = useMemo(() => ({
|
|
30
|
+
root: { gap: 8, backgroundColor: theme.colors.surface1, padding: 10 } as ViewStyle,
|
|
31
|
+
meta: { color: theme.colors.foregroundMuted, fontSize: 11, lineHeight: 16 } as TextStyle,
|
|
32
|
+
label: { color: theme.colors.foreground, fontSize: 12, fontWeight: "600" } as TextStyle,
|
|
33
|
+
result: { color: theme.colors.foreground, fontSize: 12, lineHeight: 18 } as TextStyle,
|
|
34
|
+
row: { gap: 3, paddingVertical: 4 } as ViewStyle,
|
|
35
|
+
}), [theme]);
|
|
36
|
+
|
|
37
|
+
if (detail.type === "plan") return <View style={styles.root}><PaseoAssistantMessage text={text(detail.text)} theme={theme} /></View>;
|
|
38
|
+
if (detail.type === "read" || detail.type === "write") {
|
|
39
|
+
const content = text(detail.content);
|
|
40
|
+
return <View style={styles.root}><Text selectable style={styles.meta}>{detail.filePath}{detail.offset != null ? ` · lines ${detail.offset}${detail.limit ? `–${detail.offset + detail.limit}` : ""}` : ""}</Text>{content ? <CodePanel value={content} theme={theme} /> : null}</View>;
|
|
41
|
+
}
|
|
42
|
+
if (detail.type === "edit") {
|
|
43
|
+
const diff = text(detail.unifiedDiff) || [text(detail.oldString).split("\n").map((line) => `-${line}`).join("\n"), text(detail.newString).split("\n").map((line) => `+${line}`).join("\n")].filter(Boolean).join("\n");
|
|
44
|
+
return <View style={styles.root}><Text selectable style={styles.meta}>{detail.filePath}</Text>{diff ? <CodePanel value={diff} theme={theme} diff /> : null}</View>;
|
|
45
|
+
}
|
|
46
|
+
if (detail.type === "search") {
|
|
47
|
+
const summary = [detail.numMatches != null ? `${detail.numMatches} matches` : null, detail.numFiles != null ? `${detail.numFiles} files` : null, detail.durationMs != null ? `${detail.durationMs}ms` : detail.durationSeconds != null ? `${detail.durationSeconds}s` : null, detail.truncated ? "truncated" : null].filter(Boolean).join(" · ");
|
|
48
|
+
return <View style={styles.root}><Text selectable style={styles.meta}>{detail.query}{summary ? ` · ${summary}` : ""}</Text>{detail.filePaths?.map((path: string) => <Text key={path} selectable style={styles.result}>{path}</Text>)}{detail.webResults?.map((result: { title: string; url: string }) => <View key={result.url} style={styles.row}><Text style={styles.label}>{result.title}</Text><Text selectable style={styles.meta}>{result.url}</Text></View>)}{detail.content ? <CodePanel value={text(detail.content)} theme={theme} /> : null}</View>;
|
|
49
|
+
}
|
|
50
|
+
if (detail.type === "fetch") return <View style={styles.root}><Text selectable style={styles.meta}>{detail.url}{detail.code != null ? ` · ${detail.code}${detail.codeText ? ` ${detail.codeText}` : ""}` : ""}{detail.bytes != null ? ` · ${detail.bytes} bytes` : ""}</Text>{detail.prompt ? <Text selectable style={styles.label}>{detail.prompt}</Text> : null}{detail.result ? <Text selectable style={styles.result}>{detail.result}</Text> : null}</View>;
|
|
51
|
+
if (detail.type === "plain_text") return <View style={styles.root}>{detail.label ? <Text style={styles.label}>{detail.label}</Text> : null}<Text selectable style={styles.result}>{text(detail.text)}</Text></View>;
|
|
52
|
+
if (detail.type === "sub_agent") return <View style={styles.root}>{detail.description ? <Text selectable style={styles.label}>{detail.description}</Text> : null}{detail.actions?.map((action: { index: number; toolName: string; summary?: string }) => <Text key={action.index} selectable style={styles.meta}>{action.index + 1}. {action.toolName}{action.summary ? ` · ${action.summary}` : ""}</Text>)}{detail.log ? <CodePanel value={text(detail.log)} theme={theme} /> : null}</View>;
|
|
53
|
+
if (detail.type === "worktree_setup") return <View style={styles.root}><Text selectable style={styles.meta}>{detail.branchName} · {detail.worktreePath}</Text>{detail.commands?.map((command: { index: number; command: string; status: string; exitCode?: number | null }) => <Text key={command.index} selectable style={{ color: command.status === "failed" ? theme.colors.statusDanger : theme.colors.foreground, fontFamily: "monospace", fontSize: 12 }}>{command.status === "completed" ? "✓" : command.status === "failed" ? "✕" : "○"} {command.command}{command.exitCode != null ? ` (${command.exitCode})` : ""}</Text>)}{detail.log ? <CodePanel value={text(detail.log)} theme={theme} /> : null}</View>;
|
|
54
|
+
return <View style={styles.root}><CodePanel value={text(detail)} theme={theme} /></View>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React, { useCallback, useMemo } from "react";
|
|
2
|
+
import { Text, TouchableOpacity, View, type ViewStyle, type TextStyle } from "react-native";
|
|
3
|
+
import { type PluginSurfaceProps } from "@getpaseo/plugin/client";
|
|
4
|
+
import { Icon } from "@getpaseo/plugin/client/react-native";
|
|
5
|
+
import { type WorkspaceEntry, type AgentEntry, type ProjectCardProps } from "../../shared/overview-types";
|
|
6
|
+
import { BranchRow } from "./BranchRow";
|
|
7
|
+
import { AgentRow } from "./AgentRow";
|
|
8
|
+
|
|
9
|
+
const CARD_GAP = 12;
|
|
10
|
+
|
|
11
|
+
/** Decide whether an agent should auto-expand its surroundings on first view. */
|
|
12
|
+
function shouldAutoExpandAgent(agent: AgentEntry): boolean {
|
|
13
|
+
return (
|
|
14
|
+
agent.status === "running" ||
|
|
15
|
+
agent.status === "error" ||
|
|
16
|
+
agent.requiresAttention === true
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ProjectCard({
|
|
21
|
+
projectId,
|
|
22
|
+
projectDisplayName,
|
|
23
|
+
workspaces,
|
|
24
|
+
agentsByWorkspace,
|
|
25
|
+
expanded,
|
|
26
|
+
cardExpanded,
|
|
27
|
+
onToggleCard,
|
|
28
|
+
onToggleBranch,
|
|
29
|
+
onOpenDirectory,
|
|
30
|
+
theme,
|
|
31
|
+
compact,
|
|
32
|
+
width,
|
|
33
|
+
hostLabel,
|
|
34
|
+
hostId,
|
|
35
|
+
onCreateWorktree,
|
|
36
|
+
onCreateAgent,
|
|
37
|
+
onSelectAgent,
|
|
38
|
+
}: ProjectCardProps) {
|
|
39
|
+
// Card-level expand: explicit user toggle wins; otherwise auto-expand when
|
|
40
|
+
// any branch contains a running/errored/attention agent.
|
|
41
|
+
// Derive only the agents arrays relevant to this project's workspaces,
|
|
42
|
+
// avoiding a dependency on the entire agentsByWorkspace Map.
|
|
43
|
+
const projectAgentsArrays = useMemo(
|
|
44
|
+
() => workspaces.map((ws) => agentsByWorkspace.get(ws.id) ?? []),
|
|
45
|
+
[workspaces, agentsByWorkspace],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const cardIsExpanded = useMemo(() => {
|
|
49
|
+
const explicit = cardExpanded[projectId];
|
|
50
|
+
if (explicit !== undefined) return explicit;
|
|
51
|
+
return projectAgentsArrays.some((agents) => agents.some(shouldAutoExpandAgent));
|
|
52
|
+
}, [cardExpanded, projectId, projectAgentsArrays]);
|
|
53
|
+
|
|
54
|
+
const toggleCard = useCallback(
|
|
55
|
+
() => onToggleCard(projectId, cardIsExpanded),
|
|
56
|
+
[onToggleCard, projectId, cardIsExpanded],
|
|
57
|
+
);
|
|
58
|
+
const styles = useMemo(
|
|
59
|
+
() =>
|
|
60
|
+
({
|
|
61
|
+
card: {
|
|
62
|
+
width,
|
|
63
|
+
backgroundColor: theme.colors.surface0,
|
|
64
|
+
borderRadius: 12,
|
|
65
|
+
overflow: "hidden" as const,
|
|
66
|
+
marginBottom: CARD_GAP,
|
|
67
|
+
borderWidth: 1,
|
|
68
|
+
borderColor: theme.colors.foregroundMuted + "33",
|
|
69
|
+
shadowColor: "#000",
|
|
70
|
+
shadowOpacity: 0.1,
|
|
71
|
+
shadowRadius: 12,
|
|
72
|
+
shadowOffset: { width: 0, height: 6 },
|
|
73
|
+
elevation: 3,
|
|
74
|
+
} as ViewStyle,
|
|
75
|
+
header: {
|
|
76
|
+
flexDirection: "row" as const,
|
|
77
|
+
alignItems: "center" as const,
|
|
78
|
+
justifyContent: "space-between" as const,
|
|
79
|
+
paddingHorizontal: 12,
|
|
80
|
+
paddingVertical: 10,
|
|
81
|
+
backgroundColor: theme.colors.foregroundMuted + "14",
|
|
82
|
+
borderBottomWidth: 1,
|
|
83
|
+
borderBottomColor: theme.colors.foregroundMuted + "26",
|
|
84
|
+
} as ViewStyle,
|
|
85
|
+
headerChevron: {
|
|
86
|
+
width: 16,
|
|
87
|
+
marginRight: 4,
|
|
88
|
+
alignItems: "center" as const,
|
|
89
|
+
justifyContent: "center" as const,
|
|
90
|
+
} as ViewStyle,
|
|
91
|
+
title: {
|
|
92
|
+
color: theme.colors.foreground,
|
|
93
|
+
fontSize: 15,
|
|
94
|
+
fontWeight: "600" as const,
|
|
95
|
+
flex: 1,
|
|
96
|
+
} as TextStyle,
|
|
97
|
+
counter: {
|
|
98
|
+
color: theme.colors.foregroundMuted,
|
|
99
|
+
fontSize: 11,
|
|
100
|
+
marginLeft: 8,
|
|
101
|
+
} as TextStyle,
|
|
102
|
+
createBtn: {
|
|
103
|
+
width: 26,
|
|
104
|
+
height: 26,
|
|
105
|
+
alignItems: "center" as const,
|
|
106
|
+
justifyContent: "center" as const,
|
|
107
|
+
marginLeft: 8,
|
|
108
|
+
} as ViewStyle,
|
|
109
|
+
body: {
|
|
110
|
+
paddingVertical: 0,
|
|
111
|
+
} as ViewStyle,
|
|
112
|
+
divider: {
|
|
113
|
+
height: 1,
|
|
114
|
+
backgroundColor: theme.colors.foregroundMuted + "14",
|
|
115
|
+
marginLeft: 12,
|
|
116
|
+
} as ViewStyle,
|
|
117
|
+
}),
|
|
118
|
+
[theme, width],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const totalAgents = useMemo(
|
|
122
|
+
() => workspaces.reduce((sum, ws) => sum + (agentsByWorkspace.get(ws.id)?.length ?? 0), 0),
|
|
123
|
+
[workspaces, agentsByWorkspace],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<View style={styles.card}>
|
|
128
|
+
<TouchableOpacity onPress={toggleCard} activeOpacity={0.7}>
|
|
129
|
+
<View style={styles.header}>
|
|
130
|
+
<View style={styles.headerChevron}>
|
|
131
|
+
<Icon name={cardIsExpanded ? "ChevronDown" : "ChevronRight"} color={theme.colors.foregroundMuted} size={10} />
|
|
132
|
+
</View>
|
|
133
|
+
<Text style={styles.title} numberOfLines={1}>
|
|
134
|
+
{projectDisplayName}
|
|
135
|
+
</Text>
|
|
136
|
+
<Text style={styles.counter}>
|
|
137
|
+
{workspaces.length} branch{workspaces.length !== 1 ? "es" : ""}
|
|
138
|
+
{totalAgents > 0 ? ` · ${totalAgents} agent${totalAgents !== 1 ? "s" : ""}` : ""}
|
|
139
|
+
</Text>
|
|
140
|
+
{onCreateWorktree ? (
|
|
141
|
+
<TouchableOpacity
|
|
142
|
+
accessibilityRole="button"
|
|
143
|
+
accessibilityLabel={`为 ${projectDisplayName} 新建 workspace`}
|
|
144
|
+
style={styles.createBtn}
|
|
145
|
+
onPress={(event) => {
|
|
146
|
+
event.stopPropagation();
|
|
147
|
+
onCreateWorktree();
|
|
148
|
+
}}
|
|
149
|
+
hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }}
|
|
150
|
+
>
|
|
151
|
+
<Icon name="Plus" color={theme.colors.foregroundMuted} size={18} />
|
|
152
|
+
</TouchableOpacity>
|
|
153
|
+
) : null}
|
|
154
|
+
</View>
|
|
155
|
+
</TouchableOpacity>
|
|
156
|
+
{cardIsExpanded && (
|
|
157
|
+
<View style={styles.body}>
|
|
158
|
+
{workspaces.map((ws, idx) => {
|
|
159
|
+
const wsAgents = agentsByWorkspace.get(ws.id) ?? [];
|
|
160
|
+
// If the user hasn't manually toggled this branch yet (undefined),
|
|
161
|
+
// auto-expand when it contains a running/errored/attention agent.
|
|
162
|
+
const explicit = expanded[ws.id];
|
|
163
|
+
const isExpanded = explicit !== undefined ? explicit : wsAgents.some(shouldAutoExpandAgent);
|
|
164
|
+
return (
|
|
165
|
+
<View key={ws.id}>
|
|
166
|
+
{idx > 0 && <View style={styles.divider} />}
|
|
167
|
+
<BranchRow
|
|
168
|
+
workspace={ws}
|
|
169
|
+
agents={wsAgents}
|
|
170
|
+
expanded={isExpanded}
|
|
171
|
+
onToggle={() => onToggleBranch(ws.id, isExpanded)}
|
|
172
|
+
onOpenDirectory={() => onOpenDirectory(ws.workspaceDirectory)}
|
|
173
|
+
onCreateAgent={() => onCreateAgent(ws)}
|
|
174
|
+
theme={theme}
|
|
175
|
+
compact={compact}
|
|
176
|
+
hostLabel={hostLabel}
|
|
177
|
+
/>
|
|
178
|
+
{isExpanded &&
|
|
179
|
+
wsAgents.map((agent) => (
|
|
180
|
+
<AgentRow key={agent.id} agent={agent} theme={theme} compact={compact} onSelect={() => onSelectAgent(agent)} />
|
|
181
|
+
))}
|
|
182
|
+
{isExpanded && wsAgents.length === 0 && (
|
|
183
|
+
<View style={{ paddingLeft: 32, paddingRight: 12, paddingVertical: 6 }}>
|
|
184
|
+
<Text style={{ color: theme.colors.foregroundMuted, fontSize: 11 }}>
|
|
185
|
+
No agents in this branch.
|
|
186
|
+
</Text>
|
|
187
|
+
</View>
|
|
188
|
+
)}
|
|
189
|
+
</View>
|
|
190
|
+
);
|
|
191
|
+
})}
|
|
192
|
+
</View>
|
|
193
|
+
)}
|
|
194
|
+
</View>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text, View } from "react-native";
|
|
3
|
+
import type { PluginSurfaceProps } from "@getpaseo/plugin/client";
|
|
4
|
+
|
|
5
|
+
export function ProjectIcon({
|
|
6
|
+
dataUri,
|
|
7
|
+
label,
|
|
8
|
+
size = 18,
|
|
9
|
+
theme,
|
|
10
|
+
}: {
|
|
11
|
+
dataUri?: string | null;
|
|
12
|
+
label: string;
|
|
13
|
+
size?: number;
|
|
14
|
+
theme: PluginSurfaceProps["theme"];
|
|
15
|
+
}) {
|
|
16
|
+
if (dataUri) {
|
|
17
|
+
return (
|
|
18
|
+
<View
|
|
19
|
+
style={
|
|
20
|
+
{
|
|
21
|
+
width: size,
|
|
22
|
+
height: size,
|
|
23
|
+
borderRadius: 4,
|
|
24
|
+
backgroundImage: `url("${dataUri}")`,
|
|
25
|
+
backgroundSize: "contain",
|
|
26
|
+
backgroundRepeat: "no-repeat",
|
|
27
|
+
backgroundPosition: "center",
|
|
28
|
+
} as never
|
|
29
|
+
}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return (
|
|
34
|
+
<View
|
|
35
|
+
style={{
|
|
36
|
+
width: size,
|
|
37
|
+
height: size,
|
|
38
|
+
borderRadius: 4,
|
|
39
|
+
alignItems: "center",
|
|
40
|
+
justifyContent: "center",
|
|
41
|
+
backgroundColor: theme.colors.foregroundMuted + "20",
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<Text style={{ color: theme.colors.foreground, fontSize: 10, lineHeight: 14, fontWeight: "600" }}>
|
|
45
|
+
{label.trim().charAt(0).toUpperCase() || "?"}
|
|
46
|
+
</Text>
|
|
47
|
+
</View>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Platform, View } from "react-native";
|
|
3
|
+
import { Icon } from "@getpaseo/plugin/client/react-native";
|
|
4
|
+
|
|
5
|
+
interface ProviderBrandIconProps {
|
|
6
|
+
providerId: string;
|
|
7
|
+
size?: number;
|
|
8
|
+
color: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const PROVIDER_SVGS: Record<string, { viewBox: string; body: string }> = {
|
|
12
|
+
claude: {
|
|
13
|
+
viewBox: "0 0 24 24",
|
|
14
|
+
body: '<path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"/>',
|
|
15
|
+
},
|
|
16
|
+
codex: {
|
|
17
|
+
viewBox: "0 0 24 24",
|
|
18
|
+
body: '<path fill-rule="evenodd" d="M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.553 5.553 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.02.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.002zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z"/>',
|
|
19
|
+
},
|
|
20
|
+
minimax: { viewBox: "0 0 24 24", body: '<path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z"/>' },
|
|
21
|
+
omp: { viewBox: "4 4 56 56", body: '<path d="M10 14h44v9H43v33h-9V23h-9v22h-9V23H10z"/>' },
|
|
22
|
+
opencode: { viewBox: "96 64 288 384", body: '<path d="M320 224V352H192V224H320Z" opacity=".4"/><path fill-rule="evenodd" clip-rule="evenodd" d="M384 416H128V96H384V416ZM320 160H192V352H320V160Z"/>' },
|
|
23
|
+
pi: { viewBox: "100 100 600 600", body: '<path fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path d="M517.36 400 H634.72 V634.72 H517.36 Z"/>' },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function ProviderBrandIcon({ providerId, size = 16, color }: ProviderBrandIconProps) {
|
|
27
|
+
const normalized = providerId.toLowerCase();
|
|
28
|
+
const definition =
|
|
29
|
+
PROVIDER_SVGS[normalized] ??
|
|
30
|
+
(normalized.includes("codex") || normalized.includes("openai") ? PROVIDER_SVGS.codex : null) ??
|
|
31
|
+
(normalized.includes("claude") || normalized.includes("anthropic") ? PROVIDER_SVGS.claude : null);
|
|
32
|
+
if (!definition || Platform.OS !== "web") {
|
|
33
|
+
return <Icon name="Bot" color={color} size={size} />;
|
|
34
|
+
}
|
|
35
|
+
const xml = `<svg viewBox="${definition.viewBox}" fill="black" xmlns="http://www.w3.org/2000/svg">${definition.body}</svg>`;
|
|
36
|
+
return (
|
|
37
|
+
<View
|
|
38
|
+
style={
|
|
39
|
+
{
|
|
40
|
+
width: size,
|
|
41
|
+
height: size,
|
|
42
|
+
flexShrink: 0,
|
|
43
|
+
backgroundColor: color,
|
|
44
|
+
WebkitMaskImage: `url("data:image/svg+xml,${encodeURIComponent(xml)}")`,
|
|
45
|
+
WebkitMaskSize: "contain",
|
|
46
|
+
WebkitMaskRepeat: "no-repeat",
|
|
47
|
+
WebkitMaskPosition: "center",
|
|
48
|
+
} as never
|
|
49
|
+
}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { View, type ViewStyle } from "react-native";
|
|
3
|
+
import type { PluginSurfaceProps } from "@getpaseo/plugin/client";
|
|
4
|
+
import { useResizableWidth } from "../hooks/useResizableWidth";
|
|
5
|
+
|
|
6
|
+
export interface ResizeHandleProps {
|
|
7
|
+
theme: PluginSurfaceProps["theme"];
|
|
8
|
+
initialWidth?: number;
|
|
9
|
+
minWidth?: number;
|
|
10
|
+
maxWidth?: number;
|
|
11
|
+
/** Position the handle on the left or right edge of the panel. */
|
|
12
|
+
side?: "left" | "right";
|
|
13
|
+
/** Visual style: "grip" shows a vertical bar, "line" shows a thin line. */
|
|
14
|
+
variant?: "grip" | "line";
|
|
15
|
+
/** Additional style applied to the panel container. */
|
|
16
|
+
style?: ViewStyle;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A panel wrapper with a built-in drag-to-resize handle.
|
|
22
|
+
*
|
|
23
|
+
* Uses `useResizableWidth` internally. During drag, width is written directly
|
|
24
|
+
* to the DOM to avoid React re-renders. The committed width is exposed via
|
|
25
|
+
* the render context if needed.
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <ResizeHandle theme={theme} side="left" initialWidth={420} minWidth={320} maxWidth={720}>
|
|
30
|
+
* <PanelContent />
|
|
31
|
+
* </ResizeHandle>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function ResizeHandle({
|
|
35
|
+
theme,
|
|
36
|
+
initialWidth = 420,
|
|
37
|
+
minWidth = 320,
|
|
38
|
+
maxWidth = 720,
|
|
39
|
+
side = "left",
|
|
40
|
+
variant = "grip",
|
|
41
|
+
style,
|
|
42
|
+
children,
|
|
43
|
+
}: ResizeHandleProps) {
|
|
44
|
+
const { width, isDragging, isHovered, panelRef, onPointerDown, onPointerEnter, onPointerLeave } =
|
|
45
|
+
useResizableWidth({ initialWidth, minWidth, maxWidth, direction: side });
|
|
46
|
+
|
|
47
|
+
const active = isDragging || isHovered;
|
|
48
|
+
const color = isDragging
|
|
49
|
+
? theme.colors.accent
|
|
50
|
+
: isHovered
|
|
51
|
+
? theme.colors.foregroundMuted
|
|
52
|
+
: theme.colors.foregroundMuted + "55";
|
|
53
|
+
const bg = active ? theme.colors.foregroundMuted + "15" : "transparent";
|
|
54
|
+
|
|
55
|
+
const styles = useMemo(() => {
|
|
56
|
+
const handle: ViewStyle = {
|
|
57
|
+
position: "absolute",
|
|
58
|
+
[side]: -8,
|
|
59
|
+
top: 0,
|
|
60
|
+
bottom: 0,
|
|
61
|
+
width: 16,
|
|
62
|
+
justifyContent: "center",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
zIndex: 10,
|
|
65
|
+
};
|
|
66
|
+
const bar: ViewStyle =
|
|
67
|
+
variant === "grip"
|
|
68
|
+
? { width: 3, height: 22, borderRadius: 1.5, backgroundColor: color }
|
|
69
|
+
: { width: 2, flex: 1, borderRadius: 1, backgroundColor: color };
|
|
70
|
+
return {
|
|
71
|
+
panel: { width, ...style } as ViewStyle,
|
|
72
|
+
handle: { ...handle, cursor: "ew-resize" } as unknown as ViewStyle,
|
|
73
|
+
bar,
|
|
74
|
+
bg: { backgroundColor: bg } as ViewStyle,
|
|
75
|
+
};
|
|
76
|
+
}, [width, side, variant, color, bg, style]);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<View ref={panelRef} style={styles.panel}>
|
|
80
|
+
<View
|
|
81
|
+
style={[styles.handle, styles.bg]}
|
|
82
|
+
onPointerDown={onPointerDown}
|
|
83
|
+
onPointerEnter={onPointerEnter as () => void}
|
|
84
|
+
onPointerLeave={onPointerLeave as () => void}
|
|
85
|
+
>
|
|
86
|
+
<View style={styles.bar} />
|
|
87
|
+
</View>
|
|
88
|
+
{children}
|
|
89
|
+
</View>
|
|
90
|
+
);
|
|
91
|
+
}
|