424 lines
12 KiB
TypeScript
424 lines
12 KiB
TypeScript
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;
|