视频创作解决已发现问题
@@ -671,8 +671,9 @@ def default_reply_options(
|
|||||||
*,
|
*,
|
||||||
has_context: bool = False,
|
has_context: bool = False,
|
||||||
is_video: bool = True,
|
is_video: bool = True,
|
||||||
|
assistant_text: str = "",
|
||||||
) -> list[dict[str, str]]:
|
) -> list[dict[str, str]]:
|
||||||
"""纯文字收束时给前端的可点下一步,避免用户面对一段说明不知道怎么继续。"""
|
"""只给与上一句相关的快捷回复,绝不塞与当前进度无关的固定话术。"""
|
||||||
stage = ""
|
stage = ""
|
||||||
if conversation is not None:
|
if conversation is not None:
|
||||||
try:
|
try:
|
||||||
@@ -686,17 +687,39 @@ def default_reply_options(
|
|||||||
{"label": "重新来", "text": "重新来"},
|
{"label": "重新来", "text": "重新来"},
|
||||||
]
|
]
|
||||||
if has_context:
|
if has_context:
|
||||||
if is_video:
|
text = str(assistant_text or "")
|
||||||
|
if re.search(r"颜色|色号|色彩|SKU|款式|几种", text, re.IGNORECASE):
|
||||||
return [
|
return [
|
||||||
{"label": "继续完善方案", "text": "继续完善方案"},
|
{"label": "补充颜色和顺序", "text": "我来补充每个颜色和展示顺序"},
|
||||||
{"label": "换个场景", "text": "我想换个场景"},
|
{"label": "上传各款实物图", "text": "我补充各颜色/款式的实物图"},
|
||||||
{"label": "改卖点", "text": "我想改卖点"},
|
{"label": "先按当前主款做", "text": "先按当前主款做,其他颜色后面再补"},
|
||||||
]
|
]
|
||||||
return [
|
if re.search(r"商品|产品|主推|哪款", text, re.IGNORECASE):
|
||||||
{"label": "按这个方向出图", "text": "按这个方向出图"},
|
return [
|
||||||
{"label": "换个场景", "text": "我想换个场景"},
|
{"label": "发商品列表", "text": "把商品列表发给我选"},
|
||||||
{"label": "改人物状态", "text": "我想改人物状态"},
|
{"label": "我直接说商品名", "text": "我直接告诉你商品名"},
|
||||||
]
|
{"label": "你来推荐", "text": "你根据当前需求推荐一款"},
|
||||||
|
]
|
||||||
|
if re.search(r"人物|角色|模特|出镜", text, re.IGNORECASE):
|
||||||
|
return [
|
||||||
|
{"label": "上传人物图", "text": "我上传人物参考图"},
|
||||||
|
{"label": "由你设定角色", "text": "你先帮我设定一个合适的角色"},
|
||||||
|
{"label": "不需要人物", "text": "这条先不需要人物出镜"},
|
||||||
|
]
|
||||||
|
if re.search(r"场景|地点|背景|在哪", text, re.IGNORECASE):
|
||||||
|
return [
|
||||||
|
{"label": "上传场景图", "text": "我上传场景参考图"},
|
||||||
|
{"label": "你来推荐场景", "text": "你按商品和预设推荐场景"},
|
||||||
|
{"label": "用干净日常场景", "text": "先用干净自然的日常场景"},
|
||||||
|
]
|
||||||
|
if re.search(r"卖点|功能|效果|优惠|价格", text, re.IGNORECASE):
|
||||||
|
return [
|
||||||
|
{"label": "补充真实卖点", "text": "我来补充商品真实卖点"},
|
||||||
|
{"label": "从素材里判断", "text": "先根据我上传的素材判断可表达的卖点"},
|
||||||
|
{"label": "先只突出一个点", "text": "先围绕一个最核心的卖点创作"},
|
||||||
|
]
|
||||||
|
# 没有足够语境时只留自由输入,不伪造“继续/改卖点”这种人机按钮。
|
||||||
|
return []
|
||||||
kind = "短视频" if is_video else "商品图"
|
kind = "短视频" if is_video else "商品图"
|
||||||
return [
|
return [
|
||||||
{"label": f"帮我做一条{kind}" if is_video else "帮我做一张商品图", "text": f"帮我做一条{kind}" if is_video else "帮我做一张商品图"},
|
{"label": f"帮我做一条{kind}" if is_video else "帮我做一张商品图", "text": f"帮我做一条{kind}" if is_video else "帮我做一张商品图"},
|
||||||
@@ -745,7 +768,12 @@ def ensure_turn_guides(
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
hint = default_reply_hint(conversation, has_context=has_context, is_video=is_video)
|
hint = default_reply_hint(conversation, has_context=has_context, is_video=is_video)
|
||||||
options = default_reply_options(conversation, has_context=has_context, is_video=is_video)
|
options = default_reply_options(
|
||||||
|
conversation,
|
||||||
|
has_context=has_context,
|
||||||
|
is_video=is_video,
|
||||||
|
assistant_text=last_text_bubble.text if last_text_bubble is not None else "",
|
||||||
|
)
|
||||||
events: list[dict] = []
|
events: list[dict] = []
|
||||||
if last_text_bubble is not None:
|
if last_text_bubble is not None:
|
||||||
if text_already_guides(last_text_bubble.text):
|
if text_already_guides(last_text_bubble.text):
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from .creation_agent import (
|
|||||||
build_system_prompt,
|
build_system_prompt,
|
||||||
build_messages,
|
build_messages,
|
||||||
default_reply_hint,
|
default_reply_hint,
|
||||||
|
default_reply_options,
|
||||||
ensure_turn_guides,
|
ensure_turn_guides,
|
||||||
get_creation_chat_model,
|
get_creation_chat_model,
|
||||||
get_video_gate_stage,
|
get_video_gate_stage,
|
||||||
@@ -1842,6 +1843,26 @@ class ReplyGuidanceTests(CreationAgentBaseTests):
|
|||||||
self.assertIn("按这个方向出图", str((bubble.payload or {}).get("reply_hint") or ""))
|
self.assertIn("按这个方向出图", str((bubble.payload or {}).get("reply_hint") or ""))
|
||||||
self.assertTrue((bubble.payload or {}).get("reply_options"))
|
self.assertTrue((bubble.payload or {}).get("reply_options"))
|
||||||
|
|
||||||
|
def test_reply_options_follow_the_current_question(self):
|
||||||
|
options = default_reply_options(
|
||||||
|
self.conversation,
|
||||||
|
has_context=True,
|
||||||
|
is_video=True,
|
||||||
|
assistant_text="这款耳机一共有六个配色,分别是什么颜色?",
|
||||||
|
)
|
||||||
|
labels = [option["label"] for option in options]
|
||||||
|
self.assertEqual(labels, ["补充颜色和顺序", "上传各款实物图", "先按当前主款做"])
|
||||||
|
self.assertNotIn("改卖点", labels)
|
||||||
|
|
||||||
|
def test_unknown_context_question_does_not_get_fixed_actions(self):
|
||||||
|
options = default_reply_options(
|
||||||
|
self.conversation,
|
||||||
|
has_context=True,
|
||||||
|
is_video=True,
|
||||||
|
assistant_text="我先把你刚刚提到的细节整理一下。",
|
||||||
|
)
|
||||||
|
self.assertEqual(options, [])
|
||||||
|
|
||||||
def test_text_already_guides_detects_reply_instruction(self):
|
def test_text_already_guides_detects_reply_instruction(self):
|
||||||
self.assertTrue(text_already_guides("这个方向能做。请回复:按这个方向继续。"))
|
self.assertTrue(text_already_guides("这个方向能做。请回复:按这个方向继续。"))
|
||||||
self.assertFalse(text_already_guides("这个方向能做。有想调整可以直接说。"))
|
self.assertFalse(text_already_guides("这个方向能做。有想调整可以直接说。"))
|
||||||
|
|||||||
@@ -1370,6 +1370,9 @@ class QuickCreateCoordinatorTests(TestCase):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,54 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.omni-chat-markdown {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.omni-chat-markdown > :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.omni-chat-markdown > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.omni-chat-markdown h3 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: var(--accent-black);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.omni-chat-markdown p {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.omni-chat-markdown ol,
|
||||||
|
.omni-chat-markdown ul {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.omni-chat-markdown li + li {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.omni-chat-markdown strong {
|
||||||
|
color: var(--accent-black);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.omni-chat-markdown code {
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: var(--r-sm);
|
||||||
|
color: var(--accent-black);
|
||||||
|
background: var(--black-alpha-4);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: .92em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.omni-reply-hint {
|
.omni-reply-hint {
|
||||||
margin: 8px 0 0;
|
margin: 8px 0 0;
|
||||||
|
|||||||
@@ -556,7 +556,7 @@ export function OmniHistoryPage({
|
|||||||
navigate: NavigateFn;
|
navigate: NavigateFn;
|
||||||
onNotify?: (type: "success" | "error" | "info", text: string) => void;
|
onNotify?: (type: "success" | "error" | "info", text: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const [filter, setFilter] = useState<"all" | "running" | "completed">("all");
|
const [modeFilter, setModeFilter] = useState<"all" | "video" | "image">("all");
|
||||||
const [items, setItems] = useState<CreationConversation[] | null>(null);
|
const [items, setItems] = useState<CreationConversation[] | null>(null);
|
||||||
const [deleteTarget, setDeleteTarget] = useState<CreationConversation | null>(null);
|
const [deleteTarget, setDeleteTarget] = useState<CreationConversation | null>(null);
|
||||||
// 同会话页:onNotify 是内联箭头,进依赖会让列表每次 App 重渲染都重拉一遍
|
// 同会话页:onNotify 是内联箭头,进依赖会让列表每次 App 重渲染都重拉一遍
|
||||||
@@ -567,7 +567,7 @@ export function OmniHistoryPage({
|
|||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
setItems(null);
|
setItems(null);
|
||||||
api
|
api
|
||||||
.listCreations(filter === "all" ? {} : { status: filter })
|
.listCreations(modeFilter === "all" ? {} : { mode: modeFilter })
|
||||||
.then((page) => {
|
.then((page) => {
|
||||||
if (!cancelled) setItems(page.results || []);
|
if (!cancelled) setItems(page.results || []);
|
||||||
})
|
})
|
||||||
@@ -579,7 +579,7 @@ export function OmniHistoryPage({
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [filter]);
|
}, [modeFilter]);
|
||||||
|
|
||||||
const remove = async (id: string) => {
|
const remove = async (id: string) => {
|
||||||
// 软删:会话没了,已生成的图和视频仍留在资产库里
|
// 软删:会话没了,已生成的图和视频仍留在资产库里
|
||||||
@@ -602,20 +602,20 @@ export function OmniHistoryPage({
|
|||||||
</button>
|
</button>
|
||||||
<h1>创作历史</h1>
|
<h1>创作历史</h1>
|
||||||
</div>
|
</div>
|
||||||
<p>查看和继续全能创作中的每一次独立会话。</p>
|
<p>按视频创作或图片创作查看,并继续每一次独立会话。</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="omni-history-tools">
|
<div className="omni-history-tools">
|
||||||
<div className="omni-history-toolbar">
|
<div className="omni-history-toolbar">
|
||||||
{([
|
{([
|
||||||
["all", "全部"],
|
["all", "全部"],
|
||||||
["running", "进行中"],
|
["video", "视频创作"],
|
||||||
["completed", "已完成"],
|
["image", "图片创作"],
|
||||||
] as const).map(([key, label]) => (
|
] as const).map(([key, label]) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={key}
|
key={key}
|
||||||
className={filter === key ? "active" : ""}
|
className={modeFilter === key ? "active" : ""}
|
||||||
onClick={() => setFilter(key)}
|
onClick={() => setModeFilter(key)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
@@ -634,7 +634,13 @@ export function OmniHistoryPage({
|
|||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : items.length === 0 ? (
|
||||||
<div className="omni-history-empty">
|
<div className="omni-history-empty">
|
||||||
<p>还没有创作记录,从全能创作开始。</p>
|
<p>
|
||||||
|
{modeFilter === "video"
|
||||||
|
? "还没有视频创作记录,从全能创作开始。"
|
||||||
|
: modeFilter === "image"
|
||||||
|
? "还没有图片创作记录,从全能创作开始。"
|
||||||
|
: "还没有创作记录,从全能创作开始。"}
|
||||||
|
</p>
|
||||||
<button type="button" onClick={() => navigate("omniCreate")}>
|
<button type="button" onClick={() => navigate("omniCreate")}>
|
||||||
去全能创作
|
去全能创作
|
||||||
<ChevronRight size={14} />
|
<ChevronRight size={14} />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type FormEvent, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
import { type FormEvent, type ReactNode, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
@@ -43,6 +43,94 @@ type PromptBlock =
|
|||||||
|
|
||||||
type ReplyOption = { label: string; text: string };
|
type ReplyOption = { label: string; text: string };
|
||||||
|
|
||||||
|
function renderInlineMarkdown(text: string): ReactNode[] {
|
||||||
|
return text.split(/(\*\*[^*]+\*\*|`[^`]+`|\*[^*]+\*)/g).filter(Boolean).map((part, index) => {
|
||||||
|
if (part.startsWith("**") && part.endsWith("**")) {
|
||||||
|
return <strong key={index}>{part.slice(2, -2)}</strong>;
|
||||||
|
}
|
||||||
|
if (part.startsWith("`") && part.endsWith("`")) {
|
||||||
|
return <code key={index}>{part.slice(1, -1)}</code>;
|
||||||
|
}
|
||||||
|
if (part.startsWith("*") && part.endsWith("*")) {
|
||||||
|
return <em key={index}>{part.slice(1, -1)}</em>;
|
||||||
|
}
|
||||||
|
return part;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 对话正文的安全 Markdown 子集;不使用 HTML 注入,避免模型文本影响页面结构。 */
|
||||||
|
function ChatMarkdown({ text }: { text: string }) {
|
||||||
|
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
||||||
|
const nodes: ReactNode[] = [];
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
while (index < lines.length) {
|
||||||
|
const line = lines[index].trim();
|
||||||
|
if (!line) {
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const heading = line.match(/^#{1,3}\s+(.+)$/);
|
||||||
|
if (heading) {
|
||||||
|
nodes.push(<h3 key={`heading-${index}`}>{renderInlineMarkdown(heading[1])}</h3>);
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ordered = line.match(/^(\d+)[.、.]\s+(.+)$/);
|
||||||
|
if (ordered) {
|
||||||
|
const start = Number(ordered[1]);
|
||||||
|
const items: Array<{ key: number; text: string }> = [];
|
||||||
|
while (index < lines.length) {
|
||||||
|
const match = lines[index].trim().match(/^(\d+)[.、.]\s+(.+)$/);
|
||||||
|
if (!match) break;
|
||||||
|
items.push({ key: Number(match[1]), text: match[2] });
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
nodes.push(
|
||||||
|
<ol key={`ordered-${index}`} start={start}>
|
||||||
|
{items.map((item) => <li key={item.key}>{renderInlineMarkdown(item.text)}</li>)}
|
||||||
|
</ol>,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^[-*+](?:\s+)/.test(line)) {
|
||||||
|
const items: string[] = [];
|
||||||
|
while (index < lines.length) {
|
||||||
|
const match = lines[index].trim().match(/^[-*+](?:\s+)(.+)$/);
|
||||||
|
if (!match) break;
|
||||||
|
items.push(match[1]);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
nodes.push(
|
||||||
|
<ul key={`unordered-${index}`}>
|
||||||
|
{items.map((item, itemIndex) => <li key={itemIndex}>{renderInlineMarkdown(item)}</li>)}
|
||||||
|
</ul>,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paragraph: string[] = [];
|
||||||
|
while (index < lines.length) {
|
||||||
|
const current = lines[index].trim();
|
||||||
|
if (!current || /^#{1,3}\s+/.test(current) || /^(\d+)[.、.]\s+/.test(current) || /^[-*+](?:\s+)/.test(current)) break;
|
||||||
|
paragraph.push(lines[index]);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
nodes.push(
|
||||||
|
<p key={`paragraph-${index}`}>
|
||||||
|
{paragraph.map((item, lineIndex) => (
|
||||||
|
<span key={lineIndex}>{lineIndex ? <br /> : null}{renderInlineMarkdown(item)}</span>
|
||||||
|
))}
|
||||||
|
</p>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="omni-chat-markdown">{nodes}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
function stripNumericReplyInstruction(text: string): string {
|
function stripNumericReplyInstruction(text: string): string {
|
||||||
return (text || "")
|
return (text || "")
|
||||||
.replace(
|
.replace(
|
||||||
@@ -61,7 +149,46 @@ function numberedReplyOptions(text: string): ReplyOption[] {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function replyOptionsForMessage(text: string, payload: Record<string, unknown>, isVideo: boolean): ReplyOption[] {
|
function contextualReplyOptions(text: string): ReplyOption[] {
|
||||||
|
if (/颜色|色号|色彩|SKU|款式|几种/i.test(text)) {
|
||||||
|
return [
|
||||||
|
{ label: "补充颜色和顺序", text: "我来补充每个颜色和展示顺序" },
|
||||||
|
{ label: "上传各款实物图", text: "我补充各颜色/款式的实物图" },
|
||||||
|
{ label: "先按当前主款做", text: "先按当前主款做,其他颜色后面再补" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (/商品|产品|主推|哪款/i.test(text)) {
|
||||||
|
return [
|
||||||
|
{ label: "发商品列表", text: "把商品列表发给我选" },
|
||||||
|
{ label: "我直接说商品名", text: "我直接告诉你商品名" },
|
||||||
|
{ label: "你来推荐", text: "你根据当前需求推荐一款" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (/人物|角色|模特|出镜/i.test(text)) {
|
||||||
|
return [
|
||||||
|
{ label: "上传人物图", text: "我上传人物参考图" },
|
||||||
|
{ label: "由你设定角色", text: "你先帮我设定一个合适的角色" },
|
||||||
|
{ label: "不需要人物", text: "这条先不需要人物出镜" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (/场景|地点|背景|在哪/i.test(text)) {
|
||||||
|
return [
|
||||||
|
{ label: "上传场景图", text: "我上传场景参考图" },
|
||||||
|
{ label: "你来推荐场景", text: "你按商品和预设推荐场景" },
|
||||||
|
{ label: "用干净日常场景", text: "先用干净自然的日常场景" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (/卖点|功能|效果|优惠|价格/i.test(text)) {
|
||||||
|
return [
|
||||||
|
{ label: "补充真实卖点", text: "我来补充商品真实卖点" },
|
||||||
|
{ label: "从素材里判断", text: "先根据我上传的素材判断可表达的卖点" },
|
||||||
|
{ label: "先只突出一个点", text: "先围绕一个最核心的卖点创作" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function replyOptionsForMessage(text: string, payload: Record<string, unknown>): ReplyOption[] {
|
||||||
const numbered = numberedReplyOptions(text);
|
const numbered = numberedReplyOptions(text);
|
||||||
if (numbered.length) return numbered;
|
if (numbered.length) return numbered;
|
||||||
const stored = payload.reply_options;
|
const stored = payload.reply_options;
|
||||||
@@ -71,19 +198,10 @@ function replyOptionsForMessage(text: string, payload: Record<string, unknown>,
|
|||||||
.map((item) => ({ label: String(item.label || "").trim(), text: String(item.text || "").trim() }))
|
.map((item) => ({ label: String(item.label || "").trim(), text: String(item.text || "").trim() }))
|
||||||
.filter((item) => item.label && item.text)
|
.filter((item) => item.label && item.text)
|
||||||
.slice(0, 3);
|
.slice(0, 3);
|
||||||
if (options.length) return options;
|
const legacyGeneric = ["继续完善方案", "换个场景", "改卖点", "按这个方向出图", "改人物状态"];
|
||||||
|
if (options.length && !options.every((item) => legacyGeneric.includes(item.label))) return options;
|
||||||
}
|
}
|
||||||
return isVideo
|
return contextualReplyOptions(text);
|
||||||
? [
|
|
||||||
{ label: "继续完善方案", text: "继续完善方案" },
|
|
||||||
{ label: "换个场景", text: "我想换个场景" },
|
|
||||||
{ label: "改卖点", text: "我想改卖点" },
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{ label: "按这个方向出图", text: "按这个方向出图" },
|
|
||||||
{ label: "换个场景", text: "我想换个场景" },
|
|
||||||
{ label: "改人物状态", text: "我想改人物状态" },
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReplyActions({
|
function ReplyActions({
|
||||||
@@ -954,7 +1072,7 @@ function ElicitCard({
|
|||||||
<Sparkles />
|
<Sparkles />
|
||||||
</span>
|
</span>
|
||||||
<div className="omni-chat-bubble omni-gate-bubble">
|
<div className="omni-chat-bubble omni-gate-bubble">
|
||||||
<p className="omni-chat-text">{message.text || gateLabel}</p>
|
<ChatMarkdown text={message.text || gateLabel} />
|
||||||
{!submitted ? (
|
{!submitted ? (
|
||||||
<div className="omni-gate-actions" aria-label="商品选择操作">
|
<div className="omni-gate-actions" aria-label="商品选择操作">
|
||||||
{(gateField?.options || []).map((option, index) => (
|
{(gateField?.options || []).map((option, index) => (
|
||||||
@@ -991,7 +1109,7 @@ function ElicitCard({
|
|||||||
</span>
|
</span>
|
||||||
<div className="omni-chat-stack">
|
<div className="omni-chat-stack">
|
||||||
<div className="omni-chat-bubble">
|
<div className="omni-chat-bubble">
|
||||||
<p className="omni-chat-text">{question}</p>
|
<ChatMarkdown text={question} />
|
||||||
</div>
|
</div>
|
||||||
{!submitted ? (
|
{!submitted ? (
|
||||||
<div className="omni-reply-guide" aria-label="回复操作">
|
<div className="omni-reply-guide" aria-label="回复操作">
|
||||||
@@ -2403,7 +2521,7 @@ export function OmniSessionPage({
|
|||||||
&& message.kind === "text"
|
&& message.kind === "text"
|
||||||
&& messages[messages.length - 1]?.id === message.id
|
&& messages[messages.length - 1]?.id === message.id
|
||||||
&& !streaming;
|
&& !streaming;
|
||||||
const replyOptions = showReplyGuide ? replyOptionsForMessage(body, message.payload, isVideo) : [];
|
const replyOptions = showReplyGuide ? replyOptionsForMessage(body, message.payload) : [];
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`omni-chat-row ${message.role === "user" ? "is-user" : "agent"}${pending ? " is-pending" : ""}`}
|
className={`omni-chat-row ${message.role === "user" ? "is-user" : "agent"}${pending ? " is-pending" : ""}`}
|
||||||
@@ -2418,7 +2536,7 @@ export function OmniSessionPage({
|
|||||||
{message.role === "user" ? <MentionChips refs={refs} tone="user" /> : null}
|
{message.role === "user" ? <MentionChips refs={refs} tone="user" /> : null}
|
||||||
{(body || message.role !== "user") ? (
|
{(body || message.role !== "user") ? (
|
||||||
<div className="omni-chat-bubble" aria-busy={pending || undefined}>
|
<div className="omni-chat-bubble" aria-busy={pending || undefined}>
|
||||||
{body ? <p className="omni-chat-text">{body}</p> : null}
|
{body ? (message.role === "assistant" ? <ChatMarkdown text={body} /> : <p className="omni-chat-text">{body}</p>) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{showReplyGuide ? (
|
{showReplyGuide ? (
|
||||||
@@ -2521,7 +2639,7 @@ export function OmniSessionPage({
|
|||||||
<Sparkles />
|
<Sparkles />
|
||||||
</span>
|
</span>
|
||||||
<div className="omni-chat-bubble">
|
<div className="omni-chat-bubble">
|
||||||
<p className="omni-chat-text">{liveText}</p>
|
<ChatMarkdown text={liveText} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 778 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 314 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 770 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 744 KiB |
|
Before Width: | Height: | Size: 678 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 662 KiB |
|
Before Width: | Height: | Size: 404 KiB |
|
Before Width: | Height: | Size: 842 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 843 KiB |
|
Before Width: | Height: | Size: 843 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 404 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 350 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |