add wordballoons to agents on the webapp
(i know they will be broken, this is just to give an environment for them to actually be used in)
This commit is contained in:
parent
22f0d4a008
commit
396ef67c78
1 changed files with 184 additions and 166 deletions
|
@ -1,186 +1,204 @@
|
||||||
import { createNanoEvents, Emitter, Unsubscribe } from 'nanoevents';
|
import { createNanoEvents, Emitter, Unsubscribe } from 'nanoevents';
|
||||||
import { MSAgentAddUserMessage, MSAgentChatMessage, MSAgentInitMessage, MSAgentJoinMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentRemoveUserMessage, MSAgentTalkMessage } from '@msagent-chat/protocol';
|
import {
|
||||||
|
MSAgentAddUserMessage,
|
||||||
|
MSAgentChatMessage,
|
||||||
|
MSAgentInitMessage,
|
||||||
|
MSAgentJoinMessage,
|
||||||
|
MSAgentProtocolMessage,
|
||||||
|
MSAgentProtocolMessageType,
|
||||||
|
MSAgentRemoveUserMessage,
|
||||||
|
MSAgentTalkMessage
|
||||||
|
} from '@msagent-chat/protocol';
|
||||||
import { User } from './user';
|
import { User } from './user';
|
||||||
import { agentCreateCharacterFromUrl } from '@msagent-chat/msagent.js';
|
import { agentCreateCharacterFromUrl } from '@msagent-chat/msagent.js';
|
||||||
|
|
||||||
export interface MSAgentClientEvents {
|
export interface MSAgentClientEvents {
|
||||||
close: () => void;
|
close: () => void;
|
||||||
join: () => void;
|
join: () => void;
|
||||||
adduser: (user: User) => void;
|
adduser: (user: User) => void;
|
||||||
remuser: (user: User) => void;
|
remuser: (user: User) => void;
|
||||||
chat: (user: User, msg: string) => void;
|
chat: (user: User, msg: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface APIAgentInfo {
|
export interface APIAgentInfo {
|
||||||
friendlyName: string;
|
friendlyName: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MSAgentClient {
|
export class MSAgentClient {
|
||||||
private url: string;
|
private url: string;
|
||||||
private socket: WebSocket | null;
|
private socket: WebSocket | null;
|
||||||
private events: Emitter;
|
private events: Emitter;
|
||||||
private users: User[];
|
private users: User[];
|
||||||
private playingAudio: Map<string, HTMLAudioElement> = new Map();
|
private playingAudio: Map<string, HTMLAudioElement> = new Map();
|
||||||
private charlimit: number = 0;
|
private charlimit: number = 0;
|
||||||
|
|
||||||
private username: string | null = null;
|
|
||||||
private agentContainer: HTMLElement;
|
|
||||||
private agent: string | null = null;
|
|
||||||
|
|
||||||
constructor(url: string, agentContainer: HTMLElement) {
|
private username: string | null = null;
|
||||||
this.url = url;
|
private agentContainer: HTMLElement;
|
||||||
this.agentContainer = agentContainer;
|
private agent: string | null = null;
|
||||||
this.socket = null;
|
|
||||||
this.events = createNanoEvents();
|
|
||||||
this.users = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
on<E extends keyof MSAgentClientEvents>(event: E, callback: MSAgentClientEvents[E]): Unsubscribe {
|
constructor(url: string, agentContainer: HTMLElement) {
|
||||||
return this.events.on(event, callback);
|
this.url = url;
|
||||||
}
|
this.agentContainer = agentContainer;
|
||||||
|
this.socket = null;
|
||||||
|
this.events = createNanoEvents();
|
||||||
|
this.users = [];
|
||||||
|
}
|
||||||
|
|
||||||
async getAgents() {
|
on<E extends keyof MSAgentClientEvents>(event: E, callback: MSAgentClientEvents[E]): Unsubscribe {
|
||||||
let res = await fetch(this.url + "/api/agents");
|
return this.events.on(event, callback);
|
||||||
return await res.json() as APIAgentInfo[];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Promise<void> {
|
async getAgents() {
|
||||||
return new Promise(res => {
|
let res = await fetch(this.url + '/api/agents');
|
||||||
let url = new URL(this.url);
|
return (await res.json()) as APIAgentInfo[];
|
||||||
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 = "/api/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) {
|
connect(): Promise<void> {
|
||||||
if (this.socket === null || this.socket.readyState !== this.socket.OPEN) {
|
return new Promise((res) => {
|
||||||
console.error("Tried to send data on a closed or uninitialized socket");
|
let url = new URL(this.url);
|
||||||
return;
|
switch (url.protocol) {
|
||||||
}
|
case 'http:':
|
||||||
let data = JSON.stringify(msg);
|
url.protocol = 'ws:';
|
||||||
this.socket!.send(data);
|
break;
|
||||||
}
|
case 'https:':
|
||||||
|
url.protocol = 'wss:';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown protocol ${url.protocol}`);
|
||||||
|
}
|
||||||
|
url.pathname = '/api/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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
join(username: string, agent: string) {
|
send(msg: MSAgentProtocolMessage) {
|
||||||
if (this.socket === null || this.socket.readyState !== this.socket.OPEN) {
|
if (this.socket === null || this.socket.readyState !== this.socket.OPEN) {
|
||||||
throw new Error("Tried to join() on a closed or uninitialized socket");
|
console.error('Tried to send data on a closed or uninitialized socket');
|
||||||
}
|
return;
|
||||||
return new Promise<void>(res => {
|
}
|
||||||
let msg: MSAgentJoinMessage = {
|
let data = JSON.stringify(msg);
|
||||||
op: MSAgentProtocolMessageType.Join,
|
this.socket!.send(data);
|
||||||
data: {
|
}
|
||||||
username,
|
|
||||||
agent
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let u = this.on('join', () => {
|
|
||||||
u();
|
|
||||||
res();
|
|
||||||
});
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
talk(msg: string) {
|
join(username: string, agent: string) {
|
||||||
let talkMsg: MSAgentTalkMessage = {
|
if (this.socket === null || this.socket.readyState !== this.socket.OPEN) {
|
||||||
op: MSAgentProtocolMessageType.Talk,
|
throw new Error('Tried to join() on a closed or uninitialized socket');
|
||||||
data: {
|
}
|
||||||
msg
|
return new Promise<void>((res) => {
|
||||||
}
|
let msg: MSAgentJoinMessage = {
|
||||||
};
|
op: MSAgentProtocolMessageType.Join,
|
||||||
this.send(talkMsg);
|
data: {
|
||||||
}
|
username,
|
||||||
|
agent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let u = this.on('join', () => {
|
||||||
|
u();
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
this.send(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getCharlimit() {
|
talk(msg: string) {
|
||||||
return this.charlimit;
|
let talkMsg: MSAgentTalkMessage = {
|
||||||
}
|
op: MSAgentProtocolMessageType.Talk,
|
||||||
|
data: {
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.send(talkMsg);
|
||||||
|
}
|
||||||
|
|
||||||
private async handleMessage(data: string) {
|
getCharlimit() {
|
||||||
let msg: MSAgentProtocolMessage;
|
return this.charlimit;
|
||||||
try {
|
}
|
||||||
msg = JSON.parse(data);
|
|
||||||
} catch (e) {
|
private async handleMessage(data: string) {
|
||||||
console.error(`Failed to parse message from server: ${(e as Error).message}`);
|
let msg: MSAgentProtocolMessage;
|
||||||
return;
|
try {
|
||||||
}
|
msg = JSON.parse(data);
|
||||||
switch (msg.op) {
|
} catch (e) {
|
||||||
case MSAgentProtocolMessageType.Init: {
|
console.error(`Failed to parse message from server: ${(e as Error).message}`);
|
||||||
let initMsg = msg as MSAgentInitMessage;
|
return;
|
||||||
this.username = initMsg.data.username;
|
}
|
||||||
this.agent = initMsg.data.agent;
|
switch (msg.op) {
|
||||||
this.charlimit = initMsg.data.charlimit;
|
case MSAgentProtocolMessageType.Init: {
|
||||||
for (let _user of initMsg.data.users) {
|
let initMsg = msg as MSAgentInitMessage;
|
||||||
let agent = await agentCreateCharacterFromUrl(this.url + "/api/agents/" + _user.agent);
|
this.username = initMsg.data.username;
|
||||||
agent.addToDom(this.agentContainer);
|
this.agent = initMsg.data.agent;
|
||||||
agent.show();
|
this.charlimit = initMsg.data.charlimit;
|
||||||
let user = new User(_user.username, agent);
|
for (let _user of initMsg.data.users) {
|
||||||
this.users.push(user);
|
let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + _user.agent);
|
||||||
}
|
agent.addToDom(this.agentContainer);
|
||||||
this.events.emit('join');
|
agent.show();
|
||||||
break;
|
let user = new User(_user.username, agent);
|
||||||
}
|
this.users.push(user);
|
||||||
case MSAgentProtocolMessageType.AddUser: {
|
}
|
||||||
let addUserMsg = msg as MSAgentAddUserMessage;
|
this.events.emit('join');
|
||||||
let agent = await agentCreateCharacterFromUrl(this.url + "/api/agents/" + addUserMsg.data.agent);
|
break;
|
||||||
agent.addToDom(this.agentContainer);
|
}
|
||||||
agent.show();
|
case MSAgentProtocolMessageType.AddUser: {
|
||||||
let user = new User(addUserMsg.data.username, agent);
|
let addUserMsg = msg as MSAgentAddUserMessage;
|
||||||
this.users.push(user);
|
let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + addUserMsg.data.agent);
|
||||||
this.events.emit('adduser', user);
|
agent.addToDom(this.agentContainer);
|
||||||
break;
|
agent.show();
|
||||||
}
|
let user = new User(addUserMsg.data.username, agent);
|
||||||
case MSAgentProtocolMessageType.RemoveUser: {
|
this.users.push(user);
|
||||||
let remUserMsg = msg as MSAgentRemoveUserMessage;
|
this.events.emit('adduser', user);
|
||||||
let user = this.users.find(u => u.username === remUserMsg.data.username);
|
break;
|
||||||
if (!user) return;
|
}
|
||||||
user.agent.hide(true);
|
case MSAgentProtocolMessageType.RemoveUser: {
|
||||||
if (this.playingAudio.has(user!.username)) {
|
let remUserMsg = msg as MSAgentRemoveUserMessage;
|
||||||
this.playingAudio.delete(user!.username);
|
let user = this.users.find((u) => u.username === remUserMsg.data.username);
|
||||||
}
|
if (!user) return;
|
||||||
this.users.splice(this.users.indexOf(user), 1);
|
user.agent.hide(true);
|
||||||
this.events.emit('remuser', user);
|
if (this.playingAudio.has(user!.username)) {
|
||||||
break;
|
this.playingAudio.delete(user!.username);
|
||||||
}
|
}
|
||||||
case MSAgentProtocolMessageType.Chat: {
|
this.users.splice(this.users.indexOf(user), 1);
|
||||||
let chatMsg = msg as MSAgentChatMessage;
|
this.events.emit('remuser', user);
|
||||||
let user = this.users.find(u => u.username === chatMsg.data.username);
|
break;
|
||||||
this.events.emit('chat', user, chatMsg.data.message);
|
}
|
||||||
if (chatMsg.data.audio !== undefined) {
|
case MSAgentProtocolMessageType.Chat: {
|
||||||
let audio = new Audio(this.url + chatMsg.data.audio);
|
let chatMsg = msg as MSAgentChatMessage;
|
||||||
if (this.playingAudio.has(user!.username)) {
|
let user = this.users.find((u) => u.username === chatMsg.data.username);
|
||||||
this.playingAudio.get(user!.username)?.pause();
|
this.events.emit('chat', user, chatMsg.data.message);
|
||||||
this.playingAudio.delete(user!.username);
|
if (chatMsg.data.audio !== undefined) {
|
||||||
}
|
let audio = new Audio(this.url + chatMsg.data.audio);
|
||||||
this.playingAudio.set(user!.username, audio);
|
if (this.playingAudio.has(user!.username)) {
|
||||||
audio.addEventListener('ended', () => {
|
this.playingAudio.get(user!.username)?.pause();
|
||||||
this.playingAudio.delete(user!.username);
|
this.playingAudio.delete(user!.username);
|
||||||
});
|
}
|
||||||
audio.play();
|
|
||||||
}
|
this.playingAudio.set(user!.username, audio);
|
||||||
break;
|
|
||||||
}
|
audio.addEventListener('ended', () => {
|
||||||
}
|
this.playingAudio.delete(user!.username);
|
||||||
}
|
|
||||||
}
|
// give a bit of time before the wordballoon disappears
|
||||||
|
setTimeout(() => {
|
||||||
|
user!.agent.stopSpeaking();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
user?.agent.speak(chatMsg.data.message);
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue