Filter goal refreshes by conversation note
This commit is contained in:
@@ -26,7 +26,11 @@ export default async function GoalsPage({
|
||||
|
||||
return (
|
||||
<AppShell bottomNav={false}>
|
||||
<RealtimeRefresh projectId={projectId} events={["conversation.updated"]} />
|
||||
<RealtimeRefresh
|
||||
projectId={projectId}
|
||||
events={["conversation.updated"]}
|
||||
conversationUpdatedNotes={["project_goals.updated"]}
|
||||
/>
|
||||
<StatusBar />
|
||||
<PageNav title="项目目标" backHref={`/conversations/${projectId}`} />
|
||||
<div className="flex flex-col gap-3 px-[18px] pb-6">
|
||||
|
||||
@@ -26,6 +26,7 @@ export default async function VersionsPage({
|
||||
<RealtimeRefresh
|
||||
projectId={projectId}
|
||||
events={["conversation.updated", "project.messages.updated", "ota.updated"]}
|
||||
conversationUpdatedNotes={["project_goals.updated"]}
|
||||
/>
|
||||
<StatusBar />
|
||||
<PageNav title="版本迭代记录" backHref={`/conversations/${projectId}`} />
|
||||
|
||||
@@ -254,9 +254,11 @@ export function NativeAppBridge() {
|
||||
export function RealtimeRefresh({
|
||||
events,
|
||||
projectId,
|
||||
conversationUpdatedNotes,
|
||||
}: {
|
||||
events: BossEventName[];
|
||||
projectId?: string;
|
||||
conversationUpdatedNotes?: string[];
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -268,21 +270,40 @@ export function RealtimeRefresh({
|
||||
...events,
|
||||
]));
|
||||
const shouldRefresh = (event: Event) => {
|
||||
if (!projectId || !("data" in event) || typeof event.data !== "string" || !event.data.trim()) {
|
||||
return true;
|
||||
let payload: { projectId?: string; note?: string } | null = null;
|
||||
const eventData = "data" in event && typeof event.data === "string" ? event.data : "";
|
||||
const hasPayloadData = Boolean(eventData.trim());
|
||||
|
||||
if (hasPayloadData) {
|
||||
try {
|
||||
const parsed = JSON.parse(eventData) as { projectId?: string; note?: string };
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
payload = parsed;
|
||||
}
|
||||
} catch {
|
||||
payload = null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const payload = JSON.parse(event.data) as { projectId?: string };
|
||||
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
||||
|
||||
if (projectId) {
|
||||
if (!payload || typeof payload.projectId !== "string" || !payload.projectId.trim()) {
|
||||
return true;
|
||||
}
|
||||
if (typeof payload.projectId !== "string" || !payload.projectId.trim()) {
|
||||
return true;
|
||||
if (payload.projectId !== projectId) {
|
||||
return false;
|
||||
}
|
||||
return payload.projectId === projectId;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.type === "conversation.updated" && conversationUpdatedNotes?.length) {
|
||||
if (!payload || typeof payload.note !== "string" || !payload.note.trim()) {
|
||||
return false;
|
||||
}
|
||||
if ((payload.note) && !conversationUpdatedNotes.includes(payload.note)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
const listenerMap = new Map<string, (event: Event) => void>();
|
||||
|
||||
@@ -306,7 +327,7 @@ export function RealtimeRefresh({
|
||||
}
|
||||
source.close();
|
||||
};
|
||||
}, [events, projectId, router]);
|
||||
}, [conversationUpdatedNotes, events, projectId, router]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,21 @@ test("RealtimeRefresh supports project-scoped refresh filtering", async () => {
|
||||
const source = await readWorkspaceFile("src/components/app-runtime.tsx");
|
||||
|
||||
assert.match(source, /projectId\?: string/, "expected RealtimeRefresh to accept an optional projectId");
|
||||
assert.match(source, /payload\.projectId === projectId/, "expected RealtimeRefresh to filter by matching projectId");
|
||||
assert.match(
|
||||
source,
|
||||
/conversationUpdatedNotes\?: string\[]/,
|
||||
"expected RealtimeRefresh to accept optional conversationUpdatedNotes",
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/payload\.projectId !== projectId[\s\S]*return false/,
|
||||
"expected RealtimeRefresh to filter by matching projectId",
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/payload\.note\)\s*&&\s*!conversationUpdatedNotes\.includes\(payload\.note\)/,
|
||||
"expected RealtimeRefresh to ignore unmatched conversation.updated notes",
|
||||
);
|
||||
});
|
||||
|
||||
test("project conversation pages wire project-scoped realtime refresh", async () => {
|
||||
@@ -36,4 +50,15 @@ test("project conversation pages wire project-scoped realtime refresh", async ()
|
||||
assert.match(source, /projectId=\{projectId\}/, `expected ${label} page to scope refreshes to the current project`);
|
||||
assert.match(source, /"conversation\.updated"/, `expected ${label} page to listen to conversation updates`);
|
||||
}
|
||||
|
||||
assert.match(
|
||||
goalsPage,
|
||||
/conversationUpdatedNotes=\{\["project_goals\.updated"\]\}/,
|
||||
"expected goals page to refresh only on project goal markers",
|
||||
);
|
||||
assert.match(
|
||||
versionsPage,
|
||||
/conversationUpdatedNotes=\{\["project_goals\.updated"\]\}/,
|
||||
"expected versions page to refresh only on project goal markers",
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user