Workspace onboarding (#2013)
* cleanup navbar * re-enable dev server from command bar * Add the ability to open the command bar from the `NavbarContainer.tsx` : (vibe-kanban 58189151) - dots-three-outline icon - position next to settings `vibe-kanban/frontend/src/components/ui-new/containers/NavbarContainer.tsx` * `NavbarContainer.tsx` button to go back to old UI should navigate back to task rather than attempt (vibe-kanban 2b6f4c76) `index.ts` `vibe-kanban/frontend/src/components/ui-new/containers/NavbarContainer.tsx` `vibe-kanban/frontend/src/components/ui-new/actions/index.ts` * Add new setting: beta\_workspaces, and beta\_workspaces\_invitation\_sent (vibe-kanban e2e73cb9) - Stored in the `mod.rs` - This defaults to 'false' - If a user is considered an active user (they have created more than 50 task attempts), they should be proactively invited to join the beta of workspaces - The invite will be displayed as dialog in the old UI, triggered when the user opens the `TaskAttemptPanel.tsx` - The setting can be changed later in the `GeneralSettings.tsx` , right at the bottom in a section for beta features - When the user has beta\_workspaces set to true: - Previously when they open a task in the `TaskKanbanBoard.tsx` it would open a task attempt or task, now it will always open a task - And when clicking on a task attempt in the `TaskPanel.tsx` it will open in the new workspaces view (/workspaces/[WORKSPACE\_ID]) `vibe-kanban/frontend/src/components/panels/TaskAttemptPanel.tsx` `vibe-kanban/frontend/src/pages/settings/GeneralSettings.tsx` `vibe-kanban/crates/services/src/services/config/mod.rs` `vibe-kanban/frontend/src/components/tasks/TaskKanbanBoard.tsx` `vibe-kanban/frontend/src/components/panels/TaskPanel.tsx` * Introduce a way to give feedback (vibe-kanban 463fbf1d) - Icon in `NavbarContainer.tsx` next to settings - Also triggerable from command bar - Using posthog client, already installed `vibe-kanban/frontend/src/components/ui-new/containers/NavbarContainer.tsx` * Create a new dialog that shows when users use workspaces (ui-new) for the first time. It should (vibe-kanban b0e8344a) explain the features of workspaces. The component will consist of a popup with topics in a sidebar on the left and content (text and images) in a main section to the right. It does not use the existing Dialog component. It is accessible by clicking a help icon in the `NavbarContainer.tsx` and from the command bar via `pages.ts` Create the component and some seed content that I will edit: 1. Welcome to workspaces, here are some tips to get started 2. Use the command bar to navigate 3. Create workspaces in the sidebar, also view all your workspaces and the status of each here 4. Workspaces can be created with multiple repos 5. You can create multiple sessions for each workspace 6. Preview changes 7. View diffs, comment on them 8. If any any point you want to go back to the old UI, you can turn off workspaces in settings `vibe-kanban/frontend/src/components/ui-new/containers/NavbarContainer.tsx` `vibe-kanban/frontend/src/components/ui-new/actions/pages.ts` * Update `WorkspacesGuideDialog.tsx` : (vibe-kanban b4c5d731) - Make 600px height - Add new section: Context Bar > The context bar lets you switch between panes quickly. Drag it wherever works best for you. `vibe-kanban/frontend/src/components/ui-new/dialogs/WorkspacesGuideDialog.tsx`  * The `WorkspacesGuideDialog.tsx` appears every time I reload the page, it doesn't seem that we're (vibe-kanban 7fd9e0c8) saving that the user has already seen it once and it should no longer be auto shown `vibe-kanban/frontend/src/components/ui-new/dialogs/WorkspacesGuideDialog.tsx` * Please run `check-i18n.sh` and fix the issue (vibe-kanban d3323397) `vibe-kanban/scripts/check-i18n.sh`
20
crates/db/.sqlx/query-70b21c5c0a2ba5c21c9c1132f14a68c02a8a2cd555caea74e57a0aeb206770d3.json
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT COUNT(*) as \"count!: i64\" FROM workspaces",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "count!: i64",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "70b21c5c0a2ba5c21c9c1132f14a68c02a8a2cd555caea74e57a0aeb206770d3"
|
||||
}
|
||||
@@ -609,6 +609,14 @@ impl Workspace {
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
/// Count total workspaces across all projects
|
||||
pub async fn count_all(pool: &SqlitePool) -> Result<i64, WorkspaceError> {
|
||||
sqlx::query_scalar!(r#"SELECT COUNT(*) as "count!: i64" FROM workspaces"#)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(WorkspaceError::Database)
|
||||
}
|
||||
|
||||
pub async fn find_by_id_with_status(
|
||||
pool: &SqlitePool,
|
||||
id: Uuid,
|
||||
|
||||
@@ -111,6 +111,14 @@ pub async fn get_task_attempts(
|
||||
Ok(ResponseJson(ApiResponse::success(workspaces)))
|
||||
}
|
||||
|
||||
pub async fn get_workspace_count(
|
||||
State(deployment): State<DeploymentImpl>,
|
||||
) -> Result<ResponseJson<ApiResponse<i64>>, ApiError> {
|
||||
let pool = &deployment.db().pool;
|
||||
let count = Workspace::count_all(pool).await?;
|
||||
Ok(ResponseJson(ApiResponse::success(count)))
|
||||
}
|
||||
|
||||
pub async fn get_task_attempt(
|
||||
Extension(workspace): Extension<Workspace>,
|
||||
) -> Result<ResponseJson<ApiResponse<Workspace>>, ApiError> {
|
||||
@@ -1725,6 +1733,7 @@ pub fn router(deployment: &DeploymentImpl) -> Router<DeploymentImpl> {
|
||||
|
||||
let task_attempts_router = Router::new()
|
||||
.route("/", get(get_task_attempts).post(create_task_attempt))
|
||||
.route("/count", get(get_workspace_count))
|
||||
.route("/stream/ws", get(stream_workspaces_ws))
|
||||
.route("/summary", post(workspace_summary::get_workspace_summaries))
|
||||
.nest("/{id}", task_attempt_id_router)
|
||||
|
||||
@@ -41,6 +41,10 @@ pub struct Config {
|
||||
pub pr_auto_description_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub pr_auto_description_prompt: Option<String>,
|
||||
#[serde(default)]
|
||||
pub beta_workspaces: bool,
|
||||
#[serde(default)]
|
||||
pub beta_workspaces_invitation_sent: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -66,6 +70,8 @@ impl Config {
|
||||
showcases: old_config.showcases,
|
||||
pr_auto_description_enabled: true,
|
||||
pr_auto_description_prompt: None,
|
||||
beta_workspaces: false,
|
||||
beta_workspaces_invitation_sent: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +122,8 @@ impl Default for Config {
|
||||
showcases: ShowcaseState::default(),
|
||||
pr_auto_description_enabled: true,
|
||||
pr_auto_description_prompt: None,
|
||||
beta_workspaces: false,
|
||||
beta_workspaces_invitation_sent: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
frontend/public/beta-workspaces-preview.png
Normal file
|
After Width: | Height: | Size: 729 KiB |
BIN
frontend/public/guide-images/classic-ui.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
frontend/public/guide-images/command-bar.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
frontend/public/guide-images/context-bar.png
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
frontend/public/guide-images/diffs.png
Normal file
|
After Width: | Height: | Size: 357 KiB |
BIN
frontend/public/guide-images/multi-repo.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
frontend/public/guide-images/preview.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
frontend/public/guide-images/sessions.png
Normal file
|
After Width: | Height: | Size: 478 KiB |
BIN
frontend/public/guide-images/sidebar.png
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
frontend/public/guide-images/welcome.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||
import { defineModal, type NoProps } from '@/lib/modals';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const BetaWorkspacesDialogImpl = NiceModal.create<NoProps>(() => {
|
||||
const modal = useModal();
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const handleJoinBeta = () => {
|
||||
modal.resolve(true);
|
||||
};
|
||||
|
||||
const handleMaybeLater = () => {
|
||||
modal.resolve(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={modal.visible} uncloseable>
|
||||
<DialogContent className="sm:max-w-[640px]">
|
||||
<img
|
||||
src="/beta-workspaces-preview.png"
|
||||
alt={t('betaWorkspaces.title')}
|
||||
className="w-full rounded-lg border"
|
||||
/>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl">
|
||||
{t('betaWorkspaces.title')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="text-muted-foreground space-y-4">
|
||||
<p>{t('betaWorkspaces.intro')}</p>
|
||||
<p>{t('betaWorkspaces.newUiDescription')}</p>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
<li>{t('betaWorkspaces.newFeatures.multiRepo')}</li>
|
||||
<li>{t('betaWorkspaces.newFeatures.multiAgent')}</li>
|
||||
<li>{t('betaWorkspaces.newFeatures.commandBar')}</li>
|
||||
</ul>
|
||||
<p>{t('betaWorkspaces.oldUiDescription')}</p>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
<li>{t('betaWorkspaces.oldFeatures.kanban')}</li>
|
||||
<li>{t('betaWorkspaces.oldFeatures.settings')}</li>
|
||||
<li>{t('betaWorkspaces.oldFeatures.projects')}</li>
|
||||
</ul>
|
||||
<p>{t('betaWorkspaces.transition')}</p>
|
||||
<p>{t('betaWorkspaces.optOutNote')}</p>
|
||||
</div>
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button variant="outline" onClick={handleMaybeLater}>
|
||||
{t('betaWorkspaces.maybeLater')}
|
||||
</Button>
|
||||
<Button onClick={handleJoinBeta}>
|
||||
{t('betaWorkspaces.joinBeta')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
||||
export const BetaWorkspacesDialog = defineModal<void, boolean>(
|
||||
BetaWorkspacesDialogImpl
|
||||
);
|
||||
@@ -3,6 +3,7 @@ import { useProject } from '@/contexts/ProjectContext';
|
||||
import { useTaskAttemptsWithSessions } from '@/hooks/useTaskAttempts';
|
||||
import { useTaskAttemptWithSession } from '@/hooks/useTaskAttempt';
|
||||
import { useNavigateWithSearch } from '@/hooks';
|
||||
import { useUserSystem } from '@/components/ConfigProvider';
|
||||
import { paths } from '@/lib/paths';
|
||||
import type { TaskWithAttemptStatus } from 'shared/types';
|
||||
import type { WorkspaceWithSession } from '@/types/attempt';
|
||||
@@ -21,6 +22,7 @@ const TaskPanel = ({ task }: TaskPanelProps) => {
|
||||
const { t } = useTranslation('tasks');
|
||||
const navigate = useNavigateWithSearch();
|
||||
const { projectId } = useProject();
|
||||
const { config } = useUserSystem();
|
||||
|
||||
const {
|
||||
data: attempts = [],
|
||||
@@ -115,7 +117,9 @@ const TaskPanel = ({ task }: TaskPanelProps) => {
|
||||
columns={attemptColumns}
|
||||
keyExtractor={(attempt) => attempt.id}
|
||||
onRowClick={(attempt) => {
|
||||
if (projectId) {
|
||||
if (config?.beta_workspaces) {
|
||||
navigate(`/workspaces/${attempt.id}`);
|
||||
} else if (projectId) {
|
||||
navigate(
|
||||
paths.attempt(projectId, attempt.task_id, attempt.id)
|
||||
);
|
||||
@@ -140,7 +144,9 @@ const TaskPanel = ({ task }: TaskPanelProps) => {
|
||||
columns={attemptColumns}
|
||||
keyExtractor={(attempt) => attempt.id}
|
||||
onRowClick={(attempt) => {
|
||||
if (projectId && task.id) {
|
||||
if (config?.beta_workspaces) {
|
||||
navigate(`/workspaces/${attempt.id}`);
|
||||
} else if (projectId && task.id) {
|
||||
navigate(paths.attempt(projectId, task.id, attempt.id));
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -33,6 +33,9 @@ import {
|
||||
PencilSimpleIcon,
|
||||
ArrowUpIcon,
|
||||
HighlighterIcon,
|
||||
ListIcon,
|
||||
MegaphoneIcon,
|
||||
QuestionIcon,
|
||||
} from '@phosphor-icons/react';
|
||||
import { useDiffViewStore } from '@/stores/useDiffViewStore';
|
||||
import { useUiPreferencesStore } from '@/stores/useUiPreferencesStore';
|
||||
@@ -50,6 +53,8 @@ import { CreatePRDialog } from '@/components/dialogs/tasks/CreatePRDialog';
|
||||
import { getIdeName } from '@/components/ide/IdeIcon';
|
||||
import { EditorSelectionDialog } from '@/components/dialogs/tasks/EditorSelectionDialog';
|
||||
import { StartReviewDialog } from '@/components/dialogs/tasks/StartReviewDialog';
|
||||
import posthog from 'posthog-js';
|
||||
import { WorkspacesGuideDialog } from '@/components/ui-new/dialogs/WorkspacesGuideDialog';
|
||||
|
||||
// Mirrored sidebar icon for right sidebar toggle
|
||||
const RightSidebarIcon: Icon = forwardRef<SVGSVGElement, IconProps>(
|
||||
@@ -367,6 +372,40 @@ export const Actions = {
|
||||
},
|
||||
},
|
||||
|
||||
Feedback: {
|
||||
id: 'feedback',
|
||||
label: 'Give Feedback',
|
||||
icon: MegaphoneIcon,
|
||||
requiresTarget: false,
|
||||
execute: () => {
|
||||
posthog.displaySurvey('019bb6e8-3d36-0000-1806-7330cd3c727e');
|
||||
},
|
||||
},
|
||||
|
||||
WorkspacesGuide: {
|
||||
id: 'workspaces-guide',
|
||||
label: 'Workspaces Guide',
|
||||
icon: QuestionIcon,
|
||||
requiresTarget: false,
|
||||
execute: async () => {
|
||||
await WorkspacesGuideDialog.show();
|
||||
},
|
||||
},
|
||||
|
||||
OpenCommandBar: {
|
||||
id: 'open-command-bar',
|
||||
label: 'Open Command Bar',
|
||||
icon: ListIcon,
|
||||
requiresTarget: false,
|
||||
execute: async () => {
|
||||
// Dynamic import to avoid circular dependency (pages.ts imports Actions)
|
||||
const { CommandBarDialog } = await import(
|
||||
'@/components/ui-new/dialogs/CommandBarDialog'
|
||||
);
|
||||
CommandBarDialog.show();
|
||||
},
|
||||
},
|
||||
|
||||
// === Diff View Actions ===
|
||||
ToggleDiffViewMode: {
|
||||
id: 'toggle-diff-view-mode',
|
||||
@@ -533,9 +572,7 @@ export const Actions = {
|
||||
// Fetch task lazily to get project_id
|
||||
const task = await tasksApi.getById(workspace.task_id);
|
||||
if (task?.project_id) {
|
||||
ctx.navigate(
|
||||
`/projects/${task.project_id}/tasks/${workspace.task_id}/attempts/${workspace.id}`
|
||||
);
|
||||
ctx.navigate(`/projects/${task.project_id}/tasks/${workspace.task_id}`);
|
||||
} else {
|
||||
ctx.navigate('/');
|
||||
}
|
||||
@@ -907,7 +944,11 @@ export type NavbarItem = ActionDefinition | typeof NavbarDivider;
|
||||
|
||||
// Navbar action groups define which actions appear in each section
|
||||
export const NavbarActionGroups = {
|
||||
left: [Actions.ArchiveWorkspace, Actions.OpenInOldUI] as ActionDefinition[],
|
||||
left: [
|
||||
Actions.OpenInOldUI,
|
||||
NavbarDivider,
|
||||
Actions.ArchiveWorkspace,
|
||||
] as ActionDefinition[],
|
||||
right: [
|
||||
Actions.ToggleDiffViewMode,
|
||||
Actions.ToggleAllDiffs,
|
||||
@@ -918,6 +959,11 @@ export const NavbarActionGroups = {
|
||||
Actions.ToggleLogsMode,
|
||||
Actions.TogglePreviewMode,
|
||||
Actions.ToggleGitPanel,
|
||||
NavbarDivider,
|
||||
Actions.OpenCommandBar,
|
||||
Actions.Feedback,
|
||||
Actions.WorkspacesGuide,
|
||||
Actions.Settings,
|
||||
] as NavbarItem[],
|
||||
};
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export const Pages: Record<StaticPageId, CommandBarPage> = {
|
||||
{ type: 'action', action: Actions.NewWorkspace },
|
||||
{ type: 'action', action: Actions.OpenInIDE },
|
||||
{ type: 'action', action: Actions.CopyPath },
|
||||
// { type: 'action', action: Actions.ToggleDevServer },
|
||||
{ type: 'action', action: Actions.ToggleDevServer },
|
||||
{ type: 'action', action: Actions.OpenInOldUI },
|
||||
{ type: 'childPages', id: 'workspaceActions' },
|
||||
{ type: 'childPages', id: 'gitActions' },
|
||||
@@ -86,7 +86,11 @@ export const Pages: Record<StaticPageId, CommandBarPage> = {
|
||||
{
|
||||
type: 'group',
|
||||
label: 'General',
|
||||
items: [{ type: 'action', action: Actions.Settings }],
|
||||
items: [
|
||||
{ type: 'action', action: Actions.Feedback },
|
||||
{ type: 'action', action: Actions.WorkspacesGuide },
|
||||
{ type: 'action', action: Actions.Settings },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -31,6 +31,8 @@ import { usePush } from '@/hooks/usePush';
|
||||
import { repoApi } from '@/lib/api';
|
||||
import { ConfirmDialog } from '@/components/ui-new/dialogs/ConfirmDialog';
|
||||
import { ForcePushDialog } from '@/components/dialogs/git/ForcePushDialog';
|
||||
import { WorkspacesGuideDialog } from '@/components/ui-new/dialogs/WorkspacesGuideDialog';
|
||||
import { useUserSystem } from '@/components/ConfigProvider';
|
||||
import { useDiffStream } from '@/hooks/useDiffStream';
|
||||
import { useTask } from '@/hooks/useTask';
|
||||
import { useAttemptRepo } from '@/hooks/useAttemptRepo';
|
||||
@@ -290,6 +292,35 @@ export function WorkspacesLayout() {
|
||||
// Derived state: right main panel (Changes/Logs/Preview) is visible
|
||||
const isRightMainPanelVisible = useIsRightMainPanelVisible();
|
||||
|
||||
// === Auto-show Workspaces Guide on first visit ===
|
||||
const WORKSPACES_GUIDE_ID = 'workspaces-guide';
|
||||
const {
|
||||
config,
|
||||
updateAndSaveConfig,
|
||||
loading: configLoading,
|
||||
} = useUserSystem();
|
||||
|
||||
const seenFeatures = useMemo(
|
||||
() => config?.showcases?.seen_features ?? [],
|
||||
[config?.showcases?.seen_features]
|
||||
);
|
||||
|
||||
const hasSeenGuide =
|
||||
!configLoading && seenFeatures.includes(WORKSPACES_GUIDE_ID);
|
||||
|
||||
useEffect(() => {
|
||||
if (configLoading || hasSeenGuide) return;
|
||||
|
||||
// Mark as seen immediately before showing, so page reload doesn't re-trigger
|
||||
void updateAndSaveConfig({
|
||||
showcases: { seen_features: [...seenFeatures, WORKSPACES_GUIDE_ID] },
|
||||
});
|
||||
|
||||
WorkspacesGuideDialog.show().finally(() => {
|
||||
WorkspacesGuideDialog.hide();
|
||||
});
|
||||
}, [configLoading, hasSeenGuide, seenFeatures, updateAndSaveConfig]);
|
||||
|
||||
// Read persisted draft for sidebar placeholder (works outside of CreateModeProvider)
|
||||
const { scratch: draftScratch } = useScratch(
|
||||
ScratchType.DRAFT_WORKSPACE,
|
||||
|
||||
124
frontend/src/components/ui-new/dialogs/WorkspacesGuideDialog.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { XIcon } from '@phosphor-icons/react';
|
||||
import NiceModal, { useModal } from '@ebay/nice-modal-react';
|
||||
import { defineModal, type NoProps } from '@/lib/modals';
|
||||
import { usePortalContainer } from '@/contexts/PortalContainerContext';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const TOPIC_IDS = [
|
||||
'welcome',
|
||||
'commandBar',
|
||||
'contextBar',
|
||||
'sidebar',
|
||||
'multiRepo',
|
||||
'sessions',
|
||||
'preview',
|
||||
'diffs',
|
||||
'classicUi',
|
||||
] as const;
|
||||
|
||||
const TOPIC_IMAGES: Record<(typeof TOPIC_IDS)[number], string> = {
|
||||
welcome: '/guide-images/welcome.png',
|
||||
commandBar: '/guide-images/command-bar.png',
|
||||
contextBar: '/guide-images/context-bar.png',
|
||||
sidebar: '/guide-images/sidebar.png',
|
||||
multiRepo: '/guide-images/multi-repo.png',
|
||||
sessions: '/guide-images/sessions.png',
|
||||
preview: '/guide-images/preview.png',
|
||||
diffs: '/guide-images/diffs.png',
|
||||
classicUi: '/guide-images/classic-ui.png',
|
||||
};
|
||||
|
||||
const WorkspacesGuideDialogImpl = NiceModal.create<NoProps>(() => {
|
||||
const modal = useModal();
|
||||
const container = usePortalContainer();
|
||||
const { t } = useTranslation('common');
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const selectedTopicId = TOPIC_IDS[selectedIndex];
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
modal.hide();
|
||||
modal.resolve();
|
||||
modal.remove();
|
||||
}, [modal]);
|
||||
|
||||
// Handle ESC key
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleClose]);
|
||||
|
||||
if (!container) return null;
|
||||
|
||||
return createPortal(
|
||||
<>
|
||||
{/* Overlay */}
|
||||
<div
|
||||
className="fixed inset-0 z-[9998] bg-black/50 animate-in fade-in-0 duration-200"
|
||||
onClick={handleClose}
|
||||
/>
|
||||
{/* Dialog wrapper - handles positioning */}
|
||||
<div className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[9999]">
|
||||
{/* Dialog content - handles animation */}
|
||||
<div
|
||||
className={cn(
|
||||
'w-[800px] h-[600px] flex rounded-sm overflow-hidden',
|
||||
'bg-panel/95 backdrop-blur-sm border border-border/50 shadow-lg',
|
||||
'animate-in fade-in-0 slide-in-from-bottom-4 duration-200'
|
||||
)}
|
||||
>
|
||||
{/* Sidebar */}
|
||||
<div className="w-52 bg-secondary/80 border-r border-border/50 p-3 flex flex-col gap-1 overflow-y-auto">
|
||||
{TOPIC_IDS.map((topicId, idx) => (
|
||||
<button
|
||||
key={topicId}
|
||||
onClick={() => setSelectedIndex(idx)}
|
||||
className={cn(
|
||||
'text-left px-3 py-2 rounded-sm text-sm transition-colors',
|
||||
idx === selectedIndex
|
||||
? 'bg-brand/10 text-brand font-medium'
|
||||
: 'text-normal hover:bg-primary/10'
|
||||
)}
|
||||
>
|
||||
{t(`workspacesGuide.${topicId}.title`)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div className="flex-1 p-6 flex flex-col relative overflow-y-auto">
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-panel transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-brand focus:ring-offset-2"
|
||||
>
|
||||
<XIcon className="h-4 w-4 text-normal" />
|
||||
<span className="sr-only">{t('close')}</span>
|
||||
</button>
|
||||
<h2 className="text-xl font-semibold text-high mb-4 pr-8">
|
||||
{t(`workspacesGuide.${selectedTopicId}.title`)}
|
||||
</h2>
|
||||
<img
|
||||
src={TOPIC_IMAGES[selectedTopicId]}
|
||||
alt={t(`workspacesGuide.${selectedTopicId}.title`)}
|
||||
className="w-full rounded-sm border border-border/30 mb-4"
|
||||
/>
|
||||
<p className="text-normal text-sm leading-relaxed">
|
||||
{t(`workspacesGuide.${selectedTopicId}.content`)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>,
|
||||
container
|
||||
);
|
||||
});
|
||||
|
||||
export const WorkspacesGuideDialog = defineModal<void, void>(
|
||||
WorkspacesGuideDialogImpl
|
||||
);
|
||||
@@ -114,7 +114,7 @@ export function Navbar({
|
||||
return (
|
||||
<nav
|
||||
className={cn(
|
||||
'flex items-center justify-between px-double py-half bg-secondary border-b shrink-0',
|
||||
'flex items-center justify-between px-base py-half bg-secondary border-b shrink-0',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
21
frontend/src/hooks/useWorkspaceCount.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { attemptsApi } from '@/lib/api';
|
||||
|
||||
export const workspaceCountKeys = {
|
||||
count: ['workspaceCount'] as const,
|
||||
};
|
||||
|
||||
type Options = {
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export function useWorkspaceCount(opts?: Options) {
|
||||
const enabled = opts?.enabled ?? true;
|
||||
|
||||
return useQuery<number>({
|
||||
queryKey: workspaceCountKeys.count,
|
||||
queryFn: () => attemptsApi.getCount(),
|
||||
enabled,
|
||||
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
|
||||
});
|
||||
}
|
||||
@@ -202,5 +202,63 @@
|
||||
"createFirstPrompt": "Create a project first to start working on tasks.",
|
||||
"createNew": "Create new project",
|
||||
"noProjectsFound": "No projects found"
|
||||
},
|
||||
"betaWorkspaces": {
|
||||
"title": "Try the new UI",
|
||||
"intro": "We've rebuilt Vibe Kanban's UI from the ground up, and you've been invited to try it.",
|
||||
"newUiDescription": "The new UI is optimised for day-to-day work: juggling agents, previewing changes, and reviewing code. It also unlocks features that aren't available in the old UI:",
|
||||
"newFeatures": {
|
||||
"multiRepo": "Edit multiple repositories at the same time",
|
||||
"multiAgent": "Run multiple agent conversations (even across different agents) in a single workspace",
|
||||
"commandBar": "Faster navigation with the command bar"
|
||||
},
|
||||
"oldUiDescription": "For now, a few things still live in the current UI:",
|
||||
"oldFeatures": {
|
||||
"kanban": "Managing tasks on the kanban board",
|
||||
"settings": "Changing settings",
|
||||
"projects": "Managing projects"
|
||||
},
|
||||
"transition": "During the transition over the next few weeks, you'll move between both.",
|
||||
"optOutNote": "Want to opt out? You can switch back to the current UI at any time in Settings.",
|
||||
"joinBeta": "Join Beta",
|
||||
"maybeLater": "Maybe Later"
|
||||
},
|
||||
"workspacesGuide": {
|
||||
"welcome": {
|
||||
"title": "Welcome",
|
||||
"content": "Welcome to Workspaces, a redesigned UI for Vibe Kanban. We're rolling this out to select users for early feedback. Share your thoughts anytime using the feedback icon in the navbar."
|
||||
},
|
||||
"commandBar": {
|
||||
"title": "Command Bar",
|
||||
"content": "The command bar is your central hub for navigation. Open it with CMD+K to search and access every action available in a workspace."
|
||||
},
|
||||
"contextBar": {
|
||||
"title": "Context Bar",
|
||||
"content": "The context bar lets you switch between panes quickly. Drag it wherever works best for you."
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Workspace Sidebar",
|
||||
"content": "See the status of all your workspaces at a glance. Notifications highlight which ones need attention. Archive merged workspaces to keep your sidebar clean."
|
||||
},
|
||||
"multiRepo": {
|
||||
"title": "Multi-Repo Support",
|
||||
"content": "Add multiple repos to a single workspace. Reference code from one repo while working in another, or implement changes across several repos at once."
|
||||
},
|
||||
"sessions": {
|
||||
"title": "Multiple Sessions",
|
||||
"content": "Create multiple agent conversation sessions within a single workspace, including sessions with different agents. This helps you work around conversation limits or spin up review agents in separate threads."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Preview Changes",
|
||||
"content": "Preview your work in a built-in browser without switching contexts. Test across desktop, mobile, and custom viewport sizes."
|
||||
},
|
||||
"diffs": {
|
||||
"title": "Diffs and Comments",
|
||||
"content": "The redesigned diffs panel includes a file tree of your changes. Comment directly on diffs to give feedback to the agent, and view GitHub comments when your workspace is linked to a PR."
|
||||
},
|
||||
"classicUi": {
|
||||
"title": "Return to Classic UI",
|
||||
"content": "Click the exit icon on the left side of the navbar to return to the classic kanban board. To disable the new UI entirely, update the \"Enable Workspaces Beta\" option under \"Beta Features\" in settings."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,14 @@
|
||||
"description": "Reset the onboarding flow.",
|
||||
"button": "Reset"
|
||||
}
|
||||
},
|
||||
"beta": {
|
||||
"title": "Beta Features",
|
||||
"description": "Try out experimental features before they're released.",
|
||||
"workspaces": {
|
||||
"label": "Enable Workspaces Beta",
|
||||
"helper": "Use the new workspaces interface when viewing task attempts. Tasks will open in task view first, and attempts will open in the new workspaces view."
|
||||
}
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
|
||||
@@ -202,5 +202,63 @@
|
||||
"createFirstPrompt": "Crea un proyecto primero para comenzar a trabajar en tareas.",
|
||||
"createNew": "Crear nuevo proyecto",
|
||||
"noProjectsFound": "No se encontraron proyectos"
|
||||
},
|
||||
"betaWorkspaces": {
|
||||
"title": "Prueba la nueva interfaz",
|
||||
"intro": "Hemos reconstruido la interfaz de Vibe Kanban desde cero, y has sido invitado a probarla.",
|
||||
"newUiDescription": "La nueva interfaz está optimizada para el trabajo diario: gestionar agentes, previsualizar cambios y revisar código. También desbloquea funciones que no están disponibles en la interfaz anterior:",
|
||||
"newFeatures": {
|
||||
"multiRepo": "Edita múltiples repositorios al mismo tiempo",
|
||||
"multiAgent": "Ejecuta múltiples conversaciones de agentes (incluso entre diferentes agentes) en un solo espacio de trabajo",
|
||||
"commandBar": "Navegación más rápida con la barra de comandos"
|
||||
},
|
||||
"oldUiDescription": "Por ahora, algunas cosas todavía permanecen en la interfaz actual:",
|
||||
"oldFeatures": {
|
||||
"kanban": "Gestión de tareas en el tablero kanban",
|
||||
"settings": "Cambiar configuración",
|
||||
"projects": "Gestión de proyectos"
|
||||
},
|
||||
"transition": "Durante la transición en las próximas semanas, te moverás entre ambas.",
|
||||
"optOutNote": "¿Quieres salir? Puedes volver a la interfaz actual en cualquier momento en Configuración.",
|
||||
"joinBeta": "Unirse a Beta",
|
||||
"maybeLater": "Quizás más tarde"
|
||||
},
|
||||
"workspacesGuide": {
|
||||
"welcome": {
|
||||
"title": "Bienvenido",
|
||||
"content": "Bienvenido a Workspaces, una interfaz rediseñada para Vibe Kanban. Estamos lanzando esto a usuarios seleccionados para recibir comentarios tempranos. Comparte tus opiniones en cualquier momento usando el ícono de comentarios en la barra de navegación."
|
||||
},
|
||||
"commandBar": {
|
||||
"title": "Barra de comandos",
|
||||
"content": "La barra de comandos es tu centro principal de navegación. Ábrela con CMD+K para buscar y acceder a cada acción disponible en un espacio de trabajo."
|
||||
},
|
||||
"contextBar": {
|
||||
"title": "Barra de contexto",
|
||||
"content": "La barra de contexto te permite cambiar entre paneles rápidamente. Arrástrala a donde mejor te funcione."
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "Barra lateral de espacios de trabajo",
|
||||
"content": "Ve el estado de todos tus espacios de trabajo de un vistazo. Las notificaciones resaltan cuáles necesitan atención. Archiva los espacios de trabajo fusionados para mantener tu barra lateral limpia."
|
||||
},
|
||||
"multiRepo": {
|
||||
"title": "Soporte multi-repositorio",
|
||||
"content": "Agrega múltiples repositorios a un solo espacio de trabajo. Referencia código de un repositorio mientras trabajas en otro, o implementa cambios en varios repositorios a la vez."
|
||||
},
|
||||
"sessions": {
|
||||
"title": "Múltiples sesiones",
|
||||
"content": "Crea múltiples sesiones de conversación con agentes dentro de un solo espacio de trabajo, incluyendo sesiones con diferentes agentes. Esto te ayuda a superar los límites de conversación o iniciar agentes de revisión en hilos separados."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Vista previa de cambios",
|
||||
"content": "Previsualiza tu trabajo en un navegador integrado sin cambiar de contexto. Prueba en escritorio, móvil y tamaños de ventana personalizados."
|
||||
},
|
||||
"diffs": {
|
||||
"title": "Diferencias y comentarios",
|
||||
"content": "El panel de diferencias rediseñado incluye un árbol de archivos con tus cambios. Comenta directamente en las diferencias para dar retroalimentación al agente, y ve los comentarios de GitHub cuando tu espacio de trabajo está vinculado a un PR."
|
||||
},
|
||||
"classicUi": {
|
||||
"title": "Volver a la interfaz clásica",
|
||||
"content": "Haz clic en el icono de salida en el lado izquierdo de la barra de navegación para volver al tablero kanban clásico. Para desactivar la nueva interfaz por completo, actualiza la opción \"Habilitar Beta de Workspaces\" en \"Funciones Beta\" en la configuración."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,14 @@
|
||||
"description": "Restablece el flujo de Introducción.",
|
||||
"button": "Restablecer"
|
||||
}
|
||||
},
|
||||
"beta": {
|
||||
"title": "Funciones Beta",
|
||||
"description": "Prueba funciones experimentales antes de su lanzamiento.",
|
||||
"workspaces": {
|
||||
"label": "Habilitar Beta de Workspaces",
|
||||
"helper": "Usa la nueva interfaz de workspaces al ver intentos de tareas. Las tareas se abrirán primero en la vista de tareas, y los intentos se abrirán en la nueva vista de workspaces."
|
||||
}
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
|
||||
@@ -202,5 +202,63 @@
|
||||
"createFirstPrompt": "タスクを開始するには、まずプロジェクトを作成してください。",
|
||||
"createNew": "新しいプロジェクトを作成",
|
||||
"noProjectsFound": "プロジェクトが見つかりません"
|
||||
},
|
||||
"betaWorkspaces": {
|
||||
"title": "新しいUIを試す",
|
||||
"intro": "Vibe KanbanのUIをゼロから再構築しました。あなたは試用に招待されています。",
|
||||
"newUiDescription": "新しいUIは日常業務に最適化されています:エージェントの管理、変更のプレビュー、コードのレビュー。また、旧UIでは利用できない機能も利用可能になります:",
|
||||
"newFeatures": {
|
||||
"multiRepo": "複数のリポジトリを同時に編集",
|
||||
"multiAgent": "単一のワークスペースで複数のエージェント会話(異なるエージェント間でも)を実行",
|
||||
"commandBar": "コマンドバーでより高速なナビゲーション"
|
||||
},
|
||||
"oldUiDescription": "現時点では、いくつかの機能は現在のUIに残っています:",
|
||||
"oldFeatures": {
|
||||
"kanban": "カンバンボードでのタスク管理",
|
||||
"settings": "設定の変更",
|
||||
"projects": "プロジェクトの管理"
|
||||
},
|
||||
"transition": "今後数週間の移行期間中は、両方のUIを行き来します。",
|
||||
"optOutNote": "オプトアウトしたい場合は、設定からいつでも現在のUIに戻すことができます。",
|
||||
"joinBeta": "ベータに参加",
|
||||
"maybeLater": "後で"
|
||||
},
|
||||
"workspacesGuide": {
|
||||
"welcome": {
|
||||
"title": "ようこそ",
|
||||
"content": "Vibe Kanbanの新しいUIであるWorkspacesへようこそ。早期フィードバックのために一部のユーザーに先行公開しています。ナビバーのフィードバックアイコンからいつでもご意見をお聞かせください。"
|
||||
},
|
||||
"commandBar": {
|
||||
"title": "コマンドバー",
|
||||
"content": "コマンドバーはナビゲーションの中心となるハブです。CMD+Kで開いて、ワークスペースで利用可能なすべてのアクションを検索・アクセスできます。"
|
||||
},
|
||||
"contextBar": {
|
||||
"title": "コンテキストバー",
|
||||
"content": "コンテキストバーでペイン間をすばやく切り替えられます。使いやすい場所にドラッグして配置できます。"
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "ワークスペースサイドバー",
|
||||
"content": "すべてのワークスペースの状態を一目で確認できます。通知により注意が必要なワークスペースがハイライトされます。マージ済みのワークスペースをアーカイブしてサイドバーを整理しましょう。"
|
||||
},
|
||||
"multiRepo": {
|
||||
"title": "マルチリポジトリ対応",
|
||||
"content": "1つのワークスペースに複数のリポジトリを追加できます。別のリポジトリで作業しながら他のリポジトリのコードを参照したり、複数のリポジトリにまたがる変更を同時に実装できます。"
|
||||
},
|
||||
"sessions": {
|
||||
"title": "複数セッション",
|
||||
"content": "1つのワークスペース内で複数のエージェント会話セッションを作成できます(異なるエージェントとのセッションも含む)。これにより会話の制限を回避したり、別スレッドでレビューエージェントを起動できます。"
|
||||
},
|
||||
"preview": {
|
||||
"title": "変更のプレビュー",
|
||||
"content": "コンテキストを切り替えることなく、内蔵ブラウザで作業をプレビューできます。デスクトップ、モバイル、カスタムビューポートサイズでテストできます。"
|
||||
},
|
||||
"diffs": {
|
||||
"title": "差分とコメント",
|
||||
"content": "再設計された差分パネルには変更のファイルツリーが含まれています。差分に直接コメントしてエージェントにフィードバックを送ったり、ワークスペースがPRにリンクされている場合はGitHubコメントを表示できます。"
|
||||
},
|
||||
"classicUi": {
|
||||
"title": "クラシックUIに戻る",
|
||||
"content": "ナビバー左側の終了アイコンをクリックすると、クラシックカンバンボードに戻ります。新しいUIを完全に無効にするには、設定の「ベータ機能」にある「Workspacesベータを有効にする」オプションを更新してください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,14 @@
|
||||
"description": "オンボーディングフローをリセットします。",
|
||||
"button": "リセット"
|
||||
}
|
||||
},
|
||||
"beta": {
|
||||
"title": "ベータ機能",
|
||||
"description": "リリース前の実験的な機能を試す。",
|
||||
"workspaces": {
|
||||
"label": "ワークスペースベータを有効化",
|
||||
"helper": "タスク試行を表示する際に新しいワークスペースインターフェースを使用します。タスクは最初にタスクビューで開き、試行は新しいワークスペースビューで開きます。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
|
||||
@@ -202,5 +202,63 @@
|
||||
"createFirstPrompt": "작업을 시작하려면 먼저 프로젝트를 만드세요.",
|
||||
"createNew": "새 프로젝트 만들기",
|
||||
"noProjectsFound": "프로젝트를 찾을 수 없습니다"
|
||||
},
|
||||
"betaWorkspaces": {
|
||||
"title": "새로운 UI 사용해보기",
|
||||
"intro": "Vibe Kanban의 UI를 처음부터 다시 구축했으며, 체험에 초대되었습니다.",
|
||||
"newUiDescription": "새로운 UI는 일상 업무에 최적화되어 있습니다: 에이전트 관리, 변경 사항 미리보기, 코드 검토. 또한 기존 UI에서는 사용할 수 없는 기능도 제공합니다:",
|
||||
"newFeatures": {
|
||||
"multiRepo": "여러 저장소를 동시에 편집",
|
||||
"multiAgent": "단일 워크스페이스에서 여러 에이전트 대화(다른 에이전트 간에도) 실행",
|
||||
"commandBar": "명령 바로 더 빠른 탐색"
|
||||
},
|
||||
"oldUiDescription": "현재로서는 일부 기능이 기존 UI에 남아 있습니다:",
|
||||
"oldFeatures": {
|
||||
"kanban": "칸반 보드에서 작업 관리",
|
||||
"settings": "설정 변경",
|
||||
"projects": "프로젝트 관리"
|
||||
},
|
||||
"transition": "앞으로 몇 주 동안의 전환 기간 동안 두 UI를 오가게 됩니다.",
|
||||
"optOutNote": "옵트아웃하시겠습니까? 설정에서 언제든지 기존 UI로 돌아갈 수 있습니다.",
|
||||
"joinBeta": "베타 참여",
|
||||
"maybeLater": "나중에"
|
||||
},
|
||||
"workspacesGuide": {
|
||||
"welcome": {
|
||||
"title": "환영합니다",
|
||||
"content": "Vibe Kanban의 새롭게 디자인된 UI인 Workspaces에 오신 것을 환영합니다. 초기 피드백을 위해 일부 사용자에게 먼저 공개하고 있습니다. 네비게이션 바의 피드백 아이콘을 통해 언제든지 의견을 공유해 주세요."
|
||||
},
|
||||
"commandBar": {
|
||||
"title": "명령 바",
|
||||
"content": "명령 바는 탐색의 중심 허브입니다. CMD+K로 열어 워크스페이스에서 사용 가능한 모든 작업을 검색하고 접근할 수 있습니다."
|
||||
},
|
||||
"contextBar": {
|
||||
"title": "컨텍스트 바",
|
||||
"content": "컨텍스트 바를 사용하면 패널 간에 빠르게 전환할 수 있습니다. 가장 편한 위치로 드래그하세요."
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "워크스페이스 사이드바",
|
||||
"content": "모든 워크스페이스의 상태를 한눈에 확인할 수 있습니다. 알림은 주의가 필요한 워크스페이스를 강조 표시합니다. 병합된 워크스페이스를 보관하여 사이드바를 깔끔하게 유지하세요."
|
||||
},
|
||||
"multiRepo": {
|
||||
"title": "멀티 저장소 지원",
|
||||
"content": "단일 워크스페이스에 여러 저장소를 추가할 수 있습니다. 다른 저장소에서 작업하면서 한 저장소의 코드를 참조하거나 여러 저장소에 걸쳐 변경 사항을 동시에 구현할 수 있습니다."
|
||||
},
|
||||
"sessions": {
|
||||
"title": "다중 세션",
|
||||
"content": "단일 워크스페이스 내에서 다른 에이전트와의 세션을 포함하여 여러 에이전트 대화 세션을 만들 수 있습니다. 이를 통해 대화 제한을 우회하거나 별도의 스레드에서 검토 에이전트를 시작할 수 있습니다."
|
||||
},
|
||||
"preview": {
|
||||
"title": "변경 사항 미리보기",
|
||||
"content": "컨텍스트 전환 없이 내장 브라우저에서 작업을 미리 볼 수 있습니다. 데스크톱, 모바일 및 사용자 지정 뷰포트 크기에서 테스트할 수 있습니다."
|
||||
},
|
||||
"diffs": {
|
||||
"title": "변경 사항 및 코멘트",
|
||||
"content": "새롭게 디자인된 변경 사항 패널에는 변경된 파일 트리가 포함되어 있습니다. 변경 사항에 직접 코멘트하여 에이전트에게 피드백을 제공하고, 워크스페이스가 PR에 연결된 경우 GitHub 코멘트를 볼 수 있습니다."
|
||||
},
|
||||
"classicUi": {
|
||||
"title": "클래식 UI로 돌아가기",
|
||||
"content": "네비게이션 바 왼쪽의 나가기 아이콘을 클릭하면 클래식 칸반 보드로 돌아갑니다. 새 UI를 완전히 비활성화하려면 설정의 \"베타 기능\"에서 \"Workspaces 베타 활성화\" 옵션을 업데이트하세요."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,14 @@
|
||||
"description": "온보딩 흐름을 재설정합니다.",
|
||||
"button": "초기화"
|
||||
}
|
||||
},
|
||||
"beta": {
|
||||
"title": "베타 기능",
|
||||
"description": "출시 전 실험적인 기능을 체험해보세요.",
|
||||
"workspaces": {
|
||||
"label": "워크스페이스 베타 활성화",
|
||||
"helper": "작업 시도를 볼 때 새로운 워크스페이스 인터페이스를 사용합니다. 작업은 먼저 작업 보기에서 열리고, 시도는 새로운 워크스페이스 보기에서 열립니다."
|
||||
}
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
|
||||
@@ -202,5 +202,63 @@
|
||||
"createFirstPrompt": "请先创建项目以开始处理任务。",
|
||||
"createNew": "创建新项目",
|
||||
"noProjectsFound": "未找到项目"
|
||||
},
|
||||
"betaWorkspaces": {
|
||||
"title": "试用新界面",
|
||||
"intro": "我们从头重建了 Vibe Kanban 的界面,您已被邀请试用。",
|
||||
"newUiDescription": "新界面针对日常工作进行了优化:管理代理、预览更改和审查代码。它还解锁了旧界面中不可用的功能:",
|
||||
"newFeatures": {
|
||||
"multiRepo": "同时编辑多个仓库",
|
||||
"multiAgent": "在单个工作区中运行多个代理对话(甚至跨不同代理)",
|
||||
"commandBar": "使用命令栏更快地导航"
|
||||
},
|
||||
"oldUiDescription": "目前,一些功能仍保留在当前界面中:",
|
||||
"oldFeatures": {
|
||||
"kanban": "在看板上管理任务",
|
||||
"settings": "更改设置",
|
||||
"projects": "管理项目"
|
||||
},
|
||||
"transition": "在接下来几周的过渡期间,您将在两者之间切换。",
|
||||
"optOutNote": "想要退出?您可以随时在设置中切换回当前界面。",
|
||||
"joinBeta": "加入 Beta",
|
||||
"maybeLater": "稍后再说"
|
||||
},
|
||||
"workspacesGuide": {
|
||||
"welcome": {
|
||||
"title": "欢迎",
|
||||
"content": "欢迎使用 Workspaces,这是 Vibe Kanban 的全新设计界面。我们正在向部分用户推出以获取早期反馈。您可以随时通过导航栏中的反馈图标分享您的想法。"
|
||||
},
|
||||
"commandBar": {
|
||||
"title": "命令栏",
|
||||
"content": "命令栏是您的导航中心。使用 CMD+K 打开它,可以搜索和访问工作区中的所有可用操作。"
|
||||
},
|
||||
"contextBar": {
|
||||
"title": "上下文栏",
|
||||
"content": "上下文栏让您可以快速切换不同面板。将它拖动到最适合您的位置。"
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "工作区侧边栏",
|
||||
"content": "一目了然地查看所有工作区的状态。通知会突出显示需要关注的工作区。归档已合并的工作区以保持侧边栏整洁。"
|
||||
},
|
||||
"multiRepo": {
|
||||
"title": "多仓库支持",
|
||||
"content": "将多个仓库添加到单个工作区。在处理一个仓库时可以引用另一个仓库的代码,或者同时在多个仓库中实现更改。"
|
||||
},
|
||||
"sessions": {
|
||||
"title": "多会话",
|
||||
"content": "在单个工作区内创建多个代理对话会话,包括与不同代理的会话。这可以帮助您绕过对话限制,或在单独的线程中启动审查代理。"
|
||||
},
|
||||
"preview": {
|
||||
"title": "预览更改",
|
||||
"content": "无需切换上下文,即可在内置浏览器中预览您的工作。支持桌面、移动设备和自定义视口尺寸测试。"
|
||||
},
|
||||
"diffs": {
|
||||
"title": "差异和评论",
|
||||
"content": "重新设计的差异面板包含更改的文件树。直接在差异上评论以向代理提供反馈,当您的工作区链接到 PR 时还可以查看 GitHub 评论。"
|
||||
},
|
||||
"classicUi": {
|
||||
"title": "返回经典界面",
|
||||
"content": "点击导航栏左侧的退出图标可返回经典看板。要完全禁用新界面,请在设置中的「测试版功能」下更新「启用 Workspaces 测试版」选项。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,14 @@
|
||||
"description": "重置入门流程。",
|
||||
"button": "重置"
|
||||
}
|
||||
},
|
||||
"beta": {
|
||||
"title": "Beta 功能",
|
||||
"description": "在正式发布前试用实验性功能。",
|
||||
"workspaces": {
|
||||
"label": "启用工作区 Beta",
|
||||
"helper": "查看任务尝试时使用新的工作区界面。任务将首先在任务视图中打开,尝试将在新的工作区视图中打开。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
|
||||
@@ -202,5 +202,63 @@
|
||||
"createFirstPrompt": "請先建立專案以開始處理任務。",
|
||||
"createNew": "建立新專案",
|
||||
"noProjectsFound": "找不到專案"
|
||||
},
|
||||
"betaWorkspaces": {
|
||||
"title": "試用新介面",
|
||||
"intro": "我們從頭重建了 Vibe Kanban 的介面,您已獲邀試用。",
|
||||
"newUiDescription": "新介面針對日常工作進行最佳化:管理代理、預覽變更和審查程式碼。它還解鎖了舊介面中無法使用的功能:",
|
||||
"newFeatures": {
|
||||
"multiRepo": "同時編輯多個儲存庫",
|
||||
"multiAgent": "在單一工作區中執行多個代理對話(甚至跨不同代理)",
|
||||
"commandBar": "使用命令列更快地導覽"
|
||||
},
|
||||
"oldUiDescription": "目前,部分功能仍保留在目前介面中:",
|
||||
"oldFeatures": {
|
||||
"kanban": "在看板上管理任務",
|
||||
"settings": "變更設定",
|
||||
"projects": "管理專案"
|
||||
},
|
||||
"transition": "在接下來幾週的過渡期間,您將在兩者之間切換。",
|
||||
"optOutNote": "想要退出?您可以隨時在設定中切換回目前介面。",
|
||||
"joinBeta": "加入 Beta",
|
||||
"maybeLater": "稍後再說"
|
||||
},
|
||||
"workspacesGuide": {
|
||||
"welcome": {
|
||||
"title": "歡迎",
|
||||
"content": "歡迎使用 Workspaces,這是 Vibe Kanban 的全新設計介面。我們正在向部分使用者推出以獲取早期回饋。您可以隨時透過導覽列中的回饋圖示分享您的想法。"
|
||||
},
|
||||
"commandBar": {
|
||||
"title": "命令列",
|
||||
"content": "命令列是您的導覽中心。使用 CMD+K 開啟它,可以搜尋和存取工作區中的所有可用操作。"
|
||||
},
|
||||
"contextBar": {
|
||||
"title": "上下文列",
|
||||
"content": "上下文列讓您可以快速切換不同面板。將它拖曳到最適合您的位置。"
|
||||
},
|
||||
"sidebar": {
|
||||
"title": "工作區側邊欄",
|
||||
"content": "一目了然地檢視所有工作區的狀態。通知會醒目提示需要關注的工作區。封存已合併的工作區以保持側邊欄整潔。"
|
||||
},
|
||||
"multiRepo": {
|
||||
"title": "多儲存庫支援",
|
||||
"content": "將多個儲存庫新增到單一工作區。在處理一個儲存庫時可以參考另一個儲存庫的程式碼,或者同時在多個儲存庫中實作變更。"
|
||||
},
|
||||
"sessions": {
|
||||
"title": "多工作階段",
|
||||
"content": "在單一工作區內建立多個代理對話工作階段,包括與不同代理的工作階段。這可以幫助您繞過對話限制,或在單獨的執行緒中啟動審查代理。"
|
||||
},
|
||||
"preview": {
|
||||
"title": "預覽變更",
|
||||
"content": "無需切換上下文,即可在內建瀏覽器中預覽您的工作。支援桌面、行動裝置和自訂視口尺寸測試。"
|
||||
},
|
||||
"diffs": {
|
||||
"title": "差異和評論",
|
||||
"content": "重新設計的差異面板包含變更的檔案樹。直接在差異上評論以向代理提供回饋,當您的工作區連結到 PR 時還可以檢視 GitHub 評論。"
|
||||
},
|
||||
"classicUi": {
|
||||
"title": "返回經典介面",
|
||||
"content": "點擊導覽列左側的退出圖示可返回經典看板。要完全停用新介面,請在設定中的「測試版功能」下更新「啟用 Workspaces 測試版」選項。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,14 @@
|
||||
"description": "重設入門流程。",
|
||||
"button": "重設"
|
||||
}
|
||||
},
|
||||
"beta": {
|
||||
"title": "Beta 功能",
|
||||
"description": "在正式發布前試用實驗性功能。",
|
||||
"workspaces": {
|
||||
"label": "啟用工作區 Beta",
|
||||
"helper": "查看任務嘗試時使用新的工作區介面。任務將首先在任務檢視中開啟,嘗試將在新的工作區檢視中開啟。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
|
||||
@@ -512,6 +512,12 @@ export const attemptsApi = {
|
||||
return handleApiResponse<Workspace[]>(response);
|
||||
},
|
||||
|
||||
/** Get total count of workspaces */
|
||||
getCount: async (): Promise<number> => {
|
||||
const response = await makeRequest('/api/task-attempts/count');
|
||||
return handleApiResponse<number>(response);
|
||||
},
|
||||
|
||||
get: async (attemptId: string): Promise<Workspace> => {
|
||||
const response = await makeRequest(`/api/task-attempts/${attemptId}`);
|
||||
return handleApiResponse<Workspace>(response);
|
||||
|
||||
@@ -9,8 +9,10 @@ import { tasksApi } from '@/lib/api';
|
||||
import type { RepoBranchStatus, Workspace } from 'shared/types';
|
||||
import { openTaskForm } from '@/lib/openTaskForm';
|
||||
import { FeatureShowcaseDialog } from '@/components/dialogs/global/FeatureShowcaseDialog';
|
||||
import { BetaWorkspacesDialog } from '@/components/dialogs/global/BetaWorkspacesDialog';
|
||||
import { showcases } from '@/config/showcases';
|
||||
import { useUserSystem } from '@/components/ConfigProvider';
|
||||
import { useWorkspaceCount } from '@/hooks/useWorkspaceCount';
|
||||
import { usePostHog } from 'posthog-js/react';
|
||||
|
||||
import { useSearch } from '@/contexts/SearchContext';
|
||||
@@ -227,6 +229,45 @@ export function ProjectTasks() {
|
||||
seenFeatures,
|
||||
]);
|
||||
|
||||
// Beta workspaces invitation - only fetch count if invitation not yet sent
|
||||
const shouldCheckBetaInvitation =
|
||||
isLoaded && !config?.beta_workspaces_invitation_sent;
|
||||
const { data: workspaceCount } = useWorkspaceCount({
|
||||
enabled: shouldCheckBetaInvitation,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoaded) return;
|
||||
if (config?.beta_workspaces_invitation_sent) return;
|
||||
if (workspaceCount === undefined || workspaceCount <= 50) return;
|
||||
|
||||
BetaWorkspacesDialog.show().then((joinBeta) => {
|
||||
BetaWorkspacesDialog.hide();
|
||||
void updateAndSaveConfig({
|
||||
beta_workspaces_invitation_sent: true,
|
||||
beta_workspaces: joinBeta === true,
|
||||
});
|
||||
if (joinBeta === true) {
|
||||
navigate('/workspaces');
|
||||
}
|
||||
});
|
||||
}, [
|
||||
isLoaded,
|
||||
config?.beta_workspaces_invitation_sent,
|
||||
workspaceCount,
|
||||
updateAndSaveConfig,
|
||||
navigate,
|
||||
]);
|
||||
|
||||
// Redirect beta users from old attempt URLs to the new workspaces UI
|
||||
useEffect(() => {
|
||||
if (!isLoaded) return;
|
||||
if (!config?.beta_workspaces) return;
|
||||
if (!attemptId || attemptId === 'latest') return;
|
||||
|
||||
navigate(`/workspaces/${attemptId}`, { replace: true });
|
||||
}, [isLoaded, config?.beta_workspaces, attemptId, navigate]);
|
||||
|
||||
const isLatest = attemptId === 'latest';
|
||||
const { data: attempts = [], isLoading: isAttemptsLoading } = useTaskAttempts(
|
||||
taskId,
|
||||
@@ -641,13 +682,19 @@ export function ProjectTasks() {
|
||||
if (!projectId) return;
|
||||
setSelectedSharedTaskId(null);
|
||||
|
||||
// If beta_workspaces is enabled, always navigate to task view (not attempt)
|
||||
if (config?.beta_workspaces) {
|
||||
navigateWithSearch(paths.task(projectId, task.id));
|
||||
return;
|
||||
}
|
||||
|
||||
if (attemptIdToShow) {
|
||||
navigateWithSearch(paths.attempt(projectId, task.id, attemptIdToShow));
|
||||
} else {
|
||||
navigateWithSearch(`${paths.task(projectId, task.id)}/attempts/latest`);
|
||||
}
|
||||
},
|
||||
[projectId, navigateWithSearch]
|
||||
[projectId, navigateWithSearch, config?.beta_workspaces]
|
||||
);
|
||||
|
||||
const handleViewSharedTask = useCallback(
|
||||
|
||||
@@ -711,6 +711,34 @@ export function GeneralSettings() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('settings.general.beta.title')}</CardTitle>
|
||||
<CardDescription>
|
||||
{t('settings.general.beta.description')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="beta-workspaces"
|
||||
checked={draft?.beta_workspaces ?? false}
|
||||
onCheckedChange={(checked: boolean) =>
|
||||
updateDraft({ beta_workspaces: checked })
|
||||
}
|
||||
/>
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="beta-workspaces" className="cursor-pointer">
|
||||
{t('settings.general.beta.workspaces.label')}
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('settings.general.beta.workspaces.helper')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Sticky Save Button */}
|
||||
<div className="sticky bottom-0 z-10 bg-background/80 backdrop-blur-sm border-t py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -372,7 +372,7 @@ export type DirectoryListResponse = { entries: Array<DirectoryEntry>, current_pa
|
||||
|
||||
export type SearchMode = "taskform" | "settings";
|
||||
|
||||
export type Config = { config_version: string, theme: ThemeMode, executor_profile: ExecutorProfileId, disclaimer_acknowledged: boolean, onboarding_acknowledged: boolean, notifications: NotificationConfig, editor: EditorConfig, github: GitHubConfig, analytics_enabled: boolean, workspace_dir: string | null, last_app_version: string | null, show_release_notes: boolean, language: UiLanguage, git_branch_prefix: string, showcases: ShowcaseState, pr_auto_description_enabled: boolean, pr_auto_description_prompt: string | null, };
|
||||
export type Config = { config_version: string, theme: ThemeMode, executor_profile: ExecutorProfileId, disclaimer_acknowledged: boolean, onboarding_acknowledged: boolean, notifications: NotificationConfig, editor: EditorConfig, github: GitHubConfig, analytics_enabled: boolean, workspace_dir: string | null, last_app_version: string | null, show_release_notes: boolean, language: UiLanguage, git_branch_prefix: string, showcases: ShowcaseState, pr_auto_description_enabled: boolean, pr_auto_description_prompt: string | null, beta_workspaces: boolean, beta_workspaces_invitation_sent: boolean, };
|
||||
|
||||
export type NotificationConfig = { sound_enabled: boolean, push_enabled: boolean, sound_file: SoundFile, };
|
||||
|
||||
|
||||