} // objects: recursively mark properties as readonly
10 | : T; // Primitives remain the same
11 |
--------------------------------------------------------------------------------
/src/common/types/next.page.d.ts:
--------------------------------------------------------------------------------
1 | import type { ReactElement, ReactNode } from 'react';
2 | import type { NextPage } from 'next';
3 | import type { EmotionCache } from '@emotion/react';
4 |
5 |
6 | export type NextPageWithLayout = NextPage
& {
7 | // definition of the per-page layout function, as per:
8 | // https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts#per-page-layouts
9 | getLayout?: (page: ReactElement) => ReactNode;
10 | }
11 |
12 | // Extend the AppProps type with the custom page component type
13 | declare module 'next/app' {
14 | import { AppProps } from 'next/app';
15 |
16 | type MyAppProps = AppProps & {
17 | Component: NextPageWithLayout
18 | emotionCache?: EmotionCache;
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/common/types/useful.types.ts:
--------------------------------------------------------------------------------
1 | // Useful types that are not specific to us
2 |
3 | /**
4 | * Could be a value (or void) or a promise of it
5 | */
6 | export type MaybePromise = T | Promise;
7 |
8 | /**
9 | * In our app 'undefined' and 'null' must be different, so we don't use 'Maybe' type
10 | * - undefined: missing value, never read, etc.
11 | * - null: means 'force no value', 'force empty', etc.
12 | */
13 | // export type Maybe = T | null | undefined;
14 |
--------------------------------------------------------------------------------
/src/common/util/audio/AudioPlayer.ts:
--------------------------------------------------------------------------------
1 | export namespace AudioPlayer {
2 |
3 | /**
4 | * Plays an audio file from a URL (e.g. an MP3 file).
5 | */
6 | export async function playUrl(url: string): Promise {
7 | return new Promise((resolve, reject) => {
8 | const audio = new Audio(url);
9 | audio.onended = () => resolve();
10 | audio.onerror = (e) => reject(new Error(`Error playing audio: ${e}`));
11 | audio.play().catch(reject);
12 | });
13 | }
14 |
15 | /**
16 | * Plays an audio buffer (e.g. from an ArrayBuffer).
17 | */
18 | export async function playBuffer(audioBuffer: ArrayBuffer): Promise {
19 | const audioContext = new AudioContext();
20 | const bufferSource = audioContext.createBufferSource();
21 | bufferSource.buffer = await audioContext.decodeAudioData(audioBuffer);
22 | bufferSource.connect(audioContext.destination);
23 | bufferSource.start();
24 | return new Promise((resolve) => {
25 | bufferSource.onended = () => resolve();
26 | });
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/common/util/clientFetchers.ts:
--------------------------------------------------------------------------------
1 | export const frontendSideFetch = fetch;
--------------------------------------------------------------------------------
/src/common/util/costUtils.ts:
--------------------------------------------------------------------------------
1 | export function formatModelsCost(cost: number) {
2 | return cost < 1
3 | ? `${(cost * 100).toFixed(cost < 0.010 ? 2 : 2)} ¢`
4 | : `$ ${cost.toFixed(2)}`;
5 | }
--------------------------------------------------------------------------------
/src/common/util/hooks/useToggleableBoolean.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | // in-memory map to remember the last state
4 | const toggleStates = new Map();
5 |
6 | export function useToggleableBoolean(initialValue: boolean = false, key?: string) {
7 |
8 | // Retrieve the initial value from memory if a key is provided and exists in the map
9 | const memoryValue = key ? toggleStates.get(key) : undefined;
10 |
11 | // state
12 | const [value, setValue] = React.useState(memoryValue ?? initialValue);
13 |
14 | // Define the toggle function
15 | const toggle = React.useCallback(() => {
16 | setValue(state => {
17 | const newValue = !state;
18 | // If a key is provided, update the value in the map
19 | if (key)
20 | toggleStates.set(key, newValue);
21 | return newValue;
22 | });
23 | }, [key]);
24 |
25 | return { on: value, toggle };
26 | }
27 |
28 | export type ToggleableBoolean = ReturnType;
--------------------------------------------------------------------------------
/src/common/util/jsonUtils.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | import { maybeDebuggerBreak } from '~/common/util/errorUtils';
4 |
5 |
6 | // configuration
7 | const ENABLE_NON_STANDARD = false; // if true, it will enable 'undefined' as a valid value in JSON objects
8 |
9 |
10 | //
11 | // JSON validation - used before saving to DB/sync transmission - to ensure valid structure
12 | //
13 |
14 | const literalSchema = z.union([
15 | z.string(),
16 | z.number(),
17 | z.boolean(),
18 | z.null(),
19 | // NON-STANDARD, but adding this because we do have 'undefined' in in-mem objects
20 | ...(ENABLE_NON_STANDARD ? [z.undefined()] : []),
21 | ]);
22 |
23 | type Literal = z.infer;
24 | type Json = Literal | Json[] | { [key: string]: Json };
25 |
26 | const jsonSchema: z.ZodType = z.lazy(() =>
27 | z.union([
28 | literalSchema,
29 | z.array(jsonSchema),
30 | z.record(z.string(), jsonSchema),
31 | ]),
32 | );
33 |
34 | /**
35 | * Checks if the given value is a valid JSON object, which will be serialized
36 | * without errors for storage or transmission.
37 | */
38 | export function isValidJson(value: unknown, debugLocation: string): value is Json {
39 | const result = jsonSchema.safeParse(value);
40 | if (result.success)
41 | return true;
42 |
43 | console.log(`[DEV] ${debugLocation}: Invalid JSON:`, { error: result.error });
44 | maybeDebuggerBreak();
45 | return result.success;
46 | }
47 |
--------------------------------------------------------------------------------
/src/common/util/mediasession/useMediaSessionCallbacks.ts:
--------------------------------------------------------------------------------
1 | // import * as React from 'react';
2 | //
3 | // import { useShallowStable } from '../hooks/useShallowObject';
4 | //
5 | // import { MediaSessionCallbacks, MediaSessionManager } from './MediaSessionManager';
6 | //
7 | //
8 | // // noinspection JSUnusedGlobalSymbols
9 | // /**
10 | // * Note: this does not seem to be working as of now.
11 | // * The reason is possibly related to us not having an