Skip to content

Commit

Permalink
Merge pull request #1 from jordojordo/proxy
Browse files Browse the repository at this point in the history
Add proxy for websocket connection
  • Loading branch information
jordojordo authored May 27, 2024
2 parents f01c26c + 2fc6f92 commit ce11f97
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 34 deletions.
5 changes: 4 additions & 1 deletion .env.production
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
VITE_THOTHSCRIPT_API=CLIENT_THOTHSCRIPT_API
VITE_WS_SCHEME=CLIENT_THOTHSCRIPT_PROXY_SCHEME
VITE_WS_HOST=CLIENT_THOTHSCRIPT_PROXY_HOST
VITE_WS_PORT=CLIENT_THOTHSCRIPT_PROXY_PORT
VITE_WS_PATH=CLIENT_THOTHSCRIPT_PROXY_PATH
2 changes: 1 addition & 1 deletion Containerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:latest as build-stage
FROM node:22-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN yarn
Expand Down
4 changes: 4 additions & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

interface ImportMetaEnv {
readonly VITE_THOTHSCRIPT_API: string;
readonly VITE_WS_SECURE: boolean;
readonly VITE_WS_HOST: string;
readonly VITE_WS_PORT: string;
readonly VITE_WS_PATH: string;
readonly BASE_URL: string;
readonly PROD: boolean
}
Expand Down
5 changes: 4 additions & 1 deletion nginx.conf.template
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ http {
}

location /ws/ {
proxy_pass __CLIENT_THOTHSCRIPT_API__;
proxy_pass http://__CLIENT_THOTHSCRIPT_OPERATOR_HOST__.__CLIENT_THOTHSCRIPT_OPERATOR_NAMESPACE__.svc.cluster.local:__CLIENT_THOTHSCRIPT_OPERATOR_PORT__;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
access_log /var/log/nginx/websocket_access.log main;
}

Expand Down
18 changes: 15 additions & 3 deletions scripts/start.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
#!/bin/sh

# Provide default values for CLIENT_THOTHSCRIPT_API if it's not set
: "${CLIENT_THOTHSCRIPT_API:=localhost:3000}"

# Provide default values for CLIENT_THOTHSCRIPT_API_SCHEME, CLIENT_THOTHSCRIPT_API_HOST, CLIENT_THOTHSCRIPT_API_PORT, and CLIENT_THOTHSCRIPT_API_PATH if not set
: "${CLIENT_THOTHSCRIPT_PROXY_SCHEME:=ws}"
: "${CLIENT_THOTHSCRIPT_PROXY_HOST:=localhost}"
: "${CLIENT_THOTHSCRIPT_PROXY_PORT:=80}"
: "${CLIENT_THOTHSCRIPT_PROXY_PATH:=/ws/}"

# Provide default values for CLIENT_THOTHSCRIPT_OPERATOR_HOST, CLIENT_THOTHSCRIPT_OPERATOR_NAMESPACE, and CLIENT_THOTHSCRIPT_OPERATOR_PORT if not set
: "${CLIENT_THOTHSCRIPT_OPERATOR_HOST:=thothscript-operator}"
: "${CLIENT_THOTHSCRIPT_OPERATOR_NAMESPACE:=thothscript}"
: "${CLIENT_THOTHSCRIPT_OPERATOR_PORT:=3000}"

# Substitute environment variables in nginx.conf.template and create nginx.conf
sed "s|__CLIENT_THOTHSCRIPT_API__|http:\/\/$CLIENT_THOTHSCRIPT_API|g" /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
sed -e "s|__CLIENT_THOTHSCRIPT_OPERATOR_HOST__|${CLIENT_THOTHSCRIPT_OPERATOR_HOST}|g" \
-e "s|__CLIENT_THOTHSCRIPT_OPERATOR_NAMESPACE__|${CLIENT_THOTHSCRIPT_OPERATOR_NAMESPACE}|g" \
-e "s|__CLIENT_THOTHSCRIPT_OPERATOR_PORT__|${CLIENT_THOTHSCRIPT_OPERATOR_PORT}|g" \
/etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf

for i in $(env | grep CLIENT_); do
key=$(echo $i | cut -d '=' -f 1)
Expand Down
16 changes: 10 additions & 6 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@ import websocket from '@/services/websocket';
const toolConfigStore = useToolConfigStore();
toolConfigStore.loadFromLocalStorage();
onMounted(() => {
websocket.connect(toolConfigStore.websocketUrl);
websocket.connect();
});
onUnmounted(() => {
websocket.disconnect();
});
// Watch for changes in the WebSocket URL and reconnect if it changes
watch(() => toolConfigStore.websocketUrl, (newUrl, oldUrl) => {
if ( newUrl !== oldUrl ) {
// Watch for changes in the WebSocket config and reconnect if it changes
watch(() => toolConfigStore.websocket, () => {
if ( toolConfigStore.shouldReconnect ) {
websocket.disconnect();
websocket.connect(newUrl);
websocket.connect();
toolConfigStore.setShouldReconnect(false);
}
});
}, { deep: true });
</script>

<template>
Expand Down
46 changes: 32 additions & 14 deletions src/components/Config.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useToolConfigStore } from '@/stores/useToolConfigStore';
const toolConfigStore = useToolConfigStore();
const localConfig = reactive({
websocketUrl: toolConfigStore.websocketUrl,
modelName: toolConfigStore.modelName,
maxTokens: toolConfigStore.maxTokens,
temperature: toolConfigStore.temperature,
chat: toolConfigStore.chat,
runOpts: {
websocket: {
host: toolConfigStore.websocket.host,
port: toolConfigStore.websocket.port,
keepAliveInterval: toolConfigStore.websocket.keepAliveInterval,
},
modelName: toolConfigStore.modelName,
maxTokens: toolConfigStore.maxTokens,
temperature: toolConfigStore.temperature,
chat: toolConfigStore.chat,
runOpts: {
disableCache: toolConfigStore.runOpts.disableCache,
quiet: toolConfigStore.runOpts.quiet,
workspace: toolConfigStore.runOpts.workspace,
}
quiet: toolConfigStore.runOpts.quiet,
workspace: toolConfigStore.runOpts.workspace,
},
});
const saveButton = ref('Save');
const saveConfig = () => {
toolConfigStore.websocketUrl = localConfig.websocketUrl;
toolConfigStore.websocket.host = localConfig.websocket.host;
toolConfigStore.websocket.port = localConfig.websocket.port;
toolConfigStore.websocket.keepAliveInterval = localConfig.websocket.keepAliveInterval;
toolConfigStore.modelName = localConfig.modelName;
toolConfigStore.maxTokens = localConfig.maxTokens;
toolConfigStore.temperature = localConfig.temperature;
Expand All @@ -29,25 +36,35 @@ const saveConfig = () => {
toolConfigStore.runOpts.quiet = localConfig.runOpts.quiet;
toolConfigStore.runOpts.workspace = localConfig.runOpts.workspace;
// Display feedback message
toolConfigStore.setShouldReconnect(true);
toolConfigStore.saveToLocalStorage();
saveButton.value = 'Saved!';
// Hide message after 3 seconds
setTimeout(() => {
saveButton.value = 'Save';
}, 3000);
};
</script>


<template>
<section class="chat-config">
<h2>Settings</h2>
<form @submit.prevent="saveConfig">
<fieldset>
<legend>Connection</legend>
<div>
<label>ThothScript Operator URL</label>
<input v-model="localConfig.websocketUrl" type="text" />
<label>Host</label>
<input v-model="localConfig.websocket.host" type="text" />
</div>
<div>
<label>Port</label>
<input v-model="localConfig.websocket.port" type="text" />
</div>
<div>
<label>Keep-Alive Interval (seconds)</label>
<input v-model.number="localConfig.websocket.keepAliveInterval" type="number" />
</div>
</fieldset>
<fieldset>
Expand Down Expand Up @@ -94,6 +111,7 @@ const saveConfig = () => {
</section>
</template>


<style scoped>
.chat-config {
display: flex;
Expand Down
36 changes: 33 additions & 3 deletions src/services/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,43 @@ const state = reactive<WebSocketState>({

let socket: WebSocket | null = null;
let reconnectTimeout: NodeJS.Timeout | null = null;
let keepAliveInterval: number | null = null;

const maxReconnectAttempts = 10; // Limit number of attempts within 10 minutes
const reconnectInterval = 10000; // Try to reconnect every 10 seconds

const connect = (url?: string) => {
const connect = () => {
if ( reconnectTimeout !== null ) {
clearTimeout(reconnectTimeout);
}

const toolConfigStore = useToolConfigStore();

const KEEP_ALIVE_INTERVAL = ( toolConfigStore.websocket.keepAliveInterval || 120 ) * 1000;
const { secure, host, port, path } = toolConfigStore.websocket;

const scheme = secure ? 'wss' : 'ws';
const hostStr = host ? host : window.location.hostname;
const portStr = port ? `:${port}` : '';
const pathStr = path ? path : '/ws/';
const websocketUrl = `${ scheme }://${ hostStr }${ portStr }${ pathStr }`;

socket = new WebSocket(`ws://${ url || toolConfigStore.websocketUrl }/ws/`);
// console.log(`Connecting to WebSocket: ${websocketUrl}`);
socket = new WebSocket(websocketUrl);

socket.onopen = () => {
state.isConnected = true;
state.reconnectAttempts = 0;
emitGrowl('Connected', 'success');
emitter.emit('connected');
console.log('WebSocket connected to:', websocketUrl); // eslint-disable-line no-console

// Start keep-alive interval
keepAliveInterval = window.setInterval(() => {
if ( socket && socket.readyState === WebSocket.OPEN ) {
socket.send(JSON.stringify({ type: 'ping' }));
}
}, KEEP_ALIVE_INTERVAL);
};

socket.onmessage = (event: MessageEvent) => {
Expand All @@ -53,10 +73,15 @@ const connect = (url?: string) => {
if ( state.reconnectAttempts < maxReconnectAttempts ) {
emitGrowl('Disconnected. Reconnecting...', 'warning');
state.reconnectAttempts++;
reconnectTimeout = setTimeout(() => connect(url), reconnectInterval);
reconnectTimeout = setTimeout(() => connect(), reconnectInterval);
} else {
emitGrowl('Failed to reconnect after multiple attempts.', 'error');
}

if ( keepAliveInterval ) {
clearInterval(keepAliveInterval);
keepAliveInterval = null;
}
};

socket.onerror = (error: Event) => {
Expand Down Expand Up @@ -128,6 +153,11 @@ const disconnect = () => {
if ( socket && state.isConnected ) {
socket.close();
}

if ( keepAliveInterval ) {
clearInterval(keepAliveInterval);
keepAliveInterval = null;
}
};

const emitGrowl = (message: string, type: 'success' | 'warning' | 'error') => {
Expand Down
33 changes: 28 additions & 5 deletions src/stores/useToolConfigStore.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { defineStore } from 'pinia';
import type { RunOpts } from '@gptscript-ai/gptscript';

import { getWebsocketConfig } from '@/utils/service';
import type { WebsocketConfig } from '@/types/chat';

const LOCAL_STORAGE_KEY = 'toolConfig';

export interface ToolConfigState {
maxTokens: number;
modelName: string;
temperature: number;
chat: boolean;
runOpts: RunOpts;
websocketUrl: string;
websocket: WebsocketConfig;
shouldReconnect: boolean;
}

export const useToolConfigStore = defineStore('toolConfig', {
Expand All @@ -25,7 +31,11 @@ export const useToolConfigStore = defineStore('toolConfig', {
subTool: '',
workspace: '',
},
websocketUrl: import.meta.env.VITE_THOTHSCRIPT_API || 'localhost:3000'
websocket: {
...getWebsocketConfig(),
keepAliveInterval: 120,
},
shouldReconnect: false,
}),
actions: {
updateMaxTokens(maxTokens: number) {
Expand All @@ -43,8 +53,21 @@ export const useToolConfigStore = defineStore('toolConfig', {
updateRunOpts(runOpts: RunOpts) {
this.runOpts = runOpts;
},
updateWebSocketUrl(url: string) {
this.websocketUrl = url;
}
updateWebSocketConfig(config: WebsocketConfig) {
this.websocket = config;
},
setShouldReconnect(value: boolean) {
this.shouldReconnect = value;
},
saveToLocalStorage() {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.$state));
},
loadFromLocalStorage() {
const storedState = localStorage.getItem(LOCAL_STORAGE_KEY);

if ( storedState ) {
this.$patch(JSON.parse(storedState));
}
},
},
});
8 changes: 8 additions & 0 deletions src/types/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@ export interface SystemMessage {
output: SystemOutput
error?: object;
}

export interface WebsocketConfig {
secure: boolean;
host: string;
port: string;
path: string;
keepAliveInterval?: number;
}
15 changes: 15 additions & 0 deletions src/utils/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { WebsocketConfig } from '@/types/chat';

export function getWebsocketConfig(): WebsocketConfig {
const secure = import.meta.env.VITE_WS_SECURE === true;
const host = import.meta.env.VITE_WS_HOST || window.location.hostname;
const port = import.meta.env.VITE_WS_PORT || '';
const path = import.meta.env.VITE_WS_PATH || '/ws/';

return {
secure,
host,
port,
path
};
}

0 comments on commit ce11f97

Please sign in to comment.