@vibe80/vibe80 0.2.0 → 0.2.2
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/README.md +132 -16
- package/bin/vibe80.js +1728 -16
- package/client/dist/assets/{DiffPanel-BKLnyIAZ.js → DiffPanel-BUJhQj_Q.js} +1 -1
- package/client/dist/assets/{ExplorerPanel-D3IbBsXz.js → ExplorerPanel-DugEeaO2.js} +1 -1
- package/client/dist/assets/{LogsPanel-BwJAFHRP.js → LogsPanel-BQrGxMu_.js} +1 -1
- package/client/dist/assets/{SettingsPanel-BfkchMnR.js → SettingsPanel-Ci2BdIYO.js} +1 -1
- package/client/dist/assets/{TerminalPanel-BQfMEm-u.js → TerminalPanel-C-T3t-6T.js} +1 -1
- package/client/dist/assets/index-cFi4LM0j.js +711 -0
- package/client/dist/assets/index-qNyFxUjK.css +32 -0
- package/client/dist/icon_square-512x512.png +0 -0
- package/client/dist/icon_square.svg +58 -0
- package/client/dist/index.html +3 -2
- package/client/dist/sw.js +1 -1
- package/client/index.html +1 -0
- package/client/public/icon_square-512x512.png +0 -0
- package/client/public/icon_square.svg +58 -0
- package/client/src/App.jsx +205 -2
- package/client/src/assets/vibe80_dark.png +0 -0
- package/client/src/assets/vibe80_light.png +0 -0
- package/client/src/components/Chat/ChatMessages.jsx +1 -1
- package/client/src/components/SessionGate/SessionGate.jsx +295 -91
- package/client/src/components/WorktreeTabs.css +11 -0
- package/client/src/components/WorktreeTabs.jsx +77 -47
- package/client/src/hooks/useChatSocket.js +8 -7
- package/client/src/hooks/useRepoBranchesModels.js +12 -6
- package/client/src/hooks/useWorktreeCloseConfirm.js +19 -7
- package/client/src/hooks/useWorktrees.js +3 -1
- package/client/src/index.css +26 -3
- package/client/src/locales/en.json +12 -1
- package/client/src/locales/fr.json +12 -1
- package/docs/api/openapi.json +1 -1
- package/package.json +2 -1
- package/server/scripts/rotate-workspace-secret.js +1 -1
- package/server/src/claudeClient.js +3 -3
- package/server/src/codexClient.js +3 -3
- package/server/src/config.js +6 -6
- package/server/src/index.js +14 -12
- package/server/src/middleware/auth.js +7 -7
- package/server/src/middleware/debug.js +36 -4
- package/server/src/providerLogger.js +2 -2
- package/server/src/routes/sessions.js +133 -21
- package/server/src/routes/workspaces.js +1 -1
- package/server/src/runAs.js +14 -14
- package/server/src/services/auth.js +3 -3
- package/server/src/services/session.js +182 -14
- package/server/src/services/workspace.js +86 -42
- package/server/src/storage/index.js +2 -2
- package/server/src/storage/redis.js +38 -36
- package/server/src/storage/sqlite.js +13 -13
- package/server/src/worktreeManager.js +87 -19
- package/server/tests/integration/routes/workspaces-routes.test.js +8 -8
- package/server/tests/setup/env.js +5 -5
- package/server/tests/unit/services/auth.test.js +3 -3
- package/client/dist/assets/index-BDQQz6SJ.css +0 -32
- package/client/dist/assets/index-D1UJw1oP.js +0 -711
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
faCheck,
|
|
5
|
+
faCopy,
|
|
6
|
+
faPlus,
|
|
7
|
+
faRightFromBracket,
|
|
8
|
+
faGear,
|
|
9
|
+
faSpinner,
|
|
10
|
+
faTrash,
|
|
11
|
+
faUser,
|
|
12
|
+
} from "@fortawesome/free-solid-svg-icons";
|
|
4
13
|
|
|
5
14
|
export default function SessionGate({
|
|
6
15
|
t,
|
|
@@ -40,7 +49,14 @@ export default function SessionGate({
|
|
|
40
49
|
workspaceSessions,
|
|
41
50
|
workspaceSessionsError,
|
|
42
51
|
workspaceSessionDeletingId,
|
|
52
|
+
workspaceSessionConfigId,
|
|
53
|
+
sessionConfigTarget,
|
|
54
|
+
workspaceSessionUpdatingId,
|
|
55
|
+
workspaceSessionConfigError,
|
|
43
56
|
handleResumeSession,
|
|
57
|
+
openSessionConfigure,
|
|
58
|
+
closeSessionConfigure,
|
|
59
|
+
handleUpdateAndResumeSession,
|
|
44
60
|
handleDeleteSession,
|
|
45
61
|
locale,
|
|
46
62
|
extractRepoName,
|
|
@@ -65,6 +81,18 @@ export default function SessionGate({
|
|
|
65
81
|
setDefaultInternetAccess,
|
|
66
82
|
defaultDenyGitCredentialsAccess,
|
|
67
83
|
setDefaultDenyGitCredentialsAccess,
|
|
84
|
+
sessionConfigAuthMode,
|
|
85
|
+
setSessionConfigAuthMode,
|
|
86
|
+
sessionConfigSshKey,
|
|
87
|
+
setSessionConfigSshKey,
|
|
88
|
+
sessionConfigHttpUsername,
|
|
89
|
+
setSessionConfigHttpUsername,
|
|
90
|
+
sessionConfigHttpPassword,
|
|
91
|
+
setSessionConfigHttpPassword,
|
|
92
|
+
sessionConfigInternetAccess,
|
|
93
|
+
setSessionConfigInternetAccess,
|
|
94
|
+
sessionConfigDenyGitCredentialsAccess,
|
|
95
|
+
setSessionConfigDenyGitCredentialsAccess,
|
|
68
96
|
attachmentsError,
|
|
69
97
|
sessionRequested,
|
|
70
98
|
workspaceBusy,
|
|
@@ -476,84 +504,232 @@ export default function SessionGate({
|
|
|
476
504
|
}`}
|
|
477
505
|
aria-hidden={sessionMode !== "existing"}
|
|
478
506
|
>
|
|
479
|
-
|
|
480
|
-
<div className="session-auth
|
|
481
|
-
|
|
507
|
+
{!workspaceSessionConfigId ? (
|
|
508
|
+
<div className="session-auth">
|
|
509
|
+
<div className="session-auth-title">
|
|
510
|
+
{t("Existing sessions")}
|
|
511
|
+
</div>
|
|
512
|
+
{workspaceSessionsLoading ? (
|
|
513
|
+
<div className="session-auth-hint">
|
|
514
|
+
{t("Loading sessions...")}
|
|
515
|
+
</div>
|
|
516
|
+
) : workspaceSessions.length === 0 ? (
|
|
517
|
+
<div className="session-auth-hint">
|
|
518
|
+
{t("No sessions available.")}
|
|
519
|
+
</div>
|
|
520
|
+
) : (
|
|
521
|
+
<ul className="session-list">
|
|
522
|
+
{workspaceSessions.map((session) => {
|
|
523
|
+
const repoName = extractRepoName(session.repoUrl);
|
|
524
|
+
const title =
|
|
525
|
+
session.name || repoName || session.sessionId;
|
|
526
|
+
const subtitle = session.repoUrl
|
|
527
|
+
? getTruncatedText(session.repoUrl, 72)
|
|
528
|
+
: session.sessionId;
|
|
529
|
+
const lastSeen = session.lastActivityAt
|
|
530
|
+
? new Date(session.lastActivityAt).toLocaleString(
|
|
531
|
+
locale
|
|
532
|
+
)
|
|
533
|
+
: session.createdAt
|
|
534
|
+
? new Date(
|
|
535
|
+
session.createdAt
|
|
536
|
+
).toLocaleString(locale)
|
|
537
|
+
: "";
|
|
538
|
+
const isDeleting =
|
|
539
|
+
workspaceSessionDeletingId === session.sessionId;
|
|
540
|
+
const isUpdating =
|
|
541
|
+
workspaceSessionUpdatingId === session.sessionId;
|
|
542
|
+
return (
|
|
543
|
+
<li key={session.sessionId} className="session-item">
|
|
544
|
+
<div className="session-item-row">
|
|
545
|
+
<div className="session-item-meta">
|
|
546
|
+
<div className="session-item-title">{title}</div>
|
|
547
|
+
<div className="session-item-sub">
|
|
548
|
+
{subtitle}
|
|
549
|
+
</div>
|
|
550
|
+
{lastSeen && (
|
|
551
|
+
<div className="session-item-sub">
|
|
552
|
+
{t("Last activity: {{date}}", {
|
|
553
|
+
date: lastSeen,
|
|
554
|
+
})}
|
|
555
|
+
</div>
|
|
556
|
+
)}
|
|
557
|
+
</div>
|
|
558
|
+
<div className="session-item-actions">
|
|
559
|
+
<button
|
|
560
|
+
type="button"
|
|
561
|
+
className="session-list-button session-list-icon-button"
|
|
562
|
+
onClick={() =>
|
|
563
|
+
handleResumeSession(session.sessionId)
|
|
564
|
+
}
|
|
565
|
+
disabled={formDisabled || isDeleting}
|
|
566
|
+
title={t("Resume")}
|
|
567
|
+
aria-label={t("Resume")}
|
|
568
|
+
>
|
|
569
|
+
<FontAwesomeIcon icon={faRightFromBracket} />
|
|
570
|
+
</button>
|
|
571
|
+
<button
|
|
572
|
+
type="button"
|
|
573
|
+
className="session-list-button session-list-icon-button"
|
|
574
|
+
onClick={() => openSessionConfigure(session)}
|
|
575
|
+
disabled={formDisabled || isDeleting || isUpdating}
|
|
576
|
+
title={t("Configure session")}
|
|
577
|
+
aria-label={t("Configure session")}
|
|
578
|
+
>
|
|
579
|
+
<FontAwesomeIcon icon={faGear} />
|
|
580
|
+
</button>
|
|
581
|
+
<button
|
|
582
|
+
type="button"
|
|
583
|
+
className="session-list-button session-list-icon-button is-danger"
|
|
584
|
+
onClick={() => handleDeleteSession(session)}
|
|
585
|
+
disabled={formDisabled || isDeleting}
|
|
586
|
+
title={isDeleting ? t("Deleting...") : t("Delete")}
|
|
587
|
+
aria-label={isDeleting ? t("Deleting...") : t("Delete")}
|
|
588
|
+
>
|
|
589
|
+
<FontAwesomeIcon
|
|
590
|
+
icon={isDeleting ? faSpinner : faTrash}
|
|
591
|
+
spin={isDeleting}
|
|
592
|
+
/>
|
|
593
|
+
</button>
|
|
594
|
+
</div>
|
|
595
|
+
</div>
|
|
596
|
+
</li>
|
|
597
|
+
);
|
|
598
|
+
})}
|
|
599
|
+
</ul>
|
|
600
|
+
)}
|
|
601
|
+
{workspaceSessionsError && (
|
|
602
|
+
<div className="attachments-error">
|
|
603
|
+
{workspaceSessionsError}
|
|
604
|
+
</div>
|
|
605
|
+
)}
|
|
482
606
|
</div>
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
607
|
+
) : (
|
|
608
|
+
<div className="session-form session-form--compact">
|
|
609
|
+
<div className="session-auth-title">
|
|
610
|
+
{t("Configure session")}
|
|
486
611
|
</div>
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
612
|
+
<div className="session-form-row is-compact-grid">
|
|
613
|
+
<input
|
|
614
|
+
type="text"
|
|
615
|
+
placeholder={t("Session name")}
|
|
616
|
+
value={sessionConfigTarget?.name || ""}
|
|
617
|
+
disabled
|
|
618
|
+
/>
|
|
619
|
+
<div className="session-repo-field">
|
|
620
|
+
<input
|
|
621
|
+
type="text"
|
|
622
|
+
placeholder={t("git@gitea.devops:my-org/my-repo.git")}
|
|
623
|
+
value={sessionConfigTarget?.repoUrl || ""}
|
|
624
|
+
disabled
|
|
625
|
+
/>
|
|
626
|
+
</div>
|
|
490
627
|
</div>
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
)}
|
|
551
|
-
{workspaceSessionsError && (
|
|
552
|
-
<div className="attachments-error">
|
|
553
|
-
{workspaceSessionsError}
|
|
628
|
+
<div className="session-auth">
|
|
629
|
+
<div className="session-auth-title">
|
|
630
|
+
{t("Repository authentication (optional)")}
|
|
631
|
+
</div>
|
|
632
|
+
<div className="session-auth-options">
|
|
633
|
+
<select
|
|
634
|
+
value={sessionConfigAuthMode}
|
|
635
|
+
onChange={(event) =>
|
|
636
|
+
setSessionConfigAuthMode(event.target.value)
|
|
637
|
+
}
|
|
638
|
+
disabled={formDisabled || Boolean(workspaceSessionUpdatingId)}
|
|
639
|
+
>
|
|
640
|
+
<option value="keep">{t("Keep unchanged")}</option>
|
|
641
|
+
<option value="none">{t("None")}</option>
|
|
642
|
+
<option value="ssh">
|
|
643
|
+
{t("Private SSH key (not recommended)")}
|
|
644
|
+
</option>
|
|
645
|
+
<option value="http">
|
|
646
|
+
{t("Username + password")}
|
|
647
|
+
</option>
|
|
648
|
+
</select>
|
|
649
|
+
</div>
|
|
650
|
+
{sessionConfigAuthMode === "ssh" ? (
|
|
651
|
+
<textarea
|
|
652
|
+
className="session-auth-textarea"
|
|
653
|
+
placeholder={t("-----BEGIN OPENSSH PRIVATE KEY-----")}
|
|
654
|
+
value={sessionConfigSshKey}
|
|
655
|
+
onChange={(event) =>
|
|
656
|
+
setSessionConfigSshKey(event.target.value)
|
|
657
|
+
}
|
|
658
|
+
disabled={formDisabled || Boolean(workspaceSessionUpdatingId)}
|
|
659
|
+
rows={6}
|
|
660
|
+
spellCheck={false}
|
|
661
|
+
/>
|
|
662
|
+
) : null}
|
|
663
|
+
{sessionConfigAuthMode === "http" ? (
|
|
664
|
+
<div className="session-auth-grid">
|
|
665
|
+
<input
|
|
666
|
+
type="text"
|
|
667
|
+
placeholder={t("Username")}
|
|
668
|
+
value={sessionConfigHttpUsername}
|
|
669
|
+
onChange={(event) =>
|
|
670
|
+
setSessionConfigHttpUsername(event.target.value)
|
|
671
|
+
}
|
|
672
|
+
disabled={formDisabled || Boolean(workspaceSessionUpdatingId)}
|
|
673
|
+
autoComplete="username"
|
|
674
|
+
/>
|
|
675
|
+
<input
|
|
676
|
+
type="password"
|
|
677
|
+
placeholder={t("Password or PAT")}
|
|
678
|
+
value={sessionConfigHttpPassword}
|
|
679
|
+
onChange={(event) =>
|
|
680
|
+
setSessionConfigHttpPassword(event.target.value)
|
|
681
|
+
}
|
|
682
|
+
disabled={formDisabled || Boolean(workspaceSessionUpdatingId)}
|
|
683
|
+
autoComplete="current-password"
|
|
684
|
+
/>
|
|
685
|
+
</div>
|
|
686
|
+
) : null}
|
|
554
687
|
</div>
|
|
555
|
-
|
|
556
|
-
|
|
688
|
+
<div className="session-auth session-auth-compact">
|
|
689
|
+
<div className="session-auth-title">
|
|
690
|
+
{t("Permissions")}
|
|
691
|
+
</div>
|
|
692
|
+
<div className="session-auth-options session-auth-options--compact">
|
|
693
|
+
<label className="session-auth-option">
|
|
694
|
+
<input
|
|
695
|
+
type="checkbox"
|
|
696
|
+
checked={sessionConfigInternetAccess}
|
|
697
|
+
onChange={(event) => {
|
|
698
|
+
const checked = event.target.checked;
|
|
699
|
+
setSessionConfigInternetAccess(checked);
|
|
700
|
+
if (!checked) {
|
|
701
|
+
setSessionConfigDenyGitCredentialsAccess(false);
|
|
702
|
+
}
|
|
703
|
+
}}
|
|
704
|
+
disabled={formDisabled || Boolean(workspaceSessionUpdatingId)}
|
|
705
|
+
/>
|
|
706
|
+
{t("Internet access")}
|
|
707
|
+
</label>
|
|
708
|
+
{sessionConfigInternetAccess &&
|
|
709
|
+
deploymentMode !== "mono_user" ? (
|
|
710
|
+
<label className="session-auth-option">
|
|
711
|
+
<input
|
|
712
|
+
type="checkbox"
|
|
713
|
+
checked={sessionConfigDenyGitCredentialsAccess}
|
|
714
|
+
onChange={(event) =>
|
|
715
|
+
setSessionConfigDenyGitCredentialsAccess(
|
|
716
|
+
event.target.checked
|
|
717
|
+
)
|
|
718
|
+
}
|
|
719
|
+
disabled={formDisabled || Boolean(workspaceSessionUpdatingId)}
|
|
720
|
+
/>
|
|
721
|
+
{t("Deny git credentials access")}
|
|
722
|
+
</label>
|
|
723
|
+
) : null}
|
|
724
|
+
</div>
|
|
725
|
+
</div>
|
|
726
|
+
{workspaceSessionConfigError ? (
|
|
727
|
+
<div className="attachments-error">
|
|
728
|
+
{workspaceSessionConfigError}
|
|
729
|
+
</div>
|
|
730
|
+
) : null}
|
|
731
|
+
</div>
|
|
732
|
+
)}
|
|
557
733
|
</div>
|
|
558
734
|
<div
|
|
559
735
|
className={`session-panel ${
|
|
@@ -788,21 +964,37 @@ export default function SessionGate({
|
|
|
788
964
|
</button>
|
|
789
965
|
) : showStep4 ? (
|
|
790
966
|
sessionMode === "existing" ? (
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
967
|
+
<>
|
|
968
|
+
<button
|
|
969
|
+
type="button"
|
|
970
|
+
className="session-button secondary"
|
|
971
|
+
disabled={formDisabled}
|
|
972
|
+
onClick={
|
|
973
|
+
workspaceSessionConfigId
|
|
974
|
+
? closeSessionConfigure
|
|
975
|
+
: () => {
|
|
976
|
+
setWorkspaceProvidersEditing(true);
|
|
977
|
+
setWorkspaceError("");
|
|
978
|
+
setProvidersBackStep(4);
|
|
979
|
+
loadWorkspaceProviders();
|
|
980
|
+
loadWorkspaceSessions();
|
|
981
|
+
setWorkspaceStep(2);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
>
|
|
985
|
+
{workspaceSessionConfigId ? t("Cancel") : t("AI providers")}
|
|
986
|
+
</button>
|
|
987
|
+
{workspaceSessionConfigId ? (
|
|
988
|
+
<button
|
|
989
|
+
type="button"
|
|
990
|
+
className="session-button primary"
|
|
991
|
+
disabled={formDisabled || Boolean(workspaceSessionUpdatingId)}
|
|
992
|
+
onClick={handleUpdateAndResumeSession}
|
|
993
|
+
>
|
|
994
|
+
{workspaceSessionUpdatingId ? t("Updating...") : t("Update & Resume")}
|
|
995
|
+
</button>
|
|
996
|
+
) : null}
|
|
997
|
+
</>
|
|
806
998
|
) : (
|
|
807
999
|
<>
|
|
808
1000
|
<button
|
|
@@ -844,6 +1036,18 @@ export default function SessionGate({
|
|
|
844
1036
|
{infoContent.paragraphs?.map((paragraph) => (
|
|
845
1037
|
<p key={paragraph}>{paragraph}</p>
|
|
846
1038
|
))}
|
|
1039
|
+
{infoContent.setupLink ? (
|
|
1040
|
+
<p>
|
|
1041
|
+
<a
|
|
1042
|
+
className="session-info-link"
|
|
1043
|
+
href="https://vibe80.io/docs/workspace-session-setup"
|
|
1044
|
+
target="_blank"
|
|
1045
|
+
rel="noreferrer"
|
|
1046
|
+
>
|
|
1047
|
+
{t("Click here to learn more.")}
|
|
1048
|
+
</a>
|
|
1049
|
+
</p>
|
|
1050
|
+
) : null}
|
|
847
1051
|
{infoContent.securityLink ? (
|
|
848
1052
|
<p>
|
|
849
1053
|
{t(
|
|
@@ -851,7 +1055,7 @@ export default function SessionGate({
|
|
|
851
1055
|
)}
|
|
852
1056
|
<a
|
|
853
1057
|
className="session-info-link"
|
|
854
|
-
href="https://vibe80.
|
|
1058
|
+
href="https://vibe80.io/docs/sandboxing"
|
|
855
1059
|
target="_blank"
|
|
856
1060
|
rel="noreferrer"
|
|
857
1061
|
>
|
|
@@ -258,6 +258,17 @@
|
|
|
258
258
|
font-size: 18px;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
.worktree-create-form {
|
|
262
|
+
border: 0;
|
|
263
|
+
margin: 0;
|
|
264
|
+
padding: 0;
|
|
265
|
+
min-width: 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.worktree-create-dialog.is-submitting .worktree-create-form {
|
|
269
|
+
opacity: 0.55;
|
|
270
|
+
}
|
|
271
|
+
|
|
261
272
|
.worktree-create-grid {
|
|
262
273
|
display: grid;
|
|
263
274
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -69,6 +69,7 @@ export default function WorktreeTabs({
|
|
|
69
69
|
const [newSourceWorktree, setNewSourceWorktree] = useState("main");
|
|
70
70
|
const [newModel, setNewModel] = useState("");
|
|
71
71
|
const [newReasoningEffort, setNewReasoningEffort] = useState("");
|
|
72
|
+
const [createSubmitting, setCreateSubmitting] = useState(false);
|
|
72
73
|
const [newInternetAccess, setNewInternetAccess] = useState(
|
|
73
74
|
Boolean(defaultInternetAccess)
|
|
74
75
|
);
|
|
@@ -218,34 +219,46 @@ export default function WorktreeTabs({
|
|
|
218
219
|
}
|
|
219
220
|
}, [newContext, newProvider, selectedModelDetails, newReasoningEffort]);
|
|
220
221
|
|
|
221
|
-
const handleCreate = () => {
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
222
|
+
const handleCreate = async () => {
|
|
223
|
+
if (createSubmitting) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
setCreateSubmitting(true);
|
|
227
|
+
try {
|
|
228
|
+
let created = true;
|
|
229
|
+
if (onCreate) {
|
|
230
|
+
created = await onCreate({
|
|
231
|
+
context: newContext,
|
|
232
|
+
name: newName.trim() || null,
|
|
233
|
+
provider: newProvider,
|
|
234
|
+
sourceWorktree: newContext === "fork" ? newSourceWorktree : null,
|
|
235
|
+
startingBranch: effectiveBranch || null,
|
|
236
|
+
model: newContext === "new" ? newModel || null : null,
|
|
237
|
+
reasoningEffort: newContext === "new" ? newReasoningEffort || null : null,
|
|
238
|
+
internetAccess: newInternetAccess,
|
|
239
|
+
denyGitCredentialsAccess: newDenyGitCredentialsAccess,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (!created) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
setNewName("");
|
|
246
|
+
setNewContext("new");
|
|
247
|
+
setNewProvider(providerOptions[0]);
|
|
248
|
+
setNewSourceWorktree("main");
|
|
249
|
+
setStartingBranch(defaultBranch || "");
|
|
250
|
+
setNewModel("");
|
|
251
|
+
setNewReasoningEffort("");
|
|
252
|
+
setNewInternetAccess(Boolean(defaultInternetAccess));
|
|
253
|
+
setNewDenyGitCredentialsAccess(
|
|
254
|
+
typeof defaultDenyGitCredentialsAccess === "boolean"
|
|
255
|
+
? defaultDenyGitCredentialsAccess
|
|
256
|
+
: true
|
|
257
|
+
);
|
|
258
|
+
setCreateDialogOpen(false);
|
|
259
|
+
} finally {
|
|
260
|
+
setCreateSubmitting(false);
|
|
234
261
|
}
|
|
235
|
-
setNewName("");
|
|
236
|
-
setNewContext("new");
|
|
237
|
-
setNewProvider(providerOptions[0]);
|
|
238
|
-
setNewSourceWorktree("main");
|
|
239
|
-
setStartingBranch(defaultBranch || "");
|
|
240
|
-
setNewModel("");
|
|
241
|
-
setNewReasoningEffort("");
|
|
242
|
-
setNewInternetAccess(Boolean(defaultInternetAccess));
|
|
243
|
-
setNewDenyGitCredentialsAccess(
|
|
244
|
-
typeof defaultDenyGitCredentialsAccess === "boolean"
|
|
245
|
-
? defaultDenyGitCredentialsAccess
|
|
246
|
-
: true
|
|
247
|
-
);
|
|
248
|
-
setCreateDialogOpen(false);
|
|
249
262
|
};
|
|
250
263
|
|
|
251
264
|
const handleStartEdit = (wt) => {
|
|
@@ -271,8 +284,11 @@ export default function WorktreeTabs({
|
|
|
271
284
|
};
|
|
272
285
|
|
|
273
286
|
const handleKeyDownCreate = (e) => {
|
|
287
|
+
if (createSubmitting) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
274
290
|
if (e.key === "Enter") {
|
|
275
|
-
handleCreate();
|
|
291
|
+
void handleCreate();
|
|
276
292
|
} else if (e.key === "Escape") {
|
|
277
293
|
setCreateDialogOpen(false);
|
|
278
294
|
}
|
|
@@ -416,10 +432,23 @@ export default function WorktreeTabs({
|
|
|
416
432
|
</div>
|
|
417
433
|
|
|
418
434
|
{createDialogOpen && (
|
|
419
|
-
<div
|
|
420
|
-
|
|
435
|
+
<div
|
|
436
|
+
className="worktree-create-dialog-overlay"
|
|
437
|
+
onClick={() => {
|
|
438
|
+
if (!createSubmitting) {
|
|
439
|
+
setCreateDialogOpen(false);
|
|
440
|
+
}
|
|
441
|
+
}}
|
|
442
|
+
>
|
|
443
|
+
<div
|
|
444
|
+
className={`worktree-create-dialog ${
|
|
445
|
+
createSubmitting ? "is-submitting" : ""
|
|
446
|
+
}`}
|
|
447
|
+
onClick={(e) => e.stopPropagation()}
|
|
448
|
+
>
|
|
421
449
|
<h3>{t("New worktree")}</h3>
|
|
422
|
-
<
|
|
450
|
+
<fieldset className="worktree-create-form" disabled={createSubmitting}>
|
|
451
|
+
<div className="worktree-create-grid">
|
|
423
452
|
<div className="worktree-create-field">
|
|
424
453
|
<label>{t("Name (optional)")}</label>
|
|
425
454
|
<input
|
|
@@ -579,22 +608,23 @@ export default function WorktreeTabs({
|
|
|
579
608
|
</label>
|
|
580
609
|
</div>
|
|
581
610
|
)}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
611
|
+
</div>
|
|
612
|
+
<div className="worktree-create-actions">
|
|
613
|
+
<button
|
|
614
|
+
className="worktree-btn-cancel"
|
|
615
|
+
onClick={() => setCreateDialogOpen(false)}
|
|
616
|
+
>
|
|
617
|
+
{t("Cancel")}
|
|
618
|
+
</button>
|
|
619
|
+
<button
|
|
620
|
+
className="worktree-btn-create"
|
|
621
|
+
onClick={() => void handleCreate()}
|
|
622
|
+
disabled={!isBranchValid || (newContext === "fork" && !newSourceWorktree)}
|
|
623
|
+
>
|
|
624
|
+
{createSubmitting ? t("Creating...") : t("Create")}
|
|
625
|
+
</button>
|
|
626
|
+
</div>
|
|
627
|
+
</fieldset>
|
|
598
628
|
</div>
|
|
599
629
|
</div>
|
|
600
630
|
)}
|