added websocket updates

This commit is contained in:
wea_ondara
2024-05-28 17:26:14 +02:00
parent e356717fb8
commit 6ce07e29d7
13 changed files with 226 additions and 39 deletions

View File

@@ -8,6 +8,7 @@
"name": "ai-backend", "name": "ai-backend",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@types/express-ws": "^3.0.4",
"ajv-formats": "^3.0.1", "ajv-formats": "^3.0.1",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"compression": "^1.7.4", "compression": "^1.7.4",
@@ -18,9 +19,9 @@
"filewatcher": "^3.0.1", "filewatcher": "^3.0.1",
"glob": "^10.3.16", "glob": "^10.3.16",
"knex": "^3.1.0", "knex": "^3.1.0",
"mitt": "^3.0.1",
"mysql2": "^3.9.7", "mysql2": "^3.9.7",
"objection": "^3.1.4", "objection": "^3.1.4",
"semaphore": "^1.1.0",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",
"typescript-ioc": "^3.2.2" "typescript-ioc": "^3.2.2"
}, },
@@ -2163,7 +2164,6 @@
"version": "1.19.5", "version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
"dev": true,
"dependencies": { "dependencies": {
"@types/connect": "*", "@types/connect": "*",
"@types/node": "*" "@types/node": "*"
@@ -2182,7 +2182,6 @@
"version": "3.4.38", "version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
} }
@@ -2209,7 +2208,6 @@
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"dev": true,
"dependencies": { "dependencies": {
"@types/body-parser": "*", "@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33", "@types/express-serve-static-core": "^4.17.33",
@@ -2221,7 +2219,6 @@
"version": "4.19.1", "version": "4.19.1",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.1.tgz", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.1.tgz",
"integrity": "sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA==", "integrity": "sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA==",
"dev": true,
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"@types/qs": "*", "@types/qs": "*",
@@ -2238,6 +2235,17 @@
"@types/express": "*" "@types/express": "*"
} }
}, },
"node_modules/@types/express-ws": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/express-ws/-/express-ws-3.0.4.tgz",
"integrity": "sha512-Yjj18CaivG5KndgcvzttWe8mPFinPCHJC2wvyQqVzA7hqeufM8EtWMj6mpp5omg3s8XALUexhOu8aXAyi/DyJQ==",
"license": "MIT",
"dependencies": {
"@types/express": "*",
"@types/express-serve-static-core": "*",
"@types/ws": "*"
}
},
"node_modules/@types/graceful-fs": { "node_modules/@types/graceful-fs": {
"version": "4.1.9", "version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -2256,8 +2264,7 @@
"node_modules/@types/http-errors": { "node_modules/@types/http-errors": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="
"dev": true
}, },
"node_modules/@types/istanbul-lib-coverage": { "node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6", "version": "2.0.6",
@@ -2328,8 +2335,7 @@
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
"dev": true
}, },
"node_modules/@types/multer": { "node_modules/@types/multer": {
"version": "1.4.11", "version": "1.4.11",
@@ -2351,14 +2357,12 @@
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.15", "version": "6.9.15",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
"integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg=="
"dev": true
}, },
"node_modules/@types/range-parser": { "node_modules/@types/range-parser": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
"dev": true
}, },
"node_modules/@types/semaphore": { "node_modules/@types/semaphore": {
"version": "1.1.4", "version": "1.1.4",
@@ -2371,7 +2375,6 @@
"version": "0.17.4", "version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"dev": true,
"dependencies": { "dependencies": {
"@types/mime": "^1", "@types/mime": "^1",
"@types/node": "*" "@types/node": "*"
@@ -2381,7 +2384,6 @@
"version": "1.15.7", "version": "1.15.7",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
"dev": true,
"dependencies": { "dependencies": {
"@types/http-errors": "*", "@types/http-errors": "*",
"@types/node": "*", "@types/node": "*",
@@ -6629,6 +6631,12 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}, },
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/mkdirp": { "node_modules/mkdirp": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@@ -7894,14 +7902,6 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"node_modules/semaphore": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz",
"integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.6.2", "version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",

View File

@@ -18,6 +18,7 @@
"tsoa:routes": "npm exec tsoa routes" "tsoa:routes": "npm exec tsoa routes"
}, },
"dependencies": { "dependencies": {
"@types/express-ws": "^3.0.4",
"ajv-formats": "^3.0.1", "ajv-formats": "^3.0.1",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"compression": "^1.7.4", "compression": "^1.7.4",
@@ -28,6 +29,7 @@
"filewatcher": "^3.0.1", "filewatcher": "^3.0.1",
"glob": "^10.3.16", "glob": "^10.3.16",
"knex": "^3.1.0", "knex": "^3.1.0",
"mitt": "^3.0.1",
"mysql2": "^3.9.7", "mysql2": "^3.9.7",
"objection": "^3.1.4", "objection": "^3.1.4",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",

View File

@@ -0,0 +1,7 @@
import mitt from 'mitt';
import {Events} from './events';
const emitter = mitt<Events>();
export default function getEmitter<Events>() {
return emitter;
}

View File

@@ -0,0 +1,25 @@
import AiInstance from '../models/business/AiInstance';
export type AiEvent = {
aiInstance: AiInstance,
}
export type ChatEvent = AiEvent & {
role: string,
name: string,
content: string,
};
export type DiscordOnlineEvent = AiEvent & {
online: boolean,
};
export type DiscordReactToChatEvent = AiEvent & {
reactToChat: boolean,
};
export type Events = {
chatText: ChatEvent,
discordOnline: DiscordOnlineEvent,
discordReactToChat: DiscordReactToChatEvent,
}

View File

@@ -0,0 +1,35 @@
import getEmitter from './emitter';
import {Container} from 'typescript-ioc';
import {ExpressWsProvider} from '../ioc';
import {ChatEvent, DiscordOnlineEvent, DiscordReactToChatEvent} from './events';
export function registerEvents(): void {
const emitter = getEmitter();
emitter.on('chatText', (event: ChatEvent) => {
Container.get(ExpressWsProvider).get().getWss().clients
.forEach(c => c.send(JSON.stringify({
type: 'chatText',
aiInstance: event.aiInstance.configuration.id,
role: event.role,
name: event.name,
content: event.content,
})));
});
emitter.on('discordOnline', (event: DiscordOnlineEvent) => {
Container.get(ExpressWsProvider).get().getWss().clients
.forEach(c => c.send(JSON.stringify({
type: 'discordOnline',
aiInstance: event.aiInstance.configuration.id,
online: event.online,
})));
});
emitter.on('discordReactToChat', (event: DiscordReactToChatEvent) => {
Container.get(ExpressWsProvider).get().getWss().clients
.forEach(c => c.send(JSON.stringify({
type: 'discordReactToChat',
aiInstance: event.aiInstance.configuration.id,
reactToChat: event.reactToChat,
})));
});
}

View File

@@ -1,12 +1,25 @@
import {Container, Scope} from 'typescript-ioc'; import {Container, Scope} from 'typescript-ioc';
import {IocContainer} from '@tsoa/runtime'; import {IocContainer} from '@tsoa/runtime';
import {ConfigProvider} from './config/confighelper'; import {ConfigProvider} from './config/confighelper';
import AiService from "./services/AiService"; import AiService from './services/AiService';
import AiPythonConnector from './services/AiPythonConnector'; import AiPythonConnector from './services/AiPythonConnector';
import expressWs from 'express-ws';
const iocContainer: () => IocContainer = () => Container; const iocContainer: () => IocContainer = () => Container;
Container.bind(ConfigProvider).to(ConfigProvider).scope(Scope.Singleton); Container.bind(ConfigProvider).to(ConfigProvider).scope(Scope.Singleton);
Container.bind(AiPythonConnector).to(AiPythonConnector).scope(Scope.Singleton); Container.bind(AiPythonConnector).to(AiPythonConnector).scope(Scope.Singleton);
Container.bind(AiService).to(AiService).scope(Scope.Singleton); Container.bind(AiService).to(AiService).scope(Scope.Singleton);
export {iocContainer}; export {iocContainer};
export class ExpressWsProvider {
private readonly instance: expressWs.Instance;
constructor(instance: expressWs.Instance) {
this.instance = instance;
}
get(): expressWs.Instance {
return this.instance;
}
}

View File

@@ -2,6 +2,7 @@ import AiConfiguration from '../db/AiConfiguration';
import DiscordStatus from './DiscordStatus'; import DiscordStatus from './DiscordStatus';
import AiPythonConnector from '../../services/AiPythonConnector'; import AiPythonConnector from '../../services/AiPythonConnector';
import Semaphore from '../../util/Semaphore'; import Semaphore from '../../util/Semaphore';
import getEmitter from '../../event/emitter';
export type ChatMessage = { export type ChatMessage = {
role: string, role: string,
@@ -27,9 +28,9 @@ export default class AiInstance {
try { try {
await this._messagesSemaphore.acquire(); await this._messagesSemaphore.acquire();
this.messages.push({'role': 'user', 'name': user, 'content': text}); this.messages.push({'role': 'user', 'name': user, 'content': text});
// chat_text_signal.send(sender = this.__class__, ai_name = this.configuration.name, message = this.messages[-1]); getEmitter().emit('chatText', {aiInstance: this, ...this.messages[this.messages.length - 1]!});
this.messages = await this.aiPythonConnector.chat(this.configuration.modelIdOrPath, this.messages); this.messages = await this.aiPythonConnector.chat(this.configuration.modelIdOrPath, this.messages);
// chat_text_signal.send(sender = this.__class__, ai_name = this.configuration.name, message = this.messages[-1]); getEmitter().emit('chatText', {aiInstance: this, ...this.messages[this.messages.length - 1]!});
return this.messages[this.messages.length - 1]!; return this.messages[this.messages.length - 1]!;
} finally { } finally {
this._messagesSemaphore.release(); this._messagesSemaphore.release();

View File

@@ -1,14 +1,15 @@
import type AiInstance from './AiInstance'; import type AiInstance from './AiInstance';
import DcClient from './DcClient'; import DcClient from './DcClient';
import getEmitter from '../../event/emitter';
export default class DiscordStatus { export default class DiscordStatus {
// private _instance: AiInstance; private readonly _instance: AiInstance;
private _client: DcClient; private _client: DcClient;
private _online = false; private _online = false;
private _reactToChat = false; private _reactToChat = false;
constructor(instance: AiInstance) { constructor(instance: AiInstance) {
// this._instance = instance; this._instance = instance;
this._client = new DcClient(instance); this._client = new DcClient(instance);
} }
@@ -25,7 +26,7 @@ export default class DiscordStatus {
return; return;
} }
this._online = value; this._online = value;
// discord_online_signal.send(sender = this.__class__, ai_name = this._instance.configuration.name, status = this.online); getEmitter().emit('discordOnline', {aiInstance: this._instance, online: value});
} }
isReactToChat(): boolean { isReactToChat(): boolean {
@@ -34,8 +35,7 @@ export default class DiscordStatus {
setReactToChat(value: boolean) { setReactToChat(value: boolean) {
this._reactToChat = value; this._reactToChat = value;
// discord_react_to_chat_signal.send(sender = this.__class__, ai_name = this._instance.configuration.name, getEmitter().emit('discordReactToChat', {aiInstance: this._instance, reactToChat: value});
// status = this._reactToChat);
} }
async dispose(): Promise<void> { async dispose(): Promise<void> {

View File

@@ -2,9 +2,19 @@ import {Express, static as _static} from 'express';
import type {ConfigType} from './config/confighelper'; import type {ConfigType} from './config/confighelper';
import catchAllRedirect from './middleware/CatchAllRedirect'; import catchAllRedirect from './middleware/CatchAllRedirect';
import {RegisterRoutes} from './tsoa.gen/routes'; import {RegisterRoutes} from './tsoa.gen/routes';
import expressWs from 'express-ws';
export function registerRoutes(express: Express, config: ConfigType) { export function registerRoutes(express: Express, config: ConfigType, ws: expressWs.Instance): void {
RegisterRoutes(express); RegisterRoutes(express);
// (express as any as expressWs.Application).ws('ws/dashboard', (ws, req, next) => isAuthenticatedMiddleware(req, req.res!, next));
(express as any as expressWs.Application).ws('ws/dashboard', (ws, req) => {
// ws.on('open', function () {
// console.log(this);
// });
// ws.on('message', function (msg) {
// ws.send(msg);
// });
});
express.use(_static('_client')); //for production express.use(_static('_client')); //for production
express.use(catchAllRedirect(express, '/')); express.use(catchAllRedirect(express, '/'));
} }

View File

@@ -16,7 +16,10 @@ import {initGlobals} from './util/GlobalInit';
import {seedDb} from './DbSeed'; import {seedDb} from './DbSeed';
import {uncaughtErrorHandler} from './middleware/UncaughtErrorHandler'; import {uncaughtErrorHandler} from './middleware/UncaughtErrorHandler';
import compression from 'compression'; import compression from 'compression';
import {iocContainer} from './ioc'; import {ExpressWsProvider, iocContainer} from './ioc';
import expressWs from 'express-ws';
import {Container, Scope} from 'typescript-ioc';
import {registerEvents} from './event/listener';
export default class Server { export default class Server {
// @ts-ignore TS6133 // @ts-ignore TS6133
@@ -24,6 +27,7 @@ export default class Server {
private config!: ConfigType; private config!: ConfigType;
private express: Express | undefined; private express: Express | undefined;
private expressWsInstance: expressWs.Instance | undefined;
private server: http.Server | undefined; private server: http.Server | undefined;
private expressHost: string | undefined; private expressHost: string | undefined;
private expressPort: number | undefined; private expressPort: number | undefined;
@@ -38,6 +42,7 @@ export default class Server {
initGlobals(); initGlobals();
this.initConfigChangeWatcher(); this.initConfigChangeWatcher();
this.initShutdownHooks(); this.initShutdownHooks();
registerEvents();
} }
private initConfigChangeWatcher(): void { private initConfigChangeWatcher(): void {
@@ -75,6 +80,7 @@ export default class Server {
this.expressPort = this.config.server.port ?? 3000; this.expressPort = this.config.server.port ?? 3000;
this.express = express(); this.express = express();
this.expressWsInstance = expressWs(this.express);
const sessionStore = new DbStore(); const sessionStore = new DbStore();
@@ -84,12 +90,14 @@ export default class Server {
this.express.use(session(getSessionSettings(this.config, sessionStore))); this.express.use(session(getSessionSettings(this.config, sessionStore)));
this.express.use(sessionUserdataMiddleware()); this.express.use(sessionUserdataMiddleware());
this.express.use(express.json()); this.express.use(express.json());
registerRoutes(this.express, this.config); registerRoutes(this.express, this.config, this.expressWsInstance);
// RegisterRoutes(app);
this.express.use(errorLogHandler()); this.express.use(errorLogHandler());
this.express.use(apiErrorHandler()); this.express.use(apiErrorHandler());
// registerOpenApiLast(this.express); // registerOpenApiLast(this.express);
this.express.use(uncaughtErrorHandler()); this.express.use(uncaughtErrorHandler());
const expressWsProviderFactory = () => new ExpressWsProvider(this.expressWsInstance!);
Container.bind(ExpressWsProvider).factory(expressWsProviderFactory).scope(Scope.Singleton);
} }
private async initDatabase(): Promise<void> { private async initDatabase(): Promise<void> {

View File

@@ -1,15 +1,19 @@
<script lang="ts"> <script lang="ts">
import {Options, Vue} from 'vue-class-component'; import {Options, Vue} from 'vue-class-component';
import AiInstanceTabs from "@/components/dashboard/AiInstanceTabs.vue"; import AiInstanceTabs from "@/components/dashboard/AiInstanceTabs.vue";
import WsUpdater from "@/components/dashboard/WsUpdater.vue";
@Options({ @Options({
name: 'DashBoard', name: 'DashBoard',
components: {AiInstanceTabs}, components: {WsUpdater, AiInstanceTabs},
}) })
export default class DashBoard extends Vue { export default class DashBoard extends Vue {
} }
</script> </script>
<template> <template>
<AiInstanceTabs class="m-3"/> <div>
<WsUpdater/>
<AiInstanceTabs class="m-3"/>
</div>
</template> </template>

View File

@@ -0,0 +1,78 @@
<script lang="ts">
import {Options, Vue} from 'vue-class-component';
import {AiInstanceStore} from '@/stores/AiInstanceStore';
import {ChatMessageVmV1} from 'ai-oas';
@Options({
name: 'WsUpdater',
components: {},
})
export default class WsUpdater extends Vue {
readonly baseurl = 'ws://' + window.location.host + '/';
private socket!: WebSocket;
private readonly aiInstanceStore = new AiInstanceStore();
private reconnect = false;
mounted() {
this.reconnect = true;
this.initSocket();
}
private initSocket(): void {
this.socket = new WebSocket(this.baseurl + 'ws/dashboard/');
this.socket.onmessage = (e) => {
const message = JSON.parse(e.data);
switch (message.type) {
case 'chatText': {
const aiInstanceId = message.aiInstance;
const ai = this.aiInstanceStore.aiInstances.find(e => e.configuration.id == aiInstanceId);
if (ai) {
ai.messages.push(ChatMessageVmV1.fromJson(message));
}
break;
}
case 'discordOnline': {
const aiInstanceId = message.aiInstance;
const ai = this.aiInstanceStore.aiInstances.find(e => e.configuration.id == aiInstanceId);
if (ai) {
ai.discord.online = message.online;
}
break;
}
case 'discordReactToChat': {
const aiInstanceId = message.aiInstance;
const ai = this.aiInstanceStore.aiInstances.find(e => e.configuration.id == aiInstanceId);
if (ai) {
ai.discord.reactToChat = message.reactToChat;
}
break;
}
default:
console.log('WsUpdater: Unknown message type', message);
}
};
this.socket.onopen = () => {
console.info('Chat socket connected');
}
this.socket.onclose = (event: CloseEvent) => {
if (this.reconnect) {
console.error('Chat socket closed unexpectedly. Reconnecting ...');
this.$nextTick(() => this.initSocket());
}
};
}
unmounted() {
this.reconnect = false;
this.socket.close();
}
sendMessage(message: any) {
this.socket.send(JSON.stringify(message));
}
}
</script>
<template>
</template>

View File

@@ -29,11 +29,15 @@ export default defineConfig({
}, },
}, },
clearScreen: false, clearScreen: false,
server: { server: {
proxy: {//for dev proxy: {//for dev
'^/api/.*$': { '^/api/.*$': {
target: 'http://localhost:5172', target: 'http://localhost:5172',
changeOrigin: true, changeOrigin: true,
}, '^/ws/.*$': {
target: 'http://localhost:5172',
changeOrigin: true,
ws: true,
}, },
}, },
}, },