working chat and TTS
This commit is contained in:
parent
c3c0d33e5b
commit
c7963631cb
18 changed files with 564 additions and 36 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -14,4 +14,5 @@
|
|||
**/.pnp.*
|
||||
node_modules/
|
||||
**/dist/
|
||||
**/.parcel-cache/
|
||||
**/.parcel-cache/
|
||||
server/config.toml
|
|
@ -10,8 +10,14 @@
|
|||
"scripts": {
|
||||
"build": "parcel build"
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
"type": "module",
|
||||
"source": "src/index.ts",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
=======
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "module"
|
||||
>>>>>>> 2414658 (working chat and TTS)
|
||||
}
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
"build": "tsc"
|
||||
},
|
||||
"main": "./dist/protocol.js",
|
||||
"types": "./dist/protocol.d.ts"
|
||||
"types": "./dist/protocol.d.ts",
|
||||
"type": "module"
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ export enum MSAgentProtocolMessageType {
|
|||
Init = "init",
|
||||
AddUser = "adduser",
|
||||
RemoveUser = "remuser",
|
||||
Message = "msg"
|
||||
Chat = "chat"
|
||||
}
|
||||
|
||||
export interface MSAgentProtocolMessage {
|
||||
|
@ -19,6 +19,7 @@ export interface MSAgentJoinMessage extends MSAgentProtocolMessage {
|
|||
op: MSAgentProtocolMessageType.Join,
|
||||
data: {
|
||||
username: string;
|
||||
agent: string;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,7 +35,12 @@ export interface MSAgentTalkMessage extends MSAgentProtocolMessage {
|
|||
export interface MSAgentInitMessage extends MSAgentProtocolMessage {
|
||||
op: MSAgentProtocolMessageType.Init,
|
||||
data: {
|
||||
users: string[]
|
||||
username: string
|
||||
agent: string
|
||||
users: {
|
||||
username: string,
|
||||
agent: string
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +48,7 @@ export interface MSAgentAddUserMessage extends MSAgentProtocolMessage {
|
|||
op: MSAgentProtocolMessageType.AddUser,
|
||||
data: {
|
||||
username: string;
|
||||
agent: string;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,4 +57,13 @@ export interface MSAgentRemoveUserMessage extends MSAgentProtocolMessage {
|
|||
data: {
|
||||
username: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface MSAgentChatMessage extends MSAgentProtocolMessage {
|
||||
op: MSAgentProtocolMessageType.Chat,
|
||||
data: {
|
||||
username: string;
|
||||
message: string;
|
||||
audio? : string | undefined;
|
||||
}
|
||||
}
|
11
server/config.example.toml
Normal file
11
server/config.example.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[http]
|
||||
host = "127.0.0.1"
|
||||
port = 3000
|
||||
|
||||
[tts]
|
||||
enabled = true
|
||||
# https://git.computernewb.com/computernewb/SAPIServer
|
||||
server = "http://127.0.0.1:3001"
|
||||
voice = "Microsoft Sam"
|
||||
tempDir = "/tmp/msac-tts"
|
||||
wavExpirySeconds = 60
|
|
@ -11,8 +11,11 @@
|
|||
"typescript": "^5.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/static": "^7.0.4",
|
||||
"@fastify/websocket": "^10.0.1",
|
||||
"fastify": "^4.28.1",
|
||||
"html-entities": "^2.5.2",
|
||||
"toml": "^3.0.0",
|
||||
"ws": "^8.17.1"
|
||||
},
|
||||
"type": "module"
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
import EventEmitter from "events";
|
||||
import { WebSocket } from "ws";
|
||||
import { MSAgentProtocolMessage, MSAgentProtocolMessageType } from '@msagent-chat/protocol';
|
||||
import { MSAgentJoinMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentTalkMessage } from '@msagent-chat/protocol';
|
||||
import { MSAgentChatRoom } from "./room.js";
|
||||
import * as htmlentities from 'html-entities';
|
||||
|
||||
// Event types
|
||||
|
||||
export interface Client {
|
||||
on(event: 'join', listener: () => void): this;
|
||||
on(event: 'close', listener: () => void): this;
|
||||
on(event: 'talk', listener: (msg: string) => void): this;
|
||||
|
||||
on(event: string, listener: Function): this;
|
||||
}
|
||||
|
||||
export class Client extends EventEmitter {
|
||||
username: string | null;
|
||||
agent: string | null;
|
||||
|
||||
room: MSAgentChatRoom;
|
||||
socket: WebSocket;
|
||||
constructor(socket: WebSocket, room: MSAgentChatRoom) {
|
||||
|
@ -12,6 +25,7 @@ export class Client extends EventEmitter {
|
|||
this.socket = socket;
|
||||
this.room = room;
|
||||
this.username = null;
|
||||
this.agent = null;
|
||||
this.socket.on('message', (msg, isBinary) => {
|
||||
if (isBinary) {
|
||||
this.socket.close();
|
||||
|
@ -47,11 +61,22 @@ export class Client extends EventEmitter {
|
|||
}
|
||||
switch (msg.op) {
|
||||
case MSAgentProtocolMessageType.Join: {
|
||||
|
||||
let joinMsg = msg as MSAgentJoinMessage;
|
||||
if (!joinMsg.data || !joinMsg.data.username || !joinMsg.data.username) {
|
||||
this.socket.close();
|
||||
return;
|
||||
}
|
||||
this.username = htmlentities.encode(joinMsg.data.username);
|
||||
this.agent = htmlentities.encode(joinMsg.data.agent);
|
||||
this.emit('join');
|
||||
break;
|
||||
}
|
||||
case MSAgentProtocolMessageType.Talk: {
|
||||
|
||||
let talkMsg = msg as MSAgentTalkMessage;
|
||||
if (!talkMsg.data || !talkMsg.data.msg) {
|
||||
return;
|
||||
}
|
||||
this.emit('talk', htmlentities.encode(talkMsg.data.msg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
15
server/src/config.ts
Normal file
15
server/src/config.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
export interface IConfig {
|
||||
http: {
|
||||
host: string;
|
||||
port: number;
|
||||
}
|
||||
tts: TTSConfig
|
||||
}
|
||||
|
||||
export interface TTSConfig {
|
||||
enabled: boolean;
|
||||
server: string;
|
||||
voice: string;
|
||||
tempDir: string;
|
||||
wavExpirySeconds: number;
|
||||
}
|
|
@ -1,7 +1,32 @@
|
|||
import Fastify from 'fastify';
|
||||
import FastifyWS from '@fastify/websocket';
|
||||
import FastifyStatic from '@fastify/static';
|
||||
import { Client } from './client.js';
|
||||
import { MSAgentChatRoom } from './room.js';
|
||||
import * as toml from 'toml';
|
||||
import { IConfig } from './config.js';
|
||||
import * as fs from 'fs';
|
||||
import { TTSClient } from './tts.js';
|
||||
|
||||
let config: IConfig;
|
||||
let configPath: string;
|
||||
if (process.argv.length < 3)
|
||||
configPath = "./config.toml";
|
||||
else
|
||||
configPath = process.argv[2];
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
console.error(`${configPath} not found. Please copy config.example.toml and fill out fields.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
let configRaw = fs.readFileSync(configPath, "utf-8");
|
||||
config = toml.parse(configRaw);
|
||||
} catch (e) {
|
||||
console.error(`Failed to read or parse ${configPath}: ${(e as Error).message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const app = Fastify({
|
||||
logger: true,
|
||||
|
@ -9,16 +34,24 @@ const app = Fastify({
|
|||
|
||||
app.register(FastifyWS);
|
||||
|
||||
let room = new MSAgentChatRoom();
|
||||
let tts = null;
|
||||
|
||||
app.get("/socket", {websocket: true}, (socket, req) => {
|
||||
let client = new Client(socket, room);
|
||||
room.addClient(client);
|
||||
if (config.tts.enabled) {
|
||||
tts = new TTSClient(config.tts);
|
||||
app.register(FastifyStatic, {
|
||||
root: config.tts.tempDir,
|
||||
prefix: "/api/tts/"
|
||||
});
|
||||
}
|
||||
|
||||
let room = new MSAgentChatRoom(tts);
|
||||
|
||||
app.register(async app => {
|
||||
app.get("/socket", {websocket: true}, (socket, req) => {
|
||||
let client = new Client(socket, room);
|
||||
room.addClient(client);
|
||||
});
|
||||
});
|
||||
|
||||
let port;
|
||||
if (process.argv.length < 3 || isNaN(port = parseInt(process.argv[2]))) {
|
||||
console.error("Usage: index.js [port]");
|
||||
process.exit(1);
|
||||
}
|
||||
app.listen({host: "127.0.0.1", port});
|
||||
|
||||
app.listen({host: config.http.host, port: config.http.port});
|
|
@ -1,11 +1,14 @@
|
|||
import { MSAgentAddUserMessage, MSAgentInitMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentRemoveUserMessage } from "@msagent-chat/protocol";
|
||||
import { MSAgentAddUserMessage, MSAgentChatMessage, MSAgentInitMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentRemoveUserMessage } from "@msagent-chat/protocol";
|
||||
import { Client } from "./client.js";
|
||||
import { TTSClient } from "./tts.js";
|
||||
|
||||
export class MSAgentChatRoom {
|
||||
clients: Client[];
|
||||
|
||||
constructor() {
|
||||
tts: TTSClient | null;
|
||||
msgId : number = 0;
|
||||
constructor(tts: TTSClient | null) {
|
||||
this.clients = [];
|
||||
this.tts = tts;
|
||||
}
|
||||
|
||||
addClient(client: Client) {
|
||||
|
@ -24,26 +27,47 @@ export class MSAgentChatRoom {
|
|||
}
|
||||
});
|
||||
client.on('join', () => {
|
||||
client.send(this.getInitMsg());
|
||||
let initmsg : MSAgentInitMessage = {
|
||||
op: MSAgentProtocolMessageType.Init,
|
||||
data: {
|
||||
username: client.username!,
|
||||
agent: client.agent!,
|
||||
users: this.clients.filter(c => c.username !== null).map(c => {
|
||||
return {
|
||||
username: c.username!,
|
||||
agent: c.agent!
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
client.send(initmsg);
|
||||
let msg: MSAgentAddUserMessage = {
|
||||
op: MSAgentProtocolMessageType.AddUser,
|
||||
data: {
|
||||
username: client.username!
|
||||
username: client.username!,
|
||||
agent: client.agent!
|
||||
}
|
||||
}
|
||||
for (const _client of this.getActiveClients().filter(c => c !== client)) {
|
||||
_client.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getInitMsg(): MSAgentInitMessage {
|
||||
return {
|
||||
op: MSAgentProtocolMessageType.Init,
|
||||
data: {
|
||||
users: this.clients.filter(c => c.username !== null).map(c => c.username!)
|
||||
client.on('talk', async message => {
|
||||
let msg: MSAgentChatMessage = {
|
||||
op: MSAgentProtocolMessageType.Chat,
|
||||
data: {
|
||||
username: client.username!,
|
||||
message
|
||||
}
|
||||
};
|
||||
if (this.tts !== null) {
|
||||
let filename = await this.tts.synthesizeToFile(message, (++this.msgId).toString(10));
|
||||
msg.data.audio = "/api/tts/" + filename;
|
||||
}
|
||||
}
|
||||
for (const _client of this.getActiveClients()) {
|
||||
_client.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getActiveClients() {
|
||||
|
|
70
server/src/tts.ts
Normal file
70
server/src/tts.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import path from "path";
|
||||
import * as fs from 'fs/promises';
|
||||
import { TTSConfig } from "./config.js";
|
||||
import { Readable } from "stream";
|
||||
import { ReadableStream } from 'node:stream/web';
|
||||
import { finished } from "node:stream/promises";
|
||||
|
||||
export class TTSClient {
|
||||
private config: TTSConfig;
|
||||
private deleteOps: Map<string, NodeJS.Timeout>
|
||||
|
||||
constructor(config: TTSConfig) {
|
||||
this.config = config;
|
||||
if (!this.config.tempDir.endsWith('/')) this.config.tempDir += '/';
|
||||
this.deleteOps = new Map();
|
||||
}
|
||||
|
||||
async ensureDirectoryExists() {
|
||||
let stat;
|
||||
try {
|
||||
stat = await fs.stat(this.config.tempDir);
|
||||
} catch (e) {
|
||||
let error = e as NodeJS.ErrnoException;
|
||||
switch (error.code) {
|
||||
case "ENOTDIR": {
|
||||
console.warn("File exists at TTS temp directory path. Unlinking...");
|
||||
await fs.unlink(this.config.tempDir.substring(0, this.config.tempDir.length - 1));
|
||||
// intentional fall-through
|
||||
}
|
||||
case "ENOENT": {
|
||||
await fs.mkdir(this.config.tempDir, {recursive: true});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.error(`Cannot access TTS Temp dir: ${error.message}`);
|
||||
process.exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async synthesizeToFile(text: string, id: string) : Promise<string> {
|
||||
this.ensureDirectoryExists();
|
||||
let wavFilename = id + ".wav"
|
||||
let wavPath = path.join(this.config.tempDir, wavFilename);
|
||||
try {
|
||||
await fs.unlink(wavPath);
|
||||
} catch {}
|
||||
let file = await fs.open(wavPath, fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY);
|
||||
let stream = file.createWriteStream();
|
||||
let res = await fetch(this.config.server + "/api/synthesize", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
voice: this.config.voice
|
||||
})
|
||||
});
|
||||
await finished(Readable.fromWeb(res.body as ReadableStream<any>).pipe(stream));
|
||||
await file.close();
|
||||
this.deleteOps.set(wavPath, setTimeout(async () => {
|
||||
await fs.unlink(wavPath);
|
||||
this.deleteOps.delete(wavPath);
|
||||
}, this.config.wavExpirySeconds * 1000));
|
||||
return wavFilename;
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -15,5 +15,8 @@
|
|||
"run-script-os": "^1.1.6",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"nanoevents": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ body {
|
|||
flex-grow: 1;
|
||||
margin-right: 4px;
|
||||
margin-left: 4px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#chatSentBtn {
|
||||
|
|
144
webapp/src/ts/client.ts
Normal file
144
webapp/src/ts/client.ts
Normal file
|
@ -0,0 +1,144 @@
|
|||
import { createNanoEvents, Emitter, Unsubscribe } from 'nanoevents';
|
||||
import { MSAgentAddUserMessage, MSAgentChatMessage, MSAgentInitMessage, MSAgentJoinMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentRemoveUserMessage, MSAgentTalkMessage } from '@msagent-chat/protocol';
|
||||
import { User } from './user';
|
||||
|
||||
export interface MSAgentClientEvents {
|
||||
close: () => void;
|
||||
join: () => void;
|
||||
adduser: (user: User) => void;
|
||||
remuser: (user: User) => void;
|
||||
chat: (user: User, msg: string) => void;
|
||||
}
|
||||
|
||||
export class MSAgentClient {
|
||||
private url: string;
|
||||
private socket: WebSocket | null;
|
||||
private events: Emitter;
|
||||
private users: User[];
|
||||
|
||||
private username: string | null = null;
|
||||
private agent: string | null = null;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
this.socket = null;
|
||||
this.events = createNanoEvents();
|
||||
this.users = [];
|
||||
}
|
||||
|
||||
on<E extends keyof MSAgentClientEvents>(event: E, callback: MSAgentClientEvents[E]): Unsubscribe {
|
||||
return this.events.on(event, callback);
|
||||
}
|
||||
|
||||
connect(): Promise<void> {
|
||||
return new Promise(res => {
|
||||
let url = new URL(this.url);
|
||||
switch (url.protocol) {
|
||||
case "http:":
|
||||
url.protocol = "ws:";
|
||||
break;
|
||||
case "https":
|
||||
url.protocol = "wss:";
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown protocol ${url.protocol}`);
|
||||
}
|
||||
url.pathname = "/socket"
|
||||
this.socket = new WebSocket(url);
|
||||
this.socket.addEventListener('open', () => res());
|
||||
this.socket.addEventListener('message', e => {
|
||||
if (e.data instanceof ArrayBuffer) {
|
||||
// server should not send binary
|
||||
return;
|
||||
}
|
||||
this.handleMessage(e.data);
|
||||
});
|
||||
this.socket.addEventListener('close', () => {
|
||||
this.events.emit('close');
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
send(msg: MSAgentProtocolMessage) {
|
||||
if (this.socket === null || this.socket.readyState !== this.socket.OPEN) {
|
||||
console.error("Tried to send data on a closed or uninitialized socket");
|
||||
return;
|
||||
}
|
||||
let data = JSON.stringify(msg);
|
||||
this.socket!.send(data);
|
||||
}
|
||||
|
||||
join(username: string, agent: string) {
|
||||
if (this.socket === null || this.socket.readyState !== this.socket.OPEN) {
|
||||
throw new Error("Tried to join() on a closed or uninitialized socket");
|
||||
}
|
||||
return new Promise<void>(res => {
|
||||
let msg: MSAgentJoinMessage = {
|
||||
op: MSAgentProtocolMessageType.Join,
|
||||
data: {
|
||||
username,
|
||||
agent
|
||||
}
|
||||
};
|
||||
let u = this.on('join', () => {
|
||||
u();
|
||||
res();
|
||||
});
|
||||
this.send(msg);
|
||||
});
|
||||
}
|
||||
|
||||
talk(msg: string) {
|
||||
let talkMsg: MSAgentTalkMessage = {
|
||||
op: MSAgentProtocolMessageType.Talk,
|
||||
data: {
|
||||
msg
|
||||
}
|
||||
};
|
||||
this.send(talkMsg);
|
||||
}
|
||||
|
||||
private handleMessage(data: string) {
|
||||
let msg: MSAgentProtocolMessage;
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
} catch (e) {
|
||||
console.error(`Failed to parse message from server: ${(e as Error).message}`);
|
||||
return;
|
||||
}
|
||||
switch (msg.op) {
|
||||
case MSAgentProtocolMessageType.Init: {
|
||||
let initMsg = msg as MSAgentInitMessage;
|
||||
this.username = initMsg.data.username;
|
||||
this.agent = initMsg.data.agent;
|
||||
this.users.push(...initMsg.data.users.map(u => new User(u.username, u.agent)));
|
||||
this.events.emit('join');
|
||||
break;
|
||||
}
|
||||
case MSAgentProtocolMessageType.AddUser: {
|
||||
let addUserMsg = msg as MSAgentAddUserMessage
|
||||
let user = new User(addUserMsg.data.username, addUserMsg.data.agent);
|
||||
this.events.emit('adduser', user);
|
||||
break;
|
||||
}
|
||||
case MSAgentProtocolMessageType.RemoveUser: {
|
||||
let remUserMsg = msg as MSAgentRemoveUserMessage;
|
||||
let user = this.users.find(u => u.username === remUserMsg.data.username);
|
||||
if (!user) return;
|
||||
this.users.splice(this.users.indexOf(user), 1);
|
||||
this.events.emit('remuser', user);
|
||||
break;
|
||||
}
|
||||
case MSAgentProtocolMessageType.Chat: {
|
||||
let chatMsg = msg as MSAgentChatMessage;
|
||||
let user = this.users.find(u => u.username === chatMsg.data.username);
|
||||
this.events.emit('chat', user, chatMsg.data.message);
|
||||
if (chatMsg.data.audio !== undefined) {
|
||||
let audio = new Audio(this.url + chatMsg.data.audio);
|
||||
audio.play();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,18 @@
|
|||
import { MSWindow, MSWindowStartPosition } from "./MSWindow.js";
|
||||
|
||||
import { agentInit } from "@msagent-chat/msagent.js";
|
||||
import { MSAgentClient } from "./client.js";
|
||||
|
||||
let Room : MSAgentClient | null = null;
|
||||
|
||||
const elements = {
|
||||
logonView: document.getElementById("logonView") as HTMLDivElement,
|
||||
logonWindow: document.getElementById("logonWindow") as HTMLDivElement,
|
||||
logonForm: document.getElementById("logonForm") as HTMLFormElement,
|
||||
logonUsername: document.getElementById("logonUsername") as HTMLInputElement,
|
||||
|
||||
chatView: document.getElementById("chatView") as HTMLDivElement,
|
||||
chatInput: document.getElementById("chatInput") as HTMLInputElement,
|
||||
chatSendBtn: document.getElementById("chatSendBtn") as HTMLButtonElement
|
||||
}
|
||||
|
||||
let logonWindow = new MSWindow(elements.logonWindow, {
|
||||
|
@ -21,7 +26,22 @@ logonWindow.show();
|
|||
|
||||
elements.logonForm.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
connectToRoom();
|
||||
});
|
||||
|
||||
elements.chatInput.addEventListener('keypress', e => {
|
||||
// enter
|
||||
if (e.key === "Enter") talk();
|
||||
});
|
||||
|
||||
elements.chatSendBtn.addEventListener('click', () => {
|
||||
talk();
|
||||
});
|
||||
|
||||
async function connectToRoom() {
|
||||
Room = new MSAgentClient("http://127.0.0.1:3000");
|
||||
await Room.connect();
|
||||
await Room.join(elements.logonUsername.value, "test");
|
||||
logonWindow.hide();
|
||||
elements.logonView.style.display = "none";
|
||||
elements.chatView.style.display = "block";
|
||||
|
@ -30,3 +50,10 @@ elements.logonForm.addEventListener('submit', e => {
|
|||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await agentInit();
|
||||
});
|
||||
}
|
||||
|
||||
function talk() {
|
||||
if (Room === null) return;
|
||||
Room.talk(elements.chatInput.value);
|
||||
elements.chatInput.value = "";
|
||||
}
|
||||
|
|
9
webapp/src/ts/user.ts
Normal file
9
webapp/src/ts/user.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export class User {
|
||||
username: string;
|
||||
agent: string;
|
||||
|
||||
constructor(username: string, agent: string) {
|
||||
this.username = username;
|
||||
this.agent = agent;
|
||||
}
|
||||
}
|
147
yarn.lock
147
yarn.lock
|
@ -34,6 +34,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fastify/accept-negotiator@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "@fastify/accept-negotiator@npm:1.1.0"
|
||||
checksum: 10c0/1cb9a298c992b812869158ddc6093557a877b30e5f77618a7afea985a0667c50bc7113593bf0f7f9dc9b82b94c16e8ab127a0afc3efde6677fd645539f6d08e5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fastify/ajv-compiler@npm:^3.5.0":
|
||||
version: 3.6.0
|
||||
resolution: "@fastify/ajv-compiler@npm:3.6.0"
|
||||
|
@ -70,6 +77,33 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fastify/send@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "@fastify/send@npm:2.1.0"
|
||||
dependencies:
|
||||
"@lukeed/ms": "npm:^2.0.1"
|
||||
escape-html: "npm:~1.0.3"
|
||||
fast-decode-uri-component: "npm:^1.0.1"
|
||||
http-errors: "npm:2.0.0"
|
||||
mime: "npm:^3.0.0"
|
||||
checksum: 10c0/0e1c10022660fa1f1959b7c414d1be2c47ab42be1da8e21cd72a4df3104c516fdf7b590ee67f897037dd4c85b716fac63929e894d7699623549646604f6db14b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fastify/static@npm:^7.0.4":
|
||||
version: 7.0.4
|
||||
resolution: "@fastify/static@npm:7.0.4"
|
||||
dependencies:
|
||||
"@fastify/accept-negotiator": "npm:^1.0.0"
|
||||
"@fastify/send": "npm:^2.0.0"
|
||||
content-disposition: "npm:^0.5.3"
|
||||
fastify-plugin: "npm:^4.0.0"
|
||||
fastq: "npm:^1.17.0"
|
||||
glob: "npm:^10.3.4"
|
||||
checksum: 10c0/4ca29a37226880fcc8e711b4a3b93858d4e23dd0458a9f65373b63b3df4768610c8cfbd9e51b749ab52d65b9139142a5c69571740265c9ace1660e268ebb38f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fastify/websocket@npm:^10.0.1":
|
||||
version: 10.0.1
|
||||
resolution: "@fastify/websocket@npm:10.0.1"
|
||||
|
@ -153,6 +187,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lukeed/ms@npm:^2.0.1":
|
||||
version: 2.0.2
|
||||
resolution: "@lukeed/ms@npm:2.0.2"
|
||||
checksum: 10c0/843b922717313bcde4943f478145d8bc13115b9b91d83bbaed53739b5644122984412310aed574792f4e6b492f2cbda178175f601856d310f67e14834c3f17a0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mischnic/json-sourcemap@npm:^0.1.0":
|
||||
version: 0.1.1
|
||||
resolution: "@mischnic/json-sourcemap@npm:0.1.1"
|
||||
|
@ -164,12 +205,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
<<<<<<< HEAD
|
||||
"@msagent-chat/msagent.js@npm:*, @msagent-chat/msagent.js@workspace:msagent.js":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@msagent-chat/msagent.js@workspace:msagent.js"
|
||||
dependencies:
|
||||
"@parcel/core": "npm:^2.12.0"
|
||||
parcel: "npm:^2.12.0"
|
||||
=======
|
||||
"@msagent-chat/msagent.js@workspace:msagent.js":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@msagent-chat/msagent.js@workspace:msagent.js"
|
||||
dependencies:
|
||||
>>>>>>> 2414658 (working chat and TTS)
|
||||
typescript: "npm:^5.5.3"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
@ -186,10 +234,13 @@ __metadata:
|
|||
version: 0.0.0-use.local
|
||||
resolution: "@msagent-chat/server@workspace:server"
|
||||
dependencies:
|
||||
"@fastify/static": "npm:^7.0.4"
|
||||
"@fastify/websocket": "npm:^10.0.1"
|
||||
"@types/node": "npm:^20.14.9"
|
||||
"@types/ws": "npm:^8.5.10"
|
||||
fastify: "npm:^4.28.1"
|
||||
html-entities: "npm:^2.5.2"
|
||||
toml: "npm:^3.0.0"
|
||||
typescript: "npm:^5.5.3"
|
||||
ws: "npm:^8.17.1"
|
||||
languageName: unknown
|
||||
|
@ -201,6 +252,7 @@ __metadata:
|
|||
dependencies:
|
||||
"@msagent-chat/msagent.js": "npm:*"
|
||||
"@parcel/core": "npm:^2.12.0"
|
||||
nanoevents: "npm:^9.0.0"
|
||||
parcel: "npm:^2.12.0"
|
||||
run-script-os: "npm:^1.1.6"
|
||||
typescript: "npm:^5.5.3"
|
||||
|
@ -1660,6 +1712,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"content-disposition@npm:^0.5.3":
|
||||
version: 0.5.4
|
||||
resolution: "content-disposition@npm:0.5.4"
|
||||
dependencies:
|
||||
safe-buffer: "npm:5.2.1"
|
||||
checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cookie@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "cookie@npm:0.6.0"
|
||||
|
@ -1746,6 +1807,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"depd@npm:2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "depd@npm:2.0.0"
|
||||
checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"detect-libc@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "detect-libc@npm:1.0.3"
|
||||
|
@ -1916,6 +1984,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"escape-html@npm:~1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "escape-html@npm:1.0.3"
|
||||
checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"escape-string-regexp@npm:^1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "escape-string-regexp@npm:1.0.5"
|
||||
|
@ -2034,7 +2109,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fastq@npm:^1.17.1":
|
||||
"fastq@npm:^1.17.0, fastq@npm:^1.17.1":
|
||||
version: 1.17.1
|
||||
resolution: "fastq@npm:1.17.1"
|
||||
dependencies:
|
||||
|
@ -2105,7 +2180,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^10.2.2, glob@npm:^10.3.10":
|
||||
"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.4":
|
||||
version: 10.4.2
|
||||
resolution: "glob@npm:10.4.2"
|
||||
dependencies:
|
||||
|
@ -2151,6 +2226,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-entities@npm:^2.5.2":
|
||||
version: 2.5.2
|
||||
resolution: "html-entities@npm:2.5.2"
|
||||
checksum: 10c0/f20ffb4326606245c439c231de40a7c560607f639bf40ffbfb36b4c70729fd95d7964209045f1a4e62fe17f2364cef3d6e49b02ea09016f207fde51c2211e481
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"htmlnano@npm:^2.0.0":
|
||||
version: 2.1.1
|
||||
resolution: "htmlnano@npm:2.1.1"
|
||||
|
@ -2207,6 +2289,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-errors@npm:2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "http-errors@npm:2.0.0"
|
||||
dependencies:
|
||||
depd: "npm:2.0.0"
|
||||
inherits: "npm:2.0.4"
|
||||
setprototypeof: "npm:1.2.0"
|
||||
statuses: "npm:2.0.1"
|
||||
toidentifier: "npm:1.0.1"
|
||||
checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-proxy-agent@npm:^7.0.0":
|
||||
version: 7.0.2
|
||||
resolution: "http-proxy-agent@npm:7.0.2"
|
||||
|
@ -2267,7 +2362,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"inherits@npm:^2.0.3":
|
||||
"inherits@npm:2.0.4, inherits@npm:^2.0.3":
|
||||
version: 2.0.4
|
||||
resolution: "inherits@npm:2.0.4"
|
||||
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
|
||||
|
@ -2623,6 +2718,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mime@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "mime@npm:3.0.0"
|
||||
bin:
|
||||
mime: cli.js
|
||||
checksum: 10c0/402e792a8df1b2cc41cb77f0dcc46472b7944b7ec29cb5bbcd398624b6b97096728f1239766d3fdeb20551dd8d94738344c195a6ea10c4f906eb0356323b0531
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^9.0.4":
|
||||
version: 9.0.5
|
||||
resolution: "minimatch@npm:9.0.5"
|
||||
|
@ -2785,6 +2889,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nanoevents@npm:^9.0.0":
|
||||
version: 9.0.0
|
||||
resolution: "nanoevents@npm:9.0.0"
|
||||
checksum: 10c0/af74c96d0d54ef0ddf6fd163d961b23ec4219ac51d0b70135a2cae49fac1107ddccdcbf7a120601e0bfdd38b15de811f3ce43da5bab967fdee9684e52e4cb5b7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"negotiator@npm:^0.6.3":
|
||||
version: 0.6.3
|
||||
resolution: "negotiator@npm:0.6.3"
|
||||
|
@ -3253,7 +3364,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0":
|
||||
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0":
|
||||
version: 5.2.1
|
||||
resolution: "safe-buffer@npm:5.2.1"
|
||||
checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3
|
||||
|
@ -3306,6 +3417,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"setprototypeof@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "setprototypeof@npm:1.2.0"
|
||||
checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shebang-command@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "shebang-command@npm:2.0.0"
|
||||
|
@ -3410,6 +3528,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"statuses@npm:2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "statuses@npm:2.0.1"
|
||||
checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stream-shift@npm:^1.0.2":
|
||||
version: 1.0.3
|
||||
resolution: "stream-shift@npm:1.0.3"
|
||||
|
@ -3554,6 +3679,20 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"toidentifier@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "toidentifier@npm:1.0.1"
|
||||
checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"toml@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "toml@npm:3.0.0"
|
||||
checksum: 10c0/8d7ed3e700ca602e5419fca343e1c595eb7aa177745141f0761a5b20874b58ee5c878cd045c408da9d130cb2b611c639912210ba96ce2f78e443569aa8060c18
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2.4.0":
|
||||
version: 2.6.3
|
||||
resolution: "tslib@npm:2.6.3"
|
||||
|
|
Loading…
Reference in a new issue