feat: simplify code project creation
This commit is contained in:
@@ -48,11 +48,8 @@ import { useSettingsStore } from '@/stores/settings';
|
||||
import { useAuthStore, type AuthUser } from '@/stores/auth';
|
||||
import { useCodingWorkspaceStore } from '@/stores/coding-workspace';
|
||||
import { useProviderStore } from '@/stores/providers';
|
||||
import { isCanonicalCodingProjectId } from '@/lib/coding-projects';
|
||||
import type { CodingProjectSummary } from '@/types/coding-project';
|
||||
import { useProjectConfigStore } from '@/stores/project-config';
|
||||
import type { ProjectType } from '../../../shared/project-config';
|
||||
import type { ProjectIdentityChoice } from '../../../shared/coding-project-contracts';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type OpenDialogResult = {
|
||||
@@ -72,7 +69,6 @@ type ProjectEntryError = {
|
||||
};
|
||||
|
||||
type ProjectDirectoryMode = 'use-selected-directory' | 'create-child-directory';
|
||||
type ProjectIdentityKind = ProjectIdentityChoice['kind'];
|
||||
|
||||
function getFolderName(pathValue: string): string {
|
||||
const trimmed = pathValue.trim();
|
||||
@@ -166,9 +162,6 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
|
||||
const [newProjectName, setNewProjectName] = useState('');
|
||||
const [newProjectSelectedPath, setNewProjectSelectedPath] = useState('');
|
||||
const [newProjectDirectoryMode, setNewProjectDirectoryMode] = useState<ProjectDirectoryMode>('use-selected-directory');
|
||||
const [newProjectType, setNewProjectType] = useState<ProjectType>('interactive_ai_app');
|
||||
const [newProjectIdentityKind, setNewProjectIdentityKind] = useState<ProjectIdentityKind>('create');
|
||||
const [newProjectIdentityProjectId, setNewProjectIdentityProjectId] = useState('');
|
||||
const [createProjectError, setCreateProjectError] = useState<string | null>(null);
|
||||
const [creatingProject, setCreatingProject] = useState(false);
|
||||
const [projectEntryError, setProjectEntryError] = useState<ProjectEntryError | null>(null);
|
||||
@@ -186,7 +179,6 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
|
||||
const isPaintingModule = activeModule === 'painting';
|
||||
const isRobotModule = activeModule === 'robot';
|
||||
const isPromptMuseum = location.pathname === '/image-prompts' || location.pathname.startsWith('/image-prompts/');
|
||||
const projectConfigPath = '/project-config';
|
||||
const visibleProjects = projects;
|
||||
const selectedProjectFolderName = getFolderName(newProjectSelectedPath);
|
||||
const accountName = userProfile?.displayName?.trim() || getAuthUserDisplayName(authUser) || '未登录用户';
|
||||
@@ -397,9 +389,6 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
|
||||
setNewProjectName('');
|
||||
setNewProjectSelectedPath('');
|
||||
setNewProjectDirectoryMode('use-selected-directory');
|
||||
setNewProjectType('interactive_ai_app');
|
||||
setNewProjectIdentityKind('create');
|
||||
setNewProjectIdentityProjectId('');
|
||||
setCreateProjectError(null);
|
||||
setCreateDialogOpen(true);
|
||||
};
|
||||
@@ -407,9 +396,6 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
|
||||
const closeCreateProject = () => {
|
||||
if (creatingProject) return;
|
||||
setCreateDialogOpen(false);
|
||||
setNewProjectType('interactive_ai_app');
|
||||
setNewProjectIdentityKind('create');
|
||||
setNewProjectIdentityProjectId('');
|
||||
setCreateProjectError(null);
|
||||
};
|
||||
|
||||
@@ -436,9 +422,7 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
|
||||
return;
|
||||
}
|
||||
await setActiveProject(project.id);
|
||||
navigate(result.config.initialized
|
||||
? '/chat'
|
||||
: projectConfigPath);
|
||||
navigate('/chat');
|
||||
};
|
||||
|
||||
const readAndEnterProject = async (project: CodingProjectSummary) => {
|
||||
@@ -462,20 +446,12 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
|
||||
setCreateProjectError('请选择项目路径');
|
||||
return;
|
||||
}
|
||||
const boundProjectId = newProjectIdentityProjectId.trim();
|
||||
if (newProjectIdentityKind === 'bind' && !isCanonicalCodingProjectId(boundProjectId)) {
|
||||
setCreateProjectError('请输入有效的项目 ID(小写 UUID)。');
|
||||
return;
|
||||
}
|
||||
|
||||
setCreatingProject(true);
|
||||
setCreateProjectError(null);
|
||||
try {
|
||||
const project = await createProject({
|
||||
projectType: newProjectType,
|
||||
identity: newProjectIdentityKind === 'create'
|
||||
? { kind: 'create' }
|
||||
: { kind: 'bind', projectId: boundProjectId },
|
||||
projectType: 'interactive_ai_app',
|
||||
identity: { kind: 'create' },
|
||||
...(newProjectDirectoryMode === 'create-child-directory'
|
||||
? { parentPath: selectedPath, projectName }
|
||||
: { projectPath: selectedPath }),
|
||||
@@ -756,7 +732,7 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
|
||||
<DialogHeader className="flex-row items-start justify-between gap-3 space-y-0">
|
||||
<div>
|
||||
<DialogTitle className="text-xl">新建项目</DialogTitle>
|
||||
<DialogDescription className="mt-1 text-xs">默认直接使用所选文件夹,也可以在所选位置新建下级文件夹。</DialogDescription>
|
||||
<DialogDescription className="mt-1 text-xs">只需选择项目文件夹,其他设置会自动准备好。</DialogDescription>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -775,120 +751,6 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
|
||||
void confirmCreateProject();
|
||||
}}
|
||||
>
|
||||
<fieldset className="space-y-2">
|
||||
<legend className="text-xs font-semibold">项目身份</legend>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
<label
|
||||
className={cn(
|
||||
'cursor-pointer rounded-xl border border-foreground/10 bg-white p-3 text-sm font-semibold',
|
||||
newProjectIdentityKind === 'create' ? 'ring-2 ring-brand/35 shadow-float' : 'hover:shadow-float',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="project-identity"
|
||||
value="create"
|
||||
aria-label="创建新的项目 ID"
|
||||
checked={newProjectIdentityKind === 'create'}
|
||||
onChange={() => {
|
||||
setNewProjectIdentityKind('create');
|
||||
setCreateProjectError(null);
|
||||
}}
|
||||
disabled={creatingProject}
|
||||
/>
|
||||
<span className="ml-2">创建新的项目 ID</span>
|
||||
<span className="mt-2 block text-[11px] font-medium leading-5 text-muted-foreground">
|
||||
由主进程生成一个新的小写 UUID,不会发起云端数据创建。
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
className={cn(
|
||||
'cursor-pointer rounded-xl border border-foreground/10 bg-white p-3 text-sm font-semibold',
|
||||
newProjectIdentityKind === 'bind' ? 'ring-2 ring-brand/35 shadow-float' : 'hover:shadow-float',
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="project-identity"
|
||||
value="bind"
|
||||
aria-label="绑定已有项目 ID"
|
||||
checked={newProjectIdentityKind === 'bind'}
|
||||
onChange={() => {
|
||||
setNewProjectIdentityKind('bind');
|
||||
setCreateProjectError(null);
|
||||
}}
|
||||
disabled={creatingProject}
|
||||
/>
|
||||
<span className="ml-2">绑定已有项目 ID</span>
|
||||
<span className="mt-2 block text-[11px] font-medium leading-5 text-muted-foreground">
|
||||
仅接受规范的小写 UUID;绑定不会把项目 ID 当作登录凭据。
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
{newProjectIdentityKind === 'bind' ? (
|
||||
<div className="space-y-2 rounded-xl border border-foreground/10 bg-surface-subtle p-3">
|
||||
<label className="block text-xs font-semibold" htmlFor="new-project-identity-id">
|
||||
已有项目 ID
|
||||
</label>
|
||||
<input
|
||||
id="new-project-identity-id"
|
||||
value={newProjectIdentityProjectId}
|
||||
onChange={(event) => {
|
||||
setNewProjectIdentityProjectId(event.target.value);
|
||||
setCreateProjectError(null);
|
||||
}}
|
||||
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
className="w-full rounded-md border border-foreground/15 bg-white px-3 py-2 font-mono text-sm font-medium outline-none focus:ring-2 focus:ring-brand/20"
|
||||
disabled={creatingProject}
|
||||
/>
|
||||
<p className="text-[11px] font-medium leading-5 text-muted-foreground">
|
||||
同一账号绑定相同项目 ID 会共享开发数据;项目 ID 不是凭据。
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</fieldset>
|
||||
|
||||
<fieldset className="space-y-2">
|
||||
<legend className="text-xs font-semibold">项目类型</legend>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{([
|
||||
['interactive_ai_app', '交互式 AI 应用', '创建可交互、可发布的 AI 应用项目;进入对话后可用项目初始化 Skill 生成工程骨架。'],
|
||||
['custom', '自定义项目', '只创建项目空间,不配置默认发布方式。'],
|
||||
] as const).map(([value, title, description]) => (
|
||||
<label
|
||||
key={value}
|
||||
className={cn(
|
||||
'min-h-28 cursor-pointer rounded-xl bg-white p-3 shadow-soft',
|
||||
newProjectType === value
|
||||
? 'ring-2 ring-brand/35 shadow-float'
|
||||
: 'ring-1 ring-foreground/10 hover:shadow-float',
|
||||
)}
|
||||
>
|
||||
<span className="flex items-center gap-2 text-sm font-semibold">
|
||||
<input
|
||||
type="radio"
|
||||
name="project-type"
|
||||
value={value}
|
||||
aria-label={title}
|
||||
checked={newProjectType === value}
|
||||
onChange={() => {
|
||||
setNewProjectType(value);
|
||||
setCreateProjectError(null);
|
||||
}}
|
||||
disabled={creatingProject}
|
||||
/>
|
||||
{title}
|
||||
</span>
|
||||
<span className="mt-2 block text-[11px] font-medium leading-5 text-muted-foreground [text-wrap:pretty]">
|
||||
{description}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset className="space-y-2">
|
||||
<legend className="text-xs font-semibold">项目目录方式</legend>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
|
||||
Reference in New Issue
Block a user