conductor-oss 0.3.1 → 0.3.3
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/node_modules/@conductor-oss/plugin-agent-amp/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-ccr/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-claude-code/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-codex/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-cursor-cli/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-droid/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-gemini/dist/index.d.ts +44 -2
- package/node_modules/@conductor-oss/plugin-agent-gemini/dist/index.js +203 -17
- package/node_modules/@conductor-oss/plugin-agent-gemini/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-github-copilot/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-opencode/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-qwen-code/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-mcp-server/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-notifier-desktop/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-notifier-discord/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-runtime-tmux/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-scm-github/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-terminal-web/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-tracker-github/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-workspace-worktree/package.json +1 -1
- package/package.json +24 -23
- package/web/.next/standalone/packages/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/packages/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/packages/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/page/react-loadable-manifest.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__6622b514._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__869d9ac0._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__9dc23e5a._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__c3eb4913._.js → [root-of-the-server]__a324b6db._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__b388693f._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_0e1412de._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_3acfb388._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_668c9201._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_69e05fca._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_80efe193._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_b6d31783._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_c0f0e227._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_f36ddaa9._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_@clerk_nextjs_dist_esm_app-router_129dde21._.js → node_modules_@clerk_nextjs_dist_esm_app-router_6ed7a74d._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_9e576054._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_f2ebd7a9._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_79316445._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_a078c137._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_app_page_tsx_cd282e82._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{packages_web_src_components_aad7de7c._.js → packages_web_src_components_80ce6eea._.js} +1 -1
- package/web/.next/standalone/packages/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.json +8 -8
- package/web/.next/standalone/packages/web/.next/static/chunks/25438e9094804f85.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/{5c5637796e242130.js → 34d660195aaf8635.js} +2 -2
- package/web/.next/standalone/packages/web/.next/static/chunks/{07a5ac7389686572.js → 3814813ead38f9ae.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/524a7c2a8e85ea2f.js +1 -0
- package/web/.next/{static/chunks/e8d146cc2723147b.js → standalone/packages/web/.next/static/chunks/5672ef74a562c5dd.js} +1 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/77d150e9b7dda43c.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/8ec81b945f12169b.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/aa158726d4a10331.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/d3cd3cf58c908ec9.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/df9658182f4f7d54.css +3 -0
- package/web/.next/standalone/packages/web/package.json +1 -0
- package/web/.next/standalone/packages/web/src/components/layout/Sidebar.tsx +20 -2
- package/web/.next/standalone/packages/web/src/components/layout/WorkspaceSidebarPanel.tsx +64 -74
- package/web/.next/standalone/packages/web/src/components/sessions/SessionOverview.tsx +114 -50
- package/web/.next/standalone/packages/web/src/features/dashboard/DashboardClient.tsx +459 -115
- package/web/.next/standalone/packages/web/src/features/dashboard/components/WorkspaceOverview.tsx +13 -31
- package/web/.next/standalone/packages/web/src/features/sessions/SessionPageClient.tsx +4 -1
- package/web/.next/standalone/packages/web/src/hooks/useSessions.ts +13 -0
- package/web/.next/standalone/packages/web/src/lib/types.ts +3 -0
- package/web/.next/static/chunks/25438e9094804f85.js +1 -0
- package/web/.next/static/chunks/{5c5637796e242130.js → 34d660195aaf8635.js} +2 -2
- package/web/.next/static/chunks/{07a5ac7389686572.js → 3814813ead38f9ae.js} +1 -1
- package/web/.next/static/chunks/524a7c2a8e85ea2f.js +1 -0
- package/web/.next/{standalone/packages/web/.next/static/chunks/e8d146cc2723147b.js → static/chunks/5672ef74a562c5dd.js} +1 -1
- package/web/.next/static/chunks/77d150e9b7dda43c.js +1 -0
- package/web/.next/static/chunks/8ec81b945f12169b.js +1 -0
- package/web/.next/static/chunks/aa158726d4a10331.js +1 -0
- package/web/.next/static/chunks/d3cd3cf58c908ec9.js +1 -0
- package/web/.next/static/chunks/df9658182f4f7d54.css +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_1776df7f._.js +0 -3
- package/web/.next/standalone/packages/web/.next/static/chunks/3603312f8407bd12.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/382be8f0b30c5f08.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/479b8cb58c95a8a0.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/53e4f3b495e423c9.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/5e05e67a26b7ecbc.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/6d083840413686dd.css +0 -3
- package/web/.next/standalone/packages/web/.next/static/chunks/c7c1bd729f17c8bf.js +0 -1
- package/web/.next/static/chunks/3603312f8407bd12.js +0 -1
- package/web/.next/static/chunks/382be8f0b30c5f08.js +0 -1
- package/web/.next/static/chunks/479b8cb58c95a8a0.js +0 -1
- package/web/.next/static/chunks/53e4f3b495e423c9.js +0 -1
- package/web/.next/static/chunks/5e05e67a26b7ecbc.js +0 -1
- package/web/.next/static/chunks/6d083840413686dd.css +0 -3
- package/web/.next/static/chunks/c7c1bd729f17c8bf.js +0 -1
- /package/web/.next/standalone/packages/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_buildManifest.js +0 -0
- /package/web/.next/standalone/packages/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/packages/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_ssgManifest.js +0 -0
- /package/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_buildManifest.js +0 -0
- /package/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{oFTStwDpHHac4pinj0rAJ → -Z2NIcYE-i0soCDJ1TgtS}/_ssgManifest.js +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import dynamic from "next/dynamic";
|
|
4
4
|
import { type FormEvent, memo, useCallback, useEffect, useMemo, useState } from "react";
|
|
5
5
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
6
|
+
import { GitBranchIcon, LockIcon, MarkGithubIcon, RepoIcon } from "@primer/octicons-react";
|
|
6
7
|
import {
|
|
7
8
|
getAvailableAgentModels,
|
|
8
9
|
getAvailableAgentReasoningEfforts,
|
|
@@ -32,7 +33,6 @@ import {
|
|
|
32
33
|
FolderOpen,
|
|
33
34
|
FolderGit2,
|
|
34
35
|
FolderKanban,
|
|
35
|
-
Github,
|
|
36
36
|
Hand,
|
|
37
37
|
List,
|
|
38
38
|
Loader2,
|
|
@@ -163,6 +163,77 @@ function formatCurrentModelLabel(agentName: string, modelId: string): string {
|
|
|
163
163
|
.join("-");
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
function suggestWorkspaceId(value: string): string {
|
|
167
|
+
return value
|
|
168
|
+
.trim()
|
|
169
|
+
.toLowerCase()
|
|
170
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
171
|
+
.replace(/^-+|-+$/g, "")
|
|
172
|
+
.slice(0, 64);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function extractNameFromPath(value: string): string | null {
|
|
176
|
+
const trimmed = value.trim();
|
|
177
|
+
if (!trimmed) return null;
|
|
178
|
+
const segments = trimmed.split(/[\\/]+/).filter(Boolean);
|
|
179
|
+
return segments.at(-1) ?? null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function extractRepositoryNameFromGitUrl(value: string): string | null {
|
|
183
|
+
const trimmed = value.trim();
|
|
184
|
+
if (!trimmed) return null;
|
|
185
|
+
|
|
186
|
+
const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/\s]+?)(?:\.git)?$/i);
|
|
187
|
+
if (sshMatch) {
|
|
188
|
+
return sshMatch[2] ?? null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const parsed = new URL(trimmed);
|
|
193
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
194
|
+
const last = segments.at(-1);
|
|
195
|
+
return last ? last.replace(/\.git$/i, "") : null;
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function normalizeGitHubRepositoryUrl(value: string): string | null {
|
|
202
|
+
const trimmed = value.trim();
|
|
203
|
+
if (!trimmed) return null;
|
|
204
|
+
|
|
205
|
+
const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/\s]+)\/([^/\s?#]+?)(?:\.git)?\/?$/i);
|
|
206
|
+
if (httpsMatch) {
|
|
207
|
+
return `https://github.com/${httpsMatch[1]}/${httpsMatch[2]}.git`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const sshMatch = trimmed.match(/^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?$/i);
|
|
211
|
+
if (sshMatch) {
|
|
212
|
+
return `https://github.com/${sshMatch[1]}/${sshMatch[2]}.git`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function normalizeManualGitUrl(value: string): string | null {
|
|
219
|
+
const trimmed = value.trim();
|
|
220
|
+
if (!trimmed) return null;
|
|
221
|
+
if (/^(https?:\/\/|git@)/i.test(trimmed)) {
|
|
222
|
+
return trimmed;
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function formatRepoUpdatedLabel(value: string | null | undefined): string | null {
|
|
228
|
+
if (!value) return null;
|
|
229
|
+
const timestamp = Date.parse(value);
|
|
230
|
+
if (Number.isNaN(timestamp)) return null;
|
|
231
|
+
return `Updated ${new Intl.DateTimeFormat(undefined, {
|
|
232
|
+
month: "short",
|
|
233
|
+
day: "numeric",
|
|
234
|
+
}).format(new Date(timestamp))}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
166
237
|
type NewWorkspacePayload = {
|
|
167
238
|
mode: "git" | "local";
|
|
168
239
|
projectId?: string;
|
|
@@ -206,6 +277,10 @@ type GitHubRepo = {
|
|
|
206
277
|
sshUrl: string;
|
|
207
278
|
defaultBranch: string;
|
|
208
279
|
private: boolean;
|
|
280
|
+
description?: string | null;
|
|
281
|
+
updatedAt?: string | null;
|
|
282
|
+
ownerLogin?: string | null;
|
|
283
|
+
permission?: string | null;
|
|
209
284
|
};
|
|
210
285
|
|
|
211
286
|
type DirectoryEntry = {
|
|
@@ -937,7 +1012,7 @@ export default function DashboardClient() {
|
|
|
937
1012
|
return;
|
|
938
1013
|
}
|
|
939
1014
|
|
|
940
|
-
if (
|
|
1015
|
+
if (selectedProjectId !== null && !projects.some((project) => project.id === selectedProjectId)) {
|
|
941
1016
|
setSelectedProjectId(projects[0]?.id ?? null);
|
|
942
1017
|
}
|
|
943
1018
|
}, [projects, selectedProjectId]);
|
|
@@ -1380,7 +1455,6 @@ export default function DashboardClient() {
|
|
|
1380
1455
|
|
|
1381
1456
|
const sidebarContent = useMemo(() => (
|
|
1382
1457
|
<WorkspaceSidebarPanel
|
|
1383
|
-
orgLabel="conductor-oss"
|
|
1384
1458
|
projects={projects}
|
|
1385
1459
|
selectedProjectId={selectedProjectId}
|
|
1386
1460
|
onSelectProject={handleSelectProject}
|
|
@@ -1457,6 +1531,56 @@ export default function DashboardClient() {
|
|
|
1457
1531
|
workspaceView,
|
|
1458
1532
|
]);
|
|
1459
1533
|
|
|
1534
|
+
const projectWorkspaceContent = useMemo(() => {
|
|
1535
|
+
if (!selectedProject) return workspaceMainPanel;
|
|
1536
|
+
|
|
1537
|
+
return (
|
|
1538
|
+
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
1539
|
+
<div className="border-b border-[var(--vk-border)] bg-[var(--vk-bg-panel)]/70 px-3 py-3 sm:px-4">
|
|
1540
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
1541
|
+
<div className="min-w-0">
|
|
1542
|
+
<p className="text-[11px] uppercase tracking-[0.12em] text-[var(--vk-text-muted)]">
|
|
1543
|
+
Project Workspace
|
|
1544
|
+
</p>
|
|
1545
|
+
<p className="mt-1 truncate text-[14px] text-[var(--vk-text-strong)]">
|
|
1546
|
+
{selectedProject.id} · {selectedProject.defaultBranch || "main"}
|
|
1547
|
+
</p>
|
|
1548
|
+
</div>
|
|
1549
|
+
|
|
1550
|
+
<div className="inline-flex w-fit rounded-[6px] border border-[var(--vk-border)] p-1">
|
|
1551
|
+
<button
|
|
1552
|
+
type="button"
|
|
1553
|
+
onClick={() => setWorkspaceView("chat")}
|
|
1554
|
+
className={`min-h-[32px] rounded-[4px] px-3 text-[13px] ${
|
|
1555
|
+
workspaceView === "chat"
|
|
1556
|
+
? "bg-[var(--vk-bg-active)] text-[var(--vk-text-strong)]"
|
|
1557
|
+
: "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
|
|
1558
|
+
}`}
|
|
1559
|
+
>
|
|
1560
|
+
Chat launchpad
|
|
1561
|
+
</button>
|
|
1562
|
+
<button
|
|
1563
|
+
type="button"
|
|
1564
|
+
onClick={() => setWorkspaceView("board")}
|
|
1565
|
+
className={`min-h-[32px] rounded-[4px] px-3 text-[13px] ${
|
|
1566
|
+
workspaceView === "board"
|
|
1567
|
+
? "bg-[var(--vk-bg-active)] text-[var(--vk-text-strong)]"
|
|
1568
|
+
: "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
|
|
1569
|
+
}`}
|
|
1570
|
+
>
|
|
1571
|
+
Board view
|
|
1572
|
+
</button>
|
|
1573
|
+
</div>
|
|
1574
|
+
</div>
|
|
1575
|
+
</div>
|
|
1576
|
+
|
|
1577
|
+
<div className="min-h-0 flex-1 overflow-hidden">
|
|
1578
|
+
{workspaceMainPanel}
|
|
1579
|
+
</div>
|
|
1580
|
+
</div>
|
|
1581
|
+
);
|
|
1582
|
+
}, [selectedProject, workspaceMainPanel, workspaceView]);
|
|
1583
|
+
|
|
1460
1584
|
const workspaceContent = useMemo(() => {
|
|
1461
1585
|
if (selectedSessionId) {
|
|
1462
1586
|
return <SessionDetail sessionId={selectedSessionId} />;
|
|
@@ -1465,7 +1589,7 @@ export default function DashboardClient() {
|
|
|
1465
1589
|
if (selectedProjectId !== null) {
|
|
1466
1590
|
return (
|
|
1467
1591
|
<div className="min-h-0 flex-1 overflow-hidden">
|
|
1468
|
-
{
|
|
1592
|
+
{projectWorkspaceContent}
|
|
1469
1593
|
</div>
|
|
1470
1594
|
);
|
|
1471
1595
|
}
|
|
@@ -1476,25 +1600,22 @@ export default function DashboardClient() {
|
|
|
1476
1600
|
projects={projects}
|
|
1477
1601
|
sessions={dashboardSessions}
|
|
1478
1602
|
selectedProjectId={selectedProjectId}
|
|
1479
|
-
workspaceView={workspaceView}
|
|
1480
1603
|
agentCount={agentOptions.length}
|
|
1481
1604
|
onCreateWorkspace={openWorkspaceDialog}
|
|
1482
1605
|
onSelectProject={handleSelectProject}
|
|
1483
1606
|
onSelectSession={handleSelectSession}
|
|
1484
|
-
onShowView={setWorkspaceView}
|
|
1485
1607
|
/>
|
|
1486
1608
|
</div>
|
|
1487
1609
|
);
|
|
1488
1610
|
}, [
|
|
1489
1611
|
dashboardSessions,
|
|
1612
|
+
projectWorkspaceContent,
|
|
1490
1613
|
selectedSessionId,
|
|
1491
|
-
workspaceMainPanel,
|
|
1492
1614
|
handleSelectProject,
|
|
1493
1615
|
handleSelectSession,
|
|
1494
1616
|
openWorkspaceDialog,
|
|
1495
1617
|
projects,
|
|
1496
1618
|
selectedProjectId,
|
|
1497
|
-
workspaceView,
|
|
1498
1619
|
agentOptions.length,
|
|
1499
1620
|
]);
|
|
1500
1621
|
|
|
@@ -1570,6 +1691,7 @@ function NewWorkspaceDialog({
|
|
|
1570
1691
|
}) {
|
|
1571
1692
|
const [mode, setMode] = useState<"git" | "local">("git");
|
|
1572
1693
|
const [projectId, setProjectId] = useState("");
|
|
1694
|
+
const [projectIdTouched, setProjectIdTouched] = useState(false);
|
|
1573
1695
|
const [gitUrl, setGitUrl] = useState("");
|
|
1574
1696
|
const [path, setPath] = useState("");
|
|
1575
1697
|
const [defaultBranch, setDefaultBranch] = useState("main");
|
|
@@ -1578,6 +1700,7 @@ function NewWorkspaceDialog({
|
|
|
1578
1700
|
const [initializeGit, setInitializeGit] = useState(true);
|
|
1579
1701
|
const [githubRepos, setGithubRepos] = useState<GitHubRepo[]>([]);
|
|
1580
1702
|
const [githubReposLoading, setGithubReposLoading] = useState(false);
|
|
1703
|
+
const [githubReposLoaded, setGithubReposLoaded] = useState(false);
|
|
1581
1704
|
const [githubReposError, setGithubReposError] = useState<string | null>(null);
|
|
1582
1705
|
const [githubRepoSearch, setGithubRepoSearch] = useState("");
|
|
1583
1706
|
const [selectedGithubRepo, setSelectedGithubRepo] = useState("");
|
|
@@ -1591,6 +1714,7 @@ function NewWorkspaceDialog({
|
|
|
1591
1714
|
if (!open) return;
|
|
1592
1715
|
setMode("git");
|
|
1593
1716
|
setProjectId("");
|
|
1717
|
+
setProjectIdTouched(false);
|
|
1594
1718
|
setGitUrl("");
|
|
1595
1719
|
setPath("");
|
|
1596
1720
|
setDefaultBranch("main");
|
|
@@ -1598,6 +1722,7 @@ function NewWorkspaceDialog({
|
|
|
1598
1722
|
setUseWorktree(true);
|
|
1599
1723
|
setAgent(defaultAgent);
|
|
1600
1724
|
setGithubRepos([]);
|
|
1725
|
+
setGithubReposLoaded(false);
|
|
1601
1726
|
setGithubReposError(null);
|
|
1602
1727
|
setGithubRepoSearch("");
|
|
1603
1728
|
setSelectedGithubRepo("");
|
|
@@ -1617,15 +1742,42 @@ function NewWorkspaceDialog({
|
|
|
1617
1742
|
}, [mode, open, path]);
|
|
1618
1743
|
|
|
1619
1744
|
const filteredGitHubRepos = useMemo(() => {
|
|
1620
|
-
if (githubRepoSearch.trim().length === 0) return githubRepos;
|
|
1621
1745
|
const query = githubRepoSearch.trim().toLowerCase();
|
|
1622
|
-
|
|
1746
|
+
const filtered = query.length === 0 ? githubRepos : githubRepos.filter((repo) => {
|
|
1623
1747
|
return repo.fullName.toLowerCase().includes(query)
|
|
1624
1748
|
|| repo.name.toLowerCase().includes(query)
|
|
1749
|
+
|| (repo.ownerLogin ?? "").toLowerCase().includes(query)
|
|
1750
|
+
|| (repo.description ?? "").toLowerCase().includes(query)
|
|
1625
1751
|
|| repo.defaultBranch.toLowerCase().includes(query);
|
|
1626
1752
|
});
|
|
1753
|
+
return query.length === 0 ? filtered.slice(0, 10) : filtered.slice(0, 14);
|
|
1627
1754
|
}, [githubRepoSearch, githubRepos]);
|
|
1628
1755
|
|
|
1756
|
+
const selectedGitHubRepoData = useMemo(() => {
|
|
1757
|
+
const selected = githubRepos.find((repo) => repo.httpsUrl === selectedGithubRepo);
|
|
1758
|
+
if (selected) return selected;
|
|
1759
|
+
if (!gitUrl.trim()) return null;
|
|
1760
|
+
return githubRepos.find((repo) => repo.httpsUrl === gitUrl.trim()) ?? null;
|
|
1761
|
+
}, [gitUrl, githubRepos, selectedGithubRepo]);
|
|
1762
|
+
|
|
1763
|
+
const normalizedGitHubSearchUrl = useMemo(
|
|
1764
|
+
() => normalizeGitHubRepositoryUrl(githubRepoSearch),
|
|
1765
|
+
[githubRepoSearch],
|
|
1766
|
+
);
|
|
1767
|
+
const normalizedManualGitUrl = useMemo(
|
|
1768
|
+
() => normalizeManualGitUrl(githubRepoSearch),
|
|
1769
|
+
[githubRepoSearch],
|
|
1770
|
+
);
|
|
1771
|
+
const selectedRepoUpdatedLabel = useMemo(
|
|
1772
|
+
() => formatRepoUpdatedLabel(selectedGitHubRepoData?.updatedAt),
|
|
1773
|
+
[selectedGitHubRepoData],
|
|
1774
|
+
);
|
|
1775
|
+
const showUseSearchValueAction = useMemo(() => {
|
|
1776
|
+
const normalizedUrl = normalizedGitHubSearchUrl ?? normalizedManualGitUrl;
|
|
1777
|
+
if (!normalizedUrl) return false;
|
|
1778
|
+
return normalizedUrl.toLowerCase() !== gitUrl.trim().toLowerCase();
|
|
1779
|
+
}, [gitUrl, normalizedGitHubSearchUrl, normalizedManualGitUrl]);
|
|
1780
|
+
|
|
1629
1781
|
const orderedAgentOptions = useMemo(() => {
|
|
1630
1782
|
const opts = [...new Set(agentOptions)];
|
|
1631
1783
|
if (opts.length === 0) {
|
|
@@ -1647,13 +1799,12 @@ function NewWorkspaceDialog({
|
|
|
1647
1799
|
}
|
|
1648
1800
|
}, [agent, orderedAgentOptions]);
|
|
1649
1801
|
|
|
1650
|
-
const handleFetchGitHubRepos = async () => {
|
|
1802
|
+
const handleFetchGitHubRepos = async (forceRefresh = false) => {
|
|
1651
1803
|
setGithubReposLoading(true);
|
|
1652
1804
|
setGithubReposError(null);
|
|
1653
1805
|
try {
|
|
1654
|
-
const query =
|
|
1655
|
-
const
|
|
1656
|
-
const res = await fetch(`/api/github/repos${queryParam}`);
|
|
1806
|
+
const query = forceRefresh ? "?refresh=true" : "";
|
|
1807
|
+
const res = await fetch(`/api/github/repos${query}`);
|
|
1657
1808
|
const data = (await res.json().catch(() => null)) as
|
|
1658
1809
|
| { repos?: GitHubRepo[]; error?: string }
|
|
1659
1810
|
| null;
|
|
@@ -1667,10 +1818,17 @@ function NewWorkspaceDialog({
|
|
|
1667
1818
|
err instanceof Error ? err.message : "Failed to load GitHub repositories",
|
|
1668
1819
|
);
|
|
1669
1820
|
} finally {
|
|
1821
|
+
setGithubReposLoaded(true);
|
|
1670
1822
|
setGithubReposLoading(false);
|
|
1671
1823
|
}
|
|
1672
1824
|
};
|
|
1673
1825
|
|
|
1826
|
+
useEffect(() => {
|
|
1827
|
+
if (!open || mode !== "git") return;
|
|
1828
|
+
if (githubReposLoading || githubReposLoaded) return;
|
|
1829
|
+
void handleFetchGitHubRepos();
|
|
1830
|
+
}, [githubReposLoaded, githubReposLoading, mode, open]);
|
|
1831
|
+
|
|
1674
1832
|
const handleDetectBranches = async (
|
|
1675
1833
|
sourceOverride?: { gitUrl?: string; path?: string },
|
|
1676
1834
|
) => {
|
|
@@ -1680,7 +1838,7 @@ function NewWorkspaceDialog({
|
|
|
1680
1838
|
if (effectiveGitUrl.length === 0 && effectivePath.length === 0) {
|
|
1681
1839
|
setBranchesError(
|
|
1682
1840
|
mode === "git"
|
|
1683
|
-
? "
|
|
1841
|
+
? "Choose or paste a repository first."
|
|
1684
1842
|
: "Select a local repository path first.",
|
|
1685
1843
|
);
|
|
1686
1844
|
return;
|
|
@@ -1732,19 +1890,41 @@ function NewWorkspaceDialog({
|
|
|
1732
1890
|
if (!selected) return;
|
|
1733
1891
|
|
|
1734
1892
|
setGitUrl(selected.httpsUrl);
|
|
1893
|
+
setGithubRepoSearch(selected.fullName);
|
|
1735
1894
|
setDefaultBranch(selected.defaultBranch || "main");
|
|
1736
|
-
if (
|
|
1737
|
-
const suggestedProjectId = selected.name
|
|
1738
|
-
.toLowerCase()
|
|
1739
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
1740
|
-
.replace(/^-+|-+$/g, "")
|
|
1741
|
-
.slice(0, 64);
|
|
1895
|
+
if (!projectIdTouched) {
|
|
1896
|
+
const suggestedProjectId = suggestWorkspaceId(selected.name);
|
|
1742
1897
|
setProjectId(suggestedProjectId || projectId);
|
|
1743
1898
|
}
|
|
1744
1899
|
|
|
1745
1900
|
await handleDetectBranches({ gitUrl: selected.httpsUrl });
|
|
1746
1901
|
};
|
|
1747
1902
|
|
|
1903
|
+
const handleUseSearchValueAsRepository = async () => {
|
|
1904
|
+
const normalizedUrl = normalizedGitHubSearchUrl ?? normalizedManualGitUrl;
|
|
1905
|
+
if (!normalizedUrl) return;
|
|
1906
|
+
|
|
1907
|
+
const matchingRepo = githubRepos.find((repo) => repo.httpsUrl.toLowerCase() === normalizedUrl.toLowerCase()) ?? null;
|
|
1908
|
+
if (matchingRepo) {
|
|
1909
|
+
await handleSelectGitHubRepo(matchingRepo.httpsUrl);
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
setSelectedGithubRepo("");
|
|
1914
|
+
setGitUrl(normalizedUrl);
|
|
1915
|
+
setDefaultBranch("main");
|
|
1916
|
+
setBranchOptions([]);
|
|
1917
|
+
setBranchesError(null);
|
|
1918
|
+
if (!projectIdTouched) {
|
|
1919
|
+
const repoName = extractRepositoryNameFromGitUrl(normalizedUrl);
|
|
1920
|
+
if (repoName) {
|
|
1921
|
+
setProjectId(suggestWorkspaceId(repoName));
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
await handleDetectBranches({ gitUrl: normalizedUrl });
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1748
1928
|
const openFolderPicker = (target: "clone" | "local") => {
|
|
1749
1929
|
setFolderPickerTarget(target);
|
|
1750
1930
|
setFolderPickerOpen(true);
|
|
@@ -1803,7 +1983,7 @@ function NewWorkspaceDialog({
|
|
|
1803
1983
|
<div>
|
|
1804
1984
|
<h2 className="text-[18px] leading-[22px] text-[var(--vk-text-strong)]">Add Workspace</h2>
|
|
1805
1985
|
<p className="pt-1 text-[12px] text-[var(--vk-text-muted)]">
|
|
1806
|
-
|
|
1986
|
+
Pick a GitHub repository or local folder, then confirm the branch.
|
|
1807
1987
|
</p>
|
|
1808
1988
|
</div>
|
|
1809
1989
|
<button
|
|
@@ -1828,7 +2008,7 @@ function NewWorkspaceDialog({
|
|
|
1828
2008
|
: "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
|
|
1829
2009
|
}`}
|
|
1830
2010
|
>
|
|
1831
|
-
|
|
2011
|
+
GitHub
|
|
1832
2012
|
</button>
|
|
1833
2013
|
<button
|
|
1834
2014
|
type="button"
|
|
@@ -1843,103 +2023,198 @@ function NewWorkspaceDialog({
|
|
|
1843
2023
|
</button>
|
|
1844
2024
|
</div>
|
|
1845
2025
|
|
|
1846
|
-
<label className="block">
|
|
1847
|
-
<span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">Project ID (optional)</span>
|
|
1848
|
-
<input
|
|
1849
|
-
value={projectId}
|
|
1850
|
-
onChange={(event) => setProjectId(event.target.value)}
|
|
1851
|
-
placeholder="auto-derived from repo/folder"
|
|
1852
|
-
className="h-9 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
|
|
1853
|
-
/>
|
|
1854
|
-
</label>
|
|
1855
|
-
|
|
1856
2026
|
{mode === "git" ? (
|
|
1857
2027
|
<>
|
|
1858
|
-
<div className="rounded-[
|
|
1859
|
-
<div className="flex items-
|
|
1860
|
-
<
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2028
|
+
<div className="rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] p-3">
|
|
2029
|
+
<div className="flex items-start gap-3">
|
|
2030
|
+
<span className="inline-flex h-9 w-9 items-center justify-center rounded-[5px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] text-[var(--vk-text-strong)]">
|
|
2031
|
+
<MarkGithubIcon className="h-[18px] w-[18px]" />
|
|
2032
|
+
</span>
|
|
2033
|
+
<div className="min-w-0 flex-1">
|
|
2034
|
+
<p className="text-[13px] font-medium text-[var(--vk-text-strong)]">GitHub Repository</p>
|
|
2035
|
+
<p className="pt-0.5 text-[12px] text-[var(--vk-text-muted)]">
|
|
2036
|
+
Search accessible repositories or paste a repository URL. Conductor fills the branch and clone URL after selection.
|
|
2037
|
+
</p>
|
|
2038
|
+
</div>
|
|
1864
2039
|
<button
|
|
1865
2040
|
type="button"
|
|
1866
|
-
onClick={
|
|
2041
|
+
onClick={() => {
|
|
2042
|
+
setGithubReposLoaded(false);
|
|
2043
|
+
void handleFetchGitHubRepos(true);
|
|
2044
|
+
}}
|
|
1867
2045
|
disabled={githubReposLoading}
|
|
1868
2046
|
className="inline-flex h-8 items-center rounded-[4px] border border-[var(--vk-border)] px-2 text-[12px] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)] disabled:opacity-50"
|
|
1869
2047
|
>
|
|
1870
2048
|
{githubReposLoading ? (
|
|
2049
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
2050
|
+
) : (
|
|
1871
2051
|
<>
|
|
1872
|
-
<
|
|
1873
|
-
|
|
2052
|
+
<RefreshCcw className="mr-1.5 h-3.5 w-3.5" />
|
|
2053
|
+
Refresh
|
|
1874
2054
|
</>
|
|
1875
|
-
)
|
|
2055
|
+
)}
|
|
1876
2056
|
</button>
|
|
1877
|
-
<div className="relative min-w-[220px] flex-1">
|
|
1878
|
-
<Search className="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--vk-text-muted)]" />
|
|
1879
|
-
<input
|
|
1880
|
-
value={githubRepoSearch}
|
|
1881
|
-
onChange={(event) => setGithubRepoSearch(event.target.value)}
|
|
1882
|
-
placeholder="Filter repos..."
|
|
1883
|
-
className="h-8 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent pl-7 pr-2 text-[12px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
|
|
1884
|
-
/>
|
|
1885
|
-
</div>
|
|
1886
2057
|
</div>
|
|
1887
|
-
{filteredGitHubRepos.length > 0 && (
|
|
1888
|
-
<label className="mt-2 block">
|
|
1889
|
-
<span className="mb-1 block text-[11px] text-[var(--vk-text-muted)]">Choose repository</span>
|
|
1890
|
-
<select
|
|
1891
|
-
value={selectedGithubRepo}
|
|
1892
|
-
onChange={(event) => {
|
|
1893
|
-
void handleSelectGitHubRepo(event.target.value);
|
|
1894
|
-
}}
|
|
1895
|
-
className="h-8 w-full rounded-[4px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] px-2 text-[12px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
|
|
1896
|
-
>
|
|
1897
|
-
<option value="">Select a GitHub repo...</option>
|
|
1898
|
-
{filteredGitHubRepos.map((repo) => (
|
|
1899
|
-
<option key={repo.httpsUrl} value={repo.httpsUrl}>
|
|
1900
|
-
{repo.fullName} ({repo.defaultBranch})
|
|
1901
|
-
</option>
|
|
1902
|
-
))}
|
|
1903
|
-
</select>
|
|
1904
|
-
</label>
|
|
1905
|
-
)}
|
|
1906
|
-
{githubReposError && (
|
|
1907
|
-
<p className="mt-2 text-[11px] text-[var(--vk-red)]">{githubReposError}</p>
|
|
1908
|
-
)}
|
|
1909
|
-
</div>
|
|
1910
2058
|
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
<input
|
|
1914
|
-
value={gitUrl}
|
|
1915
|
-
onChange={(event) => setGitUrl(event.target.value)}
|
|
1916
|
-
placeholder="https://github.com/org/repo.git"
|
|
1917
|
-
className="h-9 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
|
|
1918
|
-
/>
|
|
1919
|
-
</label>
|
|
1920
|
-
|
|
1921
|
-
<label className="block">
|
|
1922
|
-
<span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">
|
|
1923
|
-
Local Path (optional, clone target)
|
|
1924
|
-
</span>
|
|
1925
|
-
<div className="flex items-center gap-2">
|
|
2059
|
+
<div className="relative mt-3">
|
|
2060
|
+
<MarkGithubIcon className="pointer-events-none absolute left-3 top-1/2 h-[16px] w-[16px] -translate-y-1/2 text-[var(--vk-text-muted)]" />
|
|
1926
2061
|
<input
|
|
1927
|
-
value={
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
2062
|
+
value={githubRepoSearch}
|
|
2063
|
+
onChange={(event) => setGithubRepoSearch(event.target.value)}
|
|
2064
|
+
onKeyDown={(event) => {
|
|
2065
|
+
if (event.key === "Enter" && showUseSearchValueAction) {
|
|
2066
|
+
event.preventDefault();
|
|
2067
|
+
void handleUseSearchValueAsRepository();
|
|
2068
|
+
}
|
|
2069
|
+
}}
|
|
2070
|
+
placeholder="Search GitHub repos or paste a repository URL"
|
|
2071
|
+
className="h-10 w-full rounded-[5px] border border-[var(--vk-border)] bg-transparent pl-10 pr-3 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
|
|
1932
2072
|
/>
|
|
2073
|
+
</div>
|
|
2074
|
+
|
|
2075
|
+
{showUseSearchValueAction ? (
|
|
1933
2076
|
<button
|
|
1934
2077
|
type="button"
|
|
1935
|
-
onClick={() =>
|
|
1936
|
-
|
|
1937
|
-
|
|
2078
|
+
onClick={() => {
|
|
2079
|
+
void handleUseSearchValueAsRepository();
|
|
2080
|
+
}}
|
|
2081
|
+
className="mt-3 inline-flex items-center gap-2 rounded-[5px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] px-3 py-2 text-[12px] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)]"
|
|
1938
2082
|
>
|
|
1939
|
-
<
|
|
2083
|
+
<RepoIcon className="h-4 w-4 text-[var(--vk-text-strong)]" />
|
|
2084
|
+
<span className="truncate">
|
|
2085
|
+
Use {normalizedGitHubSearchUrl ? "this GitHub repository" : "pasted repository URL"}
|
|
2086
|
+
</span>
|
|
1940
2087
|
</button>
|
|
2088
|
+
) : null}
|
|
2089
|
+
|
|
2090
|
+
{githubReposLoading && githubRepos.length === 0 ? (
|
|
2091
|
+
<div className="mt-3 rounded-[5px] border border-[var(--vk-border)] px-3 py-3 text-[12px] text-[var(--vk-text-muted)]">
|
|
2092
|
+
Loading accessible GitHub repositories...
|
|
2093
|
+
</div>
|
|
2094
|
+
) : null}
|
|
2095
|
+
|
|
2096
|
+
{githubReposError ? (
|
|
2097
|
+
<div className="mt-3 rounded-[5px] border border-[var(--vk-red)]/40 bg-[var(--vk-bg-panel)] px-3 py-3 text-[12px] text-[var(--vk-red)]">
|
|
2098
|
+
<p>{githubReposError}</p>
|
|
2099
|
+
<button
|
|
2100
|
+
type="button"
|
|
2101
|
+
onClick={() => {
|
|
2102
|
+
setGithubReposLoaded(false);
|
|
2103
|
+
void handleFetchGitHubRepos(true);
|
|
2104
|
+
}}
|
|
2105
|
+
className="mt-2 inline-flex items-center rounded-[4px] border border-[var(--vk-border)] px-2 py-1 text-[12px] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)]"
|
|
2106
|
+
>
|
|
2107
|
+
Retry
|
|
2108
|
+
</button>
|
|
2109
|
+
</div>
|
|
2110
|
+
) : null}
|
|
2111
|
+
|
|
2112
|
+
{!githubReposError && filteredGitHubRepos.length > 0 ? (
|
|
2113
|
+
<div className="mt-3 max-h-[260px] space-y-2 overflow-y-auto pr-1">
|
|
2114
|
+
{filteredGitHubRepos.map((repo) => {
|
|
2115
|
+
const repoUpdatedLabel = formatRepoUpdatedLabel(repo.updatedAt);
|
|
2116
|
+
const selected = selectedGitHubRepoData?.httpsUrl === repo.httpsUrl;
|
|
2117
|
+
return (
|
|
2118
|
+
<button
|
|
2119
|
+
key={repo.httpsUrl}
|
|
2120
|
+
type="button"
|
|
2121
|
+
onClick={() => {
|
|
2122
|
+
void handleSelectGitHubRepo(repo.httpsUrl);
|
|
2123
|
+
}}
|
|
2124
|
+
className={`flex w-full items-start gap-3 rounded-[5px] border px-3 py-3 text-left transition ${
|
|
2125
|
+
selected
|
|
2126
|
+
? "border-[var(--vk-orange)] bg-[var(--vk-bg-panel)]"
|
|
2127
|
+
: "border-[var(--vk-border)] bg-[var(--vk-bg-panel)] hover:bg-[var(--vk-bg-hover)]"
|
|
2128
|
+
}`}
|
|
2129
|
+
>
|
|
2130
|
+
<span className="mt-0.5 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-[5px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] text-[var(--vk-text-strong)]">
|
|
2131
|
+
<RepoIcon className="h-4 w-4" />
|
|
2132
|
+
</span>
|
|
2133
|
+
<span className="min-w-0 flex-1">
|
|
2134
|
+
<span className="flex flex-wrap items-center gap-2">
|
|
2135
|
+
<span className="truncate text-[13px] font-medium text-[var(--vk-text-strong)]">
|
|
2136
|
+
{repo.fullName}
|
|
2137
|
+
</span>
|
|
2138
|
+
<span className="inline-flex items-center gap-1 rounded-full border border-[var(--vk-border)] px-2 py-0.5 text-[11px] text-[var(--vk-text-muted)]">
|
|
2139
|
+
{repo.private ? <LockIcon className="h-3 w-3" /> : <MarkGithubIcon className="h-3 w-3" />}
|
|
2140
|
+
{repo.private ? "Private" : "Public"}
|
|
2141
|
+
</span>
|
|
2142
|
+
<span className="inline-flex items-center gap-1 rounded-full border border-[var(--vk-border)] px-2 py-0.5 text-[11px] text-[var(--vk-text-muted)]">
|
|
2143
|
+
<GitBranchIcon className="h-3 w-3" />
|
|
2144
|
+
{repo.defaultBranch}
|
|
2145
|
+
</span>
|
|
2146
|
+
</span>
|
|
2147
|
+
{repo.description ? (
|
|
2148
|
+
<span className="mt-1 block line-clamp-2 text-[12px] leading-[17px] text-[var(--vk-text-muted)]">
|
|
2149
|
+
{repo.description}
|
|
2150
|
+
</span>
|
|
2151
|
+
) : null}
|
|
2152
|
+
<span className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-[var(--vk-text-muted)]">
|
|
2153
|
+
{repo.ownerLogin ? <span>{repo.ownerLogin}</span> : null}
|
|
2154
|
+
{repoUpdatedLabel ? <span>{repoUpdatedLabel}</span> : null}
|
|
2155
|
+
{repo.permission ? <span>{repo.permission.toLowerCase()}</span> : null}
|
|
2156
|
+
</span>
|
|
2157
|
+
</span>
|
|
2158
|
+
<span className="ml-auto inline-flex h-4 w-4 shrink-0 items-center justify-center text-[var(--vk-text-strong)]">
|
|
2159
|
+
{selected ? <Check className="h-4 w-4" /> : null}
|
|
2160
|
+
</span>
|
|
2161
|
+
</button>
|
|
2162
|
+
);
|
|
2163
|
+
})}
|
|
2164
|
+
</div>
|
|
2165
|
+
) : null}
|
|
2166
|
+
|
|
2167
|
+
{!githubReposLoading && !githubReposError && filteredGitHubRepos.length === 0 ? (
|
|
2168
|
+
<p className="mt-3 text-[12px] text-[var(--vk-text-muted)]">
|
|
2169
|
+
{githubRepoSearch.trim().length > 0
|
|
2170
|
+
? "No matching repositories. Try another search or paste a repository URL."
|
|
2171
|
+
: "No accessible GitHub repositories were found for this machine yet."}
|
|
2172
|
+
</p>
|
|
2173
|
+
) : null}
|
|
2174
|
+
</div>
|
|
2175
|
+
|
|
2176
|
+
{gitUrl.trim().length > 0 ? (
|
|
2177
|
+
<div className="rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] p-3">
|
|
2178
|
+
<div className="flex items-start gap-3">
|
|
2179
|
+
<span className="inline-flex h-9 w-9 items-center justify-center rounded-[5px] border border-[var(--vk-border)] bg-[var(--vk-bg-panel)] text-[var(--vk-text-strong)]">
|
|
2180
|
+
<RepoIcon className="h-[18px] w-[18px]" />
|
|
2181
|
+
</span>
|
|
2182
|
+
<div className="min-w-0 flex-1">
|
|
2183
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
2184
|
+
<span className="truncate text-[14px] font-medium text-[var(--vk-text-strong)]">
|
|
2185
|
+
{selectedGitHubRepoData?.fullName ?? gitUrl}
|
|
2186
|
+
</span>
|
|
2187
|
+
<span className="inline-flex items-center gap-1 rounded-full border border-[var(--vk-border)] px-2 py-0.5 text-[11px] text-[var(--vk-text-muted)]">
|
|
2188
|
+
{selectedGitHubRepoData ? (
|
|
2189
|
+
selectedGitHubRepoData.private ? <LockIcon className="h-3 w-3" /> : <MarkGithubIcon className="h-3 w-3" />
|
|
2190
|
+
) : (
|
|
2191
|
+
<MarkGithubIcon className="h-3 w-3" />
|
|
2192
|
+
)}
|
|
2193
|
+
{selectedGitHubRepoData ? (selectedGitHubRepoData.private ? "Private" : "Public") : "Manual URL"}
|
|
2194
|
+
</span>
|
|
2195
|
+
<span className="inline-flex items-center gap-1 rounded-full border border-[var(--vk-border)] px-2 py-0.5 text-[11px] text-[var(--vk-text-muted)]">
|
|
2196
|
+
<GitBranchIcon className="h-3 w-3" />
|
|
2197
|
+
{defaultBranch || "main"}
|
|
2198
|
+
</span>
|
|
2199
|
+
</div>
|
|
2200
|
+
{selectedGitHubRepoData?.description ? (
|
|
2201
|
+
<p className="mt-1 line-clamp-2 text-[12px] leading-[17px] text-[var(--vk-text-muted)]">
|
|
2202
|
+
{selectedGitHubRepoData.description}
|
|
2203
|
+
</p>
|
|
2204
|
+
) : (
|
|
2205
|
+
<p className="mt-1 truncate text-[12px] text-[var(--vk-text-muted)]">{gitUrl}</p>
|
|
2206
|
+
)}
|
|
2207
|
+
<div className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-[var(--vk-text-muted)]">
|
|
2208
|
+
{selectedGitHubRepoData?.ownerLogin ? <span>{selectedGitHubRepoData.ownerLogin}</span> : null}
|
|
2209
|
+
{selectedRepoUpdatedLabel ? <span>{selectedRepoUpdatedLabel}</span> : null}
|
|
2210
|
+
{selectedGitHubRepoData?.permission ? (
|
|
2211
|
+
<span>{selectedGitHubRepoData.permission.toLowerCase()}</span>
|
|
2212
|
+
) : null}
|
|
2213
|
+
</div>
|
|
2214
|
+
</div>
|
|
2215
|
+
</div>
|
|
1941
2216
|
</div>
|
|
1942
|
-
|
|
2217
|
+
) : null}
|
|
1943
2218
|
</>
|
|
1944
2219
|
) : (
|
|
1945
2220
|
<>
|
|
@@ -1975,14 +2250,27 @@ function NewWorkspaceDialog({
|
|
|
1975
2250
|
</>
|
|
1976
2251
|
)}
|
|
1977
2252
|
|
|
2253
|
+
<label className="block">
|
|
2254
|
+
<span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">Workspace Name (optional)</span>
|
|
2255
|
+
<input
|
|
2256
|
+
value={projectId}
|
|
2257
|
+
onChange={(event) => {
|
|
2258
|
+
setProjectId(event.target.value);
|
|
2259
|
+
setProjectIdTouched(true);
|
|
2260
|
+
}}
|
|
2261
|
+
placeholder="auto-derived from the selected repository or folder"
|
|
2262
|
+
className="h-9 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
|
|
2263
|
+
/>
|
|
2264
|
+
</label>
|
|
2265
|
+
|
|
1978
2266
|
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
|
|
1979
2267
|
<label className="block">
|
|
1980
|
-
<span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">
|
|
2268
|
+
<span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">Branch</span>
|
|
1981
2269
|
<div className="flex items-center gap-2">
|
|
1982
2270
|
<input
|
|
1983
2271
|
value={defaultBranch}
|
|
1984
2272
|
onChange={(event) => setDefaultBranch(event.target.value)}
|
|
1985
|
-
placeholder="
|
|
2273
|
+
placeholder="Uses the repository default branch"
|
|
1986
2274
|
className="h-9 w-full rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
|
|
1987
2275
|
/>
|
|
1988
2276
|
<button
|
|
@@ -2035,20 +2323,70 @@ function NewWorkspaceDialog({
|
|
|
2035
2323
|
</label>
|
|
2036
2324
|
</div>
|
|
2037
2325
|
|
|
2038
|
-
|
|
2039
|
-
<
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2326
|
+
{mode === "git" ? (
|
|
2327
|
+
<details className="rounded-[4px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)]">
|
|
2328
|
+
<summary className="cursor-pointer list-none px-3 py-2 text-[13px] text-[var(--vk-text-normal)] marker:hidden">
|
|
2329
|
+
<span className="inline-flex items-center gap-2">
|
|
2330
|
+
<ChevronDown className="h-3.5 w-3.5 text-[var(--vk-text-muted)]" />
|
|
2331
|
+
Advanced options
|
|
2332
|
+
</span>
|
|
2333
|
+
</summary>
|
|
2334
|
+
<div className="space-y-3 border-t border-[var(--vk-border)] px-3 py-3">
|
|
2335
|
+
<label className="block">
|
|
2336
|
+
<span className="mb-1.5 block text-[12px] text-[var(--vk-text-muted)]">
|
|
2337
|
+
Local Copy Location (optional)
|
|
2338
|
+
</span>
|
|
2339
|
+
<div className="flex items-center gap-2">
|
|
2340
|
+
<input
|
|
2341
|
+
value={path}
|
|
2342
|
+
readOnly
|
|
2343
|
+
onClick={() => openFolderPicker("clone")}
|
|
2344
|
+
placeholder="Choose a folder only if you want a specific clone location"
|
|
2345
|
+
className="h-9 w-full cursor-pointer rounded-[4px] border border-[var(--vk-border)] bg-transparent px-2 text-[14px] text-[var(--vk-text-normal)] outline-none focus:border-[var(--vk-orange)]"
|
|
2346
|
+
/>
|
|
2347
|
+
<button
|
|
2348
|
+
type="button"
|
|
2349
|
+
onClick={() => openFolderPicker("clone")}
|
|
2350
|
+
className="inline-flex h-9 items-center rounded-[4px] border border-[var(--vk-border)] px-2 text-[12px] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)]"
|
|
2351
|
+
title="Browse folders"
|
|
2352
|
+
>
|
|
2353
|
+
<FolderOpen className="h-4 w-4" />
|
|
2354
|
+
</button>
|
|
2355
|
+
</div>
|
|
2356
|
+
</label>
|
|
2357
|
+
|
|
2358
|
+
<label className="flex items-start gap-2 rounded-[4px] border border-[var(--vk-border)] px-2 py-2 text-[13px] text-[var(--vk-text-normal)]">
|
|
2359
|
+
<input
|
|
2360
|
+
type="checkbox"
|
|
2361
|
+
checked={useWorktree}
|
|
2362
|
+
onChange={(event) => setUseWorktree(event.target.checked)}
|
|
2363
|
+
className="mt-0.5 h-4 w-4 rounded border border-[var(--vk-border)] bg-transparent accent-[var(--vk-orange)]"
|
|
2364
|
+
/>
|
|
2365
|
+
<span>
|
|
2366
|
+
Keep work isolated in a new worktree
|
|
2367
|
+
<span className="block text-[11px] text-[var(--vk-text-muted)]">
|
|
2368
|
+
Turn this off only if you want sessions to run directly in the selected branch.
|
|
2369
|
+
</span>
|
|
2370
|
+
</span>
|
|
2371
|
+
</label>
|
|
2372
|
+
</div>
|
|
2373
|
+
</details>
|
|
2374
|
+
) : (
|
|
2375
|
+
<label className="flex items-start gap-2 rounded-[4px] border border-[var(--vk-border)] px-2 py-2 text-[13px] text-[var(--vk-text-normal)]">
|
|
2376
|
+
<input
|
|
2377
|
+
type="checkbox"
|
|
2378
|
+
checked={useWorktree}
|
|
2379
|
+
onChange={(event) => setUseWorktree(event.target.checked)}
|
|
2380
|
+
className="mt-0.5 h-4 w-4 rounded border border-[var(--vk-border)] bg-transparent accent-[var(--vk-orange)]"
|
|
2381
|
+
/>
|
|
2382
|
+
<span>
|
|
2383
|
+
Keep work isolated in a new worktree
|
|
2384
|
+
<span className="block text-[11px] text-[var(--vk-text-muted)]">
|
|
2385
|
+
Turn this off only if you want sessions to run directly in the selected branch.
|
|
2386
|
+
</span>
|
|
2049
2387
|
</span>
|
|
2050
|
-
</
|
|
2051
|
-
|
|
2388
|
+
</label>
|
|
2389
|
+
)}
|
|
2052
2390
|
|
|
2053
2391
|
{error && <p className="text-[12px] text-[var(--vk-red)]">{error}</p>}
|
|
2054
2392
|
</div>
|
|
@@ -2090,6 +2428,12 @@ function NewWorkspaceDialog({
|
|
|
2090
2428
|
setFolderPickerOpen(false);
|
|
2091
2429
|
if (!selectedPath) return;
|
|
2092
2430
|
setPath(selectedPath);
|
|
2431
|
+
if ((mode === "local" || folderPickerTarget === "local") && !projectIdTouched) {
|
|
2432
|
+
const folderName = extractNameFromPath(selectedPath);
|
|
2433
|
+
if (folderName) {
|
|
2434
|
+
setProjectId(suggestWorkspaceId(folderName));
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2093
2437
|
if (mode === "local" || folderPickerTarget === "local") {
|
|
2094
2438
|
void handleDetectBranches({ path: selectedPath });
|
|
2095
2439
|
}
|