Skip to content
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
9ddfa7b
feat(intelligence) Add isSupported engine method
charlesbvll Mar 10, 2025
f405be6
feat(intelligence) Use isSupported method
charlesbvll Mar 10, 2025
d243f70
Remove undefined
charlesbvll Mar 10, 2025
0aaf62b
Merge branch 'fi-feat-add-issupported-engine-method' into fi-feat-use…
charlesbvll Mar 10, 2025
931685e
Use isSupported method
charlesbvll Mar 10, 2025
25fa7d4
refactor(intelligence) Reorganize remote engine
charlesbvll Mar 10, 2025
e7717f9
Merge branch 'fi-refactor-reorganize-remote-engine' into fi-feat-use-…
charlesbvll Mar 10, 2025
facda40
Merge branch 'main' into fi-feat-add-issupported-engine-method
tanertopal Mar 10, 2025
0b249f1
Merge branch 'main' into fi-feat-add-issupported-engine-method
charlesbvll Mar 10, 2025
fcc85bc
Merge branch 'fi-feat-add-issupported-engine-method' into fi-feat-use…
charlesbvll Mar 10, 2025
4d29e0b
Merge branch 'main' into fi-feat-add-issupported-engine-method
charlesbvll Mar 10, 2025
8700e49
Fix tests
charlesbvll Mar 10, 2025
5b7346b
Merge branch 'fi-feat-add-issupported-engine-method' into fi-feat-use…
charlesbvll Mar 10, 2025
82052fa
Revert small change
charlesbvll Mar 10, 2025
cea7726
Merge branch 'main' into fi-feat-use-issupported
charlesbvll Mar 10, 2025
341ffca
Improve async code
charlesbvll Mar 10, 2025
3c79661
feat(intelligence) Make remote call for supported check
charlesbvll Mar 10, 2025
bef98a0
Fix linting
charlesbvll Mar 10, 2025
b457f7b
Use constant for sdk
charlesbvll Mar 10, 2025
6337ff9
Fix types
charlesbvll Mar 10, 2025
a47d08c
feat(intelligence) Cache requests to check model
charlesbvll Mar 10, 2025
b12c2ef
Merge branch 'main' into fi-feat-check-model-cache
charlesbvll Mar 12, 2025
67030cc
refactor(intelligence) Use engine name only in engines
charlesbvll Mar 12, 2025
9c87281
Revert example change
charlesbvll Mar 12, 2025
2b25410
Merge branch 'fi-only-use-canonical-name' into fi-feat-check-model-cache
charlesbvll Mar 12, 2025
9d30e56
Fix linting errors
charlesbvll Mar 12, 2025
f4d9d03
Fix copyright notices
charlesbvll Mar 12, 2025
393b5d9
Fix caching
charlesbvll Mar 12, 2025
d2bc5a3
Improve failure
charlesbvll Mar 12, 2025
8ec9d7a
Merge branch 'fi-only-use-canonical-name' into fi-feat-check-model-cache
charlesbvll Mar 12, 2025
f5f8b43
Fix failure
charlesbvll Mar 12, 2025
0e6ce77
Merge branch 'fi-only-use-canonical-name' into fi-feat-check-model-cache
charlesbvll Mar 12, 2025
e5bb7c6
Reorder
charlesbvll Mar 12, 2025
39ef04e
Merge branch 'main' into fi-feat-check-model-cache
charlesbvll Mar 12, 2025
afb2e70
Merge branch 'main' into fi-feat-check-model-cache
charlesbvll Mar 12, 2025
a0f5dfa
Update browser cache key
charlesbvll Mar 13, 2025
3d3a00a
Update node path
charlesbvll Mar 13, 2025
8fef856
Externalize right packages
charlesbvll Mar 13, 2025
e63dc24
Merge branch 'main' of https://github.com/adap/flower into fi-feat-ch…
charlesbvll Mar 13, 2025
d1bce46
Fix linting error
charlesbvll Mar 13, 2025
01c55c9
Merge branch 'main' into fi-feat-check-model-cache
charlesbvll Mar 13, 2025
e95d26f
Add caching folder
charlesbvll Mar 13, 2025
51bf280
Fix linting error
charlesbvll Mar 13, 2025
2b27196
Fix copyright test
charlesbvll Mar 13, 2025
25235b0
Merge branch 'main' into fi-feat-check-model-cache
charlesbvll Mar 13, 2025
02a971b
Use cleaner methods
charlesbvll Mar 13, 2025
ee85d02
Merge branch 'main' into fi-feat-check-model-cache
charlesbvll Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 124 additions & 1 deletion intelligence/ts/src/engines/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,79 @@
// =============================================================================

import { REMOTE_URL, SDK, VERSION } from '../constants';
import { isNode } from '../env';
import { FailureCode, Result } from '../typing';

const STALE_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 hours.
const CACHE_KEY = 'flowerintelligence-model-name-cache'; // For browser localStorage.

interface ModelResponse {
is_supported: boolean;
engine_model: string | undefined;
model: string | undefined;
}

export async function getEngineModelName(model: string, engine: string): Promise<Result<string>> {
interface CachedEntry {
engineModel: string;
timestamp: number;
}

interface CachedMapping {
mapping: Record<string, CachedEntry>;
}

async function getCacheFilePath(): Promise<string> {
if (!isNode) return ''; // Not used in browsers.
const path = await import('path');
const os = await import('os');
const fs = await import('fs/promises');

const homeDir = os.homedir();
const cacheFolder = path.join(homeDir, '.flwr', 'cache');
await fs.mkdir(cacheFolder, { recursive: true });
return path.join(cacheFolder, 'intelligence-model-names.json');
}

/**
* Loads the cached mapping from persistent storage.
*/
async function loadCache(): Promise<CachedMapping | null> {
if (isNode) {
try {
const { readFile } = await import('fs/promises');
const filePath = await getCacheFilePath();
const data = await readFile(filePath, 'utf-8');
return JSON.parse(data) as CachedMapping;
} catch (_) {
return null;
}
} else {
const data = localStorage.getItem(CACHE_KEY);
if (data) {
try {
return JSON.parse(data) as CachedMapping;
} catch {
return null;
}
}
return null;
}
}

/**
* Saves the cache to persistent storage.
*/
async function saveCache(cache: CachedMapping): Promise<void> {
if (isNode) {
const { writeFile } = await import('fs/promises');
const filePath = await getCacheFilePath();
await writeFile(filePath, JSON.stringify(cache), 'utf-8');
} else {
localStorage.setItem(CACHE_KEY, JSON.stringify(cache));
}
}

async function updateModel(model: string, engine: string): Promise<Result<string>> {
try {
const response = await fetch(`${REMOTE_URL}/v1/fetch-model-config`, {
method: 'POST',
Expand All @@ -48,8 +112,10 @@ export async function getEngineModelName(model: string, engine: string): Promise
const data = (await response.json()) as ModelResponse;

if (data.is_supported && data.engine_model) {
await addOrUpdateCache(model, engine, data.engine_model);
return { ok: true, value: data.engine_model };
} else {
await removeFromCache(model, engine);
return {
ok: false,
failure: {
Expand All @@ -68,3 +134,60 @@ export async function getEngineModelName(model: string, engine: string): Promise
};
}
}

/**
* Adds or updates a model mapping in the cache with the current timestamp.
*/
async function addOrUpdateCache(model: string, engine: string, engineModel: string): Promise<void> {
const now = Date.now();
let cache = await loadCache();
if (!cache) {
cache = { mapping: {} };
}
const key = `${model}_${engine}`;
cache.mapping[key] = { engineModel, timestamp: now };
await saveCache(cache);
}

/**
* Removes a model from the cache.
*/
async function removeFromCache(model: string, engine: string): Promise<void> {
const cache = await loadCache();
const key = `${model}_${engine}`;
if (cache && key in cache.mapping) {
const { [key]: removed, ...rest } = cache.mapping;
cache.mapping = rest;
await saveCache(cache);
}
}

/**
* Checks if a model is supported.
* - If the model exists in the cache and its timestamp is fresh, return it.
* - If it exists but is stale (older than 24 hours), trigger a background update (and return the stale mapping).
* - If it does not exist, update synchronously.
*/
export async function getEngineModelName(model: string, engine: string): Promise<Result<string>> {
const now = Date.now();
let cache = await loadCache();
if (!cache) {
cache = { mapping: {} };
}

const key = `${model}_${engine}`;
if (key in cache.mapping) {
const cachedEntry = cache.mapping[key];
// If the cached entry is stale, trigger a background update.
if (now - cachedEntry.timestamp > STALE_TIMEOUT_MS) {
updateModel(model, engine).catch((err: unknown) => {
console.warn(`Background update failed for model ${model}:`, String(err));
});
}
// Return the (possibly stale) cached result.
return { ok: true, value: cachedEntry.engineModel };
} else {
// Not in cache, call updateModel synchronously.
return await updateModel(model, engine);
}
}
18 changes: 18 additions & 0 deletions intelligence/ts/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2025 Flower Labs GmbH. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================

/* eslint-disable @typescript-eslint/no-unnecessary-condition */
export const isNode: boolean = typeof process !== 'undefined' && process.versions?.node != null;
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
5 changes: 1 addition & 4 deletions intelligence/ts/src/flowerintelligence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ import { TransformersEngine } from './engines/transformersEngine';
import { ChatOptions, ChatResponseResult, FailureCode, Message, Progress, Result } from './typing';
import { WebllmEngine } from './engines/webllmEngine';
import { DEFAULT_MODEL } from './constants';

/* eslint-disable @typescript-eslint/no-unnecessary-condition */
const isNode = typeof process !== 'undefined' && process.versions.node != null;
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
import { isNode } from './env';

/**
* Class representing the core intelligence service for Flower Labs.
Expand Down
10 changes: 9 additions & 1 deletion intelligence/ts/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ export default defineConfig({
fileName: (format) => `flowerintelligence.${format}.js`,
},
rollupOptions: {
external: ['@huggingface/transformers', '@mlc-ai/web-llm', 'crypto'],
external: [
'@huggingface/transformers',
'@mlc-ai/web-llm',
'crypto',
'fs',
'fs/promises',
'path',
'os',
],
},
},
});