feat: add browser environment compatibility support
- Remove Node.js-specific imports from module level - Use dynamic imports with isNode() environment checks - Wrap all file system operations in conditional blocks - Add fallback values for browser environments - Ensure code works in both Node.js and browser contexts This change enables Brainy to run in browser environments without requiring Node.js polyfills, making it truly universal. Co-Authored-By: dpsifr <noreply@dpsifr.com>
This commit is contained in:
parent
f30ddf14e0
commit
a5ce47ffdb
3 changed files with 204 additions and 76 deletions
|
|
@ -8,10 +8,8 @@
|
||||||
* - Default values from schema
|
* - Default values from schema
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { existsSync, readFileSync } from 'node:fs'
|
|
||||||
import { join } from 'node:path'
|
|
||||||
import { homedir } from 'node:os'
|
|
||||||
import { JSONSchema } from './manifest.js'
|
import { JSONSchema } from './manifest.js'
|
||||||
|
import { isNode } from '../utils/environment.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration source priority (highest to lowest)
|
* Configuration source priority (highest to lowest)
|
||||||
|
|
@ -53,14 +51,32 @@ export class AugmentationConfigResolver {
|
||||||
private resolved: any = {}
|
private resolved: any = {}
|
||||||
|
|
||||||
constructor(private options: ConfigResolverOptions) {
|
constructor(private options: ConfigResolverOptions) {
|
||||||
|
// Default config paths - include home directory paths only in Node.js
|
||||||
|
const defaultConfigPaths = [
|
||||||
|
'.brainyrc',
|
||||||
|
'.brainyrc.json',
|
||||||
|
'brainy.config.json'
|
||||||
|
]
|
||||||
|
|
||||||
|
// Add home directory paths in Node.js environments
|
||||||
|
if (isNode()) {
|
||||||
|
try {
|
||||||
|
const nodeRequire = typeof require !== 'undefined' ? require : null
|
||||||
|
if (nodeRequire) {
|
||||||
|
const path = nodeRequire('node:path')
|
||||||
|
const os = nodeRequire('node:os')
|
||||||
|
defaultConfigPaths.push(
|
||||||
|
path.join(os.homedir(), '.brainy', 'config.json'),
|
||||||
|
path.join(os.homedir(), '.brainyrc')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Silently continue without home directory paths
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.options = {
|
this.options = {
|
||||||
configPaths: [
|
configPaths: defaultConfigPaths,
|
||||||
'.brainyrc',
|
|
||||||
'.brainyrc.json',
|
|
||||||
'brainy.config.json',
|
|
||||||
join(homedir(), '.brainy', 'config.json'),
|
|
||||||
join(homedir(), '.brainyrc')
|
|
||||||
],
|
|
||||||
envPrefix: `BRAINY_AUG_${options.augmentationId.toUpperCase()}_`,
|
envPrefix: `BRAINY_AUG_${options.augmentationId.toUpperCase()}_`,
|
||||||
allowUndefined: true,
|
allowUndefined: true,
|
||||||
...options
|
...options
|
||||||
|
|
@ -133,32 +149,41 @@ export class AugmentationConfigResolver {
|
||||||
*/
|
*/
|
||||||
private loadFromFiles(): void {
|
private loadFromFiles(): void {
|
||||||
// Skip in browser environment
|
// Skip in browser environment
|
||||||
if (typeof process === 'undefined' || typeof window !== 'undefined') {
|
if (!isNode()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const configPath of this.options.configPaths || []) {
|
try {
|
||||||
try {
|
const nodeRequire = typeof require !== 'undefined' ? require : null
|
||||||
if (existsSync(configPath)) {
|
if (!nodeRequire) return
|
||||||
const content = readFileSync(configPath, 'utf8')
|
|
||||||
const config = this.parseConfigFile(content, configPath)
|
|
||||||
|
|
||||||
// Extract augmentation-specific configuration
|
const fs = nodeRequire('node:fs')
|
||||||
const augConfig = this.extractAugmentationConfig(config)
|
|
||||||
|
|
||||||
if (augConfig && Object.keys(augConfig).length > 0) {
|
for (const configPath of this.options.configPaths || []) {
|
||||||
this.sources.push({
|
try {
|
||||||
priority: ConfigPriority.FILE,
|
if (fs.existsSync(configPath)) {
|
||||||
source: `file:${configPath}`,
|
const content = fs.readFileSync(configPath, 'utf8')
|
||||||
config: augConfig
|
const config = this.parseConfigFile(content, configPath)
|
||||||
})
|
|
||||||
break // Use first found config file
|
// Extract augmentation-specific configuration
|
||||||
|
const augConfig = this.extractAugmentationConfig(config)
|
||||||
|
|
||||||
|
if (augConfig && Object.keys(augConfig).length > 0) {
|
||||||
|
this.sources.push({
|
||||||
|
priority: ConfigPriority.FILE,
|
||||||
|
source: `file:${configPath}`,
|
||||||
|
config: augConfig
|
||||||
|
})
|
||||||
|
break // Use first found config file
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Silently ignore file errors
|
||||||
|
console.debug(`Failed to load config from ${configPath}:`, error)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
// Silently ignore file errors
|
|
||||||
console.debug(`Failed to load config from ${configPath}:`, error)
|
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
// Silently continue if require fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +230,7 @@ export class AugmentationConfigResolver {
|
||||||
*/
|
*/
|
||||||
private loadFromEnvironment(): void {
|
private loadFromEnvironment(): void {
|
||||||
// Skip in browser environment
|
// Skip in browser environment
|
||||||
if (typeof process === 'undefined' || !process.env) {
|
if (!isNode() || !process.env) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -442,7 +467,7 @@ export class AugmentationConfigResolver {
|
||||||
*/
|
*/
|
||||||
async saveToFile(filepath?: string, format: 'json' = 'json'): Promise<void> {
|
async saveToFile(filepath?: string, format: 'json' = 'json'): Promise<void> {
|
||||||
// Skip in browser environment
|
// Skip in browser environment
|
||||||
if (typeof process === 'undefined' || typeof window !== 'undefined') {
|
if (!isNode()) {
|
||||||
throw new Error('Cannot save configuration files in browser environment')
|
throw new Error('Cannot save configuration files in browser environment')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,7 @@
|
||||||
|
|
||||||
import { Vector, EmbeddingFunction } from '../coreTypes.js'
|
import { Vector, EmbeddingFunction } from '../coreTypes.js'
|
||||||
import { pipeline, env } from '@huggingface/transformers'
|
import { pipeline, env } from '@huggingface/transformers'
|
||||||
import { existsSync } from 'node:fs'
|
import { isNode } from '../utils/environment.js'
|
||||||
import { join } from 'node:path'
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export type ModelPrecision = 'q8' | 'fp32'
|
export type ModelPrecision = 'q8' | 'fp32'
|
||||||
|
|
@ -125,12 +124,23 @@ export class EmbeddingManager {
|
||||||
env.allowLocalModels = true
|
env.allowLocalModels = true
|
||||||
env.useFSCache = true
|
env.useFSCache = true
|
||||||
|
|
||||||
// Check if models exist locally
|
// Check if models exist locally (only in Node.js)
|
||||||
const modelPath = join(modelsPath, ...this.modelName.split('/'))
|
if (isNode()) {
|
||||||
const hasLocalModels = existsSync(modelPath)
|
try {
|
||||||
|
const nodeRequire = typeof require !== 'undefined' ? require : null
|
||||||
|
if (nodeRequire) {
|
||||||
|
const path = nodeRequire('node:path')
|
||||||
|
const fs = nodeRequire('node:fs')
|
||||||
|
const modelPath = path.join(modelsPath, ...this.modelName.split('/'))
|
||||||
|
const hasLocalModels = fs.existsSync(modelPath)
|
||||||
|
|
||||||
if (hasLocalModels) {
|
if (hasLocalModels) {
|
||||||
console.log('✅ Using cached models from:', modelPath)
|
console.log('✅ Using cached models from:', modelPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Silently continue if require fails
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure pipeline options for the selected precision
|
// Configure pipeline options for the selected precision
|
||||||
|
|
@ -256,24 +266,60 @@ export class EmbeddingManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get models directory path
|
* Get models directory path
|
||||||
|
* Note: In browser environments, returns a simple default path
|
||||||
|
* In Node.js, checks multiple locations for the models directory
|
||||||
*/
|
*/
|
||||||
private getModelsPath(): string {
|
private getModelsPath(): string {
|
||||||
// Check various possible locations
|
// In browser environments, use a default path
|
||||||
const paths = [
|
if (!isNode()) {
|
||||||
process.env.BRAINY_MODELS_PATH,
|
return './models'
|
||||||
'./models',
|
|
||||||
join(process.cwd(), 'models'),
|
|
||||||
join(process.env.HOME || '', '.brainy', 'models')
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const path of paths) {
|
|
||||||
if (path && existsSync(path)) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default
|
// Node.js-specific model path resolution
|
||||||
return join(process.cwd(), 'models')
|
// Cache the result for performance
|
||||||
|
if (!this.modelsPathCache) {
|
||||||
|
this.modelsPathCache = this.resolveModelsPathSync()
|
||||||
|
}
|
||||||
|
return this.modelsPathCache
|
||||||
|
}
|
||||||
|
|
||||||
|
private modelsPathCache: string | null = null
|
||||||
|
|
||||||
|
private resolveModelsPathSync(): string {
|
||||||
|
// For Node.js environments, we can safely assume these modules exist
|
||||||
|
// TypeScript will handle the imports at build time
|
||||||
|
// At runtime, these will only be called if isNode() is true
|
||||||
|
|
||||||
|
// Default fallback path
|
||||||
|
const defaultPath = './models'
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a conditional require function that only works in Node
|
||||||
|
const nodeRequire = typeof require !== 'undefined' ? require : null
|
||||||
|
if (!nodeRequire) return defaultPath
|
||||||
|
|
||||||
|
const fs = nodeRequire('node:fs')
|
||||||
|
const path = nodeRequire('node:path')
|
||||||
|
|
||||||
|
const paths = [
|
||||||
|
process.env.BRAINY_MODELS_PATH,
|
||||||
|
'./models',
|
||||||
|
path.join(process.cwd(), 'models'),
|
||||||
|
path.join(process.env.HOME || '', '.brainy', 'models')
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const p of paths) {
|
||||||
|
if (p && fs.existsSync(p)) {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default Node.js path
|
||||||
|
return path.join(process.cwd(), 'models')
|
||||||
|
} catch {
|
||||||
|
// Fallback if require fails
|
||||||
|
return defaultPath
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -2,32 +2,89 @@
|
||||||
* Version utilities for Brainy
|
* Version utilities for Brainy
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFileSync } from 'node:fs'
|
import { isNode } from './environment.js'
|
||||||
import { join, dirname } from 'node:path'
|
|
||||||
import { fileURLToPath } from 'node:url'
|
|
||||||
|
|
||||||
// Get package.json path relative to this file
|
// Default version - this should be replaced at build time
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const DEFAULT_VERSION = '3.5.1'
|
||||||
const __dirname = dirname(__filename)
|
|
||||||
const packageJsonPath = join(__dirname, '../../package.json')
|
|
||||||
|
|
||||||
let cachedVersion: string | null = null
|
let cachedVersion: string | null = null
|
||||||
|
let versionPromise: Promise<string> | null = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load version from package.json in Node.js environment
|
||||||
|
*/
|
||||||
|
async function loadVersionFromPackageJson(): Promise<string> {
|
||||||
|
if (!isNode()) {
|
||||||
|
return DEFAULT_VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Dynamic imports for Node.js modules - modern approach
|
||||||
|
const [{ readFileSync }, { join, dirname }, { fileURLToPath }] = await Promise.all([
|
||||||
|
import('node:fs'),
|
||||||
|
import('node:path'),
|
||||||
|
import('node:url')
|
||||||
|
])
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
const packageJsonPath = join(__dirname, '../../package.json')
|
||||||
|
|
||||||
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
||||||
|
return packageJson.version || DEFAULT_VERSION
|
||||||
|
} catch (error) {
|
||||||
|
// Silently fall back to default version
|
||||||
|
return DEFAULT_VERSION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current Brainy package version
|
* Get the current Brainy package version
|
||||||
|
* In Node.js, attempts to read from package.json
|
||||||
|
* In browser, returns the default version
|
||||||
* @returns The current version string
|
* @returns The current version string
|
||||||
*/
|
*/
|
||||||
export function getBrainyVersion(): string {
|
export function getBrainyVersion(): string {
|
||||||
if (!cachedVersion) {
|
if (cachedVersion) {
|
||||||
try {
|
return cachedVersion
|
||||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
|
||||||
cachedVersion = packageJson.version
|
|
||||||
} catch {
|
|
||||||
// Fallback version if package.json can't be read
|
|
||||||
cachedVersion = '2.7.1'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return cachedVersion!
|
|
||||||
|
// In browser or if we need immediate response, return default
|
||||||
|
if (!isNode()) {
|
||||||
|
cachedVersion = DEFAULT_VERSION
|
||||||
|
return cachedVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Node.js, try to load synchronously first time
|
||||||
|
// This is a compromise for backward compatibility
|
||||||
|
if (!versionPromise) {
|
||||||
|
versionPromise = loadVersionFromPackageJson()
|
||||||
|
versionPromise.then(version => {
|
||||||
|
cachedVersion = version
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return default while loading
|
||||||
|
return cachedVersion || DEFAULT_VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current Brainy package version asynchronously
|
||||||
|
* Guaranteed to attempt loading from package.json in Node.js
|
||||||
|
* @returns Promise resolving to the current version string
|
||||||
|
*/
|
||||||
|
export async function getBrainyVersionAsync(): Promise<string> {
|
||||||
|
if (cachedVersion) {
|
||||||
|
return cachedVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!versionPromise) {
|
||||||
|
versionPromise = loadVersionFromPackageJson()
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = await versionPromise
|
||||||
|
cachedVersion = version
|
||||||
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue