实现小游戏与小程序项目创建发布

This commit is contained in:
2026-08-09 13:31:06 +08:00
parent a6fe14a8d6
commit 493b31cff0
26 changed files with 1827 additions and 39 deletions

View File

@@ -39,6 +39,7 @@ import type { OpencodeProject } from '@/types/opencode';
import { buildConfiguredModelOptions } from '@/lib/model-options';
import { useProjectConfigStore } from '@/stores/project-config';
import { EMPTY_AGENT_USER_PROFILE, getProfileAccountKey, isUserProfileComplete, useUserProfileStore, type UserGender } from '@/stores/user-profile';
import type { ProjectType } from '../../../shared/project-config';
import { toast } from 'sonner';
type OpenDialogResult = {
@@ -162,6 +163,7 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
const [newProjectName, setNewProjectName] = useState('');
const [newProjectSelectedPath, setNewProjectSelectedPath] = useState('');
const [newProjectDirectoryMode, setNewProjectDirectoryMode] = useState<ProjectDirectoryMode>('use-selected-directory');
const [newProjectType, setNewProjectType] = useState<ProjectType>('mini_game');
const [newProjectDefaultModel, setNewProjectDefaultModel] = useState('');
const [createProjectError, setCreateProjectError] = useState<string | null>(null);
const [creatingProject, setCreatingProject] = useState(false);
@@ -391,6 +393,7 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
setNewProjectName('');
setNewProjectSelectedPath('');
setNewProjectDirectoryMode('use-selected-directory');
setNewProjectType('mini_game');
setNewProjectDefaultModel('');
setCreateProjectError(null);
setCreateDialogOpen(true);
@@ -399,6 +402,7 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
const closeCreateProject = () => {
if (creatingProject) return;
setCreateDialogOpen(false);
setNewProjectType('mini_game');
setNewProjectDefaultModel('');
setCreateProjectError(null);
};
@@ -460,6 +464,7 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
const project = await createProject({
directoryMode: newProjectDirectoryMode,
selectedPath,
projectType: newProjectType,
...(newProjectDirectoryMode === 'create-child-directory' ? { projectName } : {}),
...(newProjectDefaultModel ? { defaultModel: newProjectDefaultModel } : {}),
});
@@ -666,6 +671,46 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
void confirmCreateProject();
}}
>
<fieldset className="space-y-2">
<legend className="text-xs font-semibold">项目类型</legend>
<div className="grid gap-2 sm:grid-cols-3">
{([
['mini_game', '小游戏', '适合实时互动、Canvas 和关卡玩法;创建后可直接提交审核。'],
['mini_program', '小程序', '适合页面、表单和轻量工具;创建后可直接提交审核。'],
['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 transition-[box-shadow,transform] active:scale-[0.96]',
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">

View File

@@ -12,6 +12,7 @@ import {
type WorksProjectMetadataInput,
} from '@/lib/works-square';
import type { OpencodeProject } from '@/types/opencode';
import type { ProjectType } from '../../../shared/project-config';
type PublishPhase = 'idle' | 'submitting' | 'polling' | 'succeeded' | 'failed' | 'uncertain';
@@ -23,6 +24,7 @@ type BuildVersion = {
type ProjectPublishActionProps = {
project: OpencodeProject;
projectType: Exclude<ProjectType, 'custom'>;
};
const BUILD_POLL_INTERVAL_MS = 2_000;
@@ -63,7 +65,7 @@ function buttonLabel(phase: PublishPhase): string {
return '一键提交审核';
}
export function ProjectPublishAction({ project }: ProjectPublishActionProps) {
export function ProjectPublishAction({ project, projectType }: ProjectPublishActionProps) {
const [phase, setPhase] = useState<PublishPhase>('idle');
const [failure, setFailure] = useState<WorksPublishFailure | null>(null);
const pollGeneration = useRef(0);
@@ -79,7 +81,7 @@ export function ProjectPublishAction({ project }: ProjectPublishActionProps) {
title,
summary: `${title},由 Makelore 创建的作品。`,
cover_url: null,
category: 'web',
category: projectType,
age_band: null,
difficulty: null,
};