perf: reduce streaming render cost and refresh activity page on demand

- Segment streaming Markdown so completed blocks are reused and only the growing tail is re-parsed; fall back to whole-document parsing for document-wide constructs
- Memoize conversation sidebar rows and cache per-conversation derived activity, ordering and pending approvals
- Activity page loads on entry, refreshes every 15 s while visible, and has a single header refresh button
- Include pending settings, model download source, device sharing and knowledge gateway changes in the working tree
- Add Electron streaming input benchmark and regression tests
This commit is contained in:
mesalogo
2026-10-01 21:49:09 +08:00
parent 016f987026
commit c8f327fc4e
75 changed files with 3042 additions and 905 deletions
+30 -14
View File
@@ -960,6 +960,7 @@ export class KnowledgeMcpGateway {
capability.brokerController.signal
])
const changeVersion = connection.dynamicToolsChangeVersion
const startedAt = performance.now()
let refreshSucceeded = false
const refresh = (async () => {
try {
@@ -979,10 +980,15 @@ export class KnowledgeMcpGateway {
refreshSucceeded = true
await this.publishCustomMcpToolListChanged(capability)
} catch (error) {
connection.bindings = []
connection.dynamicToolsChanged = true
if (effectiveSignal.aborted) {
throw effectiveSignal.reason
}
this.observeMcpFailure({
phase: 'tool-discovery', category: 'handler-failure', kind: 'custom',
elapsedMs: Math.round(performance.now() - startedAt)
})
throw new Error(
`无法刷新 MCP Server「${connection.server.name}」的工具`,
{ cause: error }
@@ -1008,6 +1014,7 @@ export class KnowledgeMcpGateway {
capability: Capability,
server: ResolvedMcpServer
): Promise<CustomMcpConnection> {
const startedAt = performance.now()
let connection: CustomMcpConnection | undefined
let dynamicToolsChangeVersion = 0
const client = new Client(
@@ -1079,6 +1086,11 @@ export class KnowledgeMcpGateway {
return connection
} catch (error) {
await client.close().catch(() => undefined)
signal.throwIfAborted()
this.observeMcpFailure({
phase: 'tool-discovery', category: 'handler-failure', kind: 'custom',
elapsedMs: Math.round(performance.now() - startedAt)
})
throw new Error(
`无法加载 MCP Server「${server.name}」的工具`,
{ cause: error }
@@ -1092,6 +1104,12 @@ export class KnowledgeMcpGateway {
refreshDynamic = true
): Promise<Map<string, CustomMcpBinding>> {
const capability = this.getCapability(token)
const effectiveSignal = AbortSignal.any([
capability.signal,
capability.brokerController.signal,
...(signal ? [signal] : [])
])
effectiveSignal.throwIfAborted()
if (capability.customMcpServers.length === 0) {
return new Map()
}
@@ -1105,15 +1123,6 @@ export class KnowledgeMcpGateway {
const connections = results.flatMap((result) =>
result.status === 'fulfilled' ? [result.value] : []
)
const failure = results.find(
(result) => result.status === 'rejected'
)
if (failure?.status === 'rejected') {
await Promise.allSettled(
connections.map((connection) => connection.client.close())
)
throw failure.reason
}
return connections
})()
}
@@ -1124,6 +1133,7 @@ export class KnowledgeMcpGateway {
capability.customMcpConnections = undefined
throw error
}
effectiveSignal.throwIfAborted()
if (refreshDynamic) {
for (const connection of connections) {
if (
@@ -1133,13 +1143,19 @@ export class KnowledgeMcpGateway {
) {
continue
}
await this.refreshDynamicTools(
capability,
connection,
signal
)
try {
await this.refreshDynamicTools(
capability,
connection,
signal
)
} catch {
// Failed catalogs are cleared by refresh; healthy servers remain usable.
effectiveSignal.throwIfAborted()
}
}
}
effectiveSignal.throwIfAborted()
const bindings = new Map<string, CustomMcpBinding>()
for (const connection of connections) {
for (const binding of connection.bindings) {