fix(canvas): soften design conversation
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
Makelore 是一个面向软件、视觉创作与智能机器人的 AI 桌面工作台。当前版本为 `2.0.0`,包含三个已开通产品模块。模块入口页采用统一的横向卡片视觉,工作区左上角入口点击后返回模块入口页:
|
||||
|
||||
- `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。
|
||||
- `Makelore Canvas|AI 绘画`:每个设计项目(Workspace)维护一份从创建起就存在的 Living Form。左侧项目栏负责新建、切换和管理 Workspace,并在桌面设计模式下以 256px 宽度常驻展开;用户在中央对话时间线中让 AI 帮助描绘想法,再直接编辑同一时间线内的制作方案;桌面端右侧同为 256px 的全高历史作品栏集中展示当前项目的制作记录与生成结果。紧凑窗口通过左侧抽屉访问项目列表,历史记录保留在时间线中。参考图从本地上传后以 `@图片N` 绑定,具体用法只写在创作提示词中。
|
||||
- `Makelore Canvas|AI 绘画`:每个设计项目(Workspace)维护一份从创建起就存在的 Living Form。左侧项目栏负责新建、切换和管理 Workspace,并在桌面设计模式下以 256px 宽度常驻展开;中央沿用 AI 编程的安静对话画布、自然消息流和底部悬浮输入器,AI 整理出的制作方案作为对话内的轻量可编辑稿持续更新;桌面端右侧同为 256px 的全高历史作品栏集中展示当前项目的制作记录与生成结果。紧凑窗口通过左侧抽屉访问项目列表,历史记录保留在时间线中。参考图从本地上传后以 `@图片N` 绑定,具体用法只写在创作提示词中。
|
||||
- `Makelore Robot|AI 机器`:管理机器人智能体、设备激活绑定、智能体配置与设备分配;机器人工作台的智能体位于 Robot 全局侧栏,选中后在内容区先查看绑定设备、再查看基础设置,当前智能体通过 URL 参数保持可分享选择;绑定设备时默认先选择“引导配网”或“已有激活码”。在 Windows 与 macOS 的引导路径中,Makelore 可在弹窗内扫描并连接附近开放的 `Xiaozhi-*` 配网热点,失败时仍可通过系统 Wi-Fi 手动连接;后续继续复用机器人现有热点配网页面,不修改固件,也不由 Makelore 接收 Wi-Fi 密码。
|
||||
|
||||
应用启动默认进入 AI 模块入口选择页。入口页可在未登录状态浏览;未登录用户点击已开通模块时进入客户端原生登录页,可使用账号密码或手机号短信验证码登录。密码登录可选“记住密码”:正式安装包仅由 Electron Main 使用系统受保护凭据存储加密保存和回填账号密码,不写入 Renderer 持久状态,未打包开发版或系统安全存储不可用时禁用该选项。登录请求由 Renderer 经 Host API 交给 Electron Main,再由 Main 调用 Works Square;成功后回到入口选择页。已登录时,Electron Main 会从 Works Square `/api/auth/me` 读取当前账号,只向 Renderer 投影用户名、账号/租户/部门标识、权限名列表与三个模块布尔开关,不透传上游资料或凭据。工作区门禁同时要求有效 Token 和完整用户身份;旧状态缺失身份时会先尝试从 Main 恢复,仍无法确认则清除残留会话并返回登录页。被管理员关闭的模块会在入口页置灰且无法点击,直接访问其工作区路径也会返回入口页。旧服务端未返回策略或缺少单项字段时默认开放;这个客户端门禁不替代服务端 API 授权。
|
||||
@@ -26,7 +26,7 @@ Makelore 是一个面向软件、视觉创作与智能机器人的 AI 桌面工
|
||||
- 客户端更新:Electron Main 按平台与架构选择更新源并保留原始诊断;设置页只显示一条脱敏后的中文状态。正式源缺少对应安装包时保持错误并允许重试,不会误报为已是最新版。
|
||||
- 个人资料:姓名、年龄、性别与个人头像按账号同步到云端;首次登录进入模块选择页时会要求先完善姓名,首页欢迎语和主平台左下角账号区优先展示个人资料姓名;头像支持 PNG、JPEG、WebP,保存时自动居中裁剪并以圆形缩略图展示,未设置或加载失败时回退为姓名首字母。
|
||||
- 会话观察同步:本地编程会话在一轮问答完成并进入空闲后,后台通过个人资料 PUT 上传该会话截至当前的完整问答快照;只保留用户/助手自然语言文本,过滤代码、路径、日志、工具调用、附件与产物。同步只从本地上行,云端不回写、不恢复或删除本地会话;失败数据留在本地等待重试。
|
||||
- AI 绘画:一个 Workspace 固定对应一个设计方向、一条 Agent Session 和一份持久 Living Form。对话与当前制作方案位于同一中央时间线,不是两套状态:聊天提取、用户修改创作提示词和制作参数都提交到同一个服务端 reducer,并由方向版本、字段来源和决策状态形成权威投影。创作提示词是公开规格 `content.concept` 的可编辑投影;媒介、画幅、时长和数量始终以紧凑控件直接显示,不用折叠面板隐藏。
|
||||
- AI 绘画:一个 Workspace 固定对应一个设计方向、一条 Agent Session 和一份持久 Living Form。中央使用与 AI 编程一致的低干扰对话布局:助手回复不套独立卡片,底部输入器是唯一强调层级;当前制作方案以对话内轻量编辑稿呈现,而不是独立硬模块。聊天提取、用户修改创作提示词和制作参数都提交到同一个服务端 reducer,并由方向版本、字段来源和决策状态形成权威投影。创作提示词是公开规格 `content.concept` 的可编辑投影;媒介、画幅、时长和数量始终以紧凑控件直接显示,不用折叠面板隐藏。
|
||||
- AI 绘画生成:服务端从当前规格编译专业图片或视频指令并返回不可变 Quote;客户端只展示创作提示词、参考图绑定、媒介、画幅、数量、格式、时长、警告和设计点,不展示或改写供应商 Prompt、模型、价格原子或存储地址。修改方案会先提交规格并重新核价,确认时只提交 Quote 身份;确认后的方案不再常驻,而是进入可展开的历史作品记录,桌面端位于右侧全高栏,紧凑窗口保留在时间线中。参考图行只负责缩略图、`@图片N` 状态、替换和删除,保留什么、改变什么等语义统一由创作提示词表达。任务与资产始终属于 Workspace。
|
||||
- AI 绘画健壮性:Renderer 为每次命令生成稳定 operation id,网络结果未知时保留原命令供原样重试,不把未知写入当失败或创建第二次生成。Main 负责 Token 刷新、Agent Gateway REST 提交和有界 Run 查询,并将可恢复事件流投影为 Host API SSE;断线后按事件游标续接并重新读取权威 Workspace。Canvas 只使用 Works Square 云端 V2 契约,没有本地语义适配器或降级路径,上游不可用时明确报错。
|
||||
- AI 绘画左侧项目栏只展示 Workspace,桌面设计模式下固定为 256px 且不提供收纳入口,不再在项目下创建独立设计会话;右侧全高历史作品栏同为 256px,展示当前 Workspace 的任务与生成资产。紧凑窗口则把项目列表放进左侧抽屉并在中央时间线保留历史记录。删除时必须完整输入项目名称;删除后项目、Living Form、任务、参考图和生成作品会从账户中隐藏且无法访问,不影响用户已另存到磁盘的副本。删除当前项目后自动打开最近更新的剩余项目,删除最后一个项目后进入空状态。
|
||||
|
||||
@@ -2,10 +2,10 @@ import { useEffect, useRef } from 'react';
|
||||
import {
|
||||
Clock3,
|
||||
Loader2,
|
||||
MessageSquareText,
|
||||
Send,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import makeloreMark from '@/assets/logo.svg';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { ImageWorkspaceApiError } from '@/lib/image-workspace';
|
||||
@@ -118,26 +118,30 @@ export function DesignConversationPane({
|
||||
return (
|
||||
<section
|
||||
data-testid="image-workspace-conversation"
|
||||
className="flex min-h-0 flex-1 flex-col bg-surface-subtle/45"
|
||||
className="flex min-h-0 flex-1 flex-col overflow-hidden bg-background"
|
||||
>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-5 sm:px-6">
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-4 pb-6 pt-4 sm:px-6">
|
||||
{workspace.turns.length === 0
|
||||
&& pendingChatMessages.length === 0 && (
|
||||
<div className="mx-auto flex h-full max-w-sm flex-col justify-center py-8">
|
||||
<div className="mb-5 flex h-11 w-11 items-center justify-center rounded-2xl bg-brand/[0.09] text-brand">
|
||||
<MessageSquareText className="h-5 w-5" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-foreground">先把脑中的画面说出来</h3>
|
||||
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
<div className="mx-auto flex h-full max-w-[50rem] flex-col items-center justify-center py-10 text-center">
|
||||
<img
|
||||
src={makeloreMark}
|
||||
alt="麦洛 M 标识"
|
||||
className="h-auto w-12 select-none sm:w-[3.25rem]"
|
||||
draggable={false}
|
||||
/>
|
||||
<h3 className="mt-6 max-w-2xl text-balance text-[clamp(1.65rem,2.45vw,2.1rem)] font-medium leading-[1.28] tracking-[-0.04em] text-foreground">
|
||||
你想让麦洛和你一起创作什么?
|
||||
</h3>
|
||||
<p className="mt-3 max-w-md text-pretty text-sm leading-6 text-muted-foreground">
|
||||
不用懂设计。说说想画什么、发生在哪里,或者希望别人看到后有什么感觉;没有想全也没关系。
|
||||
</p>
|
||||
<p className="mt-5 text-xs font-medium text-foreground">不知道怎么开头?试试这样说</p>
|
||||
<div className="mt-5 space-y-2">
|
||||
<div className="mt-7 flex max-w-2xl flex-wrap justify-center gap-2">
|
||||
{STARTER_MESSAGES.map((message) => (
|
||||
<button
|
||||
key={message}
|
||||
type="button"
|
||||
className="min-h-11 w-full rounded-xl border border-border/70 bg-background px-3 py-2.5 text-left text-xs leading-5 text-foreground transition-colors hover:border-brand/30 hover:bg-brand/[0.03] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/30"
|
||||
className="motion-press rounded-full bg-surface-subtle px-3.5 py-2 text-xs leading-5 text-foreground transition-[background-color,color] duration-150 hover:bg-surface-tertiary hover:text-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/30"
|
||||
onClick={() => setChatDraft(message)}
|
||||
>
|
||||
{message}
|
||||
@@ -147,16 +151,16 @@ export function DesignConversationPane({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mx-auto max-w-5xl space-y-4">
|
||||
<div className="mx-auto max-w-[50rem] space-y-6">
|
||||
{workspace.turns.map((turn) => (
|
||||
<div key={turn.turnId} className="space-y-3">
|
||||
<div key={turn.turnId} className="space-y-5">
|
||||
{turn.userMessage && (
|
||||
<div className="ml-auto max-w-[88%] rounded-2xl rounded-br-md border border-brand/20 bg-brand/[0.055] px-3.5 py-2.5 text-sm leading-6 text-foreground">
|
||||
<div className="chat-user-message-surface ml-auto max-w-[82%] whitespace-pre-wrap break-words rounded-[18px] rounded-tr-md px-3.5 py-2.5 text-pretty text-[15px] leading-6 text-foreground">
|
||||
{turn.userMessage}
|
||||
</div>
|
||||
)}
|
||||
{turn.assistantMessage && (
|
||||
<div className="mr-auto max-w-[92%] rounded-2xl rounded-bl-md border border-border/65 bg-background px-3.5 py-3 text-sm leading-6 text-foreground shadow-sm">
|
||||
<div className="chat-assistant-message-surface mr-auto w-full whitespace-pre-wrap break-words py-0.5 text-pretty text-[15px] leading-6 text-foreground">
|
||||
{turn.assistantMessage}
|
||||
</div>
|
||||
)}
|
||||
@@ -169,9 +173,9 @@ export function DesignConversationPane({
|
||||
data-testid={`pending-design-chat-${message.operationId}`}
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
className="ml-auto max-w-[88%]"
|
||||
className="ml-auto max-w-[82%]"
|
||||
>
|
||||
<div className="rounded-2xl rounded-br-md border border-brand/20 bg-brand/[0.055] px-3.5 py-2.5 text-sm leading-6 text-foreground">
|
||||
<div className="chat-user-message-surface whitespace-pre-wrap break-words rounded-[18px] rounded-tr-md px-3.5 py-2.5 text-pretty text-[15px] leading-6 text-foreground">
|
||||
{message.message}
|
||||
</div>
|
||||
<div className="mt-1 flex items-center justify-end gap-1.5 pr-1 text-[11px] text-muted-foreground">
|
||||
@@ -190,7 +194,7 @@ export function DesignConversationPane({
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
className="mr-auto max-w-[92%] rounded-2xl rounded-bl-md border border-brand/20 bg-background px-3.5 py-3 text-sm leading-6 text-foreground shadow-sm"
|
||||
className="chat-assistant-message-surface mr-auto w-full whitespace-pre-wrap break-words py-0.5 text-pretty text-[15px] leading-6 text-foreground"
|
||||
>
|
||||
{reply.text}
|
||||
<span
|
||||
@@ -214,36 +218,43 @@ export function DesignConversationPane({
|
||||
<div ref={scrollAnchorRef} />
|
||||
</div>
|
||||
|
||||
<footer className="border-t border-border/70 bg-background p-3.5 sm:px-6">
|
||||
<div className={cn(
|
||||
'rounded-2xl border border-border/80 bg-surface-input p-2 transition-colors focus-within:border-brand/35 focus-within:ring-2 focus-within:ring-brand/10',
|
||||
<form
|
||||
data-testid="design-message-composer"
|
||||
className="relative z-20 mx-auto w-[calc(100%_-_2rem)] max-w-[50rem] shrink-0 pb-3 pt-1"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
data-testid="design-message-composer-surface"
|
||||
className={cn(
|
||||
'chat-composer-surface border bg-background p-3 transition-[border-color,box-shadow] duration-150 focus-within:border-foreground/25 focus-within:ring-1 focus-within:ring-foreground/10',
|
||||
submittingDesignInput && 'opacity-80',
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
<Textarea
|
||||
id="design-chat-composer"
|
||||
aria-label="告诉 AI 你想创作什么"
|
||||
value={composerDraft}
|
||||
rows={2}
|
||||
rows={1}
|
||||
disabled={submittingDesignInput}
|
||||
placeholder="比如:我想做一张社团招新海报,要热闹、有活力……"
|
||||
className="min-h-[52px] resize-none border-0 bg-transparent px-2 py-1.5 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
placeholder="描述画面,或告诉麦洛想怎么调整"
|
||||
className="max-h-48 !min-h-[42px] resize-none border-0 bg-transparent px-0 py-1 text-[15px] leading-6 shadow-none placeholder:text-muted-foreground/55 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
onChange={(event) => setChatDraft(event.currentTarget.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing) return;
|
||||
event.preventDefault();
|
||||
submit();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center justify-between px-1 pt-1">
|
||||
<span className="text-[11px] text-muted-foreground">Enter 发送,Shift + Enter 换行</span>
|
||||
<div className="flex min-h-8 items-center gap-2">
|
||||
<span className="text-[11px] text-muted-foreground">Enter 发送 · Shift + Enter 换行</span>
|
||||
<Button
|
||||
type="button"
|
||||
size="icon"
|
||||
className="h-11 w-11 rounded-xl"
|
||||
type="submit"
|
||||
className="ml-auto h-8 w-8 shrink-0 rounded-full bg-foreground p-0 text-background shadow-soft hover:bg-foreground/90"
|
||||
disabled={!composerDraft.trim() || submittingDesignInput}
|
||||
aria-label="发送消息"
|
||||
onClick={submit}
|
||||
aria-label="发送"
|
||||
>
|
||||
{submittingDesignInput
|
||||
? <Loader2 className="h-4 w-4 animate-spin" />
|
||||
@@ -251,7 +262,7 @@ export function DesignConversationPane({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -346,26 +346,26 @@ export function YouthCreationCard({
|
||||
<section
|
||||
data-testid="youth-creation-card"
|
||||
aria-labelledby="youth-creation-card-title"
|
||||
className="overflow-hidden rounded-2xl border border-border/75 bg-background shadow-sm"
|
||||
className="py-2"
|
||||
>
|
||||
<header className="flex items-start justify-between gap-3 border-b border-border/60 px-4 py-3.5 sm:px-5">
|
||||
<header className="mb-3 flex items-start justify-between gap-3 px-1">
|
||||
<div>
|
||||
<h2 id="youth-creation-card-title" className="text-base font-semibold text-foreground">制作方案</h2>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">确认前可以直接修改最终内容</p>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">根据对话持续更新,确认前可以直接修改</p>
|
||||
</div>
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full bg-brand/[0.055] px-2.5 py-1 text-[11px] font-medium text-brand">
|
||||
<span className="inline-flex items-center gap-1.5 pt-1 text-[11px] font-medium text-muted-foreground">
|
||||
<Sparkles className="h-3.5 w-3.5" />
|
||||
AI 已根据对话整理
|
||||
麦洛已整理
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div className="space-y-3 px-4 py-4 sm:px-5">
|
||||
<section className="rounded-xl border border-border/70 bg-surface-subtle/25 p-3">
|
||||
<div className="space-y-4">
|
||||
<section>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<label htmlFor="creator-final-prompt" className="text-xs font-semibold text-foreground">创作提示词</label>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-8 items-center gap-1.5 rounded-lg border border-border/70 bg-background px-2.5 text-xs font-medium text-foreground hover:border-brand/25 hover:text-brand"
|
||||
className="motion-press inline-flex h-8 items-center gap-1.5 rounded-lg bg-surface-subtle px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-surface-tertiary hover:text-brand"
|
||||
disabled={busy}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => {
|
||||
@@ -387,7 +387,7 @@ export function YouthCreationCard({
|
||||
rows={4}
|
||||
disabled={busy}
|
||||
placeholder="AI 会根据前面的对话整理成最终创作提示词,你也可以直接修改。"
|
||||
className="min-h-[104px] resize-y bg-background pr-12 text-sm leading-6"
|
||||
className="min-h-[104px] resize-y rounded-xl border-border/70 bg-surface-subtle/45 pr-12 text-sm leading-6 shadow-none transition-[border-color,background-color,box-shadow] focus-visible:border-foreground/25 focus-visible:bg-background focus-visible:ring-1 focus-visible:ring-foreground/10"
|
||||
onChange={(event) => {
|
||||
const value = event.currentTarget.value;
|
||||
const cursor = event.currentTarget.selectionStart ?? value.length;
|
||||
@@ -457,6 +457,7 @@ export function YouthCreationCard({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{(references.length > 0 || unboundReferenceNumbers.length > 0) && (
|
||||
<section aria-label="参考图" className="border-t border-border/60 pt-3">
|
||||
<h3 className="text-xs font-semibold text-foreground">参考图 {references.length > 0 ? references.length : ''}</h3>
|
||||
<div className="mt-2 grid gap-2 lg:grid-cols-2">
|
||||
@@ -546,6 +547,9 @@ export function YouthCreationCard({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
<input
|
||||
ref={uploadInputRef}
|
||||
type="file"
|
||||
@@ -557,7 +561,6 @@ export function YouthCreationCard({
|
||||
if (file) void uploadReference(file);
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section aria-label="制作参数" className="flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-border/60 pt-3">
|
||||
<label className="flex items-center gap-2 text-xs font-medium text-foreground">
|
||||
@@ -646,7 +649,7 @@ export function YouthCreationCard({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<footer className="flex flex-col gap-3 border-t border-border/70 bg-surface-subtle/20 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-5">
|
||||
<footer className="mt-4 flex flex-col gap-3 border-t border-border/60 pt-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
|
||||
<span className={cn(
|
||||
'text-emerald-600',
|
||||
|
||||
@@ -356,6 +356,9 @@ test.describe('AI Design V2 workspace', () => {
|
||||
await page.getByTestId('ai-module-option-painting').click();
|
||||
await expect(page.getByTestId('image-workspace')).toBeVisible();
|
||||
await expect(page.getByTestId('image-workspace-conversation')).toBeVisible();
|
||||
await expect(page.getByTestId('image-workspace-conversation')).toHaveClass(/bg-background/);
|
||||
await expect(page.getByTestId('design-message-composer')).toHaveClass(/max-w-\[50rem\]/);
|
||||
await expect(page.getByTestId('design-message-composer-surface')).toHaveClass(/chat-composer-surface/);
|
||||
await expect(page.getByTestId('sidebar-image-workspace')).toBeVisible();
|
||||
await expect(page.getByTestId('image-workspace-history-rail')).toBeVisible();
|
||||
await expect(page.getByTestId('image-workspace-works-rail')).toHaveCount(0);
|
||||
@@ -365,6 +368,7 @@ test.describe('AI Design V2 workspace', () => {
|
||||
await expect(page.getByText('实时同步')).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: '刷新作品' })).toHaveCount(0);
|
||||
await expect(page.getByRole('heading', { name: '制作方案' })).toBeVisible();
|
||||
await expect(page.getByTestId('youth-creation-card')).not.toHaveClass(/border|shadow-sm/);
|
||||
await expect(page.getByRole('textbox', { name: '创作提示词' })).toHaveValue(
|
||||
'把便携咖啡机呈现为城市通勤中的精密工具',
|
||||
);
|
||||
|
||||
@@ -115,6 +115,10 @@ describe('AI Design Canvas page', () => {
|
||||
render(<ImageCanvas />);
|
||||
|
||||
const conversation = screen.getByTestId('image-workspace-conversation');
|
||||
expect(conversation).toHaveClass('bg-background');
|
||||
expect(conversation).not.toHaveClass('bg-surface-subtle/45');
|
||||
expect(within(conversation).getByTestId('design-message-composer')).toHaveClass('max-w-[50rem]');
|
||||
expect(within(conversation).getByTestId('design-message-composer-surface')).toHaveClass('chat-composer-surface');
|
||||
expect(within(conversation).queryByRole('heading', { name: '和 AI 一起完善创作' })).not.toBeInTheDocument();
|
||||
expect(within(conversation).getByRole('heading', { name: '制作方案' })).toBeInTheDocument();
|
||||
expect(within(conversation).getByRole('textbox', { name: '创作提示词' })).toHaveValue(
|
||||
@@ -317,7 +321,7 @@ describe('AI Design Canvas page', () => {
|
||||
render(<ImageCanvas />);
|
||||
|
||||
const conversation = screen.getByTestId('image-workspace-conversation');
|
||||
expect(within(conversation).getByRole('heading', { name: '先把脑中的画面说出来' })).toBeInTheDocument();
|
||||
expect(within(conversation).getByRole('heading', { name: '你想让麦洛和你一起创作什么?' })).toBeInTheDocument();
|
||||
expect(within(conversation).queryByText('画面想要什么感觉?')).not.toBeInTheDocument();
|
||||
fireEvent.click(within(conversation).getByRole('button', { name: '我想做一张热闹的社团招新海报' }));
|
||||
expect(within(conversation).getByRole('textbox', { name: '告诉 AI 你想创作什么' })).toHaveValue(
|
||||
@@ -381,7 +385,7 @@ describe('AI Design Canvas page', () => {
|
||||
fireEvent.change(screen.getByRole('textbox', { name: '告诉 AI 你想创作什么' }), {
|
||||
target: { value: '画一只会做饭的机器人' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '发送消息' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '发送' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toastErrorMock).toHaveBeenCalledWith(
|
||||
|
||||
@@ -120,9 +120,11 @@ describe('YouthCreationCard', () => {
|
||||
useImageWorkspaceStore.getState().reset();
|
||||
});
|
||||
|
||||
it('puts the editable creator prompt and compact visible parameters in one plan card', () => {
|
||||
it('keeps the editable prompt and visible parameters in a lightweight inline plan', () => {
|
||||
renderCard();
|
||||
|
||||
expect(screen.getByTestId('youth-creation-card')).toHaveClass('py-2');
|
||||
expect(screen.getByTestId('youth-creation-card')).not.toHaveClass('border', 'shadow-sm');
|
||||
expect(screen.getByRole('heading', { name: '制作方案' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('textbox', { name: '创作提示词' })).toHaveValue(
|
||||
'把便携咖啡机呈现为城市通勤中的精密工具',
|
||||
@@ -131,6 +133,7 @@ describe('YouthCreationCard', () => {
|
||||
expect(screen.getByRole('combobox', { name: '画幅' })).toHaveValue('3:4');
|
||||
expect(screen.getByRole('button', { name: '增加数量' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '准备制作方案' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('region', { name: '参考图' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('查看画面细节')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('手动调整')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user