feat: typing indicator mit agent-name und random phrases

- typing-phrases.js in index.html eingebunden
- startTypingIndicator() / stopTypingIndicator() in chat.js
- Agent-Name via setCurrentAgentName() aus chat-page.js gesetzt
- Phrase wechselt alle 3 Sekunden zufällig
- Cleanup bei destroy() und finally-Block
This commit is contained in:
Meleki committed 2026-03-11 14:25:30 +01:00
1 parent c5f2ed54fd
commit f941eae00a
5 files changed
+74 -6

No files matched your search

+1 -1
View File
@@ -46,7 +46,7 @@
- [x] ~~Claude (Sub-)Agents~~ – Nicht umsetzbar: Claude Code Sub-Agents sind ein CLI-Feature, nicht über die Messages API verfügbar. Eigenes Sub-Agent-System (Preparer/Review/Finalizer) deckt den Use-Case bereits ab. - [x] ~~Claude (Sub-)Agents~~ – Nicht umsetzbar: Claude Code Sub-Agents sind ein CLI-Feature, nicht über die Messages API verfügbar. Eigenes Sub-Agent-System (Preparer/Review/Finalizer) deckt den Use-Case bereits ab.
## Features aus Devy übernehmen ## Features aus Devy übernehmen
- [ ] 💬 Typing-Indicator - Animierter Indikator statt statischem "denkt nach..." - [ ] 💬 Typing-Indicator - Animierter Indikator statt statischem "Streaming..."
- [ ] 🎨 Chat-Hintergründe - Benutzerdefinierte Hintergründe pro Session - [ ] 🎨 Chat-Hintergründe - Benutzerdefinierte Hintergründe pro Session
- [x] 👤 User-Management - Admin-only: Benutzer CRUD, Login-as-User, Default-Pfade - [x] 👤 User-Management - Admin-only: Benutzer CRUD, Login-as-User, Default-Pfade
- [x] 🖥️ Windows Desktop App - WPF + WebView2 Wrapper mit konfigurierbarer URL - [x] 🖥️ Windows Desktop App - WPF + WebView2 Wrapper mit konfigurierbarer URL
+1
View File
@@ -616,6 +616,7 @@
<script src="js/reply.js"></script> <script src="js/reply.js"></script>
<script src="js/files.js"></script> <script src="js/files.js"></script>
<script src="js/file-explorer.js"></script> <script src="js/file-explorer.js"></script>
<script src="js/typing-phrases.js"></script>
<script src="js/chat.js"></script> <script src="js/chat.js"></script>
<script src="js/history.js"></script> <script src="js/history.js"></script>
<script src="js/archive.js"></script> <script src="js/archive.js"></script>
+36 -3
View File
@@ -161,6 +161,38 @@ let _currentToolBox = null; // Aktuell laufende Tool-Box (für Stream-Events)
let _chatAbandoned = false; // true wenn User weg-navigiert während Stream läuft let _chatAbandoned = false; // true wenn User weg-navigiert während Stream läuft
let _abortController = null; // AbortController für laufenden Stream let _abortController = null; // AbortController für laufenden Stream
// ── Typing Indicator ──
let _currentAgentName = null;
let _typingInterval = null;
function setCurrentAgentName(name) {
_currentAgentName = name;
}
function _randomTypingPhrase() {
return TYPING_PHRASES[Math.floor(Math.random() * TYPING_PHRASES.length)];
}
function _buildTypingHtml(phrase) {
var name = _currentAgentName || 'Agent';
return '<span><i class="fa fa-hourglass-half"></i> ' + esc(name) + ' ' + esc(phrase) + '...</span>';
}
function startTypingIndicator() {
setMetaHtml(_buildTypingHtml(_randomTypingPhrase()));
_typingInterval = setInterval(function() {
setMetaHtml(_buildTypingHtml(_randomTypingPhrase()));
}, 3000);
}
function stopTypingIndicator() {
if (_typingInterval) {
clearInterval(_typingInterval);
_typingInterval = null;
}
}
async function clearChat() { async function clearChat() {
const archive = await apiArchive(getCurrentSessionId()); const archive = await apiArchive(getCurrentSessionId());
if (!archive) return; if (!archive) return;
@@ -213,7 +245,7 @@ async function _runStream(fetchPromise) {
_currentToolBox = null; _currentToolBox = null;
_chatAbandoned = false; _chatAbandoned = false;
_stopClickCount = 0; _stopClickCount = 0;
setMetaHtml('<span><i class="fa fa-hourglass-half"></i> Streaming...</span>'); startTypingIndicator();
try { try {
const resp = await fetchPromise(_abortController.signal); const resp = await fetchPromise(_abortController.signal);
@@ -250,7 +282,7 @@ async function _runStream(fetchPromise) {
try { try {
const status = await apiIsStreaming(sid); const status = await apiIsStreaming(sid);
if (status.streaming) { if (status.streaming) {
setMetaHtml('<span><i class="fa fa-hourglass-half"></i> Streaming... (Verbindung wiederhergestellt)</span>'); startTypingIndicator();
startStreamingPoll(sid, sendBtn, status.chainId); startStreamingPoll(sid, sendBtn, status.chainId);
return; return;
} }
@@ -259,6 +291,7 @@ async function _runStream(fetchPromise) {
} }
} finally { } finally {
if (_chatAbandoned) return; if (_chatAbandoned) return;
stopTypingIndicator();
if (!isStreaming || !_streamingPollInterval) { if (!isStreaming || !_streamingPollInterval) {
sendBtn.disabled = false; sendBtn.disabled = false;
sendBtn.hidden = false; sendBtn.hidden = false;
@@ -269,7 +302,7 @@ async function _runStream(fetchPromise) {
_abortController = null; _abortController = null;
// Meta zurücksetzen falls kein done-Event kam (z.B. nach Stop) // Meta zurücksetzen falls kein done-Event kam (z.B. nach Stop)
var metaEl = $('#meta'); var metaEl = $('#meta');
if (metaEl && metaEl.textContent.includes('Streaming')) { if (metaEl && (metaEl.textContent.includes('Streaming') || metaEl.textContent.includes('...'))) {
setMeta(_stopClickCount > 0 ? 'Gestoppt' : 'Bereit'); setMeta(_stopClickCount > 0 ? 'Gestoppt' : 'Bereit');
} }
} }
+7 -2
View File
@@ -202,8 +202,11 @@ registerPage('chat', {
if (session.agentId) { if (session.agentId) {
try { try {
const agent = await apiGetAgent(session.agentId); const agent = await apiGetAgent(session.agentId);
if (agent && agent.customCommands) { if (agent) {
agentCmds = JSON.parse(agent.customCommands) || []; setCurrentAgentName(agent.name || null);
if (agent.customCommands) {
agentCmds = JSON.parse(agent.customCommands) || [];
}
} }
} catch(e) {} } catch(e) {}
} }
@@ -390,6 +393,8 @@ registerPage('chat', {
// Stream nicht abbrechen (Backend soll weiterlaufen), // Stream nicht abbrechen (Backend soll weiterlaufen),
// aber Frontend-Processing stoppen // aber Frontend-Processing stoppen
_chatAbandoned = true; _chatAbandoned = true;
stopTypingIndicator();
setCurrentAgentName(null);
closeEmojiPicker(); closeEmojiPicker();
if (this._closeMenu) { if (this._closeMenu) {
document.removeEventListener('click', this._closeMenu); document.removeEventListener('click', this._closeMenu);
+29
View File
@@ -0,0 +1,29 @@
const TYPING_PHRASES = [
'denkt si wos',
'grübelt',
'schaut deppat drein',
'brunzt',
'scheißt an Kropfn',
'krotzt si am Hirn',
'hoit sei Goschn',
'is am Schmäh fian',
'raunzt leise vor si hi',
'fladert a Idee',
'schleicht si boid',
'red mit si söba',
'brettlt durch die Daten',
'sudert',
'bockts nimma',
'schaut obs eh passt',
'mocht a Hetz',
'wuzelt si durch',
'hod an Bickn',
'zwickt si',
'draht Dahma',
'schnackselt mit der API',
'is gonz narrisch',
'hackelt',
'sudert übers Wetter',
'fetzt si mitn Code',
'is am Wurschteln',
];