Core: - ModelCatalog: periodic background sync of provider models + fuzzy resolve. Misspelled model ids map to the nearest match (cheapest on ties); no match raises ModelResolutionError with cheapest-first suggestions for the agent. mcp-server: - nexus_stt now writes the transcript to a Markdown file in the media dir so it appears in the gallery and is readable in the modal. - media handler resolves model via catalog before generating; returns a note when substituting, or a BAD_REQUEST with available models when nothing fits. - context wires a ModelCatalog per media provider with auto-sync. admin: - /media serves correct Content-Type (incl. text/markdown for transcripts). - Gallery: audio play button on cards, dedicated audio player in modal, text/transcript tiles that fetch and render the .md in the modal. - Header: auto-refresh toggle (5/10/30s) that refreshes the active tab without a full page reload; pauses while a modal is open.
97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
import {
|
|
Journal,
|
|
KeyCipher,
|
|
KeyResolver,
|
|
MediaStorage,
|
|
ModelCatalog,
|
|
ProviderRegistry,
|
|
ProvidersStore,
|
|
createNordRouterProviders,
|
|
loadConfig,
|
|
LocalWhisperProvider,
|
|
createLogger,
|
|
type NexusConfig,
|
|
} from '@nexusai/core';
|
|
|
|
const log = createLogger('context');
|
|
|
|
export interface AppContext {
|
|
config: NexusConfig;
|
|
registry: ProviderRegistry;
|
|
storage: MediaStorage;
|
|
journal: Journal;
|
|
providersStore: ProvidersStore;
|
|
/** Model catalogs keyed by provider name (for sync + fuzzy resolution). */
|
|
catalogs: Map<string, ModelCatalog>;
|
|
/** Effective default provider name after resolving DB/env. */
|
|
defaultProvider: string;
|
|
}
|
|
|
|
/** Built-in providers always considered, even without a DB row. */
|
|
const BUILTINS = [{ name: 'nordrouter', defaultBaseUrl: 'https://nordrouter.com' }];
|
|
|
|
/**
|
|
* Wire up providers, storage, journal and the encrypted providers store.
|
|
* API keys resolve with ENV > DB precedence (see KeyResolver).
|
|
*/
|
|
export function buildContext(): AppContext {
|
|
const config = loadConfig();
|
|
const storage = new MediaStorage(config.mediaDir);
|
|
const journal = new Journal(config.dbPath);
|
|
const registry = new ProviderRegistry();
|
|
|
|
const cipher = new KeyCipher(config.masterKey);
|
|
const providersStore = new ProvidersStore(config.providersDbPath, cipher);
|
|
const resolver = new KeyResolver(config, providersStore);
|
|
|
|
if (!cipher.enabled) {
|
|
log.warn('NEXUS_MASTER_KEY not set — DB-stored API keys are unavailable; using env keys only');
|
|
}
|
|
|
|
const resolved = resolver.resolveAll(BUILTINS);
|
|
const defaultProvider = resolver.resolveDefault(resolved);
|
|
const catalogs = new Map<string, ModelCatalog>();
|
|
|
|
for (const p of resolved) {
|
|
if (!p.enabled) {
|
|
log.info(`provider "${p.name}" disabled — skipped`);
|
|
continue;
|
|
}
|
|
if (p.type === 'nordrouter') {
|
|
if (!p.apiKey) {
|
|
log.warn(`provider "${p.name}" has no API key (env or DB) — tools will error when used`);
|
|
}
|
|
try {
|
|
const nr = createNordRouterProviders({
|
|
config: { apiKey: p.apiKey, baseUrl: p.baseUrl },
|
|
storage,
|
|
save: true,
|
|
});
|
|
// Register under the provider's own name so multiple nordrouter-type
|
|
// providers with different keys can coexist.
|
|
Object.defineProperty(nr.media, 'name', { value: p.name });
|
|
Object.defineProperty(nr.search, 'name', { value: p.name });
|
|
registry.registerMedia(nr.media);
|
|
registry.registerSearch(nr.search);
|
|
// Model catalog with periodic background sync + fuzzy resolution.
|
|
if (p.apiKey) {
|
|
const catalog = new ModelCatalog(nr.media);
|
|
catalog.startAutoSync();
|
|
catalogs.set(p.name, catalog);
|
|
}
|
|
log.info(`registered provider "${p.name}" (media + search, key: ${p.keySource})`);
|
|
} catch (err) {
|
|
log.warn(`failed to register "${p.name}"`, err instanceof Error ? err.message : String(err));
|
|
}
|
|
} else {
|
|
log.warn(`provider type "${p.type}" (${p.name}) not implemented yet — skipped`);
|
|
}
|
|
}
|
|
|
|
registry.registerStt(new LocalWhisperProvider(config.stt));
|
|
log.info('registered local STT provider');
|
|
log.info(`default provider: ${defaultProvider}`);
|
|
|
|
return { config, registry, storage, journal, providersStore, catalogs, defaultProvider };
|
|
}
|