mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Chat sits next to the mic in the command dock. Replies are silent and never request the microphone; the first message connects with the selected provider and runs the same map actions as voice through each provider's text API (OpenAI OPENAI_CHAT_MODEL, Gemini GEMINI_CHAT_MODEL via generateContent, which never starts Live). Only one mode runs at a time. - Chat panel rendered by the voice control presenter: conversation, recent and quick-command suggestions, Up/Down history, attachments (map-layer references or small UTF-8 text files, sent only with the message) - Chat and voice panels sit above the dock or dock into the right rail's column; minimized bars sit centred above the dock, and height animates on swaps - A centred panel steps down to its bar for the Location and Visual Presets trays without moving focus, rests above an open tray, and docks right while expanded over one; it returns to the centre once the trays close - Minimized voice previews the last sentence from each side; the empty Voice Preview suggests things to say - Settings opened from chat keep chat's frame and show the shared AI provider and quick commands; voice Settings show Back to voice - Closing chat ends a typed session; the voice panel's close turns voice off - The dock widens its voice control for the chat button and restyles its wings, which show icons only at 900px and below; assistant panels share the left panels' glass, and Cyber keeps its own surfaces - Server text-chat handlers with body, time and per-IP limits (GEV_RATELIMIT_*_CHAT_PER_MIN), documented in SECURITY.md; tests and all eleven locales Co-authored-by: Darren Dignam <[email protected]>
55 lines
1.8 KiB
JavaScript
55 lines
1.8 KiB
JavaScript
import { validateValue } from '../../../src/tools/schema.js';
|
|
|
|
export function invalidCalls(calls, tools) {
|
|
const schemas = new Map(tools.map((tool) => [tool.name, tool.parameters]));
|
|
return calls.flatMap((call, index) => {
|
|
const schema = schemas.get(call.name);
|
|
const errors = !schema
|
|
? ['Unknown tool']
|
|
: call.parseError
|
|
? ['Arguments must be valid JSON']
|
|
: validateValue(schema, call.args === undefined ? {} : call.args);
|
|
return errors.length
|
|
? [{ index, error: errors.slice(0, 4).join('; ').slice(0, 1000) }]
|
|
: [];
|
|
});
|
|
}
|
|
|
|
// Missing usage stays unknown, never an invented zero-cost request.
|
|
export function parseUsage(provider, data) {
|
|
const raw = provider === 'gemini' ? data?.usageMetadata : data?.usage;
|
|
const number = (value) =>
|
|
Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
const inputTokens = number(
|
|
provider === 'gemini' ? raw?.promptTokenCount : raw?.prompt_tokens,
|
|
);
|
|
const candidateTokens = number(
|
|
provider === 'gemini' ? raw?.candidatesTokenCount : raw?.completion_tokens,
|
|
);
|
|
const totalTokens = number(
|
|
provider === 'gemini' ? raw?.totalTokenCount : raw?.total_tokens,
|
|
);
|
|
if (inputTokens === null || candidateTokens === null || totalTokens === null)
|
|
return null;
|
|
const reasoningTokens =
|
|
number(
|
|
provider === 'gemini'
|
|
? raw?.thoughtsTokenCount
|
|
: raw?.completion_tokens_details?.reasoning_tokens,
|
|
) ?? 0;
|
|
const outputTokens =
|
|
candidateTokens + (provider === 'gemini' ? reasoningTokens : 0);
|
|
return {
|
|
inputTokens,
|
|
outputTokens,
|
|
totalTokens,
|
|
reasoningTokens,
|
|
cachedInputTokens:
|
|
number(
|
|
provider === 'gemini'
|
|
? raw?.cachedContentTokenCount
|
|
: raw?.prompt_tokens_details?.cached_tokens,
|
|
) ?? 0,
|
|
};
|
|
}
|