← back to Exo
add peer connection info to tinychat
2f0b543a1ef4042f74b9ff582c7f9e05160826f5 · 2024-12-17 17:37:40 +0000 · Alex Cheema
Files touched
M exo/tinychat/index.cssM exo/tinychat/index.htmlM exo/tinychat/index.js
Diff
commit 2f0b543a1ef4042f74b9ff582c7f9e05160826f5
Author: Alex Cheema <alexcheema123@gmail.com>
Date: Tue Dec 17 17:37:40 2024 +0000
add peer connection info to tinychat
---
exo/tinychat/index.css | 21 +++++++++++++++++++++
exo/tinychat/index.html | 46 +++++++++++++++++++++++-----------------------
exo/tinychat/index.js | 41 +++++++++++++++++++++++++++--------------
3 files changed, 71 insertions(+), 37 deletions(-)
diff --git a/exo/tinychat/index.css b/exo/tinychat/index.css
index 95c1a905..b82bae26 100644
--- a/exo/tinychat/index.css
+++ b/exo/tinychat/index.css
@@ -721,4 +721,25 @@ main {
.node-details span {
display: flex;
align-items: center;
+}
+
+.peer-connections {
+ margin-top: 8px;
+ padding-left: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.peer-connection {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.85em;
+ color: #a0a0a0;
+}
+
+.peer-connection i {
+ font-size: 0.8em;
+ color: #666;
}
\ No newline at end of file
diff --git a/exo/tinychat/index.html b/exo/tinychat/index.html
index 879b1fa7..4e0617e4 100644
--- a/exo/tinychat/index.html
+++ b/exo/tinychat/index.html
@@ -29,7 +29,7 @@
<!-- Add topology section -->
<div class="topology-section">
<h2 class="megrim-regular">Network Topology</h2>
- <div class="topology-visualization"
+ <div class="topology-visualization"
x-init="initTopology()"
x-ref="topologyViz">
<!-- Loading indicator for topology -->
@@ -42,20 +42,20 @@
</div>
<h2 class="megrim-regular" style="margin-bottom: 20px;">Models</h2>
-
+
<!-- Loading indicator -->
<div class="loading-container" x-show="Object.keys(models).length === 0">
<i class="fas fa-spinner fa-spin"></i>
<span>Loading models...</span>
</div>
-
+
<template x-for="(model, key) in models" :key="key">
- <div class="model-option"
+ <div class="model-option"
:class="{ 'selected': cstate.selectedModel === key }"
@click="cstate.selectedModel = key">
<div class="model-header">
<div class="model-name" x-text="model.name"></div>
- <button
+ <button
@click.stop="deleteModel(key, model)"
class="model-delete-button"
x-show="model.download_percentage > 0">
@@ -71,7 +71,7 @@
<template x-if="!model.loading && model.download_percentage != null">
<span>
<!-- Check if there's an active download for this model -->
- <template x-if="downloadProgress?.some(p =>
+ <template x-if="downloadProgress?.some(p =>
p.repo_id && p.repo_id.toLowerCase().includes(key.toLowerCase()) && !p.isComplete
)">
<i class="fas fa-circle-notch fa-spin"></i>
@@ -80,7 +80,7 @@
</span>
</template>
<template x-if="!model.loading && (model.download_percentage === null || model.download_percentage < 100) && !downloadProgress?.some(p => !p.isComplete)">
- <button
+ <button
@click.stop="handleDownload(key)"
class="model-download-button">
<i class="fas fa-download"></i>
@@ -90,22 +90,22 @@
</div>
</div>
<template x-if="model.total_size">
- <div class="model-size" x-text="model.total_downloaded ?
- `${formatBytes(model.total_downloaded)} / ${formatBytes(model.total_size)}` :
+ <div class="model-size" x-text="model.total_downloaded ?
+ `${formatBytes(model.total_downloaded)} / ${formatBytes(model.total_size)}` :
formatBytes(model.total_size)">
</div>
</template>
</div>
</div>
</template>
- </div>
+ </div>
<!-- Error Toast -->
<div x-show="errorMessage !== null" x-transition.opacity class="toast">
<div class="toast-header">
<span class="toast-error-message" x-text="errorMessage?.basic || ''"></span>
<div class="toast-header-buttons">
- <button @click="errorExpanded = !errorExpanded; if (errorTimeout) { clearTimeout(errorTimeout); errorTimeout = null; }"
- class="toast-expand-button"
+ <button @click="errorExpanded = !errorExpanded; if (errorTimeout) { clearTimeout(errorTimeout); errorTimeout = null; }"
+ class="toast-expand-button"
x-show="errorMessage?.stack">
<span x-text="errorExpanded ? 'Hide Details' : 'Show Details'"></span>
</button>
@@ -134,8 +134,8 @@
" x-show="home === 0" x-transition="">
<h1 class="title megrim-regular">tinychat</h1>
<template x-if="histories.length">
- <button
- @click="if(confirm('Are you sure you want to clear all history?')) clearAllHistory();"
+ <button
+ @click="if(confirm('Are you sure you want to clear all history?')) clearAllHistory();"
class="clear-history-button">
<i class="fas fa-trash"></i> Clear All History
</button>
@@ -177,14 +177,14 @@
</template>
</div>
</div>
-<button
+<button
@click="
home = 0;
cstate = { time: null, messages: [], selectedModel: cstate.selectedModel };
time_till_first = 0;
tokens_per_second = 0;
total_tokens = 0;
- "
+ "
class="back-button"
x-show="home === 2">
<i class="fas fa-arrow-left"></i>
@@ -247,7 +247,7 @@
<p><strong>Model:</strong> <span x-text="progress.repo_id + '@' + progress.repo_revision"></span></p>
<p><strong>Status:</strong> <span x-text="progress.status"></span></p>
<div class="progress-bar-container">
- <div class="progress-bar"
+ <div class="progress-bar"
:class="progress.isComplete ? 'complete' : 'in-progress'"
:style="`width: ${progress.percentage}%;`">
</div>
@@ -291,10 +291,10 @@
<i class="fas fa-times"></i>
</button>
</div>
-<textarea
- :disabled="generating || (downloadProgress?.length > 0 && downloadProgress.some(p => !p.isComplete))"
+<textarea
+ :disabled="generating || (downloadProgress?.length > 0 && downloadProgress.some(p => !p.isComplete))"
:placeholder="
- generating ? 'Generating...' :
+ generating ? 'Generating...' :
(downloadProgress?.length > 0 && downloadProgress.some(p => !p.isComplete)) ? 'Download in progress...' :
'Say something'
"
@@ -326,9 +326,9 @@
});
"
x-ref="inputForm"></textarea>
-<button
- :disabled="generating || (downloadProgress?.length > 0 && downloadProgress.some(p => !p.isComplete))"
- @click="await handleSend()"
+<button
+ :disabled="generating || (downloadProgress?.length > 0 && downloadProgress.some(p => !p.isComplete))"
+ @click="await handleSend()"
class="input-button">
<i :class="generating ? 'fa-spinner fa-spin' : 'fa-paper-plane'" class="fas"></i>
</button>
diff --git a/exo/tinychat/index.js b/exo/tinychat/index.js
index e909a57f..48c5a23c 100644
--- a/exo/tinychat/index.js
+++ b/exo/tinychat/index.js
@@ -5,7 +5,7 @@ document.addEventListener("alpine:init", () => {
time: null,
messages: [],
selectedModel: 'llama-3.2-1b',
- },
+ },
// historical state
histories: JSON.parse(localStorage.getItem("histories")) || [],
@@ -13,7 +13,7 @@ document.addEventListener("alpine:init", () => {
home: 0,
generating: false,
endpoint: `${window.location.origin}/v1`,
-
+
// Initialize error message structure
errorMessage: null,
errorExpanded: false,
@@ -51,7 +51,7 @@ document.addEventListener("alpine:init", () => {
// Start polling for download progress
this.startDownloadProgressPolling();
-
+
// Start model polling with the new pattern
this.startModelPolling();
},
@@ -85,14 +85,14 @@ document.addEventListener("alpine:init", () => {
async populateSelector() {
return new Promise((resolve, reject) => {
const evtSource = new EventSource(`${window.location.origin}/modelpool`);
-
+
evtSource.onmessage = (event) => {
if (event.data === "[DONE]") {
evtSource.close();
resolve();
return;
}
-
+
const modelData = JSON.parse(event.data);
// Update existing model data while preserving other properties
Object.entries(modelData).forEach(([modelName, data]) => {
@@ -105,7 +105,7 @@ document.addEventListener("alpine:init", () => {
}
});
};
-
+
evtSource.onerror = (error) => {
console.error('EventSource failed:', error);
evtSource.close();
@@ -455,7 +455,7 @@ document.addEventListener("alpine:init", () => {
stack: error.stack || ""
};
this.errorExpanded = false;
-
+
if (this.errorTimeout) {
clearTimeout(this.errorTimeout);
}
@@ -470,10 +470,10 @@ document.addEventListener("alpine:init", () => {
async deleteModel(modelName, model) {
const downloadedSize = model.total_downloaded || 0;
- const sizeMessage = downloadedSize > 0 ?
+ const sizeMessage = downloadedSize > 0 ?
`This will free up ${this.formatBytes(downloadedSize)} of space.` :
'This will remove any partially downloaded files.';
-
+
if (!confirm(`Are you sure you want to delete ${model.name}? ${sizeMessage}`)) {
return;
}
@@ -487,7 +487,7 @@ document.addEventListener("alpine:init", () => {
});
const data = await response.json();
-
+
if (!response.ok) {
throw new Error(data.detail || 'Failed to delete model');
}
@@ -562,10 +562,10 @@ document.addEventListener("alpine:init", () => {
initTopology() {
// Initial fetch
this.updateTopology();
-
+
// Set up periodic updates
this.topologyInterval = setInterval(() => this.updateTopology(), 5000);
-
+
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (this.topologyInterval) {
@@ -577,14 +577,24 @@ document.addEventListener("alpine:init", () => {
async updateTopology() {
const topologyData = await this.fetchTopology();
if (!topologyData) return;
-
+
const vizElement = this.$refs.topologyViz;
vizElement.innerHTML = ''; // Clear existing visualization
-
+
// Create nodes from object
Object.entries(topologyData.nodes).forEach(([nodeId, node]) => {
const nodeElement = document.createElement('div');
nodeElement.className = 'topology-node';
+
+ // Get peer connections for this node
+ const peerConnections = topologyData.peer_graph[nodeId] || [];
+ const peerConnectionsHtml = peerConnections.map(peer => `
+ <div class="peer-connection">
+ <i class="fas fa-arrow-right"></i>
+ <span>To ${peer.to_id}: ${peer.description}</span>
+ </div>
+ `).join('');
+
nodeElement.innerHTML = `
<div class="node-info">
<span class="status ${nodeId === topologyData.active_node_id ? 'active' : 'inactive'}"></span>
@@ -595,6 +605,9 @@ document.addEventListener("alpine:init", () => {
<span>${(node.memory / 1024).toFixed(1)}GB RAM</span>
<span>${node.flops.fp32.toFixed(1)} TF</span>
</div>
+ <div class="peer-connections">
+ ${peerConnectionsHtml}
+ </div>
`;
vizElement.appendChild(nodeElement);
});
← 7ac40043 change it back to collecting topology periodically even if p
·
back to Exo
·
support different network interface tests 023ddc20 →