mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 18:50:56 +08:00
123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
import { loggerService } from '@logger'
|
|
import { isDev } from '@main/constant'
|
|
import { CacheBatchSpanProcessor, FunctionSpanExporter } from '@mcp-trace/trace-core'
|
|
import { NodeTracer as MCPNodeTracer } from '@mcp-trace/trace-node/nodeTracer'
|
|
import type { SpanContext } from '@opentelemetry/api'
|
|
import { context, trace } from '@opentelemetry/api'
|
|
import { BrowserWindow, ipcMain } from 'electron'
|
|
import * as path from 'path'
|
|
|
|
import { ConfigKeys, configManager } from './ConfigManager'
|
|
import { spanCacheService } from './SpanCacheService'
|
|
|
|
export const TRACER_NAME = 'CherryStudio'
|
|
|
|
const logger = loggerService.withContext('NodeTraceService')
|
|
|
|
export class NodeTraceService {
|
|
init() {
|
|
const exporter = new FunctionSpanExporter(async (spans) => {
|
|
logger.info(`Spans length: ${spans.length}`)
|
|
})
|
|
|
|
MCPNodeTracer.init(
|
|
{
|
|
defaultTracerName: TRACER_NAME,
|
|
serviceName: TRACER_NAME
|
|
},
|
|
new CacheBatchSpanProcessor(exporter, spanCacheService)
|
|
)
|
|
}
|
|
}
|
|
|
|
const originalHandle = ipcMain.handle
|
|
ipcMain.handle = (channel: string, handler: (...args: any[]) => Promise<any>) => {
|
|
return originalHandle.call(ipcMain, channel, async (event, ...args) => {
|
|
const carray = args && args.length > 0 ? args[args.length - 1] : {}
|
|
let ctx = context.active()
|
|
let newArgs = args
|
|
if (carray && typeof carray === 'object' && 'type' in carray && carray.type === 'trace') {
|
|
const span = trace.wrapSpanContext(carray.context as SpanContext)
|
|
ctx = trace.setSpan(context.active(), span)
|
|
newArgs = args.slice(0, args.length - 1)
|
|
}
|
|
return context.with(ctx, () => handler(event, ...newArgs))
|
|
})
|
|
}
|
|
|
|
export const nodeTraceService = new NodeTraceService()
|
|
|
|
let traceWin: BrowserWindow | null = null
|
|
|
|
export function openTraceWindow(topicId: string, traceId: string, autoOpen = true, modelName?: string) {
|
|
if (traceWin && !traceWin.isDestroyed()) {
|
|
traceWin.focus()
|
|
traceWin.webContents.send('set-trace', { traceId, topicId, modelName })
|
|
return
|
|
}
|
|
|
|
if (!traceWin && !autoOpen) {
|
|
return
|
|
}
|
|
|
|
traceWin = new BrowserWindow({
|
|
width: 600,
|
|
minWidth: 500,
|
|
minHeight: 600,
|
|
height: 800,
|
|
autoHideMenuBar: true,
|
|
closable: true,
|
|
focusable: true,
|
|
movable: true,
|
|
hasShadow: true,
|
|
roundedCorners: true,
|
|
maximizable: true,
|
|
minimizable: true,
|
|
resizable: true,
|
|
title: 'Call Chain Window',
|
|
frame: true,
|
|
titleBarOverlay: { height: 40 },
|
|
webPreferences: {
|
|
preload: path.join(__dirname, '../preload/index.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
sandbox: false,
|
|
devTools: isDev ? true : false
|
|
}
|
|
})
|
|
|
|
if (isDev && process.env['ELECTRON_RENDERER_URL']) {
|
|
traceWin.loadURL(process.env['ELECTRON_RENDERER_URL'] + `/traceWindow.html`)
|
|
} else {
|
|
traceWin.loadFile(path.join(__dirname, '../renderer/traceWindow.html'))
|
|
}
|
|
traceWin.on('closed', () => {
|
|
configManager.unsubscribe(ConfigKeys.Language, setLanguageCallback)
|
|
try {
|
|
traceWin?.destroy()
|
|
} finally {
|
|
traceWin = null
|
|
}
|
|
})
|
|
|
|
traceWin.webContents.on('did-finish-load', () => {
|
|
traceWin!.webContents.send('set-trace', {
|
|
traceId,
|
|
topicId,
|
|
modelName
|
|
})
|
|
traceWin!.webContents.send('set-language', { lang: configManager.get(ConfigKeys.Language) })
|
|
configManager.subscribe(ConfigKeys.Language, setLanguageCallback)
|
|
})
|
|
}
|
|
|
|
const setLanguageCallback = (lang: string) => {
|
|
traceWin!.webContents.send('set-language', { lang })
|
|
}
|
|
|
|
export const setTraceWindowTitle = (title: string) => {
|
|
if (traceWin) {
|
|
traceWin.title = title
|
|
}
|
|
}
|