全能创作与模特库收口:品牌品名输入、确认角色循环、换款落点与鱼眼模板

补充品牌品名改为预填输入框且空骨架不跳步;痛点确认角色后「继续完善方案」可继续推进;点击换款要求每次落点不同;鱼眼换装改用小云雀连环换装 prompt 模板;模特详情标签自动换行。
This commit is contained in:
Azmat@qq.com
2026-09-21 16:47:50 +08:00
parent 0bd1db6bf9
commit 9f15b71f19
6 changed files with 395 additions and 62 deletions
+8
View File
@@ -318,9 +318,17 @@
.model-detail-portrait[role="button"] { cursor: zoom-in; }
.model-detail-tags {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 12px;
min-width: 0;
max-width: 100%;
}
.model-detail-tags .pill {
flex: 0 1 auto;
max-width: 100%;
overflow-wrap: anywhere;
}
.model-detail-triview {
width: 100%;
+40 -3
View File
@@ -48,7 +48,7 @@ type PromptBlock =
| { type: "shot"; heading: string; fields: Array<{ label: string; value: string }>; text: string }
| { type: "text"; heading: string; text: string };
type ReplyOption = { label: string; text: string; action?: "upload" };
type ReplyOption = { label: string; text: string; action?: "upload" | "compose" };
function renderInlineMarkdown(text: string): ReactNode[] {
return text.split(/(\*\*[^*]+\*\*|`[^`]+`|\*[^*]+\*)/g).filter(Boolean).map((part, index) => {
@@ -293,11 +293,18 @@ function replyOptionsForMessage(text: string, payload: Record<string, unknown>):
.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object")
.map((item) => {
const label = String(item.label || "").trim();
const rawAction = String(item.action || "").trim();
const compose = rawAction === "compose" || /我来补充/.test(label);
return {
label,
text: String(item.text || "").trim(),
// 任何引导里的“上传图片/实物图/素材图”都走本地文件选择器,避免同一套对话里有的能传、有的只是文字回复。
action: /上传.*(?:图|图片|素材)|(?:图|图片|素材).*上传/.test(label) ? "upload" as const : undefined,
// compose:只预填输入框,不直接提交空骨架。
action: /上传.*(?:图|图片|素材)|(?:图|图片|素材).*上传/.test(label)
? "upload" as const
: compose
? "compose" as const
: undefined,
};
})
.filter((item) => item.label && item.text)
@@ -1676,6 +1683,7 @@ function ElicitCard({
options: replyOptions.map((item, index) => ({
value: String(item.text || item.value || item.label || `option_${index + 1}`),
label: String(item.label || item.text || item.value || `选项 ${index + 1}`),
action: String((item as { action?: string }).action || "").trim() || (/我来补充/.test(String(item.label || "")) ? "compose" : ""),
})),
}
: null;
@@ -1722,6 +1730,17 @@ function ElicitCard({
event.preventDefault();
const text = chatAnswer.trim();
if (!text || disabled) return;
// 品牌品名空骨架未填完:留在输入框,不提交。
if (
String(message.payload.topic || "") === "product_info"
&& (/^品牌是[:]\s*\s*(?:商品名|品名)是[:]\s*$/.test(text) || text === "品牌是:,商品名是:")
) {
requestAnimationFrame(() => {
const input = document.querySelector(".omni-chat-question-input input") as HTMLInputElement | null;
input?.focus();
});
return;
}
onChatAnswer(text);
setChatAnswer("");
};
@@ -1753,6 +1772,20 @@ function ElicitCard({
onClick={() => {
// reply_options 是快捷整句回答,走聊天正文;字段 options 仍走 elicit answers。
if (replyChoiceField && !fieldChoice) {
const action = typeof option === "string" ? "" : String(option?.action || "");
// 「我来补充…」:预填输入框,等用户填完再发,禁止空骨架直接提交。
if (action === "compose" || /我来补充/.test(label) || /^品牌是[:]\s*?\s*(?:商品名|品名)是[:]\s*$/.test(value)) {
setChatAnswer(value.includes("品牌是") ? value : "品牌是:,商品名是:");
requestAnimationFrame(() => {
const input = document.querySelector(".omni-chat-question-input input") as HTMLInputElement | null;
input?.focus();
if (input && input.value.includes(":,")) {
const pos = input.value.indexOf("") + 1;
input.setSelectionRange(pos, pos);
}
});
return;
}
onChatAnswer(value);
return;
}
@@ -1772,7 +1805,11 @@ function ElicitCard({
value={chatAnswer}
disabled={disabled}
onChange={(event) => setChatAnswer(event.target.value)}
placeholder="输入你的想法…"
placeholder={
String(message.payload.topic || "") === "product_info"
? "例如:品牌浅蓝小熊,品名婴儿柔湿巾"
: "输入你的想法…"
}
aria-label="回答这个问题"
/>
<button type="submit" aria-label="发送回答" disabled={disabled || !chatAnswer.trim()}>