Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38ad2292d1 |
No files matched your search
@@ -6,11 +6,7 @@ import type {
|
||||
} from "@one-memory/contracts";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
graphNodeTooltip,
|
||||
isGraphNodeSelectable,
|
||||
MemoryGraph3D,
|
||||
} from "./MemoryGraph3D";
|
||||
import { MemoryGraph3D } from "./MemoryGraph3D";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
@@ -52,18 +48,6 @@ function makeGraph(nodeCount: number): MemoryGraphResponse {
|
||||
}
|
||||
|
||||
describe("MemoryGraph3D", () => {
|
||||
it("keeps topic annotations hover-only", () => {
|
||||
const topic = {
|
||||
...makeGraph(1).nodes[0],
|
||||
node_type: "topic" as const,
|
||||
label: "CI",
|
||||
};
|
||||
expect(isGraphNodeSelectable(topic)).toBe(false);
|
||||
expect(graphNodeTooltip(topic, "主题:")).toBe("主题:CI");
|
||||
expect(graphNodeTooltip(topic, "Topic: ")).toBe("Topic: CI");
|
||||
expect(isGraphNodeSelectable({ ...topic, node_type: "memory" })).toBe(true);
|
||||
});
|
||||
|
||||
it("没有 WebGL 时给出可访问降级提示", () => {
|
||||
render(
|
||||
<MemoryGraph3D
|
||||
|
||||
@@ -27,20 +27,6 @@ const SELECT_DURATION_MS = 180;
|
||||
const SELECTED_COLOR = new THREE.Color(0x294355);
|
||||
const SELECTED_EMISSIVE = new THREE.Color(0x607e97);
|
||||
|
||||
/** Topic nodes explain the graph; unlike memories and sources, they do not own a detail view. */
|
||||
export function isGraphNodeSelectable(node: MemoryGraphNode): boolean {
|
||||
return node.node_type !== "topic";
|
||||
}
|
||||
|
||||
export function graphNodeTooltip(
|
||||
node: MemoryGraphNode,
|
||||
topicPrefix: string,
|
||||
): string {
|
||||
return node.node_type === "topic"
|
||||
? `${topicPrefix}${node.label}`
|
||||
: node.label;
|
||||
}
|
||||
|
||||
export function MemoryGraph3D({
|
||||
graph,
|
||||
selectedNodeId,
|
||||
@@ -50,20 +36,10 @@ export function MemoryGraph3D({
|
||||
selectedNodeId: string | null;
|
||||
onSelectNode(node: MemoryGraphNode): void;
|
||||
}) {
|
||||
const { locale, text } = useMemoryLanguage();
|
||||
const { text } = useMemoryLanguage();
|
||||
const host = useRef<HTMLDivElement>(null);
|
||||
const labelHost = useRef<HTMLDivElement>(null);
|
||||
const selectRef = useRef(onSelectNode);
|
||||
const nodesByIdRef = useRef(
|
||||
new Map(graph.nodes.map((node) => [node.node_id, node])),
|
||||
);
|
||||
nodesByIdRef.current = new Map(
|
||||
graph.nodes.map((node) => [node.node_id, node]),
|
||||
);
|
||||
const topicPrefixRef = useRef("");
|
||||
topicPrefixRef.current = `${text("主题")}${
|
||||
locale.startsWith("zh") || locale.startsWith("ja") ? ":" : ": "
|
||||
}`;
|
||||
// 选中态通过 ref 传入渲染循环,而不是进 useEffect 依赖。
|
||||
// 依赖里带 selectedNodeId 会导致每次点击重建整个场景、重跑布局、重建全部 mesh。
|
||||
const selectedRef = useRef(selectedNodeId);
|
||||
@@ -181,7 +157,9 @@ export function MemoryGraph3D({
|
||||
}
|
||||
}
|
||||
const labels = graph.nodes
|
||||
.filter((node) => node.node_type === "source")
|
||||
.filter(
|
||||
(node) => node.node_type === "source" || node.node_type === "topic",
|
||||
)
|
||||
.slice(0, 4)
|
||||
.map((node) => {
|
||||
const element = document.createElement("span");
|
||||
@@ -216,7 +194,7 @@ export function MemoryGraph3D({
|
||||
}
|
||||
};
|
||||
|
||||
const hittable: THREE.Mesh[] = [];
|
||||
const selectable: THREE.Mesh[] = [];
|
||||
const materials = new Set<THREE.Material>();
|
||||
const geometryByType = {
|
||||
source: new THREE.SphereGeometry(0.24, 18, 12),
|
||||
@@ -259,7 +237,7 @@ export function MemoryGraph3D({
|
||||
mesh.scale.setScalar(1);
|
||||
mesh.visible = returning || extending;
|
||||
scene.add(mesh);
|
||||
hittable.push(mesh);
|
||||
selectable.push(mesh);
|
||||
visuals.push({
|
||||
mesh,
|
||||
material,
|
||||
@@ -320,6 +298,7 @@ export function MemoryGraph3D({
|
||||
});
|
||||
}
|
||||
|
||||
const nodesById = new Map(graph.nodes.map((node) => [node.node_id, node]));
|
||||
const raycaster = new THREE.Raycaster();
|
||||
const pointer = new THREE.Vector2();
|
||||
const hitAt = (event: PointerEvent) => {
|
||||
@@ -328,7 +307,7 @@ export function MemoryGraph3D({
|
||||
pointer.x = ((event.clientX - bounds.left) / bounds.width) * 2 - 1;
|
||||
pointer.y = -((event.clientY - bounds.top) / bounds.height) * 2 + 1;
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
return raycaster.intersectObjects(hittable, false)[0];
|
||||
return raycaster.intersectObjects(selectable, false)[0];
|
||||
};
|
||||
let pressed: { x: number; y: number; id: number; moved: boolean } | null =
|
||||
null;
|
||||
@@ -354,9 +333,8 @@ export function MemoryGraph3D({
|
||||
)
|
||||
return;
|
||||
const nodeId = hitAt(event)?.object.userData.nodeId;
|
||||
const node =
|
||||
typeof nodeId === "string" ? nodesByIdRef.current.get(nodeId) : null;
|
||||
if (node && isGraphNodeSelectable(node)) selectRef.current(node);
|
||||
const node = typeof nodeId === "string" ? nodesById.get(nodeId) : null;
|
||||
if (node) selectRef.current(node);
|
||||
};
|
||||
const hoverNode = (event: PointerEvent) => {
|
||||
if (pressed) {
|
||||
@@ -366,18 +344,12 @@ export function MemoryGraph3D({
|
||||
return;
|
||||
}
|
||||
const nodeId = hitAt(event)?.object.userData.nodeId;
|
||||
const node =
|
||||
typeof nodeId === "string" ? nodesByIdRef.current.get(nodeId) : null;
|
||||
renderer.domElement.style.cursor =
|
||||
node && isGraphNodeSelectable(node)
|
||||
? "pointer"
|
||||
: node
|
||||
? "default"
|
||||
: "grab";
|
||||
const node = typeof nodeId === "string" ? nodesById.get(nodeId) : null;
|
||||
renderer.domElement.style.cursor = node ? "pointer" : "grab";
|
||||
setHovered(
|
||||
node
|
||||
? {
|
||||
label: graphNodeTooltip(node, topicPrefixRef.current),
|
||||
label: node.label,
|
||||
x: event.clientX - element.getBoundingClientRect().left,
|
||||
y: event.clientY - element.getBoundingClientRect().top,
|
||||
}
|
||||
@@ -657,23 +629,17 @@ export function MemoryGraph3D({
|
||||
{!available ? (
|
||||
<div className="memory-graph-fallback">
|
||||
<p>{text("这台设备打不开 3D 视图,已显示关联列表。")}</p>
|
||||
{graph.nodes.map((node) =>
|
||||
isGraphNodeSelectable(node) ? (
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
key={node.node_id}
|
||||
aria-pressed={selectedNodeId === node.node_id}
|
||||
onClick={() => onSelectNode(node)}
|
||||
>
|
||||
{node.label}
|
||||
</button>
|
||||
) : (
|
||||
<span className="memory-graph-fallback-topic" key={node.node_id}>
|
||||
{graphNodeTooltip(node, topicPrefixRef.current)}
|
||||
</span>
|
||||
),
|
||||
)}
|
||||
{graph.nodes.map((node) => (
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
key={node.node_id}
|
||||
aria-pressed={selectedNodeId === node.node_id}
|
||||
onClick={() => onSelectNode(node)}
|
||||
>
|
||||
{node.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{available && (
|
||||
|
||||
@@ -149,7 +149,7 @@ it("OBS-22: the graph inspector has no editing entry and never says changes cann
|
||||
expect(screen.queryByText(/暂不能保存更改/)).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps topic annotations out of the inspector and opens detail-bearing graph nodes", async () => {
|
||||
it("replaces a topic detail with an off-page graph memory detail", async () => {
|
||||
const onOpen = vi.fn();
|
||||
const topicGraph = {
|
||||
total_memories: 2,
|
||||
@@ -221,13 +221,13 @@ it("keeps topic annotations out of the inspector and opens detail-bearing graph
|
||||
/>,
|
||||
);
|
||||
|
||||
await act(async () => {});
|
||||
onOpen.mockClear();
|
||||
await userEvent.click(screen.getByRole("button", { name: "图中选择 git" }));
|
||||
expect(onOpen).not.toHaveBeenCalled();
|
||||
expect(screen.queryByRole("heading", { name: "git" })).toBeNull();
|
||||
expect(screen.getByRole("heading", { name: "第一条" })).toBeTruthy();
|
||||
expect(screen.getByTestId("canvas").dataset.selected).toBe("memory:m0");
|
||||
expect(screen.getByRole("heading", { name: "git" })).toBeTruthy();
|
||||
expect(screen.getAllByText("第一条").length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText("对应记忆")).toBeNull();
|
||||
expect(screen.queryByText("属于主题")).toBeNull();
|
||||
expect(screen.queryByText("主题")).toBeNull();
|
||||
expect(screen.queryByText("两跳")).toBeNull();
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", { name: "图中选择 先验证,再推送" }),
|
||||
);
|
||||
|
||||
@@ -684,9 +684,6 @@ export function MemoryMap(props: Props) {
|
||||
setGroupKey(key);
|
||||
}
|
||||
function pickNode(node: MemoryGraphNode) {
|
||||
// Topics are graph annotations, not detail-bearing records. They remain
|
||||
// hoverable in the canvas but must not replace the memory inspector.
|
||||
if (node.node_type === "topic") return;
|
||||
if (node.memory_id) {
|
||||
pick(node.memory_id);
|
||||
return;
|
||||
@@ -711,6 +708,11 @@ export function MemoryMap(props: Props) {
|
||||
if (first) pick(first, false);
|
||||
}
|
||||
}
|
||||
if (node.node_type === "topic") {
|
||||
if (props.simplified) return;
|
||||
setAxis("topic");
|
||||
setGroupKey(`topic:${node.label}`);
|
||||
}
|
||||
}
|
||||
const label =
|
||||
group?.label ??
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { legacyMemoryModePlan } from "@one-memory/operations";
|
||||
import {
|
||||
cleanup,
|
||||
render,
|
||||
@@ -64,31 +65,9 @@ function prepare(mode: "lite" | "pro" | "ultra" = "pro") {
|
||||
running: [],
|
||||
});
|
||||
vi.mocked(getMemoryProcessing).mockResolvedValue({} as never);
|
||||
vi.mocked(planMemoryMode).mockImplementation(async (target) => ({
|
||||
current: mode,
|
||||
target,
|
||||
effective_target: target,
|
||||
capabilities: {
|
||||
content_plane: target === "lite" ? "raw" : "memory",
|
||||
generation_required: target !== "lite",
|
||||
graph_enabled: target === "ultra",
|
||||
evolve_available: target === "ultra",
|
||||
},
|
||||
effects: [
|
||||
...(target === "lite" ? (["higher_mode_memories_hidden"] as const) : []),
|
||||
...(mode === "lite" && target !== "lite"
|
||||
? (["earlier_raw_not_extracted", "lite_search_stops"] as const)
|
||||
: []),
|
||||
...(target === "ultra" && mode !== "ultra"
|
||||
? (["existing_memories_added_to_graph"] as const)
|
||||
: []),
|
||||
...(mode === "ultra" && target !== "ultra"
|
||||
? (["evolve_stopped"] as const)
|
||||
: []),
|
||||
],
|
||||
warnings: [],
|
||||
blockers: [],
|
||||
}));
|
||||
vi.mocked(planMemoryMode).mockImplementation(async (target) =>
|
||||
legacyMemoryModePlan(mode, target, { hasActiveModel: true }),
|
||||
);
|
||||
}
|
||||
|
||||
async function chooseMode(
|
||||
@@ -227,6 +206,20 @@ it("uses the isolated legacy plan only for a route-level old-Core 404", async ()
|
||||
expect(await screen.findByText(/已有记忆继续保留/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("keeps downgrade effects when an old Core falls back from ultra to pro", async () => {
|
||||
prepare("ultra");
|
||||
vi.mocked(planMemoryMode).mockRejectedValue(new Error("404"));
|
||||
vi.mocked(memoryModePlanUnsupported).mockReturnValue(true);
|
||||
const user = userEvent.setup();
|
||||
render(<MemoryModeSettings hasActiveModel onConfigureModel={vi.fn()} />);
|
||||
|
||||
await chooseMode(user, /智能记忆(pro)/);
|
||||
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(within(dialog).getAllByText("已有数据")).toHaveLength(1);
|
||||
expect(within(dialog).getByText("每天自动整合")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("explains model cost, search scope and the next step before switching to ultra", async () => {
|
||||
prepare("lite");
|
||||
const user = userEvent.setup();
|
||||
@@ -238,6 +231,8 @@ it("explains model cost, search scope and the next step before switching to ultr
|
||||
expect(screen.getByText("搜索范围")).toBeTruthy();
|
||||
expect(screen.getByText("切换后")).toBeTruthy();
|
||||
expect(screen.getByText(/已经处理完的旧对话不会补做/)).toBeTruthy();
|
||||
expect(screen.getByText("知识图谱")).toBeTruthy();
|
||||
expect(screen.getAllByText("已有数据")).toHaveLength(1);
|
||||
expect(screen.getByText(/切换本身不会立即整合/)).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -248,9 +243,27 @@ it("distinguishes regular memories from higher-level Ultra data when switching t
|
||||
|
||||
await chooseMode(user, /智能记忆(pro)/);
|
||||
|
||||
expect(screen.getByText(/普通沉淀记忆仍可使用/)).toBeTruthy();
|
||||
expect(screen.getByText(/更高层记忆和图谱数据继续保留/)).toBeTruthy();
|
||||
expect(screen.getByText(/每天自动整合会停止/)).toBeTruthy();
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(within(dialog).getAllByText("已有数据")).toHaveLength(1);
|
||||
expect(within(dialog).getByText(/普通沉淀记忆仍可使用/)).toBeTruthy();
|
||||
expect(within(dialog).getByText(/更高层记忆和图谱数据继续保留/)).toBeTruthy();
|
||||
expect(within(dialog).getByText("每天自动整合")).toBeTruthy();
|
||||
expect(
|
||||
within(dialog).getByText("每天自动整合会停止;已生成的整合结果继续保留。"),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not show automatic consolidation when switching from lite to pro", async () => {
|
||||
prepare("lite");
|
||||
const user = userEvent.setup();
|
||||
render(<MemoryModeSettings hasActiveModel onConfigureModel={vi.fn()} />);
|
||||
|
||||
await chooseMode(user, /智能记忆(pro)/);
|
||||
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(within(dialog).getAllByText("已有数据")).toHaveLength(1);
|
||||
expect(within(dialog).getByText(/已经处理完的旧对话不会补做/)).toBeTruthy();
|
||||
expect(within(dialog).queryByText("每天自动整合")).toBeNull();
|
||||
});
|
||||
|
||||
it("focuses the Evolve settings after switching to ultra", async () => {
|
||||
|
||||
@@ -85,18 +85,20 @@ function planRows(plan: MemoryModePlan) {
|
||||
if (plan.effects.includes("higher_mode_memories_hidden"))
|
||||
rows.push({
|
||||
label: "已有数据",
|
||||
content: "已有记忆继续保留,但高级记忆在当前模式下不显示、不参与召回。",
|
||||
content:
|
||||
plan.effective_target === "pro"
|
||||
? "普通沉淀记忆仍可使用;深度记忆新增的更高层记忆和图谱数据继续保留,但在智能记忆下不显示、不参与召回。原始对话不会删除。"
|
||||
: "已有记忆继续保留,但在基础记忆下不显示、不参与召回;原始对话不会删除。",
|
||||
});
|
||||
if (plan.effects.includes("existing_memories_added_to_graph"))
|
||||
rows.push({
|
||||
label: "已有数据",
|
||||
label: "知识图谱",
|
||||
content: "已有记忆会在后台加入知识图谱,期间仍可使用。",
|
||||
});
|
||||
if (plan.effects.includes("evolve_stopped"))
|
||||
rows.push({
|
||||
label: "已有数据",
|
||||
content:
|
||||
"普通沉淀记忆仍可使用;更高层记忆和图谱数据继续保留,但不参与当前召回;每天自动整合会停止。",
|
||||
label: "每天自动整合",
|
||||
content: "每天自动整合会停止,已经生成的数据不会删除。",
|
||||
});
|
||||
if (plan.capabilities.evolve_available)
|
||||
rows.push({
|
||||
|
||||
@@ -981,14 +981,6 @@ button.map-tool {
|
||||
background: #fff;
|
||||
box-shadow: var(--shadow-pop);
|
||||
}
|
||||
.memory-graph-fallback-topic {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-block: 6px;
|
||||
padding: 8px 14px;
|
||||
color: var(--tx2);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.graph-labels {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
@@ -275,9 +275,10 @@ describe("Mode catalog", () => {
|
||||
"搜索沉淀记忆,并用知识图谱补充关联。",
|
||||
"搜索沉淀后的记忆。",
|
||||
"已经处理完的旧对话不会补做;原始对话全部保留。",
|
||||
"已有记忆继续保留,但高级记忆在当前模式下不显示、不参与召回。",
|
||||
"知识图谱",
|
||||
"普通沉淀记忆仍可使用;深度记忆新增的更高层记忆和图谱数据继续保留,但在智能记忆下不显示、不参与召回。原始对话不会删除。",
|
||||
"已有记忆会在后台加入知识图谱,期间仍可使用。",
|
||||
"普通沉淀记忆仍可使用;更高层记忆和图谱数据继续保留,但不参与当前召回;每天自动整合会停止。",
|
||||
"每天自动整合会停止,已经生成的数据不会删除。",
|
||||
"记忆维护正在进行,完成后才能切换模式。",
|
||||
"模式切换影响暂时无法读取,请重试。",
|
||||
] as const;
|
||||
|
||||
@@ -47,7 +47,7 @@ const modeChineseMessages = {
|
||||
"以前生成的高级记忆会继续保留,但在 lite 下不会显示或参与记忆召回。":
|
||||
"高级记忆会保留,但在 lite 下不显示、不参与召回。",
|
||||
"每天自动整合会停止,已经生成的数据不会删除。":
|
||||
"自动整合会停止,已有数据保留。",
|
||||
"每天自动整合会停止;已生成的整合结果继续保留。",
|
||||
"已有高级记忆会在后台加入知识图谱,期间可以继续使用 MemHub。":
|
||||
"已有高级记忆将在后台加入知识图谱。",
|
||||
"知识图谱仍在补建,不会重复启动。": "知识图谱仍在补建,不会重复启动。",
|
||||
@@ -97,6 +97,7 @@ const modeChineseMessages = {
|
||||
模型调用与费用: "模型调用与费用",
|
||||
搜索范围: "搜索范围",
|
||||
已有数据: "已有数据",
|
||||
知识图谱: "知识图谱",
|
||||
切换后: "切换后",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"之后只保存原始对话,不再生成新记忆。",
|
||||
@@ -295,6 +296,7 @@ export const modeEnglishMessages: ModeMessages = {
|
||||
模型调用与费用: "Model calls and cost",
|
||||
搜索范围: "Search scope",
|
||||
已有数据: "Existing data",
|
||||
知识图谱: "Knowledge graph",
|
||||
切换后: "After switching",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"New conversations are saved as original text only; no new memories are created.",
|
||||
@@ -498,6 +500,7 @@ export const modeSpanishMessages: ModeMessages = {
|
||||
模型调用与费用: "Llamadas al modelo y coste",
|
||||
搜索范围: "Ámbito de búsqueda",
|
||||
已有数据: "Datos existentes",
|
||||
知识图谱: "Grafo de conocimiento",
|
||||
切换后: "Después del cambio",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"Las conversaciones nuevas se guardan solo como texto original; no se crean memorias nuevas.",
|
||||
@@ -693,6 +696,7 @@ export const modeTraditionalChineseMessages: ModeMessages = {
|
||||
模型调用与费用: "模型呼叫與費用",
|
||||
搜索范围: "搜尋範圍",
|
||||
已有数据: "現有資料",
|
||||
知识图谱: "知識圖譜",
|
||||
切换后: "切換後",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"之後只儲存原始對話,不再產生新記憶。",
|
||||
@@ -843,6 +847,7 @@ export const modeJapaneseMessages: ModeMessages = {
|
||||
模型调用与费用: "モデル呼び出しと料金",
|
||||
搜索范围: "検索範囲",
|
||||
已有数据: "既存データ",
|
||||
知识图谱: "ナレッジグラフ",
|
||||
切换后: "切り替え後",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"以降は元の会話のみを保存し、新しい記憶は生成しません。",
|
||||
@@ -1043,6 +1048,7 @@ export const modeGermanMessages: ModeMessages = {
|
||||
模型调用与费用: "Modellaufrufe und Kosten",
|
||||
搜索范围: "Suchbereich",
|
||||
已有数据: "Vorhandene Daten",
|
||||
知识图谱: "Wissensgraph",
|
||||
切换后: "Nach dem Wechsel",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"Neue Unterhaltungen werden nur noch im Original gespeichert; es werden keine neuen Erinnerungen erstellt.",
|
||||
@@ -1246,6 +1252,7 @@ export const modePortugueseMessages: ModeMessages = {
|
||||
模型调用与费用: "Chamadas ao modelo e custos",
|
||||
搜索范围: "Âmbito da pesquisa",
|
||||
已有数据: "Dados existentes",
|
||||
知识图谱: "Grafo de conhecimento",
|
||||
切换后: "Após a mudança",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"As novas conversas passam a ser guardadas apenas como texto original; não são criadas novas memórias.",
|
||||
@@ -1451,6 +1458,7 @@ export const modeFrenchMessages: ModeMessages = {
|
||||
模型调用与费用: "Appels au modèle et coût",
|
||||
搜索范围: "Périmètre de recherche",
|
||||
已有数据: "Données existantes",
|
||||
知识图谱: "Graphe de connaissances",
|
||||
切换后: "Après le changement",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"Les nouvelles conversations sont uniquement enregistrées dans leur forme originale ; aucune nouvelle mémoire n’est créée.",
|
||||
@@ -1651,6 +1659,7 @@ export const modeKoreanMessages: ModeMessages = {
|
||||
模型调用与费用: "모델 호출 및 비용",
|
||||
搜索范围: "검색 범위",
|
||||
已有数据: "기존 데이터",
|
||||
知识图谱: "지식 그래프",
|
||||
切换后: "전환 후",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"이후 새 대화는 원본만 저장되며 새 메모리는 생성되지 않습니다.",
|
||||
@@ -1850,6 +1859,7 @@ export const modeRussianMessages: ModeMessages = {
|
||||
模型调用与费用: "Вызовы модели и расходы",
|
||||
搜索范围: "Область поиска",
|
||||
已有数据: "Существующие данные",
|
||||
知识图谱: "Граф знаний",
|
||||
切换后: "После переключения",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"Новые диалоги будут сохраняться только в исходном виде; новые воспоминания создаваться не будут.",
|
||||
@@ -2052,6 +2062,7 @@ export const modeIndonesianMessages: ModeMessages = {
|
||||
模型调用与费用: "Panggilan model dan biaya",
|
||||
搜索范围: "Cakupan pencarian",
|
||||
已有数据: "Data yang ada",
|
||||
知识图谱: "Graf pengetahuan",
|
||||
切换后: "Setelah beralih",
|
||||
"之后只保存原始对话,不再生成新记忆。":
|
||||
"Percakapan baru hanya disimpan sebagai teks asli; tidak ada memori baru yang dibuat.",
|
||||
|
||||
@@ -32,7 +32,31 @@ describe("memory mode plan", () => {
|
||||
expect(parseMemoryModePlan({ ...valid, blockers: ["unknown"] })).toBeNull();
|
||||
});
|
||||
|
||||
it("isolates the old-Core matrix in one helper", () => {
|
||||
it.each([
|
||||
["lite", "pro", ["earlier_raw_not_extracted", "lite_search_stops"]],
|
||||
[
|
||||
"lite",
|
||||
"ultra",
|
||||
[
|
||||
"earlier_raw_not_extracted",
|
||||
"lite_search_stops",
|
||||
"existing_memories_added_to_graph",
|
||||
],
|
||||
],
|
||||
["pro", "lite", ["higher_mode_memories_hidden"]],
|
||||
["pro", "ultra", ["existing_memories_added_to_graph"]],
|
||||
["ultra", "lite", ["higher_mode_memories_hidden", "evolve_stopped"]],
|
||||
["ultra", "pro", ["higher_mode_memories_hidden", "evolve_stopped"]],
|
||||
] as const)(
|
||||
"keeps the old-Core %s to %s effects aligned with the plan route",
|
||||
(current, target, effects) => {
|
||||
expect(
|
||||
legacyMemoryModePlan(current, target, { hasActiveModel: true }).effects,
|
||||
).toEqual(effects);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps the old-Core model blocker and target capabilities", () => {
|
||||
expect(
|
||||
legacyMemoryModePlan("lite", "ultra", { hasActiveModel: false }),
|
||||
).toMatchObject({
|
||||
@@ -42,10 +66,6 @@ describe("memory mode plan", () => {
|
||||
graph_enabled: true,
|
||||
},
|
||||
blockers: ["active_model_required"],
|
||||
effects: expect.arrayContaining([
|
||||
"earlier_raw_not_extracted",
|
||||
"existing_memories_added_to_graph",
|
||||
]),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -106,12 +106,15 @@ export function legacyMemoryModePlan(
|
||||
options: { hasActiveModel: boolean },
|
||||
): MemoryModePlan {
|
||||
const effects: MemoryModeChange["effects"] = [];
|
||||
if (target === "lite") {
|
||||
effects.push("higher_mode_memories_hidden");
|
||||
if (current === "ultra") effects.push("evolve_stopped");
|
||||
} else if (current === "lite") {
|
||||
const rank: Record<MemoryPipelineMode, number> = {
|
||||
lite: 0,
|
||||
pro: 1,
|
||||
ultra: 2,
|
||||
};
|
||||
if (current !== target && current === "lite")
|
||||
effects.push("earlier_raw_not_extracted", "lite_search_stops");
|
||||
}
|
||||
if (rank[target] < rank[current]) effects.push("higher_mode_memories_hidden");
|
||||
if (current !== target && current === "ultra") effects.push("evolve_stopped");
|
||||
if (target === "ultra" && current !== "ultra")
|
||||
effects.push("existing_memories_added_to_graph");
|
||||
return {
|
||||
|
||||
Reference in new issue
Block a user