mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Typed chat sessions no longer end on the voice inactivity timeout, and a failed or stopped reply keeps the conversation: Stop cancels only the reply in progress, and map actions that already ran stay in the model's context. Each upstream request and map action gets its own deadline, old tool results shrink instead of the history overflowing, and step limits, a missing or rejected key, the OAuth lock and throttling each get their own message. - Server: OpenAI chat is off under GEV_PREFER_CODEX_OAUTH; chat throttles default to the provider's throttle; replies carry API_KEY_MISSING, API_KEY_REJECTED, CODEX_OAUTH_REQUIRED and RATE_LIMITED codes; upstream Retry-After passes through, and a rejected key (Gemini's invalid-key 400 included) cools down for a minute - Pinokio forwards the chat throttle settings - UI: the reply reveal publishes only the message list; the border orbit runs only while busy; layers attach with a button or Enter; the log is aria-busy while a reply types out; auto-scroll respects a reader who scrolled up; Escape closes the attach menu; the chat gear says Settings; a note says chat bills the OpenAI key when voice uses ChatGPT sign-in - Layout does less work while panel height animates; unused blurs removed - Gemini voice start ends chat; the voice preview clears on every new session - Tests for the route gates, client limits, attachments and the fixes
144 lines
4.8 KiB
JavaScript
144 lines
4.8 KiB
JavaScript
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import path from 'node:path';
|
|
import { parseEnv } from 'node:util';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const DEFAULT_ENVIRONMENT_FILE = path.join(ROOT, 'pinokio', 'ENVIRONMENT');
|
|
|
|
export const PINOKIO_CONFIG_FIELDS = Object.freeze([
|
|
'GOOGLE_MAPS_API_KEY',
|
|
'GOOGLE_MAPS_SERVER_API_KEY',
|
|
'CESIUM_ION_TOKEN',
|
|
'OPENAI_API_KEY',
|
|
'AISSTREAM_API_KEY',
|
|
'FIRMS_MAP_KEY',
|
|
'TOMTOM_API_KEY',
|
|
'OPENSKY_CLIENT_ID',
|
|
'OPENSKY_CLIENT_SECRET',
|
|
'LL2_API_TOKEN',
|
|
'GEV_RATELIMIT_OPENAI_PER_MIN',
|
|
'GEV_RATELIMIT_GOOGLE_PER_MIN',
|
|
'GEV_RATELIMIT_OPENAI_CHAT_PER_MIN',
|
|
'GEV_RATELIMIT_GEMINI_CHAT_PER_MIN',
|
|
'PINOKIO_SHARE_CLOUDFLARE',
|
|
'PINOKIO_SHARE_LOCAL',
|
|
'PINOKIO_SHARE_VAR',
|
|
]);
|
|
|
|
const PINOKIO_DEFAULTS = Object.freeze({
|
|
GEV_RATELIMIT_OPENAI_PER_MIN: '30',
|
|
GEV_RATELIMIT_GOOGLE_PER_MIN: '120',
|
|
PINOKIO_SHARE_CLOUDFLARE: 'false',
|
|
PINOKIO_SHARE_LOCAL: 'false',
|
|
PINOKIO_SHARE_VAR: '__gev_sharing_disabled__',
|
|
});
|
|
|
|
const PINOKIO_SHARE_SENTINEL = '__gev_sharing_disabled__';
|
|
const PINOKIO_SHARING_FIELDS = Object.freeze([
|
|
'PINOKIO_SHARE_CLOUDFLARE',
|
|
'PINOKIO_SHARE_LOCAL',
|
|
'PINOKIO_SHARE_VAR',
|
|
]);
|
|
|
|
function appendEnvironmentLine(source, line) {
|
|
const prefix = source.length > 0 && !source.endsWith('\n') ? '\n' : '';
|
|
return `${source}${prefix}${line}\n`;
|
|
}
|
|
|
|
function detectEnvironmentEncoding(buffer) {
|
|
if (buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe) return 'utf-16le';
|
|
if (buffer.length >= 2 && buffer[0] === 0xfe && buffer[1] === 0xff) return 'utf-16be';
|
|
|
|
let evenNulls = 0;
|
|
let oddNulls = 0;
|
|
const sampleLength = Math.min(buffer.length, 512);
|
|
for (let index = 0; index < sampleLength; index += 1) {
|
|
if (buffer[index] !== 0) continue;
|
|
if (index % 2 === 0) evenNulls += 1;
|
|
else oddNulls += 1;
|
|
}
|
|
const minimumNulls = Math.max(2, Math.floor(sampleLength / 16));
|
|
if (oddNulls >= minimumNulls && oddNulls > evenNulls * 2) return 'utf-16le';
|
|
if (evenNulls >= minimumNulls && evenNulls > oddNulls * 2) return 'utf-16be';
|
|
return 'utf-8';
|
|
}
|
|
|
|
export function readEnvironmentSource(filepath) {
|
|
if (!existsSync(filepath)) return '';
|
|
const buffer = readFileSync(filepath);
|
|
try {
|
|
return new TextDecoder(detectEnvironmentEncoding(buffer), { fatal: true }).decode(buffer);
|
|
} catch {
|
|
throw new Error('Pinokio ENVIRONMENT could not be decoded as UTF-8 or UTF-16.');
|
|
}
|
|
}
|
|
|
|
/** Persist only the non-secret controls Pinokio itself re-reads at local.set. */
|
|
export function ensurePinokioSharingBoundary(filepath = DEFAULT_ENVIRONMENT_FILE) {
|
|
const original = existsSync(filepath) ? readFileSync(filepath) : null;
|
|
let source = readEnvironmentSource(filepath);
|
|
try {
|
|
if (source) parseEnv(source);
|
|
} catch {
|
|
throw new Error('Pinokio ENVIRONMENT could not be parsed.');
|
|
}
|
|
|
|
// Pinokio re-reads this file after the child preflight. Remove every legacy,
|
|
// blank, or duplicate control before appending one canonical safe block so
|
|
// that its later global/app merge cannot diverge from the checked state.
|
|
const sharingLine = new RegExp(
|
|
`^[\\t ]*(?:${PINOKIO_SHARING_FIELDS.join('|')})[\\t ]*=.*(?:\\r?\\n|$)`,
|
|
'gm',
|
|
);
|
|
source = source.replace(sharingLine, '');
|
|
source = appendEnvironmentLine(source, [
|
|
'PINOKIO_SHARE_CLOUDFLARE=false',
|
|
'PINOKIO_SHARE_LOCAL=false',
|
|
`PINOKIO_SHARE_VAR=${PINOKIO_SHARE_SENTINEL}`,
|
|
].join('\n'));
|
|
|
|
let configured;
|
|
try {
|
|
configured = parseEnv(source);
|
|
} catch {
|
|
throw new Error('Pinokio ENVIRONMENT could not be parsed.');
|
|
}
|
|
|
|
const encoded = Buffer.from(source, 'utf8');
|
|
if (!original || !original.equals(encoded)) {
|
|
writeFileSync(filepath, source, { mode: 0o600 });
|
|
}
|
|
return configured;
|
|
}
|
|
|
|
/** Read the app-scoped Pinokio configuration without exposing its values. */
|
|
export function readPinokioEnvironment(filepath = DEFAULT_ENVIRONMENT_FILE) {
|
|
if (!existsSync(filepath)) return {};
|
|
try {
|
|
return parseEnv(readEnvironmentSource(filepath));
|
|
} catch {
|
|
throw new Error('Pinokio ENVIRONMENT could not be parsed.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make the app-scoped Pinokio file authoritative over Pinokio-global values.
|
|
* Pinokio removes blank entries before merging environments, so each child
|
|
* must restore the raw app value before diagnosis or Vite configuration.
|
|
*/
|
|
export function applyPinokioEnvironment({
|
|
environment = process.env,
|
|
filepath = DEFAULT_ENVIRONMENT_FILE,
|
|
} = {}) {
|
|
const configured = ensurePinokioSharingBoundary(filepath);
|
|
for (const field of PINOKIO_CONFIG_FIELDS) {
|
|
environment[field] = String(configured[field] ?? PINOKIO_DEFAULTS[field] ?? '');
|
|
}
|
|
|
|
// Sharing is unsupported on Pinokio 8.0.40. Never let a global passcode
|
|
// enter the child even if the host's global Pinokio environment defines it.
|
|
environment.PINOKIO_SHARE_PASSCODE = '';
|
|
return configured;
|
|
}
|