feat(composer): carry reference image names and submit on Enter
Propagate an optional imageName through the agent content pipeline (frontend types → API → domain → prompt building) so reference images render with their name instead of a bare "image" label. Also switch PromptComposer to submit on plain Enter (Shift+Enter for newline, IME-safe) and add a submitDisabled guard wired from the canvas and home composers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -306,6 +306,7 @@ export type AgentContent =
|
|||||||
| {
|
| {
|
||||||
type: "image";
|
type: "image";
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
|
imageName?: string;
|
||||||
imageWidth?: number;
|
imageWidth?: number;
|
||||||
imageHeight?: number;
|
imageHeight?: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ export const CanvasAgentComposer = forwardRef<PromptComposerHandle, CanvasAgentC
|
|||||||
onAnnotationPreviewChange={onAnnotationPreviewChange}
|
onAnnotationPreviewChange={onAnnotationPreviewChange}
|
||||||
onPasteFile={onUploadFile}
|
onPasteFile={onUploadFile}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
|
submitDisabled={generating || !canSubmit}
|
||||||
/>
|
/>
|
||||||
{previewSlot}
|
{previewSlot}
|
||||||
<div className="assistant-composer-toolbar">
|
<div className="assistant-composer-toolbar">
|
||||||
|
|||||||
@@ -632,6 +632,7 @@ export const PromptComposer = forwardRef<
|
|||||||
onAnnotationPreviewChange?: (preview: AnnotationPreviewRequest | null) => void;
|
onAnnotationPreviewChange?: (preview: AnnotationPreviewRequest | null) => void;
|
||||||
onPasteFile?: (file: File | null) => void | Promise<void>;
|
onPasteFile?: (file: File | null) => void | Promise<void>;
|
||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
|
submitDisabled?: boolean;
|
||||||
}
|
}
|
||||||
>(function PromptComposer(
|
>(function PromptComposer(
|
||||||
{
|
{
|
||||||
@@ -648,7 +649,8 @@ export const PromptComposer = forwardRef<
|
|||||||
onAnnotationsChange,
|
onAnnotationsChange,
|
||||||
onAnnotationPreviewChange,
|
onAnnotationPreviewChange,
|
||||||
onPasteFile,
|
onPasteFile,
|
||||||
onSubmit
|
onSubmit,
|
||||||
|
submitDisabled = false
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) {
|
) {
|
||||||
@@ -883,7 +885,8 @@ export const PromptComposer = forwardRef<
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: "image",
|
type: "image",
|
||||||
imageUrl: part.reference.publicUrl
|
imageUrl: part.reference.publicUrl,
|
||||||
|
imageName: part.reference.name
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((part): part is AgentContent => Boolean(part));
|
.filter((part): part is AgentContent => Boolean(part));
|
||||||
@@ -1067,9 +1070,10 @@ export const PromptComposer = forwardRef<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
const isComposing = event.nativeEvent.isComposing || event.keyCode === 229;
|
||||||
|
if (event.key === "Enter" && !event.shiftKey && !isComposing) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onSubmit?.();
|
if (!submitDisabled) onSubmit?.();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
|
|||||||
@@ -5393,18 +5393,22 @@ function agentContentsToDisplayText(contents: AgentContent[]) {
|
|||||||
return contents
|
return contents
|
||||||
.map((content, index) => {
|
.map((content, index) => {
|
||||||
if (content.type === "text") return content.text;
|
if (content.type === "text") return content.text;
|
||||||
return `参考图 ${index + 1}(image):${content.imageUrl}`;
|
return `参考图 ${index + 1}(${agentContentImageName(content)}):${content.imageUrl}`;
|
||||||
})
|
})
|
||||||
.filter((item) => item.trim())
|
.filter((item) => item.trim())
|
||||||
.join("\n\n");
|
.join("\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function agentContentImageName(content: Extract<AgentContent, { type: "image" }>) {
|
||||||
|
return content.imageName?.replace(/\s+/g, " ").trim() || "image";
|
||||||
|
}
|
||||||
|
|
||||||
function imageGeneratorSubmittedPrompt(contents: AgentContent[]) {
|
function imageGeneratorSubmittedPrompt(contents: AgentContent[]) {
|
||||||
return cleanImageGeneratorPrompt(
|
return cleanImageGeneratorPrompt(
|
||||||
contents
|
contents
|
||||||
.map((content, index) => {
|
.map((content, index) => {
|
||||||
if (content.type === "text" && !isSettingsTextSource(content.textSource)) return content.text;
|
if (content.type === "text" && !isSettingsTextSource(content.textSource)) return content.text;
|
||||||
if (content.type === "image") return `参考图 ${index + 1}:${content.imageUrl}`;
|
if (content.type === "image") return `参考图 ${index + 1}(${agentContentImageName(content)}):${content.imageUrl}`;
|
||||||
return "";
|
return "";
|
||||||
})
|
})
|
||||||
.filter((item) => item.trim())
|
.filter((item) => item.trim())
|
||||||
|
|||||||
@@ -736,6 +736,7 @@ export function HomePage() {
|
|||||||
onReferenceRemoved={handleReferenceRemoved}
|
onReferenceRemoved={handleReferenceRemoved}
|
||||||
onPasteFile={attachImage}
|
onPasteFile={attachImage}
|
||||||
onSubmit={() => createProject()}
|
onSubmit={() => createProject()}
|
||||||
|
submitDisabled={loading || !canSubmitPrompt}
|
||||||
/>
|
/>
|
||||||
<div className="prompt-actions">
|
<div className="prompt-actions">
|
||||||
<input ref={fileInputRef} type="file" accept="image/*" hidden onChange={(event) => void attachImage(event.target.files?.[0] ?? null)} />
|
<input ref={fileInputRef} type="file" accept="image/*" hidden onChange={(event) => void attachImage(event.target.files?.[0] ?? null)} />
|
||||||
|
|||||||
@@ -369,6 +369,7 @@ type AgentContent {
|
|||||||
Text string `json:"text,optional"`
|
Text string `json:"text,optional"`
|
||||||
TextSource string `json:"textSource,optional"`
|
TextSource string `json:"textSource,optional"`
|
||||||
ImageUrl string `json:"imageUrl,optional"`
|
ImageUrl string `json:"imageUrl,optional"`
|
||||||
|
ImageName string `json:"imageName,optional"`
|
||||||
ImageWidth int64 `json:"imageWidth,optional"`
|
ImageWidth int64 `json:"imageWidth,optional"`
|
||||||
ImageHeight int64 `json:"imageHeight,optional"`
|
ImageHeight int64 `json:"imageHeight,optional"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4510,7 +4510,13 @@ func promptFromAgentMessages(messages []design.AgentChatMessage) string {
|
|||||||
if builder.Len() > 0 {
|
if builder.Len() > 0 {
|
||||||
builder.WriteString("\n")
|
builder.WriteString("\n")
|
||||||
}
|
}
|
||||||
builder.WriteString("Reference image: ")
|
builder.WriteString("Reference image")
|
||||||
|
if imageName := strings.TrimSpace(content.ImageName); imageName != "" {
|
||||||
|
builder.WriteString(" (")
|
||||||
|
builder.WriteString(compactReferenceImageName(imageName))
|
||||||
|
builder.WriteString(")")
|
||||||
|
}
|
||||||
|
builder.WriteString(": ")
|
||||||
builder.WriteString(imageURL)
|
builder.WriteString(imageURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4519,6 +4525,10 @@ func promptFromAgentMessages(messages []design.AgentChatMessage) string {
|
|||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func compactReferenceImageName(value string) string {
|
||||||
|
return strings.Join(strings.Fields(value), " ")
|
||||||
|
}
|
||||||
|
|
||||||
func hasUserPromptTextInAgentMessages(messages []design.AgentChatMessage) bool {
|
func hasUserPromptTextInAgentMessages(messages []design.AgentChatMessage) bool {
|
||||||
for _, message := range messages {
|
for _, message := range messages {
|
||||||
if strings.TrimSpace(message.Role) != "" && strings.ToLower(strings.TrimSpace(message.Role)) != "user" {
|
if strings.TrimSpace(message.Role) != "" && strings.ToLower(strings.TrimSpace(message.Role)) != "user" {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type AgentContent struct {
|
|||||||
Text string
|
Text string
|
||||||
TextSource string
|
TextSource string
|
||||||
ImageURL string
|
ImageURL string
|
||||||
|
ImageName string
|
||||||
ImageWidth int64
|
ImageWidth int64
|
||||||
ImageHeight int64
|
ImageHeight int64
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ type socketContent struct {
|
|||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
TextSource string `json:"text_source"`
|
TextSource string `json:"text_source"`
|
||||||
ImageURL string `json:"image_url"`
|
ImageURL string `json:"image_url"`
|
||||||
|
ImageName string `json:"image_name"`
|
||||||
ImageWidth int64 `json:"image_width"`
|
ImageWidth int64 `json:"image_width"`
|
||||||
ImageHeight int64 `json:"image_height"`
|
ImageHeight int64 `json:"image_height"`
|
||||||
}
|
}
|
||||||
@@ -155,6 +156,7 @@ func (data socketStartData) toDomainChat(projectID string) design.AgentChatReque
|
|||||||
Text: content.Text,
|
Text: content.Text,
|
||||||
TextSource: content.TextSource,
|
TextSource: content.TextSource,
|
||||||
ImageURL: content.ImageURL,
|
ImageURL: content.ImageURL,
|
||||||
|
ImageName: content.ImageName,
|
||||||
ImageWidth: content.ImageWidth,
|
ImageWidth: content.ImageWidth,
|
||||||
ImageHeight: content.ImageHeight,
|
ImageHeight: content.ImageHeight,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ func toDomainAgentChat(req *types.AgentChatRequest) design.AgentChatRequest {
|
|||||||
Text: content.Text,
|
Text: content.Text,
|
||||||
TextSource: content.TextSource,
|
TextSource: content.TextSource,
|
||||||
ImageURL: content.ImageUrl,
|
ImageURL: content.ImageUrl,
|
||||||
|
ImageName: content.ImageName,
|
||||||
ImageWidth: content.ImageWidth,
|
ImageWidth: content.ImageWidth,
|
||||||
ImageHeight: content.ImageHeight,
|
ImageHeight: content.ImageHeight,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type AgentContent struct {
|
|||||||
Text string `json:"text,optional"`
|
Text string `json:"text,optional"`
|
||||||
TextSource string `json:"textSource,optional"`
|
TextSource string `json:"textSource,optional"`
|
||||||
ImageUrl string `json:"imageUrl,optional"`
|
ImageUrl string `json:"imageUrl,optional"`
|
||||||
|
ImageName string `json:"imageName,optional"`
|
||||||
ImageWidth int64 `json:"imageWidth,optional"`
|
ImageWidth int64 `json:"imageWidth,optional"`
|
||||||
ImageHeight int64 `json:"imageHeight,optional"`
|
ImageHeight int64 `json:"imageHeight,optional"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user