agent-Specialization/static/src/components/overlay/HostWorkspaceCreateDialog.vue
JOJO 08d9812f45 feat(docker): support isolated projects in web mode
Implement Docker Web project management on top of workspace_id while keeping host mode workspace behavior intact.

Backend changes:

- Move Docker user files to users/<user>/projects/<project_id>/{project,data,logs} and shared user state to users/<user>/personal.

- Add automatic legacy migration from old project/data/logs/agentskills layouts and from the temporary project/shared layout.

- Generate project IDs server-side from the project name, reserve internal IDs, and hide project paths from the UI.

- Add project rename/delete APIs; deleting a Docker project removes its project directory and releases terminal/container state.

- Add host workspace rename/delete APIs; deleting a host workspace only removes catalog entries and keeps files on disk.

- Scope Docker containers, terminals, uploads, conversations, storage, and running task metadata by workspace_id.

- Store private skills under users/<user>/personal/agentskills and sync them into each project workspace.

- Inject the current project/workspace name through prompts/workspace_system.txt.

Frontend changes:

- Replace Docker file-tree area with project management UX and reuse host multi-workspace running-task behavior.

- Add a management dialog for creating, renaming, and deleting projects/workspaces.

- Show project labels instead of internal IDs in toasts and project lists.

- Clear stale conversation history during project/workspace switching and show loading until the new list is ready.

- Keep running-task loader/check behavior consistent across host workspaces and Docker projects.

Validation:

- python3 -m py_compile modules/user_manager.py modules/host_workspace_manager.py modules/skills_manager.py core/main_terminal_parts/tools_execution.py server/auth.py server/context.py server/status.py server/conversation.py server/tasks.py utils/context_manager.py

- python3 -m unittest test.test_server_refactor_smoke

- npm run build --silent 2>&1 | tail -n 20
2026-05-29 19:49:48 +08:00

221 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
class="host-workspace-dialog-overlay"
role="dialog"
aria-modal="true"
:aria-label="workspaceKind === 'project' ? '新建项目' : '新建工作区'"
@click.self="handleOverlayClose"
>
<form class="host-workspace-dialog" @submit.prevent="$emit('submit')">
<div class="host-workspace-dialog__header">
<div class="host-workspace-dialog__title">
{{ workspaceKind === 'project' ? '新建项目' : '新建工作区' }}
</div>
<button
type="button"
class="host-workspace-dialog__close"
:disabled="submitting"
@click="$emit('close')"
>
关闭
</button>
</div>
<div class="host-workspace-dialog__body">
<label v-if="workspaceKind !== 'project'" class="host-workspace-dialog__field">
<span class="host-workspace-dialog__label">
工作区路径
</span>
<input
:value="pathValue"
type="text"
class="host-workspace-dialog__input"
placeholder="请输入绝对路径或相对仓库路径"
autocomplete="off"
:disabled="submitting"
@input="$emit('update:pathValue', ($event.target as HTMLInputElement).value)"
/>
</label>
<label class="host-workspace-dialog__field">
<span class="host-workspace-dialog__label">
{{ workspaceKind === 'project' ? '项目名称' : '工作区名称(可选)' }}
</span>
<input
:value="labelValue"
type="text"
class="host-workspace-dialog__input"
:placeholder="workspaceKind === 'project' ? '例如:客户 A 项目' : '例如客户A项目'"
autocomplete="off"
:disabled="submitting"
@input="$emit('update:labelValue', ($event.target as HTMLInputElement).value)"
/>
</label>
<div v-if="errorMessage" class="host-workspace-dialog__error">{{ errorMessage }}</div>
</div>
<div class="host-workspace-dialog__actions">
<button type="button" class="host-workspace-dialog__btn ghost" :disabled="submitting" @click="$emit('close')">
取消
</button>
<button type="submit" class="host-workspace-dialog__btn primary" :disabled="submitting">
{{ submitting ? '创建中...' : (workspaceKind === 'project' ? '创建项目' : '创建工作区') }}
</button>
</div>
</form>
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'HostWorkspaceCreateDialog' });
const props = defineProps<{
open: boolean;
pathValue: string;
labelValue: string;
submitting?: boolean;
errorMessage?: string;
workspaceKind?: 'workspace' | 'project';
}>();
const emits = defineEmits<{
(event: 'close'): void;
(event: 'submit'): void;
(event: 'update:pathValue', value: string): void;
(event: 'update:labelValue', value: string): void;
}>();
const workspaceKind = props.workspaceKind || 'workspace';
const handleOverlayClose = () => {
if (props.submitting) return;
emits('close');
};
</script>
<style scoped>
.host-workspace-dialog-overlay {
position: fixed;
inset: 0;
z-index: 1300;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
background: var(--theme-overlay-scrim);
backdrop-filter: blur(10px);
}
.host-workspace-dialog {
width: min(520px, 96vw);
border-radius: 18px;
border: 1px solid var(--theme-control-border-strong);
background: var(--theme-surface-card);
box-shadow: var(--theme-shadow-soft);
display: flex;
flex-direction: column;
}
.host-workspace-dialog__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 18px;
border-bottom: 1px solid var(--theme-control-border);
}
.host-workspace-dialog__title {
font-size: 16px;
font-weight: 700;
color: var(--claude-text);
}
.host-workspace-dialog__close {
border: 1px solid var(--theme-control-border-strong);
background: transparent;
color: var(--claude-text-secondary);
border-radius: 10px;
padding: 6px 12px;
cursor: pointer;
}
.host-workspace-dialog__close:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.host-workspace-dialog__body {
padding: 16px 18px;
display: grid;
gap: 12px;
}
.host-workspace-dialog__field {
display: grid;
gap: 6px;
}
.host-workspace-dialog__label {
font-size: 13px;
color: var(--claude-text-secondary);
}
.host-workspace-dialog__input {
border: 1px solid var(--theme-control-border-strong);
border-radius: 10px;
background: var(--theme-surface-soft);
color: var(--claude-text);
padding: 10px 12px;
font-size: 14px;
line-height: 1.4;
outline: none;
}
.host-workspace-dialog__input:focus {
border-color: var(--claude-accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--claude-accent) 20%, transparent);
}
.host-workspace-dialog__input:disabled {
opacity: 0.7;
}
.host-workspace-dialog__error {
font-size: 13px;
color: #ef4444;
}
.host-workspace-dialog__actions {
padding: 16px 18px;
border-top: 1px solid var(--theme-control-border);
display: flex;
justify-content: flex-end;
gap: 10px;
}
.host-workspace-dialog__btn {
border-radius: 10px;
padding: 8px 14px;
border: 1px solid var(--theme-control-border-strong);
cursor: pointer;
}
.host-workspace-dialog__btn.ghost {
background: transparent;
color: var(--claude-text-secondary);
}
.host-workspace-dialog__btn.primary {
background: var(--claude-accent);
border-color: var(--claude-accent-strong);
color: #fff;
}
.host-workspace-dialog__btn:disabled {
opacity: 0.65;
cursor: not-allowed;
}
</style>