任务理解修改
This commit is contained in:
1 parent
1cfa93e855
commit
4a3cfb9dfa
3 files changed
+452
-30
No files matched your search
@@ -0,0 +1,304 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import type { ElementType } from "react";
|
||||||
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Bell,
|
||||||
|
Bot,
|
||||||
|
Brain,
|
||||||
|
CalendarClock,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
ChevronsUpDown,
|
||||||
|
CircleHelp,
|
||||||
|
Database,
|
||||||
|
FileBox,
|
||||||
|
FileText,
|
||||||
|
Folder,
|
||||||
|
FolderOpen,
|
||||||
|
Gauge,
|
||||||
|
Grid3X3,
|
||||||
|
HelpCircle,
|
||||||
|
History,
|
||||||
|
Layout,
|
||||||
|
Library,
|
||||||
|
List,
|
||||||
|
ListChecks,
|
||||||
|
ListTodo,
|
||||||
|
MessageSquareCode,
|
||||||
|
MessageSquarePlus,
|
||||||
|
Network,
|
||||||
|
NotebookPen,
|
||||||
|
Plus,
|
||||||
|
Settings,
|
||||||
|
Sparkles,
|
||||||
|
Target,
|
||||||
|
User,
|
||||||
|
Users,
|
||||||
|
Wrench,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { NotificationDialog } from "@/components/workspace/notification-dialog";
|
||||||
|
import { SettingsDialog } from "@/components/workspace/settings";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
import { getStoredAuth } from "@/core/auth";
|
||||||
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
|
import { useUnreadCount } from "@/core/notifications";
|
||||||
|
|
||||||
|
type MenuItem = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
icon: ElementType;
|
||||||
|
path?: string;
|
||||||
|
adminOnly?: boolean;
|
||||||
|
children?: MenuItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type PageSidebarDarkProps = {
|
||||||
|
collapsed: boolean;
|
||||||
|
onToggleCollapsed: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about";
|
||||||
|
|
||||||
|
const menuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
id: "qa-center",
|
||||||
|
label: "问答中心",
|
||||||
|
icon: HelpCircle,
|
||||||
|
children: [
|
||||||
|
{ id: "general-qa", label: "通用问答", icon: CircleHelp, path: "/page/workspace/chats/new" },
|
||||||
|
{ id: "qa-zzjc", label: "zzjc支持", icon: Bot },
|
||||||
|
{ id: "qa-zzxd", label: "zzXD应用", icon: Layout },
|
||||||
|
{ id: "qa-research", label: "课题研究", icon: FileText, path: "/page/canvas/ai-writing" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "light-app-center",
|
||||||
|
label: "轻应用",
|
||||||
|
icon: Grid3X3,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "light-app-note-management",
|
||||||
|
label: "笔记管理",
|
||||||
|
icon: NotebookPen,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "light-app-new-note",
|
||||||
|
label: "新建笔记",
|
||||||
|
icon: Plus,
|
||||||
|
path: "/page/strategy/light-app/new-note",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "light-app-note-list",
|
||||||
|
label: "笔记列表",
|
||||||
|
icon: List,
|
||||||
|
path: "/page/strategy/light-app/notes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "light-app-note-qa",
|
||||||
|
label: "笔记问答",
|
||||||
|
icon: MessageSquareCode,
|
||||||
|
path: "/page/strategy/light-app/note-qa",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ id: "light-app-square", label: "应用广场", icon: Layout },
|
||||||
|
{ id: "light-app-records", label: "运行记录", icon: History },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "task-management",
|
||||||
|
label: "任务管理",
|
||||||
|
icon: Target,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "xk-center-task",
|
||||||
|
label: "XK中任务",
|
||||||
|
icon: Folder,
|
||||||
|
children: [
|
||||||
|
{ id: "xk-task-jc", label: "任务JC", icon: ListTodo, path: "/page/strategy/task-management/xk-task-jc" },
|
||||||
|
{ id: "xk-task-dispatch", label: "任务调度", icon: ListTodo, path: "/page/strategy/task-management/xk-task-dispatch" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "coh-center-task",
|
||||||
|
label: "COH中任务",
|
||||||
|
icon: Folder,
|
||||||
|
children: [
|
||||||
|
{ id: "coh-task-3qfx", label: "3QFX", icon: ListTodo },
|
||||||
|
{ id: "coh-task-fx", label: "任务FX", icon: ListTodo },
|
||||||
|
{ id: "coh-task-xdfx", label: "XDFX", icon: ListTodo },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pg-center-task",
|
||||||
|
label: "PG中任务",
|
||||||
|
icon: Folder,
|
||||||
|
children: [
|
||||||
|
{ id: "pg-task-pg", label: "任务PG", icon: ListTodo },
|
||||||
|
{ id: "pg-task-xdpg", label: "XDPG", icon: ListTodo },
|
||||||
|
{ id: "pg-task-info-pg", label: "信息PG", icon: ListTodo },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ty-center-task",
|
||||||
|
label: "TY中任务",
|
||||||
|
icon: Folder,
|
||||||
|
children: [
|
||||||
|
{ id: "ty-task-xdty", label: "XDTY任务", icon: ListTodo },
|
||||||
|
{ id: "ty-task-plan", label: "方案TY任务", icon: ListTodo },
|
||||||
|
{ id: "ty-task-xxty", label: "XXTY任务", icon: ListTodo },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "comprehensive-management",
|
||||||
|
label: "综合管理",
|
||||||
|
icon: Settings,
|
||||||
|
children: [
|
||||||
|
{ id: "mgmt-agents", label: "智能体管理", icon: Bot, path: "/page/strategy/agents" },
|
||||||
|
{ id: "mgmt-skills", label: "技能管理", icon: Sparkles, path: "/page/strategy/skills" },
|
||||||
|
{ id: "mgmt-memory", label: "记忆管理", icon: Brain, path: "/page/strategy/memory" },
|
||||||
|
{
|
||||||
|
id: "mgmt-tasks",
|
||||||
|
label: "任务管理",
|
||||||
|
icon: ListChecks,
|
||||||
|
path: "/page/strategy/scheduled/tasks",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "mgmt-recommended-questions",
|
||||||
|
label: "推荐问题配置",
|
||||||
|
icon: MessageSquarePlus,
|
||||||
|
path: "/page/strategy/settings/recommended-questions",
|
||||||
|
adminOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "mgmt-prompt-prefix",
|
||||||
|
label: "问答前缀配置",
|
||||||
|
icon: MessageSquareCode,
|
||||||
|
path: "/page/strategy/settings/prompt-prefix",
|
||||||
|
adminOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "mgmt-llm-metrics",
|
||||||
|
label: "大模型调用统计",
|
||||||
|
icon: Gauge,
|
||||||
|
path: "/page/strategy/settings/llm-metrics",
|
||||||
|
adminOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "mgmt-users",
|
||||||
|
label: "用户管理",
|
||||||
|
icon: Users,
|
||||||
|
path: "/page/strategy/admin/users",
|
||||||
|
adminOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "mgmt-admin-scheduled-tasks",
|
||||||
|
label: "定时任务管理",
|
||||||
|
icon: CalendarClock,
|
||||||
|
path: "/page/strategy/admin/scheduled-tasks",
|
||||||
|
adminOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "mgmt-user-activity",
|
||||||
|
label: "用户活动审计",
|
||||||
|
icon: History,
|
||||||
|
path: "/page/strategy/admin/user-activity",
|
||||||
|
adminOnly: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "resource-management",
|
||||||
|
label: "资源管理",
|
||||||
|
icon: Database,
|
||||||
|
children: [
|
||||||
|
{ id: "resource-network-xwt", label: "网络xwt资源管理", icon: Network },
|
||||||
|
{ id: "resource-knowledge-base", label: "知识库资源管理", icon: Library },
|
||||||
|
{ id: "resource-zz", label: "ZZ资源管理", icon: FileBox },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultExpandedIds = new Set([
|
||||||
|
"qa-center",
|
||||||
|
"light-app-center",
|
||||||
|
"light-app-note-management",
|
||||||
|
"task-management",
|
||||||
|
"xk-center-task",
|
||||||
|
]);
|
||||||
|
|
||||||
|
function filterMenuItemsByRole(items: MenuItem[], isAdmin: boolean): MenuItem[] {
|
||||||
|
return items
|
||||||
|
.filter((item) => !item.adminOnly || isAdmin)
|
||||||
|
.map((item) =>
|
||||||
|
item.children
|
||||||
|
? { ...item, children: filterMenuItemsByRole(item.children, isAdmin) }
|
||||||
|
: item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PageSidebarDark({
|
||||||
|
collapsed,
|
||||||
|
onToggleCollapsed,
|
||||||
|
}: PageSidebarDarkProps) {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const isDark = theme === "dark";
|
||||||
|
const isAdmin = getStoredAuth()?.system_role === "admin";
|
||||||
|
const visibleMenuItems = filterMenuItemsByRole(menuItems, !!isAdmin);
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { data: unreadCount = 0 } = useUnreadCount();
|
||||||
|
const [expandedIds, setExpandedIds] = useState<Set<string>>(defaultExpandedIds);
|
||||||
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
|
const [settingsDefaultSection, setSettingsDefaultSection] =
|
||||||
|
useState<SettingsSection>("appearance");
|
||||||
|
const [notificationOpen, setNotificationOpen] = useState(false);
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openSettings = (section: SettingsSection) => {
|
||||||
|
setSettingsDefaultSection(section);
|
||||||
|
setSettingsOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleExpanded = (id: string) => {
|
||||||
|
setExpandedIds((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(id)) {
|
||||||
|
next.delete(id);
|
||||||
|
} else {
|
||||||
|
next.add(id);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingsDialog
|
||||||
|
open={settingsOpen}
|
||||||
|
onOpenChange={setSettingsOpen}
|
||||||
|
defaultSection={settingsDefaultSection}
|
||||||
|
/>
|
||||||
|
<NotificationDialog
|
||||||
|
open={notificationOpen}
|
||||||
|
onOpenChange={setNotificationOpen}
|
||||||
|
/>
|
||||||
|
{/* TODO: 在这里编写深色主题的侧边栏 HTML 结构 */}
|
||||||
|
<div>深色主题侧边栏 - HTML 待补充</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@ import { useMemo, useState } from "react";
|
|||||||
import { Navigate, Route, Routes } from "react-router-dom";
|
import { Navigate, Route, Routes } from "react-router-dom";
|
||||||
|
|
||||||
import { PageSidebar } from "@/components/page-sidebar";
|
import { PageSidebar } from "@/components/page-sidebar";
|
||||||
|
import { PageSidebarDark } from "@/components/page-sidebar-dark";
|
||||||
import { readSidebarEnabled } from "@/core/page-layout/sidebar-enabled";
|
import { readSidebarEnabled } from "@/core/page-layout/sidebar-enabled";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
import WorkspaceRoutes from "./WorkspaceRoutes";
|
import WorkspaceRoutes from "./WorkspaceRoutes";
|
||||||
import StrategyRoutes from "./StrategyRoutes";
|
import StrategyRoutes from "./StrategyRoutes";
|
||||||
@@ -13,16 +15,25 @@ import RoundtableRoutes from "./RoundtableRoutes";
|
|||||||
function PageLayout() {
|
function PageLayout() {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const sidebarEnabled = useMemo(() => readSidebarEnabled(), []);
|
const sidebarEnabled = useMemo(() => readSidebarEnabled(), []);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const isDark = theme === "dark";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen flex flex-col bg-muted/20">
|
<div className="h-screen flex flex-col bg-muted/20">
|
||||||
{sidebarEnabled && <Header collapsed={collapsed} />}
|
{sidebarEnabled && <Header collapsed={collapsed} />}
|
||||||
<div className="flex flex-1 overflow-hidden">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
{sidebarEnabled && (
|
{sidebarEnabled && (
|
||||||
<PageSidebar
|
isDark ? (
|
||||||
collapsed={collapsed}
|
<PageSidebarDark
|
||||||
onToggleCollapsed={() => setCollapsed((prev) => !prev)}
|
collapsed={collapsed}
|
||||||
/>
|
onToggleCollapsed={() => setCollapsed((prev) => !prev)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PageSidebar
|
||||||
|
collapsed={collapsed}
|
||||||
|
onToggleCollapsed={() => setCollapsed((prev) => !prev)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
<main
|
<main
|
||||||
className={`flex-1 overflow-hidden transition-[padding] duration-300 ${
|
className={`flex-1 overflow-hidden transition-[padding] duration-300 ${
|
||||||
|
|||||||
@@ -135,8 +135,9 @@ export function newCoordinatorName(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 后端 intent agent 输出的 `[INTENT_READY]\n\`\`\`json {...}\`\`\`` 块。
|
* 后端 intent agent 输出的**标准** `[INTENT_READY]\n\`\`\`json {...}\`\`\`` 块形状。
|
||||||
* 前端用同样形状的正则把摘要从对话气泡里抽出来,渲染成绿色摘要卡片。
|
* 仅作格式参考 / 快速匹配用;实际从气泡正文里抽摘要走 `splitIntentReadyBlock` 的
|
||||||
|
* 容错提取(marker 定位 + 括号配对 + 多级降级 parse),不再依赖这条严格正则。
|
||||||
*/
|
*/
|
||||||
export const INTENT_READY_RE = /\[INTENT_READY\]\s*```json\s*(\{[\s\S]*?\})\s*```/;
|
export const INTENT_READY_RE = /\[INTENT_READY\]\s*```json\s*(\{[\s\S]*?\})\s*```/;
|
||||||
|
|
||||||
@@ -185,33 +186,136 @@ function sanitizeJsonControlChars(raw: string): string {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIntentSummary(raw: string): IntentSummary | null {
|
/**
|
||||||
const attempt = (s: string): IntentSummary | null => {
|
* 从 `fromIndex` 起找第一个 `{`,按花括号配对(跳过字符串内部的括号)找到匹配的 `}`,
|
||||||
const parsed = JSON.parse(s) as unknown;
|
* 返回 JSON 子串及其在原文里的起止下标。
|
||||||
if (
|
*
|
||||||
parsed &&
|
* 比 `\{[\s\S]*?\}` 这种正则更抗模型跑偏:容忍 JSON 内嵌套对象、字符串里的裸换行 /
|
||||||
typeof parsed === "object" &&
|
* 花括号,以及模型漏写 ```json 代码围栏(直接 `[INTENT_READY] {...}`)的情况。
|
||||||
typeof (parsed as { objective?: unknown }).objective === "string"
|
*/
|
||||||
) {
|
function extractBalancedJson(
|
||||||
return parsed as IntentSummary;
|
text: string,
|
||||||
|
fromIndex: number,
|
||||||
|
): { json: string; end: number } | null {
|
||||||
|
const start = text.indexOf("{", fromIndex);
|
||||||
|
if (start < 0) return null;
|
||||||
|
let depth = 0;
|
||||||
|
let inString = false;
|
||||||
|
let escaped = false;
|
||||||
|
for (let i = start; i < text.length; i += 1) {
|
||||||
|
const ch = text[i];
|
||||||
|
if (escaped) {
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
return null;
|
if (ch === "\\") {
|
||||||
};
|
escaped = true;
|
||||||
try {
|
continue;
|
||||||
return attempt(raw);
|
}
|
||||||
} catch {
|
if (ch === '"') {
|
||||||
// 字符串值里有裸换行 / 制表符 → 转义后重试,避免整块摘要丢失导致 Step 2 无法解锁。
|
inString = !inString;
|
||||||
try {
|
continue;
|
||||||
return attempt(sanitizeJsonControlChars(raw));
|
}
|
||||||
} catch {
|
if (inString) continue;
|
||||||
return null;
|
if (ch === "{") depth += 1;
|
||||||
|
else if (ch === "}") {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) return { json: text.slice(start, i + 1), end: i + 1 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null; // 括号未闭合(流式中段 / 模型截断)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 去掉对象 / 数组里 `}` `]` 之前的尾随逗号(只在字符串外处理)。 */
|
||||||
|
function stripTrailingCommas(raw: string): string {
|
||||||
|
let out = "";
|
||||||
|
let inString = false;
|
||||||
|
let escaped = false;
|
||||||
|
for (let i = 0; i < raw.length; i += 1) {
|
||||||
|
const ch = raw[i];
|
||||||
|
if (escaped) {
|
||||||
|
out += ch;
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === "\\") {
|
||||||
|
out += ch;
|
||||||
|
escaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === '"') {
|
||||||
|
inString = !inString;
|
||||||
|
out += ch;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!inString && ch === ",") {
|
||||||
|
let j = i + 1;
|
||||||
|
while (j < raw.length && /\s/.test(raw[j])) j += 1;
|
||||||
|
if (raw[j] === "}" || raw[j] === "]") continue; // 丢弃尾随逗号
|
||||||
|
}
|
||||||
|
out += ch;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 把任意值归一成 string[]:数组逐项转字符串、单个字符串包成单元素、其它视为空。 */
|
||||||
|
function toStringArray(value: unknown): string[] {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value
|
||||||
|
.map((x) => (typeof x === "string" ? x : x == null ? "" : String(x)))
|
||||||
|
.filter((x) => x.trim().length > 0);
|
||||||
|
}
|
||||||
|
if (typeof value === "string" && value.trim().length > 0) return [value];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把解析出的对象归一成 IntentSummary。只要 `objective` 是非空字符串就认。
|
||||||
|
* constraints / assumptions 容忍模型写成字符串 / null / 缺失,统一归到 string[]。
|
||||||
|
*/
|
||||||
|
function normalizeIntentSummary(parsed: unknown): IntentSummary | null {
|
||||||
|
if (!parsed || typeof parsed !== "object") return null;
|
||||||
|
const obj = parsed as Record<string, unknown>;
|
||||||
|
const objective =
|
||||||
|
typeof obj.objective === "string" ? obj.objective.trim() : "";
|
||||||
|
if (!objective) return null;
|
||||||
|
return {
|
||||||
|
objective,
|
||||||
|
constraints: toStringArray(obj.constraints),
|
||||||
|
assumptions: toStringArray(obj.assumptions),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 INTENT_READY 的 JSON 块。内网模型输出不可控,这里逐级降级重试:
|
||||||
|
* 1) 原文直接 parse
|
||||||
|
* 2) 转义字符串内的裸控制字符(换行 / 制表符)后再 parse
|
||||||
|
* 3) 再去掉尾随逗号后 parse
|
||||||
|
* 三次都失败才放弃。任一成功都过 `normalizeIntentSummary` 兜字段类型。
|
||||||
|
*/
|
||||||
|
function parseIntentSummary(raw: string): IntentSummary | null {
|
||||||
|
const sanitized = sanitizeJsonControlChars(raw);
|
||||||
|
const candidates = [raw, sanitized, stripTrailingCommas(sanitized)];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try {
|
||||||
|
const summary = normalizeIntentSummary(JSON.parse(candidate));
|
||||||
|
if (summary) return summary;
|
||||||
|
} catch {
|
||||||
|
// 下一个候选
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const INTENT_READY_MARKER_RE = /\[INTENT_READY\]/i;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把含 [INTENT_READY] 标记的气泡正文拆成前 / 摘要 / 后三段。
|
* 把含 [INTENT_READY] 标记的气泡正文拆成前 / 摘要 / 后三段。
|
||||||
* 摘要 JSON 格式异常时整体保留在 `before`,用户仍能看到原文。
|
* 摘要 JSON 格式异常时整体保留在 `before`,用户仍能看到原文。
|
||||||
|
*
|
||||||
|
* 提取不再依赖严格的 ```json 围栏正则:先定位 [INTENT_READY](大小写不敏感),
|
||||||
|
* 再用括号配对抓出后面的第一个完整 JSON 对象,最后多级降级 parse。这样模型漏写
|
||||||
|
* 代码围栏、JSON 里有裸换行 / 尾随逗号、字段类型写偏等情况都能尽量恢复。
|
||||||
*/
|
*/
|
||||||
export function splitIntentReadyBlock(text: string): {
|
export function splitIntentReadyBlock(text: string): {
|
||||||
before: string;
|
before: string;
|
||||||
@@ -219,13 +323,16 @@ export function splitIntentReadyBlock(text: string): {
|
|||||||
after: string;
|
after: string;
|
||||||
} {
|
} {
|
||||||
if (!text) return { before: text, summary: null, after: "" };
|
if (!text) return { before: text, summary: null, after: "" };
|
||||||
const m = INTENT_READY_RE.exec(text);
|
const marker = INTENT_READY_MARKER_RE.exec(text);
|
||||||
if (!m) return { before: text, summary: null, after: "" };
|
if (!marker) return { before: text, summary: null, after: "" };
|
||||||
const summary = parseIntentSummary(m[1]);
|
const block = extractBalancedJson(text, marker.index + marker[0].length);
|
||||||
|
if (!block) return { before: text, summary: null, after: "" };
|
||||||
|
const summary = parseIntentSummary(block.json);
|
||||||
if (!summary) return { before: text, summary: null, after: "" };
|
if (!summary) return { before: text, summary: null, after: "" };
|
||||||
return {
|
return {
|
||||||
before: text.slice(0, m.index).trimEnd(),
|
before: text.slice(0, marker.index).trimEnd(),
|
||||||
summary,
|
summary,
|
||||||
after: text.slice(m.index + m[0].length).trimStart(),
|
// 丢掉 JSON 块之后残留的代码围栏 ``` 与模型多说的话(SOUL 要求 READY 后即停)。
|
||||||
|
after: text.slice(block.end).replace(/^\s*```+/, "").trimStart(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user