diff --git a/assistant/package.json b/assistant/package.json index 9f06e2b..2a21cac 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -47,7 +47,7 @@ "@kevisual/logger": "^0.0.4", "@kevisual/query": "0.0.33", "@kevisual/query-login": "0.0.7", - "@kevisual/router": "^0.0.51", + "@kevisual/router": "^0.0.52", "@kevisual/types": "^0.0.10", "@kevisual/use-config": "^1.0.21", "@types/bun": "^1.3.5", @@ -76,7 +76,7 @@ "access": "public" }, "dependencies": { - "@kevisual/ha-api": "^0.0.5", + "@kevisual/ha-api": "^0.0.6", "@kevisual/video-tools": "^0.0.13", "eventemitter3": "^5.0.1", "lowdb": "^7.0.1", diff --git a/assistant/src/app.ts b/assistant/src/app.ts index b27f79f..b35dc43 100644 --- a/assistant/src/app.ts +++ b/assistant/src/app.ts @@ -68,6 +68,9 @@ app.route({ key: 'list', description: '获取路由列表', }).define(async (ctx) => { - const list = ctx.app.getList() + const list = ctx.app.getList((item) => { + if (item.id === 'auth') return false; + return true; + }) ctx.body = { list } }).addTo(app); \ No newline at end of file diff --git a/assistant/src/module/assistant/html/login.ts b/assistant/src/module/assistant/html/login.ts new file mode 100644 index 0000000..d170c24 --- /dev/null +++ b/assistant/src/module/assistant/html/login.ts @@ -0,0 +1,52 @@ +export const renderNoAuthAndLogin = (text: string) => { + return ` + + + + + Login Required + + + +
+

${text}

+ 转到首页 +
+ +`; +} \ No newline at end of file diff --git a/assistant/src/module/local-apps/src/modules/manager.ts b/assistant/src/module/local-apps/src/modules/manager.ts index 2de18e9..31317e1 100644 --- a/assistant/src/module/local-apps/src/modules/manager.ts +++ b/assistant/src/module/local-apps/src/modules/manager.ts @@ -453,7 +453,6 @@ export const LoadApp = async (app: AppInfo, opts?: { mainApp?: any, pm2Connect?: console.log('gateway app not support'); } else if (app.type === AppType.Pm2SystemApp) { const pathEntry = path.join(app.path, app.entry); - console.log('pm2 system app start', pathEntry); const pm2Manager = new Pm2Manager({ appName: app.key, script: pathEntry, @@ -473,7 +472,6 @@ export const LoadApp = async (app: AppInfo, opts?: { mainApp?: any, pm2Connect?: if (!pm2Options.cwd) { pm2Options.cwd = path.join(app.path, '../..'); } - console.log('pm2 start options', pm2Options); await pm2Manager.start(pm2Options); } else if (app.type === AppType.ScriptApp) { // console.log('script app 直接运行,不需要启动'); diff --git a/assistant/src/services/asr/asr-manager.ts b/assistant/src/services/asr/asr-manager.ts new file mode 100644 index 0000000..4fc9bb5 --- /dev/null +++ b/assistant/src/services/asr/asr-manager.ts @@ -0,0 +1,25 @@ + +import { QwenAsrRelatime } from "@kevisual/video-tools/src/asr/index.ts"; + +export class AsrManger { + map: Map; + constructor() { + this.map = new Map(); + } + + getAsr(id: string) { + const asr = this.map.get(id); + return asr; + } + + setAsr(id: string, asr: QwenAsrRelatime) { + this.map.set(id, asr); + return asr; + } + + deleteAsr(id: string) { + this.map.delete(id); + } +} + +export const arrManager = new AsrManger(); \ No newline at end of file diff --git a/assistant/src/services/asr/qwen-asr.ts b/assistant/src/services/asr/qwen-asr.ts index c4bd12b..314caa4 100644 --- a/assistant/src/services/asr/qwen-asr.ts +++ b/assistant/src/services/asr/qwen-asr.ts @@ -4,7 +4,7 @@ import { Listener, WebSocketListenerFun, WebSocketReq } from "@kevisual/router"; import { lightHA } from "@/routes/ha-api/ha.ts"; import { assistantConfig } from "@/app.ts"; -const func: WebSocketListenerFun = async (req: WebSocketReq<{ asr: QwenAsrRelatime, msgId: string, startTime?: number }>, res) => { +const func: WebSocketListenerFun = async (req: WebSocketReq<{ asr: QwenAsrRelatime, msgId: string, startTime?: number, loading?: boolean }>, res) => { const { ws, emitter, id, data } = req; let asr = ws.data.asr; @@ -14,6 +14,9 @@ const func: WebSocketListenerFun = async (req: WebSocketReq<{ asr: QwenAsrRelati return; } if (!asr) { + if (ws.data.loading) return; + ws.data.loading = true; + const confg = assistantConfig.getConfig(); const asrConfig = confg?.asr; if (!asrConfig?.enabled) { @@ -27,77 +30,84 @@ const func: WebSocketListenerFun = async (req: WebSocketReq<{ asr: QwenAsrRelati ws.close(); return; } + const onConnect = () => { + const asr = ws.data.asr as QwenAsrRelatime; + ws.send(JSON.stringify({ type: 'asr', code: 200, message: 'asr服务已连接', time: Date.now() })); + if (!asr) return; + asr.emitter.on('message', (message) => { + // console.log('ASR message', message); + }); + asr.emitter.on('partial', (message) => { + // console.log('ASR message', message); + let msgId = ws.data.msgId || Math.random().toString(36).substring(2, 10); + ws.data.msgId = msgId; + ws.send(JSON.stringify({ + type: 'partial', + msgId: msgId, + time: Date.now(), + text: message?.text, + raw: message?.raw, + })); + }); + asr.emitter.on('result', async ({ text, raw }) => { + let msgId = ws.data.msgId; + ws.data.msgId = Math.random().toString(36).substring(2, 10); + const endTime = Date.now(); + console.log('cost time', ws.data.startTime ? (endTime - ws.data.startTime) : 0); + ws.send(JSON.stringify({ + type: 'result', + msgId: msgId, + time: Date.now(), + text, + })); + if (!text) return; + const command = text?.trim().slice(0, 20); + type ParseCommand = { + type?: '打开' | '关闭', + appName?: string, + command?: string, + } + let obj: ParseCommand = {}; + if (command.startsWith('打开')) { + obj.appName = command.replace('打开', '').trim(); + obj.type = '打开'; + } else if (command.startsWith('关闭')) { + obj.appName = command.replace('关闭', '').trim(); + obj.type = '关闭'; + } + if (obj.type) { + try { + const search = await lightHA.searchLight(obj.appName || ''); + console.log('searchTime', Date.now() - endTime); + if (search.id) { + await lightHA.runService({ entity_id: search.id, service: obj.type === '打开' ? 'turn_on' : 'turn_off' }); + } else if (search.hasMore) { + const [first] = search.result; + await lightHA.runService({ entity_id: first.entity_id, service: obj.type === '打开' ? 'turn_on' : 'turn_off' }); + } else { + console.log('未找到对应设备:', obj.appName); + } + console.log('解析到控制指令', obj); + } catch (e) { + console.error('控制失败', e); + } + } + console.log('toogle light time', Date.now() - endTime); + }); + asr.start(); + } // 第一次请求 asr = new QwenAsrRelatime({ token, - onConnect: () => { - const asr = ws.data.asr as QwenAsrRelatime; - ws.send(JSON.stringify({ type: 'asr', code: 200, message: 'asr服务已连接', time: Date.now() })); - if (!asr) return; - asr.emitter.on('message', (message) => { - // console.log('ASR message', message); - }); - asr.emitter.on('partial', (message) => { - // console.log('ASR message', message); - let msgId = ws.data.msgId || Math.random().toString(36).substring(2, 10); - ws.data.msgId = msgId; - ws.send(JSON.stringify({ - type: 'partial', - msgId: msgId, - time: Date.now(), - text: message?.text, - raw: message?.raw, - })); - }); - asr.emitter.on('result', async ({ text, raw }) => { - let msgId = ws.data.msgId; - ws.data.msgId = Math.random().toString(36).substring(2, 10); - const endTime = Date.now(); - console.log('cost time', ws.data.startTime ? (endTime - ws.data.startTime) : 0); - ws.send(JSON.stringify({ - type: 'result', - msgId: msgId, - time: Date.now(), - text, - })); - if (!text) return; - const command = text?.trim().slice(0, 20); - type ParseCommand = { - type?: '打开' | '关闭', - appName?: string, - command?: string, - } - let obj: ParseCommand = {}; - if (command.startsWith('打开')) { - obj.appName = command.replace('打开', '').trim(); - obj.type = '打开'; - } else if (command.startsWith('关闭')) { - obj.appName = command.replace('关闭', '').trim(); - obj.type = '关闭'; - } - if (obj.type) { - try { - const search = await lightHA.searchLight(obj.appName || ''); - console.log('searchTime', Date.now() - endTime); - if (search.id) { - await lightHA.runService({ entity_id: search.id, service: obj.type === '打开' ? 'turn_on' : 'turn_off' }); - } else if (search.hasMore) { - const [first] = search.result; - await lightHA.runService({ entity_id: first.entity_id, service: obj.type === '打开' ? 'turn_on' : 'turn_off' }); - } else { - console.log('未找到对应设备:', obj.appName); - } - console.log('解析到控制指令', obj); - } catch (e) { - console.error('控制失败', e); - } - } - console.log('toogle light time', Date.now() - endTime); - }); - asr.start(); - } + emitter, + onConnect, }) ws.data.asr = asr; + ws.data.loading = false; + emitter.on('close--' + id, () => { + console.log('ASR websocket 关闭, 释放资源'); + asr?.close?.(); + }); } const isConnected = await asr.checkConnected(); if (!isConnected) return; diff --git a/assistant/src/services/proxy/proxy-page-index.ts b/assistant/src/services/proxy/proxy-page-index.ts index 66b4e52..f9e83d6 100644 --- a/assistant/src/services/proxy/proxy-page-index.ts +++ b/assistant/src/services/proxy/proxy-page-index.ts @@ -7,6 +7,7 @@ import { getToken } from '@/module/http-token.ts'; import { getTokenUserCache } from '@/routes/index.ts'; import type { WebSocketListenerFun } from "@kevisual/router"; import WebSocket from 'ws'; +import { renderNoAuthAndLogin } from '@/module/assistant/html/login.ts'; const localProxy = new LocalProxy({}); localProxy.initFromAssistantConfig(assistantConfig); @@ -161,7 +162,8 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp } const filter = await authFilter(req, res); if (filter) { - return res.end('Not Authorized Proxy'); + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + return res.end(renderNoAuthAndLogin('Not Authorized Proxy')); } const localProxyProxyList = localProxy.getLocalProxyList(); const localProxyProxy = localProxyProxyList.find((item) => pathname.startsWith(item.path)); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 683f606..9d41ca5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,8 +115,8 @@ importers: assistant: dependencies: '@kevisual/ha-api': - specifier: ^0.0.5 - version: 0.0.5 + specifier: ^0.0.6 + version: 0.0.6 '@kevisual/video-tools': specifier: ^0.0.13 version: 0.0.13(dotenv@17.2.3)(supports-color@10.2.2) @@ -155,8 +155,8 @@ importers: specifier: 0.0.7 version: 0.0.7(@kevisual/query@0.0.33) '@kevisual/router': - specifier: ^0.0.51 - version: 0.0.51(supports-color@10.2.2) + specifier: ^0.0.52 + version: 0.0.52(supports-color@10.2.2) '@kevisual/types': specifier: ^0.0.10 version: 0.0.10 @@ -1308,8 +1308,8 @@ packages: resolution: {integrity: sha512-4T/m2LqhtwWEW+lWmg7jLxKFW7VtIAftsWFDDZvh10bZunqFf8iXxChHcVSQWikghJb4cq1IkWzPkvc2l+Asdw==} hasBin: true - '@kevisual/ha-api@0.0.5': - resolution: {integrity: sha512-0A4Hmw797yqB9B92G2vPIn4sxIoNYWBm8D3+yWkUkH+XjEBOE65ScK2j4aJVBfwBNd470QqGlK4DcZ5NmQT5iw==} + '@kevisual/ha-api@0.0.6': + resolution: {integrity: sha512-pZwcE4XYCDItTpMhIP0dIuo2+C07YmhWukVMgTvUuUQBgNo4KJmpItYjeGIvBGsvEM4AjsDGV1mCjTOB1zLu3Q==} '@kevisual/hot-api@0.0.3': resolution: {integrity: sha512-qZ4CNK08StZP4+DR1vWwJhKVDoSXXC+PBFG4ZxtkXF5vO2rybE055zp1n3dg5jo8GwW5wxpqMIG3KBp3pYSTkg==} @@ -1364,6 +1364,9 @@ packages: '@kevisual/router@0.0.51': resolution: {integrity: sha512-i9qYBeS/um78oC912oWJD3iElB+5NTKyTrz1Hzf4DckiUFnjLL81UPwjIh5I2l9+ul0IZ/Pxx+sFSF99fJkzKg==} + '@kevisual/router@0.0.52': + resolution: {integrity: sha512-Qiv3P1XjzD813Tm79S+atrDb2eickGCI9tuy/aCu512LcoYYJqZhwwkeT4ES0DinnA13Ckqd43QWBR6UmuYkHQ==} + '@kevisual/types@0.0.10': resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==} @@ -6615,7 +6618,7 @@ snapshots: transitivePeerDependencies: - typescript - '@kevisual/ha-api@0.0.5': + '@kevisual/ha-api@0.0.6': dependencies: '@kevisual/cache': 0.0.4 fuse.js: 7.1.0 @@ -6759,6 +6762,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@kevisual/router@0.0.52(supports-color@10.2.2)': + dependencies: + eventemitter3: 5.0.1 + path-to-regexp: 8.3.0 + selfsigned: 5.4.0 + send: 1.2.1(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + '@kevisual/types@0.0.10': {} '@kevisual/use-config@1.0.21(dotenv@17.2.3)': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ec4051f..f99edec 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,7 @@ packages: - - 'assistant' - - 'cli-center' \ No newline at end of file + - assistant + - cli-center + +onlyBuiltDependencies: + - esbuild + - sharp