Files
Genarrative/src/components/creative-agent/CreativeAgentHome.tsx
kdletters f336352d37 收口智能创作与大鱼动作按钮
将智能创作首页 prompt suggestion 按钮迁移到共享动作按钮组件
将大鱼吃小鱼结果页 hero 动作迁移到共享按钮组件
补充轻量列表行暂不单独抽象的团队决策与收口文档
2026-06-10 16:05:05 +08:00

417 lines
12 KiB
TypeScript
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.
import {
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 { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformIconButton } from '../common/PlatformIconButton';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
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 (
<PlatformActionButton
tone="secondary"
shape="pill"
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}
</PlatformActionButton>
);
}
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 />
<PlatformIconButton
onClick={onClose}
label="关闭侧边栏"
title="关闭"
icon={<PanelLeftClose className="h-4 w-4" />}
/>
</header>
<div className="shrink-0 space-y-3 px-5">
<PlatformActionButton
onClick={() => {
onStartNewChat();
onClose();
}}
className="creative-agent-drawer__new-chat"
fullWidth
>
<MessageCircle className="h-5 w-5" />
<span></span>
</PlatformActionButton>
<PlatformActionButton
onClick={() => {
onOpenDrafts();
onClose();
}}
className="creative-agent-drawer__nav-row"
align="start"
fullWidth
>
<Bookmark className="h-5 w-5" />
<span></span>
<ChevronRight className="ml-auto h-4 w-4 opacity-55" />
</PlatformActionButton>
</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>
))
) : (
<PlatformEmptyState surface="subpanel" size="inline">
</PlatformEmptyState>
)}
</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))]">
<PlatformIconButton
onClick={() => {
onOpenAccount();
onClose();
}}
className="creative-agent-drawer__avatar"
label="账号"
title="账号"
icon={<UserRound className="h-5 w-5" />}
/>
<div className="flex items-center gap-3">
<PlatformIconButton
onClick={() => {
onOpenSettings();
onClose();
}}
label="外观"
title="外观"
icon={<Moon className="h-4 w-4" />}
/>
<PlatformIconButton
onClick={() => {
onOpenSettings();
onClose();
}}
label="设置"
title="设置"
icon={<Settings className="h-4 w-4" />}
/>
</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">
<PlatformIconButton
onClick={() => setDrawerOpen(true)}
className="creative-agent-home__topbar-button"
label="打开侧边栏"
title="菜单"
icon={<Menu className="h-6 w-6" />}
/>
<RpgEntryBrandLogo className="creative-agent-home__brand" decorative />
</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)}
/>
))}
<PlatformActionButton
className="creative-agent-home__reward"
disabled={isBusy}
onClick={() => submitText('帮我做一个能马上分享的创意拼图。')}
shape="pill"
>
<Sparkles className="h-6 w-6" />
<span> 1亿</span>
</PlatformActionButton>
</div>
{error ? (
<PlatformStatusMessage
tone="error"
surface="platform"
size="md"
className="creative-agent-home__error font-semibold"
>
{error}
</PlatformStatusMessage>
) : 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;