mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
fix chat typing stuff and alert feeder
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -78,8 +78,8 @@
|
|||||||
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
||||||
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-CZg2vwtM.js"></script>
|
<script type="module" crossorigin src="/assets/index-CsI4p8Hs.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CflZqP0e.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-B_VrMPCY.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
1. make google tts the default everywhere but roverd
|
1. fix rover request spam queue cheat
|
||||||
2. fix rover request spam queue cheat
|
2. fix this:
|
||||||
3. make alert feed.jsx show more alerts at once
|
|
||||||
4. unify typing row and chat row, should be simple
|
|
||||||
5. fix google TTS speeds
|
|
||||||
6. fix this:
|
|
||||||
`Jun 18 15:14:18 roombaserver.local node[216731]: /home/daniel/MultiRoombaRover/server/src/services/roverManager/socketHandlers.js:92
|
`Jun 18 15:14:18 roombaserver.local node[216731]: /home/daniel/MultiRoombaRover/server/src/services/roverManager/socketHandlers.js:92
|
||||||
Jun 18 15:14:18 roombaserver.local node[216731]: cb({ error: err.message });
|
Jun 18 15:14:18 roombaserver.local node[216731]: cb({ error: err.message });
|
||||||
Jun 18 15:14:18 roombaserver.local node[216731]: ^
|
Jun 18 15:14:18 roombaserver.local node[216731]: ^
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { useEffect, useMemo, useState } from 'react';
|
|||||||
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
|
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
|
||||||
import { useSocket } from '../../context/SocketContext.jsx';
|
import { useSocket } from '../../context/SocketContext.jsx';
|
||||||
import ChatMessageRow from '../ChatMessageRow/index.jsx';
|
import ChatMessageRow from '../ChatMessageRow/index.jsx';
|
||||||
import ChatTypingRow from '../ChatTypingRow/index.jsx';
|
|
||||||
import ButtonBoxTile from '../ButtonBoxTile/index.jsx';
|
import ButtonBoxTile from '../ButtonBoxTile/index.jsx';
|
||||||
|
|
||||||
const LIFETIME_MS = 3000;
|
const LIFETIME_MS = 3000;
|
||||||
@@ -104,7 +103,7 @@ export default function AlertFeed({ scale = 1 }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
visible: Array.from(visibleByKey.values()).slice(-3),
|
visible: Array.from(visibleByKey.values()).slice(-6),
|
||||||
nextExpiryAt: soonestExpiryAt,
|
nextExpiryAt: soonestExpiryAt,
|
||||||
};
|
};
|
||||||
}, [latest, now]);
|
}, [latest, now]);
|
||||||
@@ -234,7 +233,7 @@ function AlertToast({ alert }) {
|
|||||||
return <ChatMessageRow message={alert.payload} />;
|
return <ChatMessageRow message={alert.payload} />;
|
||||||
}
|
}
|
||||||
if (alert.kind === 'chat-typing' && alert.payload) {
|
if (alert.kind === 'chat-typing' && alert.payload) {
|
||||||
return <ChatTypingRow message={alert.payload} />;
|
return <ChatMessageRow message={alert.payload} variant="typing" />;
|
||||||
}
|
}
|
||||||
if (alert.kind === 'barcode-scan' && alert.payload) {
|
if (alert.kind === 'barcode-scan' && alert.payload) {
|
||||||
return <BarcodeScanToast payload={alert.payload} />;
|
return <BarcodeScanToast payload={alert.payload} />;
|
||||||
|
|||||||
@@ -138,7 +138,15 @@ export function ChatIdentity({ message, toolsToggle = null }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function chatRowClass(message) {
|
function chatRowClass(message, variant = 'message') {
|
||||||
|
// Typing indicators intentionally reuse the normal chat row shell so avatar,
|
||||||
|
// nickname, rover identity, and Discord identity all stay visually aligned
|
||||||
|
// with real chat messages. The darker slate treatment separates the temporary
|
||||||
|
// "typing..." state without requiring a second component with duplicated
|
||||||
|
// identity markup.
|
||||||
|
if (variant === 'typing') {
|
||||||
|
return 'flex flex-col gap-0.5 rounded-md border border-slate-600/40 bg-slate-900/40 px-0.5 py-0.5 text-sm text-neutral-100 italic opacity-80';
|
||||||
|
}
|
||||||
if (isBotMessage(message)) {
|
if (isBotMessage(message)) {
|
||||||
return 'flex flex-col gap-0.5 rounded-md border border-emerald-500/40 bg-emerald-950 px-0.5 py-0.5 text-sm text-neutral-100';
|
return 'flex flex-col gap-0.5 rounded-md border border-emerald-500/40 bg-emerald-950 px-0.5 py-0.5 text-sm text-neutral-100';
|
||||||
}
|
}
|
||||||
@@ -153,12 +161,17 @@ function chatRowClass(message) {
|
|||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatMessageRow({ message }) {
|
export default function ChatMessageRow({ message, variant = 'message' }) {
|
||||||
const isBot = isBotMessage(message);
|
const isBot = isBotMessage(message);
|
||||||
|
const isTyping = variant === 'typing';
|
||||||
const role = useSessionSelector((state) => state.session?.role || null);
|
const role = useSessionSelector((state) => state.session?.role || null);
|
||||||
const isSpectator = role === 'spectator';
|
const isSpectator = role === 'spectator';
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const toolCalls = Array.isArray(message?.toolCalls) ? message.toolCalls : [];
|
// Typing payloads are transient presence events, not persisted chat messages.
|
||||||
|
// Treating them as a row variant prevents tool expansion and message-specific
|
||||||
|
// controls from appearing if a future typing payload happens to carry fields
|
||||||
|
// that overlap with stored chat message data.
|
||||||
|
const toolCalls = !isTyping && Array.isArray(message?.toolCalls) ? message.toolCalls : [];
|
||||||
const hasToolCalls = toolCalls.length > 0;
|
const hasToolCalls = toolCalls.length > 0;
|
||||||
// Spectator chat should expose tool details without requiring interaction, but
|
// Spectator chat should expose tool details without requiring interaction, but
|
||||||
// only rows that actually contain tools need the expanded two-section layout.
|
// only rows that actually contain tools need the expanded two-section layout.
|
||||||
@@ -167,6 +180,7 @@ export default function ChatMessageRow({ message }) {
|
|||||||
// row open even when there is no dropdown content to show.
|
// row open even when there is no dropdown content to show.
|
||||||
const isOpen = hasToolCalls && (isSpectator || open);
|
const isOpen = hasToolCalls && (isSpectator || open);
|
||||||
const hasText = Boolean(String(message?.text || '').trim());
|
const hasText = Boolean(String(message?.text || '').trim());
|
||||||
|
const rowText = isTyping ? 'typing...' : message.text;
|
||||||
const toolsToggle =
|
const toolsToggle =
|
||||||
hasToolCalls ? (
|
hasToolCalls ? (
|
||||||
<button
|
<button
|
||||||
@@ -181,17 +195,19 @@ export default function ChatMessageRow({ message }) {
|
|||||||
</button>
|
</button>
|
||||||
) : null;
|
) : null;
|
||||||
return (
|
return (
|
||||||
<div className={chatRowClass(message)}>
|
<div className={chatRowClass(message, variant)}>
|
||||||
<div className="flex w-full items-start gap-0.5">
|
<div className="flex w-full items-start gap-0.5">
|
||||||
<span
|
<span
|
||||||
className={`min-w-0 flex-1 break-words leading-tight whitespace-pre-wrap ${isBot ? 'text-emerald-100' : 'text-slate-100'}`}
|
className={`min-w-0 flex-1 break-words leading-tight whitespace-pre-wrap ${isBot ? 'text-emerald-100' : 'text-slate-100'}`}
|
||||||
>
|
>
|
||||||
<ChatIdentity message={message} toolsToggle={toolsToggle} />
|
<ChatIdentity message={message} toolsToggle={toolsToggle} />
|
||||||
{!isOpen && hasText ? ` ${message.text}` : ''}
|
{!isOpen && (hasText || isTyping) ? ` ${rowText}` : ''}
|
||||||
</span>
|
|
||||||
<span className="shrink-0 text-[0.65rem] text-slate-400/60">
|
|
||||||
{formatTime(message.ts)}
|
|
||||||
</span>
|
</span>
|
||||||
|
{!isTyping ? (
|
||||||
|
<span className="shrink-0 text-[0.65rem] text-slate-400/60">
|
||||||
|
{formatTime(message.ts)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{isOpen && hasToolCalls ? (
|
{isOpen && hasToolCalls ? (
|
||||||
<div className="w-full rounded border border-slate-700/70 bg-slate-900/70 p-0.5 text-[0.68rem] text-slate-200">
|
<div className="w-full rounded border border-slate-700/70 bg-slate-900/70 p-0.5 text-[0.68rem] text-slate-200">
|
||||||
@@ -216,5 +232,3 @@ export default function ChatMessageRow({ message }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { chatRowClass };
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { useChatActions, useChatTimeline } from '../../context/ChatContext.jsx';
|
|||||||
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
||||||
import { useSettingsNamespace } from '../../settings/index.js';
|
import { useSettingsNamespace } from '../../settings/index.js';
|
||||||
import ChatMessageRow from '../ChatMessageRow/index.jsx';
|
import ChatMessageRow from '../ChatMessageRow/index.jsx';
|
||||||
import ChatTypingRow from '../ChatTypingRow/index.jsx';
|
|
||||||
import CardFrame from '../CardFrame/index.jsx';
|
import CardFrame from '../CardFrame/index.jsx';
|
||||||
import NicknameForm from '../NicknameForm/index.jsx';
|
import NicknameForm from '../NicknameForm/index.jsx';
|
||||||
|
|
||||||
@@ -88,7 +87,7 @@ function ChatMessageList({ fillHeight = false }) {
|
|||||||
sorted.map((msg) => <ChatMessageRow key={msg.id} message={msg} />)
|
sorted.map((msg) => <ChatMessageRow key={msg.id} message={msg} />)
|
||||||
)}
|
)}
|
||||||
{typingRows.map((entry) => (
|
{typingRows.map((entry) => (
|
||||||
<ChatTypingRow key={`typing-${entry.typingId || entry.id}`} message={entry} />
|
<ChatMessageRow key={`typing-${entry.typingId || entry.id}`} message={entry} variant="typing" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
// Chat Typing Row
|
|
||||||
// Purpose: Defines the Chat Typing Row module and the local helpers/components used in this file.
|
|
||||||
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
|
|
||||||
import { ChatIdentity, chatRowClass } from '../ChatMessageRow/index.jsx';
|
|
||||||
|
|
||||||
export default function ChatTypingRow({ message }) {
|
|
||||||
return (
|
|
||||||
<div className={`${chatRowClass(message)} !flex-row items-center italic opacity-80 border-slate-600/40 bg-slate-900/40`}>
|
|
||||||
<ChatIdentity message={message} />
|
|
||||||
<span className="text-slate-300">typing...</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user