prepare info display to user for review
This commit is contained in:
@@ -54,11 +54,43 @@ export interface EditorAgentGeneratedAudio {
|
||||
audioSrc: string;
|
||||
}
|
||||
|
||||
export interface EditorAgentToolCallStringArg {
|
||||
name: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface EditorAgentToolCallImageRef {
|
||||
imageId: string;
|
||||
imageSrc: string;
|
||||
objectKey?: string | null;
|
||||
thumbnailSrc?: string | null;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
}
|
||||
|
||||
export interface EditorAgentToolCallImageArg {
|
||||
name: string;
|
||||
label: string;
|
||||
refs: EditorAgentToolCallImageRef[];
|
||||
}
|
||||
|
||||
export interface EditorAgentToolCallDisplayExtras {
|
||||
priceMudPoints: number;
|
||||
}
|
||||
|
||||
export interface EditorAgentToolCallDisplayArgs {
|
||||
stringArgs: EditorAgentToolCallStringArg[];
|
||||
imageArgs: EditorAgentToolCallImageArg[];
|
||||
extras: EditorAgentToolCallDisplayExtras;
|
||||
}
|
||||
|
||||
export interface EditorAgentToolCall {
|
||||
toolName: string;
|
||||
summary: string;
|
||||
status: EditorAgentToolCallStatus;
|
||||
args: unknown;
|
||||
displayArgs: EditorAgentToolCallDisplayArgs;
|
||||
images: EditorAgentGeneratedImage[];
|
||||
// Older persisted conversation documents do not contain these media fields.
|
||||
videos?: EditorAgentGeneratedVideo[];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,12 +27,10 @@ impl EditorToolContext {
|
||||
///
|
||||
/// 通用 `Tool` 仍只负责参数校验;价格依赖 api-server 的运行时配置,不能下沉到
|
||||
/// `module-editor-agent`。实际执行和扣费仍由既有生成 BFF 负责。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) trait EditorAgentPricedTool: Tool {
|
||||
fn pricing(&self, pricing: &EditorGenerationPricingConfig, args: &<Self as Tool>::Args) -> u32;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn editor_agent_image_mud_points(
|
||||
pricing: &EditorGenerationPricingConfig,
|
||||
kind: Option<&str>,
|
||||
|
||||
@@ -24,11 +24,8 @@ pub struct GenerateVideoTool {
|
||||
pub context: crate::editor_agent::editor_tools::common::EditorToolContext,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const DEFAULT_VIDEO_MODEL: &str = "seedance2.0-fast";
|
||||
#[allow(dead_code)]
|
||||
const DEFAULT_VIDEO_RESOLUTION: &str = "720p";
|
||||
#[allow(dead_code)]
|
||||
const DEFAULT_VIDEO_DURATION_SECONDS: u32 = 4;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -99,14 +99,60 @@ pub struct EditorAgentGeneratedAudio {
|
||||
pub audio_src: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorAgentToolCallDisplayArgs {
|
||||
pub string_args: Vec<EditorAgentToolCallStringArg>,
|
||||
pub image_args: Vec<EditorAgentToolCallImageArg>,
|
||||
pub extras: EditorAgentToolCallDisplayExtras,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorAgentToolCallDisplayExtras {
|
||||
pub price_mud_points: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorAgentToolCallStringArg {
|
||||
pub name: String,
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorAgentToolCallImageArg {
|
||||
pub name: String,
|
||||
pub label: String,
|
||||
pub refs: Vec<EditorAgentToolCallImageRef>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorAgentToolCallImageRef {
|
||||
pub image_id: String,
|
||||
pub image_src: String,
|
||||
#[serde(default)]
|
||||
pub object_key: Option<String>,
|
||||
#[serde(default)]
|
||||
pub thumbnail_src: Option<String>,
|
||||
#[serde(default)]
|
||||
pub label: Option<String>,
|
||||
#[serde(default)]
|
||||
pub width: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub height: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct EditorAgentToolCall {
|
||||
pub tool_name: String,
|
||||
#[serde(default)]
|
||||
pub summary: String,
|
||||
pub status: EditorAgentToolCallStatus,
|
||||
pub args: serde_json::Value,
|
||||
pub display_args: EditorAgentToolCallDisplayArgs,
|
||||
#[serde(default)]
|
||||
pub images: Vec<EditorAgentGeneratedImage>,
|
||||
#[serde(default)]
|
||||
@@ -195,46 +241,79 @@ pub struct EditorAgentMessageResponse {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn tool_call_media_results_use_camel_case_and_default_for_existing_documents() {
|
||||
let legacy: EditorAgentToolCall = serde_json::from_value(json!({
|
||||
#[test]
|
||||
fn tool_call_display_args_and_media_use_camel_case() {
|
||||
let missing_display_args = serde_json::from_value::<EditorAgentToolCall>(json!({
|
||||
"toolName": "generate-video",
|
||||
"status": "completed",
|
||||
"args": {},
|
||||
"images": []
|
||||
}))
|
||||
.expect("existing message documents should remain readable");
|
||||
assert!(legacy.videos.is_empty());
|
||||
assert!(legacy.audios.is_empty());
|
||||
}));
|
||||
assert!(missing_display_args.is_err());
|
||||
|
||||
let tool_call = EditorAgentToolCall {
|
||||
tool_name: "generate-video".to_string(),
|
||||
summary: String::new(),
|
||||
status: EditorAgentToolCallStatus::Completed,
|
||||
args: json!({}),
|
||||
images: Vec::new(),
|
||||
videos: vec![EditorAgentGeneratedVideo {
|
||||
resource_id: Some("resource-video-1".to_string()),
|
||||
object_key: Some("generated/video.mp4".to_string()),
|
||||
asset_object_id: Some("asset-video-1".to_string()),
|
||||
video_src: "/generated-video.mp4".to_string(),
|
||||
thumbnail_src: Some("/generated-video.png".to_string()),
|
||||
width: Some(1280),
|
||||
height: Some(720),
|
||||
}],
|
||||
audios: vec![EditorAgentGeneratedAudio {
|
||||
resource_id: Some("resource-audio-1".to_string()),
|
||||
object_key: Some("generated/audio.mp3".to_string()),
|
||||
asset_object_id: Some("asset-audio-1".to_string()),
|
||||
audio_src: "/generated-audio.mp3".to_string(),
|
||||
}],
|
||||
error: None,
|
||||
};
|
||||
let payload = serde_json::to_value(tool_call).expect("tool call should serialize");
|
||||
assert_eq!(payload["videos"][0]["videoSrc"], "/generated-video.mp4");
|
||||
assert_eq!(payload["audios"][0]["audioSrc"], "/generated-audio.mp3");
|
||||
}
|
||||
let tool_call = EditorAgentToolCall {
|
||||
tool_name: "generate-video".to_string(),
|
||||
status: EditorAgentToolCallStatus::Completed,
|
||||
args: json!({}),
|
||||
display_args: EditorAgentToolCallDisplayArgs {
|
||||
string_args: vec![EditorAgentToolCallStringArg {
|
||||
name: "prompt".to_string(),
|
||||
label: "提示词".to_string(),
|
||||
value: "生成像素风角色".to_string(),
|
||||
}],
|
||||
image_args: vec![EditorAgentToolCallImageArg {
|
||||
name: "reference_image_ids".to_string(),
|
||||
label: "参考图片".to_string(),
|
||||
refs: vec![EditorAgentToolCallImageRef {
|
||||
image_id: "sha256:reference-1".to_string(),
|
||||
image_src: "/reference.png".to_string(),
|
||||
object_key: Some("generated/reference.png".to_string()),
|
||||
thumbnail_src: Some("/reference-thumbnail.png".to_string()),
|
||||
label: Some("角色参考".to_string()),
|
||||
width: Some(512),
|
||||
height: Some(512),
|
||||
}],
|
||||
}],
|
||||
extras: EditorAgentToolCallDisplayExtras {
|
||||
price_mud_points: 5,
|
||||
},
|
||||
},
|
||||
images: Vec::new(),
|
||||
videos: vec![EditorAgentGeneratedVideo {
|
||||
resource_id: Some("resource-video-1".to_string()),
|
||||
object_key: Some("generated/video.mp4".to_string()),
|
||||
asset_object_id: Some("asset-video-1".to_string()),
|
||||
video_src: "/generated-video.mp4".to_string(),
|
||||
thumbnail_src: Some("/generated-video.png".to_string()),
|
||||
width: Some(1280),
|
||||
height: Some(720),
|
||||
}],
|
||||
audios: vec![EditorAgentGeneratedAudio {
|
||||
resource_id: Some("resource-audio-1".to_string()),
|
||||
object_key: Some("generated/audio.mp3".to_string()),
|
||||
asset_object_id: Some("asset-audio-1".to_string()),
|
||||
audio_src: "/generated-audio.mp3".to_string(),
|
||||
}],
|
||||
error: None,
|
||||
};
|
||||
let payload = serde_json::to_value(tool_call).expect("tool call should serialize");
|
||||
assert_eq!(payload["videos"][0]["videoSrc"], "/generated-video.mp4");
|
||||
assert_eq!(payload["audios"][0]["audioSrc"], "/generated-audio.mp3");
|
||||
assert_eq!(
|
||||
payload["displayArgs"]["stringArgs"][0]["value"],
|
||||
"生成像素风角色"
|
||||
);
|
||||
assert_eq!(
|
||||
payload["displayArgs"]["imageArgs"][0]["refs"][0]["imageId"],
|
||||
"sha256:reference-1"
|
||||
);
|
||||
assert_eq!(
|
||||
payload["displayArgs"]["imageArgs"][0]["refs"][0]["objectKey"],
|
||||
"generated/reference.png"
|
||||
);
|
||||
assert_eq!(payload["displayArgs"]["extras"]["priceMudPoints"], 5);
|
||||
}
|
||||
}
|
||||
|
||||
+54
-9
@@ -122,13 +122,53 @@ function createPendingToolCallMessage(): EditorAgentMessage {
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'pending_confirmation',
|
||||
args: {
|
||||
object_image_id: 'source-image-1',
|
||||
reference_image_ids: ['reference-image-1', 'reference-image-2'],
|
||||
prompt: '把角色换成像素风',
|
||||
},
|
||||
displayArgs: {
|
||||
stringArgs: [
|
||||
{
|
||||
name: 'prompt',
|
||||
label: '提示词',
|
||||
value: '把角色换成像素风',
|
||||
},
|
||||
],
|
||||
imageArgs: [
|
||||
{
|
||||
name: 'object_image_id',
|
||||
label: '目标图片',
|
||||
refs: [
|
||||
{
|
||||
imageId: 'source-image-1',
|
||||
imageSrc: 'data:image/png;base64,c291cmNl',
|
||||
label: '原角色',
|
||||
width: 512,
|
||||
height: 512,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'reference_image_ids',
|
||||
label: '参考图片',
|
||||
refs: [
|
||||
{
|
||||
imageId: 'reference-image-1',
|
||||
imageSrc: 'data:image/png;base64,cmVmZXJlbmNlLTE=',
|
||||
label: '像素风参考',
|
||||
},
|
||||
{
|
||||
imageId: 'reference-image-2',
|
||||
imageSrc: 'data:image/png;base64,cmVmZXJlbmNlLTI=',
|
||||
label: '配色参考',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
extras: { priceMudPoints: 3 },
|
||||
},
|
||||
images: [],
|
||||
error: null,
|
||||
},
|
||||
@@ -500,8 +540,14 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
);
|
||||
|
||||
expect(await screen.findByText('把角色换成像素风')).toBeTruthy();
|
||||
expect(screen.getByText('source-image-1')).toBeTruthy();
|
||||
expect(screen.getByText('提示词')).toBeTruthy();
|
||||
expect(screen.getByText('2 张')).toBeTruthy();
|
||||
expect(screen.getByAltText('目标图片:原角色')).toBeTruthy();
|
||||
expect(screen.getByAltText('参考图片:像素风参考')).toBeTruthy();
|
||||
expect(screen.getByAltText('参考图片:配色参考')).toBeTruthy();
|
||||
expect(screen.getByText('预计消耗 3泥点')).toBeTruthy();
|
||||
expect(screen.queryByText('source-image-1')).toBeNull();
|
||||
expect(screen.queryByText('reference-image-1')).toBeNull();
|
||||
expect(
|
||||
screen.queryByText('internal system prompt that must stay hidden'),
|
||||
).toBeNull();
|
||||
@@ -511,13 +557,12 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
expect(client.confirmToolCall).toHaveBeenCalledWith('conversation-1', 2);
|
||||
});
|
||||
expect(
|
||||
(screen.getByRole('button', { name: '执行中' }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
).toBe(true);
|
||||
expect(
|
||||
(screen.getByRole('button', { name: '取消' }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
).toBe(true);
|
||||
within(screen.getByRole('article', { name: 'Agent操作' })).getByText(
|
||||
'执行中',
|
||||
),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: '确认' })).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: '取消' })).toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
resolveConfirmation({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Check, Loader2, Pencil, X } from 'lucide-react';
|
||||
import { Check, Coins, Loader2, Pencil, X } from 'lucide-react';
|
||||
|
||||
import type { EditorAgentToolCall } from '@/packages/shared/src/contracts';
|
||||
import { editorAgentToolLabel } from '@/src/components/image-editor/EditorAgentConversation/toolCallPresentation.ts';
|
||||
import { ResolvedAssetImage } from '@/src/components/ResolvedAssetImage.tsx';
|
||||
|
||||
type PendingToolCallAction = 'confirm' | 'cancel' | null;
|
||||
|
||||
@@ -13,37 +14,10 @@ type PendingToolCallProps = {
|
||||
onCancel: (messageId: number) => void;
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
|
||||
function readString(value: unknown) {
|
||||
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
||||
}
|
||||
|
||||
function readStringArray(value: unknown) {
|
||||
return Array.isArray(value)
|
||||
? value.map(readString).filter((item): item is string => item !== null)
|
||||
: [];
|
||||
}
|
||||
|
||||
function readPendingToolDetails(toolCall: EditorAgentToolCall) {
|
||||
const args = isRecord(toolCall.args) ? toolCall.args : {};
|
||||
const prompt = readString(args.prompt);
|
||||
const targetImage =
|
||||
readString(args.object_image_id) ?? readString(args.objectImageId);
|
||||
const referenceImages = readStringArray(
|
||||
args.reference_image_ids ?? args.referenceImageIds,
|
||||
);
|
||||
const summary = readString(toolCall.summary);
|
||||
|
||||
return {
|
||||
prompt: prompt ?? (summary?.startsWith('{') ? null : summary),
|
||||
targetImage,
|
||||
referenceImageCount: referenceImages.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function PendingToolCall({
|
||||
messageId,
|
||||
toolCall,
|
||||
@@ -51,7 +25,7 @@ export function PendingToolCall({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: PendingToolCallProps) {
|
||||
const details = readPendingToolDetails(toolCall);
|
||||
const displayArgs = toolCall.displayArgs;
|
||||
const label = editorAgentToolLabel(toolCall.toolName);
|
||||
const isBusy = busyAction !== null;
|
||||
|
||||
@@ -66,32 +40,66 @@ export function PendingToolCall({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{details.prompt ? (
|
||||
<p className="mt-2 whitespace-pre-wrap break-words text-sm leading-5">
|
||||
{details.prompt}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="mt-3 space-y-3 empty:hidden">
|
||||
{displayArgs.stringArgs.map((argument, index) => (
|
||||
<div
|
||||
key={`${argument.name}-${index}`}
|
||||
className="rounded-lg bg-slate-50 px-2.5 py-2"
|
||||
>
|
||||
<div className="text-xs font-medium text-slate-500">
|
||||
{argument.label}
|
||||
</div>
|
||||
<div className="mt-1 whitespace-pre-wrap break-words text-sm leading-5 text-slate-800">
|
||||
{argument.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{details.targetImage || details.referenceImageCount > 0 ? (
|
||||
<dl className="mt-2 space-y-1 text-xs text-slate-500">
|
||||
{details.targetImage ? (
|
||||
<div className="flex min-w-0 gap-2">
|
||||
<dt className="shrink-0">目标图片</dt>
|
||||
<dd className="min-w-0 break-all text-slate-700">
|
||||
{details.targetImage}
|
||||
</dd>
|
||||
{displayArgs.imageArgs.map((argument, argumentIndex) => (
|
||||
<div key={`${argument.name}-${argumentIndex}`}>
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-slate-500">
|
||||
<span>{argument.label}</span>
|
||||
<span className="font-normal text-slate-400">
|
||||
{argument.refs.length} 张
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{details.referenceImageCount > 0 ? (
|
||||
<div className="flex gap-2">
|
||||
<dt>参考图片</dt>
|
||||
<dd className="text-slate-700">
|
||||
{details.referenceImageCount} 张
|
||||
</dd>
|
||||
</div>
|
||||
) : null}
|
||||
</dl>
|
||||
) : null}
|
||||
{argument.refs.length ? (
|
||||
<div className="mt-1.5 flex flex-wrap gap-2">
|
||||
{argument.refs.map((image, imageIndex) => {
|
||||
const imageLabel =
|
||||
readString(image.label) ?? `图片 ${imageIndex + 1}`;
|
||||
return (
|
||||
<figure
|
||||
key={`${image.imageId}-${imageIndex}`}
|
||||
className="w-20 min-w-0"
|
||||
>
|
||||
<div className="overflow-hidden rounded-lg border border-slate-200 bg-slate-100">
|
||||
<ResolvedAssetImage
|
||||
src={image.thumbnailSrc ?? image.imageSrc}
|
||||
objectKey={image.objectKey}
|
||||
refreshKey={image.imageId}
|
||||
alt={`${argument.label}:${imageLabel}`}
|
||||
className="h-20 w-20 object-contain"
|
||||
/>
|
||||
</div>
|
||||
{image.label ? (
|
||||
<figcaption className="mt-1 truncate text-center text-[11px] text-slate-500">
|
||||
{image.label}
|
||||
</figcaption>
|
||||
) : null}
|
||||
</figure>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex items-center gap-1.5 rounded-lg border border-amber-200 bg-amber-50 px-2.5 py-2 text-xs font-medium text-amber-800">
|
||||
<Coins className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
||||
<span>预计消耗 {displayArgs.extras.priceMudPoints}泥点</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex justify-end gap-2">
|
||||
<button
|
||||
|
||||
+29
-5
@@ -12,6 +12,26 @@ import {
|
||||
useEditorAgentConversation,
|
||||
} from './useEditorAgentConversation.ts';
|
||||
|
||||
function createEditImageDisplayArgs(prompt: string) {
|
||||
return {
|
||||
stringArgs: [{ name: 'prompt', label: '修改要求', value: prompt }],
|
||||
imageArgs: [
|
||||
{
|
||||
name: 'object_image_id',
|
||||
label: '目标图片',
|
||||
refs: [
|
||||
{
|
||||
imageId: 'source-image',
|
||||
imageSrc: '/source-image.png',
|
||||
label: '源图片',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
extras: { priceMudPoints: 3 },
|
||||
};
|
||||
}
|
||||
|
||||
function createClient(): EditorAgentConversationClient {
|
||||
return {
|
||||
listConversations: vi.fn().mockResolvedValue([
|
||||
@@ -61,9 +81,13 @@ function createClient(): EditorAgentConversationClient {
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'generate_image',
|
||||
summary: '生成像素风格角色',
|
||||
status: 'completed',
|
||||
args: {},
|
||||
displayArgs: {
|
||||
stringArgs: [],
|
||||
imageArgs: [],
|
||||
extras: { priceMudPoints: 0 },
|
||||
},
|
||||
images: [
|
||||
{
|
||||
resourceId: 'resource-result-1',
|
||||
@@ -252,12 +276,12 @@ describe('useEditorAgentConversation', () => {
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'pending_confirmation',
|
||||
args: {
|
||||
object_image_id: 'source-image',
|
||||
prompt: '换成像素风',
|
||||
},
|
||||
displayArgs: createEditImageDisplayArgs('换成像素风'),
|
||||
images: [],
|
||||
error: null,
|
||||
},
|
||||
@@ -273,12 +297,12 @@ describe('useEditorAgentConversation', () => {
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'completed',
|
||||
args: {
|
||||
object_image_id: 'source-image',
|
||||
prompt: '换成像素风',
|
||||
},
|
||||
displayArgs: createEditImageDisplayArgs('换成像素风'),
|
||||
images: [
|
||||
{
|
||||
resourceId: null,
|
||||
@@ -339,9 +363,9 @@ describe('useEditorAgentConversation', () => {
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'pending_confirmation',
|
||||
args: { object_image_id: 'source-image', prompt: '换成像素风' },
|
||||
displayArgs: createEditImageDisplayArgs('换成像素风'),
|
||||
images: [],
|
||||
error: null,
|
||||
},
|
||||
@@ -407,9 +431,9 @@ describe('useEditorAgentConversation', () => {
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'pending_confirmation' as const,
|
||||
args: { object_image_id: 'source-image', prompt: '换成像素风' },
|
||||
displayArgs: createEditImageDisplayArgs('换成像素风'),
|
||||
images: [],
|
||||
error: null,
|
||||
},
|
||||
|
||||
@@ -69,6 +69,7 @@ const getEditorAgentConversationMock = vi.hoisted(() =>
|
||||
conversationId: 'editor-agent-conv-test',
|
||||
projectId: 'editor-project-default',
|
||||
title: '画布 Agent',
|
||||
createdAt: '2026-07-03T00:00:00.000Z',
|
||||
updatedAt: '2026-07-03T00:00:00.000Z',
|
||||
messages: [],
|
||||
})),
|
||||
@@ -176,12 +177,32 @@ function createEditorAgentConversationSummary(
|
||||
};
|
||||
}
|
||||
|
||||
function createEditImageToolCallDisplayArgs(prompt: string) {
|
||||
return {
|
||||
stringArgs: [{ name: 'prompt', label: '修改要求', value: prompt }],
|
||||
imageArgs: [
|
||||
{
|
||||
name: 'object_image_id',
|
||||
label: '目标图片',
|
||||
refs: [
|
||||
{
|
||||
imageId: 'source-image',
|
||||
imageSrc: '/source-image.png',
|
||||
label: '源图片',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
extras: { priceMudPoints: 3 },
|
||||
};
|
||||
}
|
||||
|
||||
function createEditorAgentDetailWithGeneration(
|
||||
resourceId: string,
|
||||
): EditorAgentConversationDetail {
|
||||
const summary = createEditorAgentConversationSummary();
|
||||
const conversationSummary = createEditorAgentConversationSummary();
|
||||
return {
|
||||
...summary,
|
||||
...conversationSummary,
|
||||
title: '画布 Agent',
|
||||
createdAt: '2026-07-03T00:00:00.000Z',
|
||||
messages: [
|
||||
@@ -192,9 +213,13 @@ function createEditorAgentDetailWithGeneration(
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'generate_image',
|
||||
summary: '',
|
||||
status: 'completed',
|
||||
args: {},
|
||||
displayArgs: {
|
||||
stringArgs: [],
|
||||
imageArgs: [],
|
||||
extras: { priceMudPoints: 0 },
|
||||
},
|
||||
images: [
|
||||
{
|
||||
resourceId,
|
||||
@@ -1855,12 +1880,13 @@ describe('ImageCanvasEditorView', () => {
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'pending_confirmation',
|
||||
args: {
|
||||
object_image_id: 'source-image',
|
||||
prompt: '把图片换成像素风',
|
||||
},
|
||||
displayArgs:
|
||||
createEditImageToolCallDisplayArgs('把图片换成像素风'),
|
||||
images: [],
|
||||
error: null,
|
||||
},
|
||||
@@ -1877,9 +1903,10 @@ describe('ImageCanvasEditorView', () => {
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'completed',
|
||||
args: {},
|
||||
displayArgs:
|
||||
createEditImageToolCallDisplayArgs('把图片换成像素风'),
|
||||
images: [
|
||||
{
|
||||
resourceId: null,
|
||||
|
||||
@@ -154,9 +154,13 @@ describe('editorAgentClient', () => {
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'completed',
|
||||
args: {},
|
||||
displayArgs: {
|
||||
stringArgs: [],
|
||||
imageArgs: [],
|
||||
extras: { priceMudPoints: 0 },
|
||||
},
|
||||
images: [],
|
||||
error: null,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user