1
This commit is contained in:
423
src/components/creative-agent/CreativeAgentHome.tsx
Normal file
423
src/components/creative-agent/CreativeAgentHome.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user