This commit is contained in:
2026-05-08 11:44:42 +08:00
parent b08127031c
commit abf1f1ebea
249 changed files with 39411 additions and 887 deletions

View File

@@ -0,0 +1,423 @@
import {
Bell,
Bookmark,
ChevronRight,
Gamepad2,
Menu,
MessageCircle,
Moon,
Music,
PanelLeftClose,
Settings,
Sparkles,
UserRound,
} from 'lucide-react';
import { useMemo, useState } from 'react';
import type { CreativeAgentInputPart } from '../../../packages/shared/src/contracts/creativeAgent';
import type { CreationWorkShelfItem } from '../custom-world-home/creationWorkShelf';
import { RpgEntryBrandLogo } from '../rpg-entry/RpgEntryBrandLogo';
import { CreativeAgentInputComposer } from './CreativeAgentInputComposer';
import { createCreativeAgentClientMessageId } from './creativeAgentViewModel';
type CreativeAgentHomePrompt = {
id: string;
label: string;
prompt: string;
icon: typeof Sparkles;
tone: 'cool' | 'green' | 'warm' | 'purple' | 'rose';
badge?: string;
};
export type CreativeAgentHistoryItem = {
id: string;
title: string;
groupLabel: string;
source: CreationWorkShelfItem;
};
type CreativeAgentHomeProps = {
recentItems: CreativeAgentHistoryItem[];
isBusy: boolean;
error: string | null;
onStartNewChat: () => void;
onOpenHistoryItem: (item: CreationWorkShelfItem) => void;
onOpenDrafts: () => void;
onOpenAccount: () => void;
onOpenSettings: () => void;
onSubmitMessage: (payload: {
clientMessageId: string;
content: CreativeAgentInputPart[];
}) => void;
};
const PROMPT_SUGGESTIONS: CreativeAgentHomePrompt[] = [
{
id: 'identity',
label: '你是谁',
prompt: '介绍一下你能帮我创作什么。',
icon: Sparkles,
tone: 'cool',
},
{
id: 'flash-app',
label: '一句话生成闪应用',
prompt: '帮我把一个灵感做成可互动的小应用。',
icon: Moon,
tone: 'green',
},
{
id: 'mini-game',
label: '捏个小游戏',
prompt: '帮我做一个适合马上玩的创意小游戏。',
icon: Gamepad2,
tone: 'warm',
},
{
id: 'world-model',
label: '体验世界模型',
prompt: '用一个世界设定帮我生成可体验的互动内容。',
icon: Bookmark,
tone: 'purple',
badge: 'Beta',
},
{
id: 'music',
label: '音乐扭蛋',
prompt: '把一段音乐灵感做成互动拼图。',
icon: Music,
tone: 'rose',
},
];
function buildCreativeHomeInputParts(payload: {
text: string;
image: { imageUrl: string; thumbnailUrl: string } | null;
}): CreativeAgentInputPart[] {
const content: CreativeAgentInputPart[] = [];
if (payload.text) {
content.push({
type: 'input_text',
text: payload.text,
});
}
if (payload.image) {
content.push({
type: 'input_image',
imageUrl: payload.image.imageUrl,
thumbnailUrl: payload.image.thumbnailUrl,
assetId: null,
});
}
return content;
}
function groupRecentItemsByLabel(items: CreativeAgentHistoryItem[]) {
const groups: Array<{ label: string; items: CreativeAgentHistoryItem[] }> = [];
for (const item of items) {
const lastGroup = groups[groups.length - 1];
if (lastGroup?.label === item.groupLabel) {
lastGroup.items.push(item);
continue;
}
groups.push({
label: item.groupLabel,
items: [item],
});
}
return groups;
}
function CreativeAgentPromptButton({
item,
disabled,
onClick,
}: {
item: CreativeAgentHomePrompt;
disabled: boolean;
onClick: () => void;
}) {
const Icon = item.icon;
return (
<button
type="button"
disabled={disabled}
onClick={onClick}
className={`creative-agent-home__prompt creative-agent-home__prompt--${item.tone}`}
>
<Icon className="h-5 w-5 shrink-0" />
<span className="truncate">{item.label}</span>
{item.badge ? (
<span className="creative-agent-home__prompt-badge">{item.badge}</span>
) : null}
</button>
);
}
function CreativeAgentDrawer({
open,
recentItems,
onClose,
onStartNewChat,
onOpenHistoryItem,
onOpenDrafts,
onOpenAccount,
onOpenSettings,
}: {
open: boolean;
recentItems: CreativeAgentHistoryItem[];
onClose: () => void;
onStartNewChat: () => void;
onOpenHistoryItem: (item: CreationWorkShelfItem) => void;
onOpenDrafts: () => void;
onOpenAccount: () => void;
onOpenSettings: () => void;
}) {
const groupedItems = useMemo(
() => groupRecentItemsByLabel(recentItems),
[recentItems],
);
return (
<>
<div
className={`creative-agent-drawer-backdrop ${open ? 'creative-agent-drawer-backdrop--open' : ''}`}
onClick={onClose}
/>
<aside
className={`creative-agent-drawer ${open ? 'creative-agent-drawer--open' : ''}`}
aria-hidden={!open}
>
<div className="flex h-full min-h-0 flex-col">
<header className="flex shrink-0 items-center justify-between gap-3 px-5 pb-5 pt-[max(1.1rem,env(safe-area-inset-top))]">
<RpgEntryBrandLogo decorative />
<button
type="button"
onClick={onClose}
className="platform-icon-button"
aria-label="关闭侧边栏"
title="关闭"
>
<PanelLeftClose className="h-4 w-4" />
</button>
</header>
<div className="shrink-0 space-y-3 px-5">
<button
type="button"
onClick={() => {
onStartNewChat();
onClose();
}}
className="creative-agent-drawer__new-chat"
>
<MessageCircle className="h-5 w-5" />
<span></span>
</button>
<button
type="button"
onClick={() => {
onOpenDrafts();
onClose();
}}
className="creative-agent-drawer__nav-row"
>
<Bookmark className="h-5 w-5" />
<span></span>
<ChevronRight className="ml-auto h-4 w-4 opacity-55" />
</button>
</div>
<div className="mt-5 min-h-0 flex-1 overflow-y-auto px-5 pb-5">
{groupedItems.length > 0 ? (
groupedItems.map((group) => (
<section key={group.label} className="mb-6">
<div className="creative-agent-drawer__group-label">
{group.label}
</div>
<div className="mt-3 space-y-3">
{group.items.map((item) => (
<button
key={item.id}
type="button"
onClick={() => {
onOpenHistoryItem(item.source);
onClose();
}}
className="creative-agent-drawer__history-item"
>
{item.title}
</button>
))}
</div>
</section>
))
) : (
<div className="creative-agent-drawer__empty"></div>
)}
</div>
<footer className="flex shrink-0 items-center justify-between gap-3 px-5 py-5 pb-[max(1.15rem,env(safe-area-inset-bottom))]">
<button
type="button"
onClick={() => {
onOpenAccount();
onClose();
}}
className="creative-agent-drawer__avatar"
aria-label="账号"
>
<UserRound className="h-5 w-5" />
</button>
<div className="flex items-center gap-3">
<button
type="button"
onClick={() => {
onOpenSettings();
onClose();
}}
className="platform-icon-button"
aria-label="外观"
title="外观"
>
<Moon className="h-4 w-4" />
</button>
<button
type="button"
onClick={() => {
onOpenSettings();
onClose();
}}
className="platform-icon-button"
aria-label="设置"
title="设置"
>
<Settings className="h-4 w-4" />
</button>
</div>
</footer>
</div>
</aside>
</>
);
}
export function CreativeAgentHome({
recentItems,
isBusy,
error,
onStartNewChat,
onOpenHistoryItem,
onOpenDrafts,
onOpenAccount,
onOpenSettings,
onSubmitMessage,
}: CreativeAgentHomeProps) {
const [drawerOpen, setDrawerOpen] = useState(false);
const submitText = (text: string) => {
const trimmedText = text.trim();
if (!trimmedText || isBusy) {
return;
}
onSubmitMessage({
clientMessageId: createCreativeAgentClientMessageId(),
content: [
{
type: 'input_text',
text: trimmedText,
},
],
});
};
return (
<div className="creative-agent-home platform-remap-surface">
<div className="creative-agent-home__backdrop" />
<header className="creative-agent-home__topbar">
<button
type="button"
onClick={() => setDrawerOpen(true)}
className="creative-agent-home__topbar-button"
aria-label="打开侧边栏"
title="菜单"
>
<Menu className="h-6 w-6" />
</button>
<RpgEntryBrandLogo className="creative-agent-home__brand" decorative />
<button
type="button"
onClick={onOpenAccount}
className="creative-agent-home__topbar-button"
aria-label="通知与账户"
title="通知与账户"
>
<Bell className="h-5 w-5" />
</button>
</header>
<main className="creative-agent-home__main">
<div className="creative-agent-home__hero">
<h1>Hi, </h1>
<p></p>
</div>
<div className="creative-agent-home__prompt-grid">
{PROMPT_SUGGESTIONS.map((item) => (
<CreativeAgentPromptButton
key={item.id}
item={item}
disabled={isBusy}
onClick={() => submitText(item.prompt)}
/>
))}
<button
type="button"
className="creative-agent-home__reward"
disabled={isBusy}
onClick={() => submitText('帮我做一个能马上分享的创意拼图。')}
>
<Sparkles className="h-6 w-6" />
<span> 1亿</span>
</button>
</div>
{error ? (
<div className="creative-agent-home__error">{error}</div>
) : null}
</main>
<div className="creative-agent-home__composer">
<CreativeAgentInputComposer
variant="floating"
isBusy={isBusy}
placeholder="问一问百梦"
onSubmit={(payload) => {
const content = buildCreativeHomeInputParts(payload);
if (content.length === 0) {
return;
}
onSubmitMessage({
clientMessageId: createCreativeAgentClientMessageId(),
content,
});
}}
/>
</div>
<CreativeAgentDrawer
open={drawerOpen}
recentItems={recentItems}
onClose={() => setDrawerOpen(false)}
onStartNewChat={onStartNewChat}
onOpenHistoryItem={onOpenHistoryItem}
onOpenDrafts={onOpenDrafts}
onOpenAccount={onOpenAccount}
onOpenSettings={onOpenSettings}
/>
</div>
);
}
export default CreativeAgentHome;