mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
spectator nicknames, overseer common issue filtering
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-CXr-XVVB.js"></script>
|
<script type="module" crossorigin src="/assets/index-zQNOJD7V.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DaajoH22.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DaajoH22.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -100,10 +100,21 @@ function buildModelMessages({
|
|||||||
});
|
});
|
||||||
(conversationMessages || []).forEach((message) => {
|
(conversationMessages || []).forEach((message) => {
|
||||||
if (!message || !message.role || !message.content) return;
|
if (!message || !message.role || !message.content) return;
|
||||||
message.content.replace("sourceMapping", "");
|
const content = String(message.content || '')
|
||||||
message.content.replace("SourceMapping", "");
|
// The overseer can sometimes write plain-text lines that look like tool
|
||||||
message.content.replace("sourcemapping", "");
|
// calls instead of returning real structured tool_calls to Ollama. If
|
||||||
messages.push(message);
|
// those fake calls are replayed in later context, the model sees its own
|
||||||
|
// malformed pattern as chat history and tends to repeat it. This removes
|
||||||
|
// any whole line shaped like "(tool_name {"json": "payload"})" regardless
|
||||||
|
// of the invented tool name, while leaving normal prose and legitimate
|
||||||
|
// structured tool metadata untouched.
|
||||||
|
.replace(/^\s*\([a-zA-Z_][a-zA-Z0-9_]*\s+\{.*\}\)\s*$/gm, '')
|
||||||
|
.replace(/sourceMapping/g, '')
|
||||||
|
.replace(/SourceMapping/g, '')
|
||||||
|
.replace(/sourcemapping/g, '')
|
||||||
|
.trim();
|
||||||
|
if (!content) return;
|
||||||
|
messages.push({ ...message, content });
|
||||||
});
|
});
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,29 +2,28 @@
|
|||||||
// Purpose: Defines the Nickname Form module and the local helpers/components used in this file.
|
// Purpose: Defines the Nickname Form module and the local helpers/components used in this file.
|
||||||
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
|
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
|
import { useSessionActions } from '../../context/SessionContext.jsx';
|
||||||
import { useSettingsNamespace } from '../../settings/index.js';
|
import { useSettingsNamespace } from '../../settings/index.js';
|
||||||
|
|
||||||
export default function NicknameForm({ compact = false }) {
|
export default function NicknameForm({ compact = false }) {
|
||||||
const role = useSessionSelector((state) => state.session?.role || null);
|
|
||||||
const { setNickname } = useSessionActions();
|
const { setNickname } = useSessionActions();
|
||||||
const { value, save } = useSettingsNamespace('profile', { nickname: '' });
|
const { value, save } = useSettingsNamespace('profile', { nickname: '' });
|
||||||
const [nicknameInput, setNicknameInput] = useState(value.nickname || '');
|
const [nicknameInput, setNicknameInput] = useState(value.nickname || '');
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
const canSetNickname = role !== 'spectator';
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNicknameInput(value.nickname || '');
|
setNicknameInput(value.nickname || '');
|
||||||
}, [value.nickname]);
|
}, [value.nickname]);
|
||||||
|
|
||||||
async function handleSave(event) {
|
async function handleSave(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!canSetNickname) return;
|
|
||||||
const trimmed = (nicknameInput || '').trim().slice(0, 32);
|
const trimmed = (nicknameInput || '').trim().slice(0, 32);
|
||||||
if (!trimmed) return;
|
if (!trimmed) return;
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
|
// Nicknames are identity metadata rather than rover-control permission.
|
||||||
|
// Spectator pages use the same socket action and local profile storage as
|
||||||
|
// the driver UI so chat/user-list labels stay consistent across routes.
|
||||||
await setNickname(trimmed);
|
await setNickname(trimmed);
|
||||||
save({ nickname: trimmed });
|
save({ nickname: trimmed });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -47,12 +46,11 @@ export default function NicknameForm({ compact = false }) {
|
|||||||
}}
|
}}
|
||||||
maxLength={32}
|
maxLength={32}
|
||||||
placeholder="Enter a nickname"
|
placeholder="Enter a nickname"
|
||||||
disabled={!canSetNickname}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={!canSetNickname || saving}
|
disabled={saving}
|
||||||
className="button-dark h-full shrink-0 whitespace-nowrap px-0.5 py-0 disabled:opacity-50"
|
className="button-dark h-full shrink-0 whitespace-nowrap px-0.5 py-0 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saving ? 'Saving…' : compact ? 'Set' : 'Save'}
|
{saving ? 'Saving…' : compact ? 'Set' : 'Save'}
|
||||||
|
|||||||
@@ -36,10 +36,8 @@ export default function RawUserPilePanel({
|
|||||||
fillHeight = false,
|
fillHeight = false,
|
||||||
compact = false,
|
compact = false,
|
||||||
}) {
|
}) {
|
||||||
const role = useSessionSelector((state) => state.session?.role || null);
|
|
||||||
const users = useSessionSelector((state) => state.session?.users ?? []);
|
const users = useSessionSelector((state) => state.session?.users ?? []);
|
||||||
const selfId = useSessionSelector((state) => state.session?.socketId || null);
|
const selfId = useSessionSelector((state) => state.session?.socketId || null);
|
||||||
const canSetNickname = role !== 'spectator';
|
|
||||||
|
|
||||||
const sorted = useMemo(
|
const sorted = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -76,7 +74,6 @@ export default function RawUserPilePanel({
|
|||||||
</div>
|
</div>
|
||||||
<SocialButtonsGrid />
|
<SocialButtonsGrid />
|
||||||
</div>
|
</div>
|
||||||
{!canSetNickname && <p className="text-xs text-slate-500">Spectators cannot set nicknames.</p>}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -56,13 +56,11 @@ export default function UserListPanel({
|
|||||||
compact = false,
|
compact = false,
|
||||||
showBothTurnsAndUsers = false,
|
showBothTurnsAndUsers = false,
|
||||||
}) {
|
}) {
|
||||||
const role = useSessionSelector((state) => state.session?.role || null);
|
|
||||||
const users = useSessionSelector((state) => state.session?.users ?? []);
|
const users = useSessionSelector((state) => state.session?.users ?? []);
|
||||||
const selfId = useSessionSelector((state) => state.session?.socketId || null);
|
const selfId = useSessionSelector((state) => state.session?.socketId || null);
|
||||||
const mode = useSessionSelector((state) => state.session?.mode || null);
|
const mode = useSessionSelector((state) => state.session?.mode || null);
|
||||||
const turnQueues = useSessionSelector((state) => state.session?.turnQueues || {});
|
const turnQueues = useSessionSelector((state) => state.session?.turnQueues || {});
|
||||||
const roster = useSessionSelector((state) => state.session?.roster || []);
|
const roster = useSessionSelector((state) => state.session?.roster || []);
|
||||||
const canSetNickname = role !== 'spectator';
|
|
||||||
const isTurnsMode = mode === 'turns';
|
const isTurnsMode = mode === 'turns';
|
||||||
const [turnView, setTurnView] = useState('queues');
|
const [turnView, setTurnView] = useState('queues');
|
||||||
|
|
||||||
@@ -168,7 +166,6 @@ export default function UserListPanel({
|
|||||||
</div>
|
</div>
|
||||||
<SocialButtonsGrid />
|
<SocialButtonsGrid />
|
||||||
</div>
|
</div>
|
||||||
{!canSetNickname && <p className="text-xs text-slate-500">Spectators cannot set nicknames.</p>}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { useSession } from '../../context/SessionContext.jsx';
|
import { useSession } from '../../context/SessionContext.jsx';
|
||||||
import { useSpectatorMode } from '../../hooks/useSpectatorMode.js';
|
import { useSpectatorMode } from '../../hooks/useSpectatorMode.js';
|
||||||
import useDefaultNickname from '../../hooks/useDefaultNickname.js';
|
import useDefaultNickname from '../../hooks/useDefaultNickname.js';
|
||||||
|
import useUserIdentitySync from '../../hooks/useUserIdentitySync.js';
|
||||||
import ChatPanel from '../../components/ChatPanel/index.jsx';
|
import ChatPanel from '../../components/ChatPanel/index.jsx';
|
||||||
import AlertFeed from '../../components/AlertFeed/index.jsx';
|
import AlertFeed from '../../components/AlertFeed/index.jsx';
|
||||||
import GlobalObjectiveBanner from '../../components/GlobalObjectiveBanner/index.jsx';
|
import GlobalObjectiveBanner from '../../components/GlobalObjectiveBanner/index.jsx';
|
||||||
@@ -20,6 +21,11 @@ export default function SpectatorContent() {
|
|||||||
const { session } = useSession();
|
const { session } = useSession();
|
||||||
const inLockdown = session?.mode === 'lockdown';
|
const inLockdown = session?.mode === 'lockdown';
|
||||||
useDefaultNickname();
|
useDefaultNickname();
|
||||||
|
// The spectator route is not rendered through App.jsx, so it must opt into
|
||||||
|
// the same persisted identity heartbeat here. That keeps the existing
|
||||||
|
// cookie-backed identity and saved nickname behavior active without adding a
|
||||||
|
// separate spectator identity path.
|
||||||
|
useUserIdentitySync();
|
||||||
useSpectatorMode();
|
useSpectatorMode();
|
||||||
const isPortraitLayout = usePortraitLayout();
|
const isPortraitLayout = usePortraitLayout();
|
||||||
const roster = session?.roster ?? [];
|
const roster = session?.roster ?? [];
|
||||||
|
|||||||
Reference in New Issue
Block a user