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;
|