1ad25e30f8
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
183 lines
4.9 KiB
TypeScript
183 lines
4.9 KiB
TypeScript
import { ArrowUp, ImagePlus, Loader2, Plus } from 'lucide-react';
|
|
import { type ChangeEvent, useState } from 'react';
|
|
|
|
import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage';
|
|
import { PlatformIconButton } from '../common/PlatformIconButton';
|
|
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
|
import { PlatformSubpanel } from '../common/PlatformSubpanel';
|
|
import { PlatformTextField } from '../common/PlatformTextField';
|
|
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
|
|
|
|
export type CreativeAgentComposerImage = {
|
|
imageUrl: string;
|
|
thumbnailUrl: string;
|
|
label: string;
|
|
};
|
|
|
|
type CreativeAgentInputComposerProps = {
|
|
isBusy: boolean;
|
|
variant?: 'panel' | 'floating';
|
|
placeholder?: string;
|
|
onSubmit: (payload: {
|
|
text: string;
|
|
image: CreativeAgentComposerImage | null;
|
|
}) => void;
|
|
};
|
|
|
|
export function CreativeAgentInputComposer({
|
|
isBusy,
|
|
variant = 'panel',
|
|
placeholder = '想做成什么拼图?',
|
|
onSubmit,
|
|
}: CreativeAgentInputComposerProps) {
|
|
const [text, setText] = useState('');
|
|
const [image, setImage] = useState<CreativeAgentComposerImage | null>(null);
|
|
const [imageError, setImageError] = useState<string | null>(null);
|
|
const canSubmit = !isBusy && Boolean(text.trim() || image);
|
|
|
|
const handleImageChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
event.currentTarget.value = '';
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const dataUrl = await readPuzzleReferenceImageAsDataUrl(file);
|
|
setImage({
|
|
imageUrl: dataUrl,
|
|
thumbnailUrl: dataUrl,
|
|
label: file.name.trim() || '参考图',
|
|
});
|
|
setImageError(null);
|
|
} catch (error) {
|
|
setImageError(
|
|
error instanceof Error ? error.message : '参考图读取失败,请重试。',
|
|
);
|
|
}
|
|
};
|
|
|
|
const submit = () => {
|
|
if (!canSubmit) {
|
|
return;
|
|
}
|
|
onSubmit({
|
|
text: text.trim(),
|
|
image,
|
|
});
|
|
setText('');
|
|
setImage(null);
|
|
setImageError(null);
|
|
};
|
|
|
|
const floating = variant === 'floating';
|
|
|
|
const composerContent = (
|
|
<>
|
|
<div className="flex items-end gap-2">
|
|
<PlatformIconButton
|
|
asChild="label"
|
|
className={`h-11 w-11 shrink-0 ${floating ? 'creative-agent-composer__media-button' : ''} ${isBusy ? 'cursor-not-allowed opacity-55' : 'cursor-pointer'}`}
|
|
label={image ? '更换参考图' : '添加参考图'}
|
|
title={image ? '更换参考图' : '添加参考图'}
|
|
icon={
|
|
<>
|
|
{floating ? (
|
|
<Plus className="h-5 w-5" />
|
|
) : (
|
|
<ImagePlus className="h-4 w-4" />
|
|
)}
|
|
<input
|
|
type="file"
|
|
accept="image/png,image/jpeg,image/webp"
|
|
disabled={isBusy}
|
|
onChange={(event) => {
|
|
void handleImageChange(event);
|
|
}}
|
|
className="hidden"
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
|
|
<PlatformTextField
|
|
variant="textarea"
|
|
value={text}
|
|
disabled={isBusy}
|
|
rows={2}
|
|
size="xs"
|
|
density="roomy"
|
|
onChange={(event) => setText(event.target.value)}
|
|
onKeyDown={(event) => {
|
|
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
event.preventDefault();
|
|
submit();
|
|
}
|
|
}}
|
|
className="min-h-11 flex-1"
|
|
placeholder={placeholder}
|
|
aria-label="智能创作输入"
|
|
/>
|
|
|
|
<PlatformIconButton
|
|
disabled={!canSubmit}
|
|
onClick={submit}
|
|
className="h-11 w-11 shrink-0"
|
|
label="发送"
|
|
title="发送"
|
|
icon={
|
|
isBusy ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<ArrowUp className="h-4 w-4" />
|
|
)
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{image ? (
|
|
<PlatformUploadPreviewCard
|
|
layout="inline"
|
|
imageSrc={image.thumbnailUrl}
|
|
imageAlt="创作参考图"
|
|
caption={image.label}
|
|
removeLabel="移除参考图"
|
|
disabled={isBusy}
|
|
onRemove={() => setImage(null)}
|
|
className="mt-3"
|
|
removeButtonProps={{ title: '移除参考图' }}
|
|
/>
|
|
) : null}
|
|
|
|
{imageError ? (
|
|
<PlatformStatusMessage
|
|
tone="error"
|
|
surface="profile"
|
|
size="xs"
|
|
className="mt-2"
|
|
>
|
|
{imageError}
|
|
</PlatformStatusMessage>
|
|
) : null}
|
|
</>
|
|
);
|
|
|
|
if (!floating) {
|
|
return (
|
|
<PlatformSubpanel
|
|
padding="sm"
|
|
radius="lg"
|
|
className="creative-agent-composer sm:p-4"
|
|
>
|
|
{composerContent}
|
|
</PlatformSubpanel>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section className="creative-agent-composer creative-agent-composer--floating">
|
|
{composerContent}
|
|
</section>
|
|
);
|
|
}
|