fix: updated logger and model caching minor bugfix #release (#895)
Browse files* fix: updated logger and model caching
* usage token stream issue fix
* minor changes
* updated starter template change to fix the app title
* starter template bigfix
* fixed hydretion errors and raw logs
* removed raw log
* made auto select template false by default
* more cleaner logs and updated logic to call dynamicModels only if not found in static models
* updated starter template instructions
* browser console log improved for firefox
* provider icons fix icons
- app/components/chat/BaseChat.tsx +48 -42
- app/components/chat/Chat.client.tsx +4 -3
- app/components/settings/providers/ProvidersTab.tsx +2 -1
- app/entry.server.tsx +1 -2
- app/lib/.server/llm/stream-text.ts +35 -10
- app/lib/modules/llm/base-provider.ts +52 -0
- app/lib/modules/llm/manager.ts +90 -10
- app/lib/modules/llm/providers/huggingface.ts +42 -0
- app/lib/modules/llm/providers/hyperbolic.ts +25 -31
- app/lib/modules/llm/providers/lmstudio.ts +18 -24
- app/lib/modules/llm/providers/ollama.ts +21 -26
- app/lib/modules/llm/providers/open-router.ts +0 -1
- app/lib/modules/llm/providers/openai-like.ts +22 -27
- app/lib/modules/llm/providers/openai.ts +1 -0
- app/lib/modules/llm/providers/together.ts +25 -30
- app/lib/runtime/message-parser.ts +2 -1
- app/lib/stores/settings.ts +1 -1
- app/routes/api.chat.ts +35 -20
- app/utils/constants.ts +0 -306
- app/utils/logger.ts +20 -12
- app/utils/selectStarterTemplate.ts +21 -11
- package.json +1 -0
- pnpm-lock.yaml +7 -4
app/components/chat/BaseChat.tsx
CHANGED
|
@@ -168,30 +168,32 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
| 168 |
}, []);
|
| 169 |
|
| 170 |
useEffect(() => {
|
| 171 |
-
|
| 172 |
-
|
|
|
|
| 173 |
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
}
|
| 183 |
-
setIsModelLoading('all');
|
| 184 |
-
initializeModelList({ apiKeys: parsedApiKeys, providerSettings })
|
| 185 |
-
.then((modelList) => {
|
| 186 |
-
console.log('Model List: ', modelList);
|
| 187 |
-
setModelList(modelList);
|
| 188 |
-
})
|
| 189 |
-
.catch((error) => {
|
| 190 |
-
console.error('Error initializing model list:', error);
|
| 191 |
-
})
|
| 192 |
-
.finally(() => {
|
| 193 |
-
setIsModelLoading(undefined);
|
| 194 |
-
});
|
| 195 |
}, [providerList]);
|
| 196 |
|
| 197 |
const onApiKeysChange = async (providerName: string, apiKey: string) => {
|
|
@@ -401,28 +403,32 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
| 401 |
<rect className={classNames(styles.PromptShine)} x="48" y="24" width="70" height="1"></rect>
|
| 402 |
</svg>
|
| 403 |
<div>
|
| 404 |
-
<
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
)}
|
| 425 |
-
</
|
| 426 |
</div>
|
| 427 |
<FilePreview
|
| 428 |
files={uploadedFiles}
|
|
|
|
| 168 |
}, []);
|
| 169 |
|
| 170 |
useEffect(() => {
|
| 171 |
+
if (typeof window !== 'undefined') {
|
| 172 |
+
const providerSettings = getProviderSettings();
|
| 173 |
+
let parsedApiKeys: Record<string, string> | undefined = {};
|
| 174 |
|
| 175 |
+
try {
|
| 176 |
+
parsedApiKeys = getApiKeysFromCookies();
|
| 177 |
+
setApiKeys(parsedApiKeys);
|
| 178 |
+
} catch (error) {
|
| 179 |
+
console.error('Error loading API keys from cookies:', error);
|
| 180 |
|
| 181 |
+
// Clear invalid cookie data
|
| 182 |
+
Cookies.remove('apiKeys');
|
| 183 |
+
}
|
| 184 |
+
setIsModelLoading('all');
|
| 185 |
+
initializeModelList({ apiKeys: parsedApiKeys, providerSettings })
|
| 186 |
+
.then((modelList) => {
|
| 187 |
+
// console.log('Model List: ', modelList);
|
| 188 |
+
setModelList(modelList);
|
| 189 |
+
})
|
| 190 |
+
.catch((error) => {
|
| 191 |
+
console.error('Error initializing model list:', error);
|
| 192 |
+
})
|
| 193 |
+
.finally(() => {
|
| 194 |
+
setIsModelLoading(undefined);
|
| 195 |
+
});
|
| 196 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
}, [providerList]);
|
| 198 |
|
| 199 |
const onApiKeysChange = async (providerName: string, apiKey: string) => {
|
|
|
|
| 403 |
<rect className={classNames(styles.PromptShine)} x="48" y="24" width="70" height="1"></rect>
|
| 404 |
</svg>
|
| 405 |
<div>
|
| 406 |
+
<ClientOnly>
|
| 407 |
+
{() => (
|
| 408 |
+
<div className={isModelSettingsCollapsed ? 'hidden' : ''}>
|
| 409 |
+
<ModelSelector
|
| 410 |
+
key={provider?.name + ':' + modelList.length}
|
| 411 |
+
model={model}
|
| 412 |
+
setModel={setModel}
|
| 413 |
+
modelList={modelList}
|
| 414 |
+
provider={provider}
|
| 415 |
+
setProvider={setProvider}
|
| 416 |
+
providerList={providerList || (PROVIDER_LIST as ProviderInfo[])}
|
| 417 |
+
apiKeys={apiKeys}
|
| 418 |
+
modelLoading={isModelLoading}
|
| 419 |
+
/>
|
| 420 |
+
{(providerList || []).length > 0 && provider && (
|
| 421 |
+
<APIKeyManager
|
| 422 |
+
provider={provider}
|
| 423 |
+
apiKey={apiKeys[provider.name] || ''}
|
| 424 |
+
setApiKey={(key) => {
|
| 425 |
+
onApiKeysChange(provider.name, key);
|
| 426 |
+
}}
|
| 427 |
+
/>
|
| 428 |
+
)}
|
| 429 |
+
</div>
|
| 430 |
)}
|
| 431 |
+
</ClientOnly>
|
| 432 |
</div>
|
| 433 |
<FilePreview
|
| 434 |
files={uploadedFiles}
|
app/components/chat/Chat.client.tsx
CHANGED
|
@@ -168,7 +168,8 @@ export const ChatImpl = memo(
|
|
| 168 |
});
|
| 169 |
useEffect(() => {
|
| 170 |
const prompt = searchParams.get('prompt');
|
| 171 |
-
|
|
|
|
| 172 |
|
| 173 |
if (prompt) {
|
| 174 |
setSearchParams({});
|
|
@@ -289,14 +290,14 @@ export const ChatImpl = memo(
|
|
| 289 |
|
| 290 |
// reload();
|
| 291 |
|
| 292 |
-
const template = await selectStarterTemplate({
|
| 293 |
message: messageInput,
|
| 294 |
model,
|
| 295 |
provider,
|
| 296 |
});
|
| 297 |
|
| 298 |
if (template !== 'blank') {
|
| 299 |
-
const temResp = await getTemplates(template);
|
| 300 |
|
| 301 |
if (temResp) {
|
| 302 |
const { assistantMessage, userMessage } = temResp;
|
|
|
|
| 168 |
});
|
| 169 |
useEffect(() => {
|
| 170 |
const prompt = searchParams.get('prompt');
|
| 171 |
+
|
| 172 |
+
// console.log(prompt, searchParams, model, provider);
|
| 173 |
|
| 174 |
if (prompt) {
|
| 175 |
setSearchParams({});
|
|
|
|
| 290 |
|
| 291 |
// reload();
|
| 292 |
|
| 293 |
+
const { template, title } = await selectStarterTemplate({
|
| 294 |
message: messageInput,
|
| 295 |
model,
|
| 296 |
provider,
|
| 297 |
});
|
| 298 |
|
| 299 |
if (template !== 'blank') {
|
| 300 |
+
const temResp = await getTemplates(template, title);
|
| 301 |
|
| 302 |
if (temResp) {
|
| 303 |
const { assistantMessage, userMessage } = temResp;
|
app/components/settings/providers/ProvidersTab.tsx
CHANGED
|
@@ -6,9 +6,10 @@ import type { IProviderConfig } from '~/types/model';
|
|
| 6 |
import { logStore } from '~/lib/stores/logs';
|
| 7 |
|
| 8 |
// Import a default fallback icon
|
| 9 |
-
import DefaultIcon from '/icons/Default.svg'; // Adjust the path as necessary
|
| 10 |
import { providerBaseUrlEnvKeys } from '~/utils/constants';
|
| 11 |
|
|
|
|
|
|
|
| 12 |
export default function ProvidersTab() {
|
| 13 |
const { providers, updateProviderSettings, isLocalModel } = useSettings();
|
| 14 |
const [filteredProviders, setFilteredProviders] = useState<IProviderConfig[]>([]);
|
|
|
|
| 6 |
import { logStore } from '~/lib/stores/logs';
|
| 7 |
|
| 8 |
// Import a default fallback icon
|
|
|
|
| 9 |
import { providerBaseUrlEnvKeys } from '~/utils/constants';
|
| 10 |
|
| 11 |
+
const DefaultIcon = '/icons/Default.svg'; // Adjust the path as necessary
|
| 12 |
+
|
| 13 |
export default function ProvidersTab() {
|
| 14 |
const { providers, updateProviderSettings, isLocalModel } = useSettings();
|
| 15 |
const [filteredProviders, setFilteredProviders] = useState<IProviderConfig[]>([]);
|
app/entry.server.tsx
CHANGED
|
@@ -5,7 +5,6 @@ import { renderToReadableStream } from 'react-dom/server';
|
|
| 5 |
import { renderHeadToString } from 'remix-island';
|
| 6 |
import { Head } from './root';
|
| 7 |
import { themeStore } from '~/lib/stores/theme';
|
| 8 |
-
import { initializeModelList } from '~/utils/constants';
|
| 9 |
|
| 10 |
export default async function handleRequest(
|
| 11 |
request: Request,
|
|
@@ -14,7 +13,7 @@ export default async function handleRequest(
|
|
| 14 |
remixContext: EntryContext,
|
| 15 |
_loadContext: AppLoadContext,
|
| 16 |
) {
|
| 17 |
-
await initializeModelList({});
|
| 18 |
|
| 19 |
const readable = await renderToReadableStream(<RemixServer context={remixContext} url={request.url} />, {
|
| 20 |
signal: request.signal,
|
|
|
|
| 5 |
import { renderHeadToString } from 'remix-island';
|
| 6 |
import { Head } from './root';
|
| 7 |
import { themeStore } from '~/lib/stores/theme';
|
|
|
|
| 8 |
|
| 9 |
export default async function handleRequest(
|
| 10 |
request: Request,
|
|
|
|
| 13 |
remixContext: EntryContext,
|
| 14 |
_loadContext: AppLoadContext,
|
| 15 |
) {
|
| 16 |
+
// await initializeModelList({});
|
| 17 |
|
| 18 |
const readable = await renderToReadableStream(<RemixServer context={remixContext} url={request.url} />, {
|
| 19 |
signal: request.signal,
|
app/lib/.server/llm/stream-text.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { getSystemPrompt } from '~/lib/common/prompts/prompts';
|
|
| 4 |
import {
|
| 5 |
DEFAULT_MODEL,
|
| 6 |
DEFAULT_PROVIDER,
|
| 7 |
-
getModelList,
|
| 8 |
MODEL_REGEX,
|
| 9 |
MODIFICATIONS_TAG_NAME,
|
| 10 |
PROVIDER_LIST,
|
|
@@ -15,6 +14,8 @@ import ignore from 'ignore';
|
|
| 15 |
import type { IProviderSetting } from '~/types/model';
|
| 16 |
import { PromptLibrary } from '~/lib/common/prompt-library';
|
| 17 |
import { allowedHTMLElements } from '~/utils/markdown';
|
|
|
|
|
|
|
| 18 |
|
| 19 |
interface ToolResult<Name extends string, Args, Result> {
|
| 20 |
toolCallId: string;
|
|
@@ -142,6 +143,8 @@ function extractPropertiesFromMessage(message: Message): { model: string; provid
|
|
| 142 |
return { model, provider, content: cleanedContent };
|
| 143 |
}
|
| 144 |
|
|
|
|
|
|
|
| 145 |
export async function streamText(props: {
|
| 146 |
messages: Messages;
|
| 147 |
env: Env;
|
|
@@ -158,15 +161,10 @@ export async function streamText(props: {
|
|
| 158 |
|
| 159 |
let currentModel = DEFAULT_MODEL;
|
| 160 |
let currentProvider = DEFAULT_PROVIDER.name;
|
| 161 |
-
const MODEL_LIST = await getModelList({ apiKeys, providerSettings, serverEnv: serverEnv as any });
|
| 162 |
const processedMessages = messages.map((message) => {
|
| 163 |
if (message.role === 'user') {
|
| 164 |
const { model, provider, content } = extractPropertiesFromMessage(message);
|
| 165 |
-
|
| 166 |
-
if (MODEL_LIST.find((m) => m.name === model)) {
|
| 167 |
-
currentModel = model;
|
| 168 |
-
}
|
| 169 |
-
|
| 170 |
currentProvider = provider;
|
| 171 |
|
| 172 |
return { ...message, content };
|
|
@@ -183,11 +181,36 @@ export async function streamText(props: {
|
|
| 183 |
return message;
|
| 184 |
});
|
| 185 |
|
| 186 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
-
|
| 189 |
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
let systemPrompt =
|
| 193 |
PromptLibrary.getPropmtFromLibrary(promptId || 'default', {
|
|
@@ -201,6 +224,8 @@ export async function streamText(props: {
|
|
| 201 |
systemPrompt = `${systemPrompt}\n\n ${codeContext}`;
|
| 202 |
}
|
| 203 |
|
|
|
|
|
|
|
| 204 |
return _streamText({
|
| 205 |
model: provider.getModelInstance({
|
| 206 |
model: currentModel,
|
|
|
|
| 4 |
import {
|
| 5 |
DEFAULT_MODEL,
|
| 6 |
DEFAULT_PROVIDER,
|
|
|
|
| 7 |
MODEL_REGEX,
|
| 8 |
MODIFICATIONS_TAG_NAME,
|
| 9 |
PROVIDER_LIST,
|
|
|
|
| 14 |
import type { IProviderSetting } from '~/types/model';
|
| 15 |
import { PromptLibrary } from '~/lib/common/prompt-library';
|
| 16 |
import { allowedHTMLElements } from '~/utils/markdown';
|
| 17 |
+
import { LLMManager } from '~/lib/modules/llm/manager';
|
| 18 |
+
import { createScopedLogger } from '~/utils/logger';
|
| 19 |
|
| 20 |
interface ToolResult<Name extends string, Args, Result> {
|
| 21 |
toolCallId: string;
|
|
|
|
| 143 |
return { model, provider, content: cleanedContent };
|
| 144 |
}
|
| 145 |
|
| 146 |
+
const logger = createScopedLogger('stream-text');
|
| 147 |
+
|
| 148 |
export async function streamText(props: {
|
| 149 |
messages: Messages;
|
| 150 |
env: Env;
|
|
|
|
| 161 |
|
| 162 |
let currentModel = DEFAULT_MODEL;
|
| 163 |
let currentProvider = DEFAULT_PROVIDER.name;
|
|
|
|
| 164 |
const processedMessages = messages.map((message) => {
|
| 165 |
if (message.role === 'user') {
|
| 166 |
const { model, provider, content } = extractPropertiesFromMessage(message);
|
| 167 |
+
currentModel = model;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
currentProvider = provider;
|
| 169 |
|
| 170 |
return { ...message, content };
|
|
|
|
| 181 |
return message;
|
| 182 |
});
|
| 183 |
|
| 184 |
+
const provider = PROVIDER_LIST.find((p) => p.name === currentProvider) || DEFAULT_PROVIDER;
|
| 185 |
+
const staticModels = LLMManager.getInstance().getStaticModelListFromProvider(provider);
|
| 186 |
+
let modelDetails = staticModels.find((m) => m.name === currentModel);
|
| 187 |
+
|
| 188 |
+
if (!modelDetails) {
|
| 189 |
+
const modelsList = [
|
| 190 |
+
...(provider.staticModels || []),
|
| 191 |
+
...(await LLMManager.getInstance().getModelListFromProvider(provider, {
|
| 192 |
+
apiKeys,
|
| 193 |
+
providerSettings,
|
| 194 |
+
serverEnv: serverEnv as any,
|
| 195 |
+
})),
|
| 196 |
+
];
|
| 197 |
+
|
| 198 |
+
if (!modelsList.length) {
|
| 199 |
+
throw new Error(`No models found for provider ${provider.name}`);
|
| 200 |
+
}
|
| 201 |
|
| 202 |
+
modelDetails = modelsList.find((m) => m.name === currentModel);
|
| 203 |
|
| 204 |
+
if (!modelDetails) {
|
| 205 |
+
// Fallback to first model
|
| 206 |
+
logger.warn(
|
| 207 |
+
`MODEL [${currentModel}] not found in provider [${provider.name}]. Falling back to first model. ${modelsList[0].name}`,
|
| 208 |
+
);
|
| 209 |
+
modelDetails = modelsList[0];
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
const dynamicMaxTokens = modelDetails && modelDetails.maxTokenAllowed ? modelDetails.maxTokenAllowed : MAX_TOKENS;
|
| 214 |
|
| 215 |
let systemPrompt =
|
| 216 |
PromptLibrary.getPropmtFromLibrary(promptId || 'default', {
|
|
|
|
| 224 |
systemPrompt = `${systemPrompt}\n\n ${codeContext}`;
|
| 225 |
}
|
| 226 |
|
| 227 |
+
logger.info(`Sending llm call to ${provider.name} with model ${modelDetails.name}`);
|
| 228 |
+
|
| 229 |
return _streamText({
|
| 230 |
model: provider.getModelInstance({
|
| 231 |
model: currentModel,
|
app/lib/modules/llm/base-provider.ts
CHANGED
|
@@ -8,6 +8,10 @@ export abstract class BaseProvider implements ProviderInfo {
|
|
| 8 |
abstract name: string;
|
| 9 |
abstract staticModels: ModelInfo[];
|
| 10 |
abstract config: ProviderConfig;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
getApiKeyLink?: string;
|
| 13 |
labelForGetApiKey?: string;
|
|
@@ -49,6 +53,54 @@ export abstract class BaseProvider implements ProviderInfo {
|
|
| 49 |
apiKey,
|
| 50 |
};
|
| 51 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
// Declare the optional getDynamicModels method
|
| 54 |
getDynamicModels?(
|
|
|
|
| 8 |
abstract name: string;
|
| 9 |
abstract staticModels: ModelInfo[];
|
| 10 |
abstract config: ProviderConfig;
|
| 11 |
+
cachedDynamicModels?: {
|
| 12 |
+
cacheId: string;
|
| 13 |
+
models: ModelInfo[];
|
| 14 |
+
};
|
| 15 |
|
| 16 |
getApiKeyLink?: string;
|
| 17 |
labelForGetApiKey?: string;
|
|
|
|
| 53 |
apiKey,
|
| 54 |
};
|
| 55 |
}
|
| 56 |
+
getModelsFromCache(options: {
|
| 57 |
+
apiKeys?: Record<string, string>;
|
| 58 |
+
providerSettings?: Record<string, IProviderSetting>;
|
| 59 |
+
serverEnv?: Record<string, string>;
|
| 60 |
+
}): ModelInfo[] | null {
|
| 61 |
+
if (!this.cachedDynamicModels) {
|
| 62 |
+
// console.log('no dynamic models',this.name);
|
| 63 |
+
return null;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
const cacheKey = this.cachedDynamicModels.cacheId;
|
| 67 |
+
const generatedCacheKey = this.getDynamicModelsCacheKey(options);
|
| 68 |
+
|
| 69 |
+
if (cacheKey !== generatedCacheKey) {
|
| 70 |
+
// console.log('cache key mismatch',this.name,cacheKey,generatedCacheKey);
|
| 71 |
+
this.cachedDynamicModels = undefined;
|
| 72 |
+
return null;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
return this.cachedDynamicModels.models;
|
| 76 |
+
}
|
| 77 |
+
getDynamicModelsCacheKey(options: {
|
| 78 |
+
apiKeys?: Record<string, string>;
|
| 79 |
+
providerSettings?: Record<string, IProviderSetting>;
|
| 80 |
+
serverEnv?: Record<string, string>;
|
| 81 |
+
}) {
|
| 82 |
+
return JSON.stringify({
|
| 83 |
+
apiKeys: options.apiKeys?.[this.name],
|
| 84 |
+
providerSettings: options.providerSettings?.[this.name],
|
| 85 |
+
serverEnv: options.serverEnv,
|
| 86 |
+
});
|
| 87 |
+
}
|
| 88 |
+
storeDynamicModels(
|
| 89 |
+
options: {
|
| 90 |
+
apiKeys?: Record<string, string>;
|
| 91 |
+
providerSettings?: Record<string, IProviderSetting>;
|
| 92 |
+
serverEnv?: Record<string, string>;
|
| 93 |
+
},
|
| 94 |
+
models: ModelInfo[],
|
| 95 |
+
) {
|
| 96 |
+
const cacheId = this.getDynamicModelsCacheKey(options);
|
| 97 |
+
|
| 98 |
+
// console.log('caching dynamic models',this.name,cacheId);
|
| 99 |
+
this.cachedDynamicModels = {
|
| 100 |
+
cacheId,
|
| 101 |
+
models,
|
| 102 |
+
};
|
| 103 |
+
}
|
| 104 |
|
| 105 |
// Declare the optional getDynamicModels method
|
| 106 |
getDynamicModels?(
|
app/lib/modules/llm/manager.ts
CHANGED
|
@@ -2,7 +2,9 @@ import type { IProviderSetting } from '~/types/model';
|
|
| 2 |
import { BaseProvider } from './base-provider';
|
| 3 |
import type { ModelInfo, ProviderInfo } from './types';
|
| 4 |
import * as providers from './registry';
|
|
|
|
| 5 |
|
|
|
|
| 6 |
export class LLMManager {
|
| 7 |
private static _instance: LLMManager;
|
| 8 |
private _providers: Map<string, BaseProvider> = new Map();
|
|
@@ -40,22 +42,22 @@ export class LLMManager {
|
|
| 40 |
try {
|
| 41 |
this.registerProvider(provider);
|
| 42 |
} catch (error: any) {
|
| 43 |
-
|
| 44 |
}
|
| 45 |
}
|
| 46 |
}
|
| 47 |
} catch (error) {
|
| 48 |
-
|
| 49 |
}
|
| 50 |
}
|
| 51 |
|
| 52 |
registerProvider(provider: BaseProvider) {
|
| 53 |
if (this._providers.has(provider.name)) {
|
| 54 |
-
|
| 55 |
return;
|
| 56 |
}
|
| 57 |
|
| 58 |
-
|
| 59 |
this._providers.set(provider.name, provider);
|
| 60 |
this._modelList = [...this._modelList, ...provider.staticModels];
|
| 61 |
}
|
|
@@ -93,12 +95,28 @@ export class LLMManager {
|
|
| 93 |
(provider): provider is BaseProvider & Required<Pick<ProviderInfo, 'getDynamicModels'>> =>
|
| 94 |
!!provider.getDynamicModels,
|
| 95 |
)
|
| 96 |
-
.map((provider) =>
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
);
|
| 103 |
|
| 104 |
// Combine static and dynamic models
|
|
@@ -110,6 +128,68 @@ export class LLMManager {
|
|
| 110 |
|
| 111 |
return modelList;
|
| 112 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
getDefaultProvider(): BaseProvider {
|
| 115 |
const firstProvider = this._providers.values().next().value;
|
|
|
|
| 2 |
import { BaseProvider } from './base-provider';
|
| 3 |
import type { ModelInfo, ProviderInfo } from './types';
|
| 4 |
import * as providers from './registry';
|
| 5 |
+
import { createScopedLogger } from '~/utils/logger';
|
| 6 |
|
| 7 |
+
const logger = createScopedLogger('LLMManager');
|
| 8 |
export class LLMManager {
|
| 9 |
private static _instance: LLMManager;
|
| 10 |
private _providers: Map<string, BaseProvider> = new Map();
|
|
|
|
| 42 |
try {
|
| 43 |
this.registerProvider(provider);
|
| 44 |
} catch (error: any) {
|
| 45 |
+
logger.warn('Failed To Register Provider: ', provider.name, 'error:', error.message);
|
| 46 |
}
|
| 47 |
}
|
| 48 |
}
|
| 49 |
} catch (error) {
|
| 50 |
+
logger.error('Error registering providers:', error);
|
| 51 |
}
|
| 52 |
}
|
| 53 |
|
| 54 |
registerProvider(provider: BaseProvider) {
|
| 55 |
if (this._providers.has(provider.name)) {
|
| 56 |
+
logger.warn(`Provider ${provider.name} is already registered. Skipping.`);
|
| 57 |
return;
|
| 58 |
}
|
| 59 |
|
| 60 |
+
logger.info('Registering Provider: ', provider.name);
|
| 61 |
this._providers.set(provider.name, provider);
|
| 62 |
this._modelList = [...this._modelList, ...provider.staticModels];
|
| 63 |
}
|
|
|
|
| 95 |
(provider): provider is BaseProvider & Required<Pick<ProviderInfo, 'getDynamicModels'>> =>
|
| 96 |
!!provider.getDynamicModels,
|
| 97 |
)
|
| 98 |
+
.map(async (provider) => {
|
| 99 |
+
const cachedModels = provider.getModelsFromCache(options);
|
| 100 |
+
|
| 101 |
+
if (cachedModels) {
|
| 102 |
+
return cachedModels;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
const dynamicModels = await provider
|
| 106 |
+
.getDynamicModels(apiKeys, providerSettings?.[provider.name], serverEnv)
|
| 107 |
+
.then((models) => {
|
| 108 |
+
logger.info(`Caching ${models.length} dynamic models for ${provider.name}`);
|
| 109 |
+
provider.storeDynamicModels(options, models);
|
| 110 |
+
|
| 111 |
+
return models;
|
| 112 |
+
})
|
| 113 |
+
.catch((err) => {
|
| 114 |
+
logger.error(`Error getting dynamic models ${provider.name} :`, err);
|
| 115 |
+
return [];
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
return dynamicModels;
|
| 119 |
+
}),
|
| 120 |
);
|
| 121 |
|
| 122 |
// Combine static and dynamic models
|
|
|
|
| 128 |
|
| 129 |
return modelList;
|
| 130 |
}
|
| 131 |
+
getStaticModelList() {
|
| 132 |
+
return [...this._providers.values()].flatMap((p) => p.staticModels || []);
|
| 133 |
+
}
|
| 134 |
+
async getModelListFromProvider(
|
| 135 |
+
providerArg: BaseProvider,
|
| 136 |
+
options: {
|
| 137 |
+
apiKeys?: Record<string, string>;
|
| 138 |
+
providerSettings?: Record<string, IProviderSetting>;
|
| 139 |
+
serverEnv?: Record<string, string>;
|
| 140 |
+
},
|
| 141 |
+
): Promise<ModelInfo[]> {
|
| 142 |
+
const provider = this._providers.get(providerArg.name);
|
| 143 |
+
|
| 144 |
+
if (!provider) {
|
| 145 |
+
throw new Error(`Provider ${providerArg.name} not found`);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
const staticModels = provider.staticModels || [];
|
| 149 |
+
|
| 150 |
+
if (!provider.getDynamicModels) {
|
| 151 |
+
return staticModels;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
const { apiKeys, providerSettings, serverEnv } = options;
|
| 155 |
+
|
| 156 |
+
const cachedModels = provider.getModelsFromCache({
|
| 157 |
+
apiKeys,
|
| 158 |
+
providerSettings,
|
| 159 |
+
serverEnv,
|
| 160 |
+
});
|
| 161 |
+
|
| 162 |
+
if (cachedModels) {
|
| 163 |
+
logger.info(`Found ${cachedModels.length} cached models for ${provider.name}`);
|
| 164 |
+
return [...cachedModels, ...staticModels];
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
logger.info(`Getting dynamic models for ${provider.name}`);
|
| 168 |
+
|
| 169 |
+
const dynamicModels = await provider
|
| 170 |
+
.getDynamicModels?.(apiKeys, providerSettings?.[provider.name], serverEnv)
|
| 171 |
+
.then((models) => {
|
| 172 |
+
logger.info(`Got ${models.length} dynamic models for ${provider.name}`);
|
| 173 |
+
provider.storeDynamicModels(options, models);
|
| 174 |
+
|
| 175 |
+
return models;
|
| 176 |
+
})
|
| 177 |
+
.catch((err) => {
|
| 178 |
+
logger.error(`Error getting dynamic models ${provider.name} :`, err);
|
| 179 |
+
return [];
|
| 180 |
+
});
|
| 181 |
+
|
| 182 |
+
return [...dynamicModels, ...staticModels];
|
| 183 |
+
}
|
| 184 |
+
getStaticModelListFromProvider(providerArg: BaseProvider) {
|
| 185 |
+
const provider = this._providers.get(providerArg.name);
|
| 186 |
+
|
| 187 |
+
if (!provider) {
|
| 188 |
+
throw new Error(`Provider ${providerArg.name} not found`);
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
return [...(provider.staticModels || [])];
|
| 192 |
+
}
|
| 193 |
|
| 194 |
getDefaultProvider(): BaseProvider {
|
| 195 |
const firstProvider = this._providers.values().next().value;
|
app/lib/modules/llm/providers/huggingface.ts
CHANGED
|
@@ -25,6 +25,30 @@ export default class HuggingFaceProvider extends BaseProvider {
|
|
| 25 |
provider: 'HuggingFace',
|
| 26 |
maxTokenAllowed: 8000,
|
| 27 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
{
|
| 29 |
name: 'meta-llama/Llama-3.1-70B-Instruct',
|
| 30 |
label: 'Llama-3.1-70B-Instruct (HuggingFace)',
|
|
@@ -37,6 +61,24 @@ export default class HuggingFaceProvider extends BaseProvider {
|
|
| 37 |
provider: 'HuggingFace',
|
| 38 |
maxTokenAllowed: 8000,
|
| 39 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
];
|
| 41 |
|
| 42 |
getModelInstance(options: {
|
|
|
|
| 25 |
provider: 'HuggingFace',
|
| 26 |
maxTokenAllowed: 8000,
|
| 27 |
},
|
| 28 |
+
{
|
| 29 |
+
name: 'codellama/CodeLlama-34b-Instruct-hf',
|
| 30 |
+
label: 'CodeLlama-34b-Instruct (HuggingFace)',
|
| 31 |
+
provider: 'HuggingFace',
|
| 32 |
+
maxTokenAllowed: 8000,
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
name: 'NousResearch/Hermes-3-Llama-3.1-8B',
|
| 36 |
+
label: 'Hermes-3-Llama-3.1-8B (HuggingFace)',
|
| 37 |
+
provider: 'HuggingFace',
|
| 38 |
+
maxTokenAllowed: 8000,
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
name: 'Qwen/Qwen2.5-Coder-32B-Instruct',
|
| 42 |
+
label: 'Qwen2.5-Coder-32B-Instruct (HuggingFace)',
|
| 43 |
+
provider: 'HuggingFace',
|
| 44 |
+
maxTokenAllowed: 8000,
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
name: 'Qwen/Qwen2.5-72B-Instruct',
|
| 48 |
+
label: 'Qwen2.5-72B-Instruct (HuggingFace)',
|
| 49 |
+
provider: 'HuggingFace',
|
| 50 |
+
maxTokenAllowed: 8000,
|
| 51 |
+
},
|
| 52 |
{
|
| 53 |
name: 'meta-llama/Llama-3.1-70B-Instruct',
|
| 54 |
label: 'Llama-3.1-70B-Instruct (HuggingFace)',
|
|
|
|
| 61 |
provider: 'HuggingFace',
|
| 62 |
maxTokenAllowed: 8000,
|
| 63 |
},
|
| 64 |
+
{
|
| 65 |
+
name: '01-ai/Yi-1.5-34B-Chat',
|
| 66 |
+
label: 'Yi-1.5-34B-Chat (HuggingFace)',
|
| 67 |
+
provider: 'HuggingFace',
|
| 68 |
+
maxTokenAllowed: 8000,
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
name: 'codellama/CodeLlama-34b-Instruct-hf',
|
| 72 |
+
label: 'CodeLlama-34b-Instruct (HuggingFace)',
|
| 73 |
+
provider: 'HuggingFace',
|
| 74 |
+
maxTokenAllowed: 8000,
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
name: 'NousResearch/Hermes-3-Llama-3.1-8B',
|
| 78 |
+
label: 'Hermes-3-Llama-3.1-8B (HuggingFace)',
|
| 79 |
+
provider: 'HuggingFace',
|
| 80 |
+
maxTokenAllowed: 8000,
|
| 81 |
+
},
|
| 82 |
];
|
| 83 |
|
| 84 |
getModelInstance(options: {
|
app/lib/modules/llm/providers/hyperbolic.ts
CHANGED
|
@@ -50,40 +50,35 @@ export default class HyperbolicProvider extends BaseProvider {
|
|
| 50 |
settings?: IProviderSetting,
|
| 51 |
serverEnv: Record<string, string> = {},
|
| 52 |
): Promise<ModelInfo[]> {
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
const baseUrl = fetchBaseUrl || 'https://api.hyperbolic.xyz/v1';
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
|
| 73 |
-
|
| 74 |
|
| 75 |
-
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
} catch (error: any) {
|
| 84 |
-
console.error('Error getting Hyperbolic models:', error.message);
|
| 85 |
-
return [];
|
| 86 |
-
}
|
| 87 |
}
|
| 88 |
|
| 89 |
getModelInstance(options: {
|
|
@@ -103,8 +98,7 @@ export default class HyperbolicProvider extends BaseProvider {
|
|
| 103 |
});
|
| 104 |
|
| 105 |
if (!apiKey) {
|
| 106 |
-
|
| 107 |
-
throw new Error(`Missing configuration for ${this.name} provider`);
|
| 108 |
}
|
| 109 |
|
| 110 |
const openai = createOpenAI({
|
|
|
|
| 50 |
settings?: IProviderSetting,
|
| 51 |
serverEnv: Record<string, string> = {},
|
| 52 |
): Promise<ModelInfo[]> {
|
| 53 |
+
const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey({
|
| 54 |
+
apiKeys,
|
| 55 |
+
providerSettings: settings,
|
| 56 |
+
serverEnv,
|
| 57 |
+
defaultBaseUrlKey: '',
|
| 58 |
+
defaultApiTokenKey: 'HYPERBOLIC_API_KEY',
|
| 59 |
+
});
|
| 60 |
+
const baseUrl = fetchBaseUrl || 'https://api.hyperbolic.xyz/v1';
|
|
|
|
| 61 |
|
| 62 |
+
if (!apiKey) {
|
| 63 |
+
throw `Missing Api Key configuration for ${this.name} provider`;
|
| 64 |
+
}
|
| 65 |
|
| 66 |
+
const response = await fetch(`${baseUrl}/models`, {
|
| 67 |
+
headers: {
|
| 68 |
+
Authorization: `Bearer ${apiKey}`,
|
| 69 |
+
},
|
| 70 |
+
});
|
| 71 |
|
| 72 |
+
const res = (await response.json()) as any;
|
| 73 |
|
| 74 |
+
const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat);
|
| 75 |
|
| 76 |
+
return data.map((m: any) => ({
|
| 77 |
+
name: m.id,
|
| 78 |
+
label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`,
|
| 79 |
+
provider: this.name,
|
| 80 |
+
maxTokenAllowed: m.context_length || 8000,
|
| 81 |
+
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
}
|
| 83 |
|
| 84 |
getModelInstance(options: {
|
|
|
|
| 98 |
});
|
| 99 |
|
| 100 |
if (!apiKey) {
|
| 101 |
+
throw `Missing Api Key configuration for ${this.name} provider`;
|
|
|
|
| 102 |
}
|
| 103 |
|
| 104 |
const openai = createOpenAI({
|
app/lib/modules/llm/providers/lmstudio.ts
CHANGED
|
@@ -22,33 +22,27 @@ export default class LMStudioProvider extends BaseProvider {
|
|
| 22 |
settings?: IProviderSetting,
|
| 23 |
serverEnv: Record<string, string> = {},
|
| 24 |
): Promise<ModelInfo[]> {
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
});
|
| 33 |
-
|
| 34 |
-
if (!baseUrl) {
|
| 35 |
-
return [];
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
const response = await fetch(`${baseUrl}/v1/models`);
|
| 39 |
-
const data = (await response.json()) as { data: Array<{ id: string }> };
|
| 40 |
-
|
| 41 |
-
return data.data.map((model) => ({
|
| 42 |
-
name: model.id,
|
| 43 |
-
label: model.id,
|
| 44 |
-
provider: this.name,
|
| 45 |
-
maxTokenAllowed: 8000,
|
| 46 |
-
}));
|
| 47 |
-
} catch (error: any) {
|
| 48 |
-
console.log('Error getting LMStudio models:', error.message);
|
| 49 |
|
|
|
|
| 50 |
return [];
|
| 51 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
getModelInstance: (options: {
|
| 54 |
model: string;
|
|
|
|
| 22 |
settings?: IProviderSetting,
|
| 23 |
serverEnv: Record<string, string> = {},
|
| 24 |
): Promise<ModelInfo[]> {
|
| 25 |
+
const { baseUrl } = this.getProviderBaseUrlAndKey({
|
| 26 |
+
apiKeys,
|
| 27 |
+
providerSettings: settings,
|
| 28 |
+
serverEnv,
|
| 29 |
+
defaultBaseUrlKey: 'LMSTUDIO_API_BASE_URL',
|
| 30 |
+
defaultApiTokenKey: '',
|
| 31 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
+
if (!baseUrl) {
|
| 34 |
return [];
|
| 35 |
}
|
| 36 |
+
|
| 37 |
+
const response = await fetch(`${baseUrl}/v1/models`);
|
| 38 |
+
const data = (await response.json()) as { data: Array<{ id: string }> };
|
| 39 |
+
|
| 40 |
+
return data.data.map((model) => ({
|
| 41 |
+
name: model.id,
|
| 42 |
+
label: model.id,
|
| 43 |
+
provider: this.name,
|
| 44 |
+
maxTokenAllowed: 8000,
|
| 45 |
+
}));
|
| 46 |
}
|
| 47 |
getModelInstance: (options: {
|
| 48 |
model: string;
|
app/lib/modules/llm/providers/ollama.ts
CHANGED
|
@@ -45,34 +45,29 @@ export default class OllamaProvider extends BaseProvider {
|
|
| 45 |
settings?: IProviderSetting,
|
| 46 |
serverEnv: Record<string, string> = {},
|
| 47 |
): Promise<ModelInfo[]> {
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
if (!baseUrl) {
|
| 58 |
-
return [];
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
const response = await fetch(`${baseUrl}/api/tags`);
|
| 62 |
-
const data = (await response.json()) as OllamaApiResponse;
|
| 63 |
-
|
| 64 |
-
// console.log({ ollamamodels: data.models });
|
| 65 |
-
|
| 66 |
-
return data.models.map((model: OllamaModel) => ({
|
| 67 |
-
name: model.name,
|
| 68 |
-
label: `${model.name} (${model.details.parameter_size})`,
|
| 69 |
-
provider: this.name,
|
| 70 |
-
maxTokenAllowed: 8000,
|
| 71 |
-
}));
|
| 72 |
-
} catch (e) {
|
| 73 |
-
console.error('Failed to get Ollama models:', e);
|
| 74 |
return [];
|
| 75 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
}
|
| 77 |
getModelInstance: (options: {
|
| 78 |
model: string;
|
|
|
|
| 45 |
settings?: IProviderSetting,
|
| 46 |
serverEnv: Record<string, string> = {},
|
| 47 |
): Promise<ModelInfo[]> {
|
| 48 |
+
const { baseUrl } = this.getProviderBaseUrlAndKey({
|
| 49 |
+
apiKeys,
|
| 50 |
+
providerSettings: settings,
|
| 51 |
+
serverEnv,
|
| 52 |
+
defaultBaseUrlKey: 'OLLAMA_API_BASE_URL',
|
| 53 |
+
defaultApiTokenKey: '',
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
if (!baseUrl) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
return [];
|
| 58 |
}
|
| 59 |
+
|
| 60 |
+
const response = await fetch(`${baseUrl}/api/tags`);
|
| 61 |
+
const data = (await response.json()) as OllamaApiResponse;
|
| 62 |
+
|
| 63 |
+
// console.log({ ollamamodels: data.models });
|
| 64 |
+
|
| 65 |
+
return data.models.map((model: OllamaModel) => ({
|
| 66 |
+
name: model.name,
|
| 67 |
+
label: `${model.name} (${model.details.parameter_size})`,
|
| 68 |
+
provider: this.name,
|
| 69 |
+
maxTokenAllowed: 8000,
|
| 70 |
+
}));
|
| 71 |
}
|
| 72 |
getModelInstance: (options: {
|
| 73 |
model: string;
|
app/lib/modules/llm/providers/open-router.ts
CHANGED
|
@@ -27,7 +27,6 @@ export default class OpenRouterProvider extends BaseProvider {
|
|
| 27 |
};
|
| 28 |
|
| 29 |
staticModels: ModelInfo[] = [
|
| 30 |
-
{ name: 'gpt-4o', label: 'GPT-4o', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 31 |
{
|
| 32 |
name: 'anthropic/claude-3.5-sonnet',
|
| 33 |
label: 'Anthropic: Claude 3.5 Sonnet (OpenRouter)',
|
|
|
|
| 27 |
};
|
| 28 |
|
| 29 |
staticModels: ModelInfo[] = [
|
|
|
|
| 30 |
{
|
| 31 |
name: 'anthropic/claude-3.5-sonnet',
|
| 32 |
label: 'Anthropic: Claude 3.5 Sonnet (OpenRouter)',
|
app/lib/modules/llm/providers/openai-like.ts
CHANGED
|
@@ -19,37 +19,32 @@ export default class OpenAILikeProvider extends BaseProvider {
|
|
| 19 |
settings?: IProviderSetting,
|
| 20 |
serverEnv: Record<string, string> = {},
|
| 21 |
): Promise<ModelInfo[]> {
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
});
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
|
| 41 |
-
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
} catch (error) {
|
| 50 |
-
console.error('Error getting OpenAILike models:', error);
|
| 51 |
-
return [];
|
| 52 |
-
}
|
| 53 |
}
|
| 54 |
|
| 55 |
getModelInstance(options: {
|
|
|
|
| 19 |
settings?: IProviderSetting,
|
| 20 |
serverEnv: Record<string, string> = {},
|
| 21 |
): Promise<ModelInfo[]> {
|
| 22 |
+
const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({
|
| 23 |
+
apiKeys,
|
| 24 |
+
providerSettings: settings,
|
| 25 |
+
serverEnv,
|
| 26 |
+
defaultBaseUrlKey: 'OPENAI_LIKE_API_BASE_URL',
|
| 27 |
+
defaultApiTokenKey: 'OPENAI_LIKE_API_KEY',
|
| 28 |
+
});
|
|
|
|
| 29 |
|
| 30 |
+
if (!baseUrl || !apiKey) {
|
| 31 |
+
return [];
|
| 32 |
+
}
|
| 33 |
|
| 34 |
+
const response = await fetch(`${baseUrl}/models`, {
|
| 35 |
+
headers: {
|
| 36 |
+
Authorization: `Bearer ${apiKey}`,
|
| 37 |
+
},
|
| 38 |
+
});
|
| 39 |
|
| 40 |
+
const res = (await response.json()) as any;
|
| 41 |
|
| 42 |
+
return res.data.map((model: any) => ({
|
| 43 |
+
name: model.id,
|
| 44 |
+
label: model.id,
|
| 45 |
+
provider: this.name,
|
| 46 |
+
maxTokenAllowed: 8000,
|
| 47 |
+
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
|
| 50 |
getModelInstance(options: {
|
app/lib/modules/llm/providers/openai.ts
CHANGED
|
@@ -13,6 +13,7 @@ export default class OpenAIProvider extends BaseProvider {
|
|
| 13 |
};
|
| 14 |
|
| 15 |
staticModels: ModelInfo[] = [
|
|
|
|
| 16 |
{ name: 'gpt-4o-mini', label: 'GPT-4o Mini', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 17 |
{ name: 'gpt-4-turbo', label: 'GPT-4 Turbo', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 18 |
{ name: 'gpt-4', label: 'GPT-4', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
|
|
|
| 13 |
};
|
| 14 |
|
| 15 |
staticModels: ModelInfo[] = [
|
| 16 |
+
{ name: 'gpt-4o', label: 'GPT-4o', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 17 |
{ name: 'gpt-4o-mini', label: 'GPT-4o Mini', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 18 |
{ name: 'gpt-4-turbo', label: 'GPT-4 Turbo', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 19 |
{ name: 'gpt-4', label: 'GPT-4', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
app/lib/modules/llm/providers/together.ts
CHANGED
|
@@ -38,41 +38,36 @@ export default class TogetherProvider extends BaseProvider {
|
|
| 38 |
settings?: IProviderSetting,
|
| 39 |
serverEnv: Record<string, string> = {},
|
| 40 |
): Promise<ModelInfo[]> {
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
const baseUrl = fetchBaseUrl || 'https://api.together.xyz/v1';
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
|
| 55 |
-
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
} catch (error: any) {
|
| 73 |
-
console.error('Error getting Together models:', error.message);
|
| 74 |
-
return [];
|
| 75 |
-
}
|
| 76 |
}
|
| 77 |
|
| 78 |
getModelInstance(options: {
|
|
|
|
| 38 |
settings?: IProviderSetting,
|
| 39 |
serverEnv: Record<string, string> = {},
|
| 40 |
): Promise<ModelInfo[]> {
|
| 41 |
+
const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey({
|
| 42 |
+
apiKeys,
|
| 43 |
+
providerSettings: settings,
|
| 44 |
+
serverEnv,
|
| 45 |
+
defaultBaseUrlKey: 'TOGETHER_API_BASE_URL',
|
| 46 |
+
defaultApiTokenKey: 'TOGETHER_API_KEY',
|
| 47 |
+
});
|
| 48 |
+
const baseUrl = fetchBaseUrl || 'https://api.together.xyz/v1';
|
|
|
|
| 49 |
|
| 50 |
+
if (!baseUrl || !apiKey) {
|
| 51 |
+
return [];
|
| 52 |
+
}
|
| 53 |
|
| 54 |
+
// console.log({ baseUrl, apiKey });
|
| 55 |
|
| 56 |
+
const response = await fetch(`${baseUrl}/models`, {
|
| 57 |
+
headers: {
|
| 58 |
+
Authorization: `Bearer ${apiKey}`,
|
| 59 |
+
},
|
| 60 |
+
});
|
| 61 |
|
| 62 |
+
const res = (await response.json()) as any;
|
| 63 |
+
const data = (res || []).filter((model: any) => model.type === 'chat');
|
| 64 |
|
| 65 |
+
return data.map((m: any) => ({
|
| 66 |
+
name: m.id,
|
| 67 |
+
label: `${m.display_name} - in:$${m.pricing.input.toFixed(2)} out:$${m.pricing.output.toFixed(2)} - context ${Math.floor(m.context_length / 1000)}k`,
|
| 68 |
+
provider: this.name,
|
| 69 |
+
maxTokenAllowed: 8000,
|
| 70 |
+
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
getModelInstance(options: {
|
app/lib/runtime/message-parser.ts
CHANGED
|
@@ -55,7 +55,8 @@ interface MessageState {
|
|
| 55 |
function cleanoutMarkdownSyntax(content: string) {
|
| 56 |
const codeBlockRegex = /^\s*```\w*\n([\s\S]*?)\n\s*```\s*$/;
|
| 57 |
const match = content.match(codeBlockRegex);
|
| 58 |
-
|
|
|
|
| 59 |
|
| 60 |
if (match) {
|
| 61 |
return match[1]; // Remove common leading 4-space indent
|
|
|
|
| 55 |
function cleanoutMarkdownSyntax(content: string) {
|
| 56 |
const codeBlockRegex = /^\s*```\w*\n([\s\S]*?)\n\s*```\s*$/;
|
| 57 |
const match = content.match(codeBlockRegex);
|
| 58 |
+
|
| 59 |
+
// console.log('matching', !!match, content);
|
| 60 |
|
| 61 |
if (match) {
|
| 62 |
return match[1]; // Remove common leading 4-space indent
|
app/lib/stores/settings.ts
CHANGED
|
@@ -54,5 +54,5 @@ export const promptStore = atom<string>('default');
|
|
| 54 |
|
| 55 |
export const latestBranchStore = atom(false);
|
| 56 |
|
| 57 |
-
export const autoSelectStarterTemplate = atom(
|
| 58 |
export const enableContextOptimizationStore = atom(false);
|
|
|
|
| 54 |
|
| 55 |
export const latestBranchStore = atom(false);
|
| 56 |
|
| 57 |
+
export const autoSelectStarterTemplate = atom(false);
|
| 58 |
export const enableContextOptimizationStore = atom(false);
|
app/routes/api.chat.ts
CHANGED
|
@@ -5,11 +5,14 @@ import { CONTINUE_PROMPT } from '~/lib/common/prompts/prompts';
|
|
| 5 |
import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text';
|
| 6 |
import SwitchableStream from '~/lib/.server/llm/switchable-stream';
|
| 7 |
import type { IProviderSetting } from '~/types/model';
|
|
|
|
| 8 |
|
| 9 |
export async function action(args: ActionFunctionArgs) {
|
| 10 |
return chatAction(args);
|
| 11 |
}
|
| 12 |
|
|
|
|
|
|
|
| 13 |
function parseCookies(cookieHeader: string): Record<string, string> {
|
| 14 |
const cookies: Record<string, string> = {};
|
| 15 |
|
|
@@ -54,7 +57,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
|
|
| 54 |
const options: StreamingOptions = {
|
| 55 |
toolChoice: 'none',
|
| 56 |
onFinish: async ({ text: content, finishReason, usage }) => {
|
| 57 |
-
|
| 58 |
|
| 59 |
if (usage) {
|
| 60 |
cumulativeUsage.completionTokens += usage.completionTokens || 0;
|
|
@@ -63,23 +66,33 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
|
|
| 63 |
}
|
| 64 |
|
| 65 |
if (finishReason !== 'length') {
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
totalTokens: cumulativeUsage.totalTokens,
|
| 76 |
-
},
|
| 77 |
-
});
|
| 78 |
},
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
)
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
}
|
| 84 |
|
| 85 |
if (stream.switches >= MAX_RESPONSE_SEGMENTS) {
|
|
@@ -88,7 +101,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
|
|
| 88 |
|
| 89 |
const switchesLeft = MAX_RESPONSE_SEGMENTS - stream.switches;
|
| 90 |
|
| 91 |
-
|
| 92 |
|
| 93 |
messages.push({ role: 'assistant', content });
|
| 94 |
messages.push({ role: 'user', content: CONTINUE_PROMPT });
|
|
@@ -104,7 +117,9 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
|
|
| 104 |
contextOptimization,
|
| 105 |
});
|
| 106 |
|
| 107 |
-
|
|
|
|
|
|
|
| 108 |
},
|
| 109 |
};
|
| 110 |
|
|
@@ -128,7 +143,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
|
|
| 128 |
},
|
| 129 |
});
|
| 130 |
} catch (error: any) {
|
| 131 |
-
|
| 132 |
|
| 133 |
if (error.message?.includes('API key')) {
|
| 134 |
throw new Response('Invalid or missing API key', {
|
|
|
|
| 5 |
import { streamText, type Messages, type StreamingOptions } from '~/lib/.server/llm/stream-text';
|
| 6 |
import SwitchableStream from '~/lib/.server/llm/switchable-stream';
|
| 7 |
import type { IProviderSetting } from '~/types/model';
|
| 8 |
+
import { createScopedLogger } from '~/utils/logger';
|
| 9 |
|
| 10 |
export async function action(args: ActionFunctionArgs) {
|
| 11 |
return chatAction(args);
|
| 12 |
}
|
| 13 |
|
| 14 |
+
const logger = createScopedLogger('api.chat');
|
| 15 |
+
|
| 16 |
function parseCookies(cookieHeader: string): Record<string, string> {
|
| 17 |
const cookies: Record<string, string> = {};
|
| 18 |
|
|
|
|
| 57 |
const options: StreamingOptions = {
|
| 58 |
toolChoice: 'none',
|
| 59 |
onFinish: async ({ text: content, finishReason, usage }) => {
|
| 60 |
+
logger.debug('usage', JSON.stringify(usage));
|
| 61 |
|
| 62 |
if (usage) {
|
| 63 |
cumulativeUsage.completionTokens += usage.completionTokens || 0;
|
|
|
|
| 66 |
}
|
| 67 |
|
| 68 |
if (finishReason !== 'length') {
|
| 69 |
+
const encoder = new TextEncoder();
|
| 70 |
+
const usageStream = createDataStream({
|
| 71 |
+
async execute(dataStream) {
|
| 72 |
+
dataStream.writeMessageAnnotation({
|
| 73 |
+
type: 'usage',
|
| 74 |
+
value: {
|
| 75 |
+
completionTokens: cumulativeUsage.completionTokens,
|
| 76 |
+
promptTokens: cumulativeUsage.promptTokens,
|
| 77 |
+
totalTokens: cumulativeUsage.totalTokens,
|
|
|
|
|
|
|
|
|
|
| 78 |
},
|
| 79 |
+
});
|
| 80 |
+
},
|
| 81 |
+
onError: (error: any) => `Custom error: ${error.message}`,
|
| 82 |
+
}).pipeThrough(
|
| 83 |
+
new TransformStream({
|
| 84 |
+
transform: (chunk, controller) => {
|
| 85 |
+
// Convert the string stream to a byte stream
|
| 86 |
+
const str = typeof chunk === 'string' ? chunk : JSON.stringify(chunk);
|
| 87 |
+
controller.enqueue(encoder.encode(str));
|
| 88 |
+
},
|
| 89 |
+
}),
|
| 90 |
+
);
|
| 91 |
+
await stream.switchSource(usageStream);
|
| 92 |
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
| 93 |
+
stream.close();
|
| 94 |
+
|
| 95 |
+
return;
|
| 96 |
}
|
| 97 |
|
| 98 |
if (stream.switches >= MAX_RESPONSE_SEGMENTS) {
|
|
|
|
| 101 |
|
| 102 |
const switchesLeft = MAX_RESPONSE_SEGMENTS - stream.switches;
|
| 103 |
|
| 104 |
+
logger.info(`Reached max token limit (${MAX_TOKENS}): Continuing message (${switchesLeft} switches left)`);
|
| 105 |
|
| 106 |
messages.push({ role: 'assistant', content });
|
| 107 |
messages.push({ role: 'user', content: CONTINUE_PROMPT });
|
|
|
|
| 117 |
contextOptimization,
|
| 118 |
});
|
| 119 |
|
| 120 |
+
stream.switchSource(result.toDataStream());
|
| 121 |
+
|
| 122 |
+
return;
|
| 123 |
},
|
| 124 |
};
|
| 125 |
|
|
|
|
| 143 |
},
|
| 144 |
});
|
| 145 |
} catch (error: any) {
|
| 146 |
+
logger.error(error);
|
| 147 |
|
| 148 |
if (error.message?.includes('API key')) {
|
| 149 |
throw new Response('Invalid or missing API key', {
|
app/utils/constants.ts
CHANGED
|
@@ -19,312 +19,6 @@ export const DEFAULT_PROVIDER = llmManager.getDefaultProvider();
|
|
| 19 |
|
| 20 |
let MODEL_LIST = llmManager.getModelList();
|
| 21 |
|
| 22 |
-
/*
|
| 23 |
-
*const PROVIDER_LIST_OLD: ProviderInfo[] = [
|
| 24 |
-
* {
|
| 25 |
-
* name: 'Anthropic',
|
| 26 |
-
* staticModels: [
|
| 27 |
-
* {
|
| 28 |
-
* name: 'claude-3-5-sonnet-latest',
|
| 29 |
-
* label: 'Claude 3.5 Sonnet (new)',
|
| 30 |
-
* provider: 'Anthropic',
|
| 31 |
-
* maxTokenAllowed: 8000,
|
| 32 |
-
* },
|
| 33 |
-
* {
|
| 34 |
-
* name: 'claude-3-5-sonnet-20240620',
|
| 35 |
-
* label: 'Claude 3.5 Sonnet (old)',
|
| 36 |
-
* provider: 'Anthropic',
|
| 37 |
-
* maxTokenAllowed: 8000,
|
| 38 |
-
* },
|
| 39 |
-
* {
|
| 40 |
-
* name: 'claude-3-5-haiku-latest',
|
| 41 |
-
* label: 'Claude 3.5 Haiku (new)',
|
| 42 |
-
* provider: 'Anthropic',
|
| 43 |
-
* maxTokenAllowed: 8000,
|
| 44 |
-
* },
|
| 45 |
-
* { name: 'claude-3-opus-latest', label: 'Claude 3 Opus', provider: 'Anthropic', maxTokenAllowed: 8000 },
|
| 46 |
-
* { name: 'claude-3-sonnet-20240229', label: 'Claude 3 Sonnet', provider: 'Anthropic', maxTokenAllowed: 8000 },
|
| 47 |
-
* { name: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', provider: 'Anthropic', maxTokenAllowed: 8000 },
|
| 48 |
-
* ],
|
| 49 |
-
* getApiKeyLink: 'https://console.anthropic.com/settings/keys',
|
| 50 |
-
* },
|
| 51 |
-
* {
|
| 52 |
-
* name: 'Ollama',
|
| 53 |
-
* staticModels: [],
|
| 54 |
-
* getDynamicModels: getOllamaModels,
|
| 55 |
-
* getApiKeyLink: 'https://ollama.com/download',
|
| 56 |
-
* labelForGetApiKey: 'Download Ollama',
|
| 57 |
-
* icon: 'i-ph:cloud-arrow-down',
|
| 58 |
-
* },
|
| 59 |
-
* {
|
| 60 |
-
* name: 'OpenAILike',
|
| 61 |
-
* staticModels: [],
|
| 62 |
-
* getDynamicModels: getOpenAILikeModels,
|
| 63 |
-
* },
|
| 64 |
-
* {
|
| 65 |
-
* name: 'Cohere',
|
| 66 |
-
* staticModels: [
|
| 67 |
-
* { name: 'command-r-plus-08-2024', label: 'Command R plus Latest', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 68 |
-
* { name: 'command-r-08-2024', label: 'Command R Latest', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 69 |
-
* { name: 'command-r-plus', label: 'Command R plus', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 70 |
-
* { name: 'command-r', label: 'Command R', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 71 |
-
* { name: 'command', label: 'Command', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 72 |
-
* { name: 'command-nightly', label: 'Command Nightly', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 73 |
-
* { name: 'command-light', label: 'Command Light', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 74 |
-
* { name: 'command-light-nightly', label: 'Command Light Nightly', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 75 |
-
* { name: 'c4ai-aya-expanse-8b', label: 'c4AI Aya Expanse 8b', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 76 |
-
* { name: 'c4ai-aya-expanse-32b', label: 'c4AI Aya Expanse 32b', provider: 'Cohere', maxTokenAllowed: 4096 },
|
| 77 |
-
* ],
|
| 78 |
-
* getApiKeyLink: 'https://dashboard.cohere.com/api-keys',
|
| 79 |
-
* },
|
| 80 |
-
* {
|
| 81 |
-
* name: 'OpenRouter',
|
| 82 |
-
* staticModels: [
|
| 83 |
-
* { name: 'gpt-4o', label: 'GPT-4o', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 84 |
-
* {
|
| 85 |
-
* name: 'anthropic/claude-3.5-sonnet',
|
| 86 |
-
* label: 'Anthropic: Claude 3.5 Sonnet (OpenRouter)',
|
| 87 |
-
* provider: 'OpenRouter',
|
| 88 |
-
* maxTokenAllowed: 8000,
|
| 89 |
-
* },
|
| 90 |
-
* {
|
| 91 |
-
* name: 'anthropic/claude-3-haiku',
|
| 92 |
-
* label: 'Anthropic: Claude 3 Haiku (OpenRouter)',
|
| 93 |
-
* provider: 'OpenRouter',
|
| 94 |
-
* maxTokenAllowed: 8000,
|
| 95 |
-
* },
|
| 96 |
-
* {
|
| 97 |
-
* name: 'deepseek/deepseek-coder',
|
| 98 |
-
* label: 'Deepseek-Coder V2 236B (OpenRouter)',
|
| 99 |
-
* provider: 'OpenRouter',
|
| 100 |
-
* maxTokenAllowed: 8000,
|
| 101 |
-
* },
|
| 102 |
-
* {
|
| 103 |
-
* name: 'google/gemini-flash-1.5',
|
| 104 |
-
* label: 'Google Gemini Flash 1.5 (OpenRouter)',
|
| 105 |
-
* provider: 'OpenRouter',
|
| 106 |
-
* maxTokenAllowed: 8000,
|
| 107 |
-
* },
|
| 108 |
-
* {
|
| 109 |
-
* name: 'google/gemini-pro-1.5',
|
| 110 |
-
* label: 'Google Gemini Pro 1.5 (OpenRouter)',
|
| 111 |
-
* provider: 'OpenRouter',
|
| 112 |
-
* maxTokenAllowed: 8000,
|
| 113 |
-
* },
|
| 114 |
-
* { name: 'x-ai/grok-beta', label: 'xAI Grok Beta (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 8000 },
|
| 115 |
-
* {
|
| 116 |
-
* name: 'mistralai/mistral-nemo',
|
| 117 |
-
* label: 'OpenRouter Mistral Nemo (OpenRouter)',
|
| 118 |
-
* provider: 'OpenRouter',
|
| 119 |
-
* maxTokenAllowed: 8000,
|
| 120 |
-
* },
|
| 121 |
-
* {
|
| 122 |
-
* name: 'qwen/qwen-110b-chat',
|
| 123 |
-
* label: 'OpenRouter Qwen 110b Chat (OpenRouter)',
|
| 124 |
-
* provider: 'OpenRouter',
|
| 125 |
-
* maxTokenAllowed: 8000,
|
| 126 |
-
* },
|
| 127 |
-
* { name: 'cohere/command', label: 'Cohere Command (OpenRouter)', provider: 'OpenRouter', maxTokenAllowed: 4096 },
|
| 128 |
-
* ],
|
| 129 |
-
* getDynamicModels: getOpenRouterModels,
|
| 130 |
-
* getApiKeyLink: 'https://openrouter.ai/settings/keys',
|
| 131 |
-
* },
|
| 132 |
-
* {
|
| 133 |
-
* name: 'Google',
|
| 134 |
-
* staticModels: [
|
| 135 |
-
* { name: 'gemini-1.5-flash-latest', label: 'Gemini 1.5 Flash', provider: 'Google', maxTokenAllowed: 8192 },
|
| 136 |
-
* { name: 'gemini-2.0-flash-exp', label: 'Gemini 2.0 Flash', provider: 'Google', maxTokenAllowed: 8192 },
|
| 137 |
-
* { name: 'gemini-1.5-flash-002', label: 'Gemini 1.5 Flash-002', provider: 'Google', maxTokenAllowed: 8192 },
|
| 138 |
-
* { name: 'gemini-1.5-flash-8b', label: 'Gemini 1.5 Flash-8b', provider: 'Google', maxTokenAllowed: 8192 },
|
| 139 |
-
* { name: 'gemini-1.5-pro-latest', label: 'Gemini 1.5 Pro', provider: 'Google', maxTokenAllowed: 8192 },
|
| 140 |
-
* { name: 'gemini-1.5-pro-002', label: 'Gemini 1.5 Pro-002', provider: 'Google', maxTokenAllowed: 8192 },
|
| 141 |
-
* { name: 'gemini-exp-1206', label: 'Gemini exp-1206', provider: 'Google', maxTokenAllowed: 8192 },
|
| 142 |
-
* ],
|
| 143 |
-
* getApiKeyLink: 'https://aistudio.google.com/app/apikey',
|
| 144 |
-
* },
|
| 145 |
-
* {
|
| 146 |
-
* name: 'Groq',
|
| 147 |
-
* staticModels: [
|
| 148 |
-
* { name: 'llama-3.1-8b-instant', label: 'Llama 3.1 8b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
| 149 |
-
* { name: 'llama-3.2-11b-vision-preview', label: 'Llama 3.2 11b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
| 150 |
-
* { name: 'llama-3.2-90b-vision-preview', label: 'Llama 3.2 90b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
| 151 |
-
* { name: 'llama-3.2-3b-preview', label: 'Llama 3.2 3b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
| 152 |
-
* { name: 'llama-3.2-1b-preview', label: 'Llama 3.2 1b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
| 153 |
-
* { name: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70b (Groq)', provider: 'Groq', maxTokenAllowed: 8000 },
|
| 154 |
-
* ],
|
| 155 |
-
* getApiKeyLink: 'https://console.groq.com/keys',
|
| 156 |
-
* },
|
| 157 |
-
* {
|
| 158 |
-
* name: 'HuggingFace',
|
| 159 |
-
* staticModels: [
|
| 160 |
-
* {
|
| 161 |
-
* name: 'Qwen/Qwen2.5-Coder-32B-Instruct',
|
| 162 |
-
* label: 'Qwen2.5-Coder-32B-Instruct (HuggingFace)',
|
| 163 |
-
* provider: 'HuggingFace',
|
| 164 |
-
* maxTokenAllowed: 8000,
|
| 165 |
-
* },
|
| 166 |
-
* {
|
| 167 |
-
* name: '01-ai/Yi-1.5-34B-Chat',
|
| 168 |
-
* label: 'Yi-1.5-34B-Chat (HuggingFace)',
|
| 169 |
-
* provider: 'HuggingFace',
|
| 170 |
-
* maxTokenAllowed: 8000,
|
| 171 |
-
* },
|
| 172 |
-
* {
|
| 173 |
-
* name: 'codellama/CodeLlama-34b-Instruct-hf',
|
| 174 |
-
* label: 'CodeLlama-34b-Instruct (HuggingFace)',
|
| 175 |
-
* provider: 'HuggingFace',
|
| 176 |
-
* maxTokenAllowed: 8000,
|
| 177 |
-
* },
|
| 178 |
-
* {
|
| 179 |
-
* name: 'NousResearch/Hermes-3-Llama-3.1-8B',
|
| 180 |
-
* label: 'Hermes-3-Llama-3.1-8B (HuggingFace)',
|
| 181 |
-
* provider: 'HuggingFace',
|
| 182 |
-
* maxTokenAllowed: 8000,
|
| 183 |
-
* },
|
| 184 |
-
* {
|
| 185 |
-
* name: 'Qwen/Qwen2.5-Coder-32B-Instruct',
|
| 186 |
-
* label: 'Qwen2.5-Coder-32B-Instruct (HuggingFace)',
|
| 187 |
-
* provider: 'HuggingFace',
|
| 188 |
-
* maxTokenAllowed: 8000,
|
| 189 |
-
* },
|
| 190 |
-
* {
|
| 191 |
-
* name: 'Qwen/Qwen2.5-72B-Instruct',
|
| 192 |
-
* label: 'Qwen2.5-72B-Instruct (HuggingFace)',
|
| 193 |
-
* provider: 'HuggingFace',
|
| 194 |
-
* maxTokenAllowed: 8000,
|
| 195 |
-
* },
|
| 196 |
-
* {
|
| 197 |
-
* name: 'meta-llama/Llama-3.1-70B-Instruct',
|
| 198 |
-
* label: 'Llama-3.1-70B-Instruct (HuggingFace)',
|
| 199 |
-
* provider: 'HuggingFace',
|
| 200 |
-
* maxTokenAllowed: 8000,
|
| 201 |
-
* },
|
| 202 |
-
* {
|
| 203 |
-
* name: 'meta-llama/Llama-3.1-405B',
|
| 204 |
-
* label: 'Llama-3.1-405B (HuggingFace)',
|
| 205 |
-
* provider: 'HuggingFace',
|
| 206 |
-
* maxTokenAllowed: 8000,
|
| 207 |
-
* },
|
| 208 |
-
* {
|
| 209 |
-
* name: '01-ai/Yi-1.5-34B-Chat',
|
| 210 |
-
* label: 'Yi-1.5-34B-Chat (HuggingFace)',
|
| 211 |
-
* provider: 'HuggingFace',
|
| 212 |
-
* maxTokenAllowed: 8000,
|
| 213 |
-
* },
|
| 214 |
-
* {
|
| 215 |
-
* name: 'codellama/CodeLlama-34b-Instruct-hf',
|
| 216 |
-
* label: 'CodeLlama-34b-Instruct (HuggingFace)',
|
| 217 |
-
* provider: 'HuggingFace',
|
| 218 |
-
* maxTokenAllowed: 8000,
|
| 219 |
-
* },
|
| 220 |
-
* {
|
| 221 |
-
* name: 'NousResearch/Hermes-3-Llama-3.1-8B',
|
| 222 |
-
* label: 'Hermes-3-Llama-3.1-8B (HuggingFace)',
|
| 223 |
-
* provider: 'HuggingFace',
|
| 224 |
-
* maxTokenAllowed: 8000,
|
| 225 |
-
* },
|
| 226 |
-
* ],
|
| 227 |
-
* getApiKeyLink: 'https://huggingface.co/settings/tokens',
|
| 228 |
-
* },
|
| 229 |
-
* {
|
| 230 |
-
* name: 'OpenAI',
|
| 231 |
-
* staticModels: [
|
| 232 |
-
* { name: 'gpt-4o-mini', label: 'GPT-4o Mini', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 233 |
-
* { name: 'gpt-4-turbo', label: 'GPT-4 Turbo', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 234 |
-
* { name: 'gpt-4', label: 'GPT-4', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 235 |
-
* { name: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', provider: 'OpenAI', maxTokenAllowed: 8000 },
|
| 236 |
-
* ],
|
| 237 |
-
* getApiKeyLink: 'https://platform.openai.com/api-keys',
|
| 238 |
-
* },
|
| 239 |
-
* {
|
| 240 |
-
* name: 'xAI',
|
| 241 |
-
* staticModels: [{ name: 'grok-beta', label: 'xAI Grok Beta', provider: 'xAI', maxTokenAllowed: 8000 }],
|
| 242 |
-
* getApiKeyLink: 'https://docs.x.ai/docs/quickstart#creating-an-api-key',
|
| 243 |
-
* },
|
| 244 |
-
* {
|
| 245 |
-
* name: 'Deepseek',
|
| 246 |
-
* staticModels: [
|
| 247 |
-
* { name: 'deepseek-coder', label: 'Deepseek-Coder', provider: 'Deepseek', maxTokenAllowed: 8000 },
|
| 248 |
-
* { name: 'deepseek-chat', label: 'Deepseek-Chat', provider: 'Deepseek', maxTokenAllowed: 8000 },
|
| 249 |
-
* ],
|
| 250 |
-
* getApiKeyLink: 'https://platform.deepseek.com/apiKeys',
|
| 251 |
-
* },
|
| 252 |
-
* {
|
| 253 |
-
* name: 'Mistral',
|
| 254 |
-
* staticModels: [
|
| 255 |
-
* { name: 'open-mistral-7b', label: 'Mistral 7B', provider: 'Mistral', maxTokenAllowed: 8000 },
|
| 256 |
-
* { name: 'open-mixtral-8x7b', label: 'Mistral 8x7B', provider: 'Mistral', maxTokenAllowed: 8000 },
|
| 257 |
-
* { name: 'open-mixtral-8x22b', label: 'Mistral 8x22B', provider: 'Mistral', maxTokenAllowed: 8000 },
|
| 258 |
-
* { name: 'open-codestral-mamba', label: 'Codestral Mamba', provider: 'Mistral', maxTokenAllowed: 8000 },
|
| 259 |
-
* { name: 'open-mistral-nemo', label: 'Mistral Nemo', provider: 'Mistral', maxTokenAllowed: 8000 },
|
| 260 |
-
* { name: 'ministral-8b-latest', label: 'Mistral 8B', provider: 'Mistral', maxTokenAllowed: 8000 },
|
| 261 |
-
* { name: 'mistral-small-latest', label: 'Mistral Small', provider: 'Mistral', maxTokenAllowed: 8000 },
|
| 262 |
-
* { name: 'codestral-latest', label: 'Codestral', provider: 'Mistral', maxTokenAllowed: 8000 },
|
| 263 |
-
* { name: 'mistral-large-latest', label: 'Mistral Large Latest', provider: 'Mistral', maxTokenAllowed: 8000 },
|
| 264 |
-
* ],
|
| 265 |
-
* getApiKeyLink: 'https://console.mistral.ai/api-keys/',
|
| 266 |
-
* },
|
| 267 |
-
* {
|
| 268 |
-
* name: 'LMStudio',
|
| 269 |
-
* staticModels: [],
|
| 270 |
-
* getDynamicModels: getLMStudioModels,
|
| 271 |
-
* getApiKeyLink: 'https://lmstudio.ai/',
|
| 272 |
-
* labelForGetApiKey: 'Get LMStudio',
|
| 273 |
-
* icon: 'i-ph:cloud-arrow-down',
|
| 274 |
-
* },
|
| 275 |
-
* {
|
| 276 |
-
* name: 'Together',
|
| 277 |
-
* getDynamicModels: getTogetherModels,
|
| 278 |
-
* staticModels: [
|
| 279 |
-
* {
|
| 280 |
-
* name: 'Qwen/Qwen2.5-Coder-32B-Instruct',
|
| 281 |
-
* label: 'Qwen/Qwen2.5-Coder-32B-Instruct',
|
| 282 |
-
* provider: 'Together',
|
| 283 |
-
* maxTokenAllowed: 8000,
|
| 284 |
-
* },
|
| 285 |
-
* {
|
| 286 |
-
* name: 'meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo',
|
| 287 |
-
* label: 'meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo',
|
| 288 |
-
* provider: 'Together',
|
| 289 |
-
* maxTokenAllowed: 8000,
|
| 290 |
-
* },
|
| 291 |
-
*
|
| 292 |
-
* {
|
| 293 |
-
* name: 'mistralai/Mixtral-8x7B-Instruct-v0.1',
|
| 294 |
-
* label: 'Mixtral 8x7B Instruct',
|
| 295 |
-
* provider: 'Together',
|
| 296 |
-
* maxTokenAllowed: 8192,
|
| 297 |
-
* },
|
| 298 |
-
* ],
|
| 299 |
-
* getApiKeyLink: 'https://api.together.xyz/settings/api-keys',
|
| 300 |
-
* },
|
| 301 |
-
* {
|
| 302 |
-
* name: 'Perplexity',
|
| 303 |
-
* staticModels: [
|
| 304 |
-
* {
|
| 305 |
-
* name: 'llama-3.1-sonar-small-128k-online',
|
| 306 |
-
* label: 'Sonar Small Online',
|
| 307 |
-
* provider: 'Perplexity',
|
| 308 |
-
* maxTokenAllowed: 8192,
|
| 309 |
-
* },
|
| 310 |
-
* {
|
| 311 |
-
* name: 'llama-3.1-sonar-large-128k-online',
|
| 312 |
-
* label: 'Sonar Large Online',
|
| 313 |
-
* provider: 'Perplexity',
|
| 314 |
-
* maxTokenAllowed: 8192,
|
| 315 |
-
* },
|
| 316 |
-
* {
|
| 317 |
-
* name: 'llama-3.1-sonar-huge-128k-online',
|
| 318 |
-
* label: 'Sonar Huge Online',
|
| 319 |
-
* provider: 'Perplexity',
|
| 320 |
-
* maxTokenAllowed: 8192,
|
| 321 |
-
* },
|
| 322 |
-
* ],
|
| 323 |
-
* getApiKeyLink: 'https://www.perplexity.ai/settings/api',
|
| 324 |
-
* },
|
| 325 |
-
*];
|
| 326 |
-
*/
|
| 327 |
-
|
| 328 |
const providerBaseUrlEnvKeys: Record<string, { baseUrlKey?: string; apiTokenKey?: string }> = {};
|
| 329 |
PROVIDER_LIST.forEach((provider) => {
|
| 330 |
providerBaseUrlEnvKeys[provider.name] = {
|
|
|
|
| 19 |
|
| 20 |
let MODEL_LIST = llmManager.getModelList();
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
const providerBaseUrlEnvKeys: Record<string, { baseUrlKey?: string; apiTokenKey?: string }> = {};
|
| 23 |
PROVIDER_LIST.forEach((provider) => {
|
| 24 |
providerBaseUrlEnvKeys[provider.name] = {
|
app/utils/logger.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
| 1 |
export type DebugLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
type LoggerFunction = (...messages: any[]) => void;
|
| 4 |
|
|
@@ -13,9 +16,6 @@ interface Logger {
|
|
| 13 |
|
| 14 |
let currentLevel: DebugLevel = (import.meta.env.VITE_LOG_LEVEL ?? import.meta.env.DEV) ? 'debug' : 'info';
|
| 15 |
|
| 16 |
-
const isWorker = 'HTMLRewriter' in globalThis;
|
| 17 |
-
const supportsColor = !isWorker;
|
| 18 |
-
|
| 19 |
export const logger: Logger = {
|
| 20 |
trace: (...messages: any[]) => log('trace', undefined, messages),
|
| 21 |
debug: (...messages: any[]) => log('debug', undefined, messages),
|
|
@@ -63,14 +63,8 @@ function log(level: DebugLevel, scope: string | undefined, messages: any[]) {
|
|
| 63 |
return `${acc} ${current}`;
|
| 64 |
}, '');
|
| 65 |
|
| 66 |
-
if (!supportsColor) {
|
| 67 |
-
console.log(`[${level.toUpperCase()}]`, allMessages);
|
| 68 |
-
|
| 69 |
-
return;
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
const labelBackgroundColor = getColorForLevel(level);
|
| 73 |
-
const labelTextColor = level === 'warn' ? '
|
| 74 |
|
| 75 |
const labelStyles = getLabelStyles(labelBackgroundColor, labelTextColor);
|
| 76 |
const scopeStyles = getLabelStyles('#77828D', 'white');
|
|
@@ -81,7 +75,21 @@ function log(level: DebugLevel, scope: string | undefined, messages: any[]) {
|
|
| 81 |
styles.push('', scopeStyles);
|
| 82 |
}
|
| 83 |
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
}
|
| 86 |
|
| 87 |
function getLabelStyles(color: string, textColor: string) {
|
|
@@ -104,7 +112,7 @@ function getColorForLevel(level: DebugLevel): string {
|
|
| 104 |
return '#EE4744';
|
| 105 |
}
|
| 106 |
default: {
|
| 107 |
-
return '
|
| 108 |
}
|
| 109 |
}
|
| 110 |
}
|
|
|
|
| 1 |
export type DebugLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
| 2 |
+
import { Chalk } from 'chalk';
|
| 3 |
+
|
| 4 |
+
const chalk = new Chalk({ level: 3 });
|
| 5 |
|
| 6 |
type LoggerFunction = (...messages: any[]) => void;
|
| 7 |
|
|
|
|
| 16 |
|
| 17 |
let currentLevel: DebugLevel = (import.meta.env.VITE_LOG_LEVEL ?? import.meta.env.DEV) ? 'debug' : 'info';
|
| 18 |
|
|
|
|
|
|
|
|
|
|
| 19 |
export const logger: Logger = {
|
| 20 |
trace: (...messages: any[]) => log('trace', undefined, messages),
|
| 21 |
debug: (...messages: any[]) => log('debug', undefined, messages),
|
|
|
|
| 63 |
return `${acc} ${current}`;
|
| 64 |
}, '');
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
const labelBackgroundColor = getColorForLevel(level);
|
| 67 |
+
const labelTextColor = level === 'warn' ? '#000000' : '#FFFFFF';
|
| 68 |
|
| 69 |
const labelStyles = getLabelStyles(labelBackgroundColor, labelTextColor);
|
| 70 |
const scopeStyles = getLabelStyles('#77828D', 'white');
|
|
|
|
| 75 |
styles.push('', scopeStyles);
|
| 76 |
}
|
| 77 |
|
| 78 |
+
let labelText = formatText(` ${level.toUpperCase()} `, labelTextColor, labelBackgroundColor);
|
| 79 |
+
|
| 80 |
+
if (scope) {
|
| 81 |
+
labelText = `${labelText} ${formatText(` ${scope} `, '#FFFFFF', '77828D')}`;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
if (typeof window !== 'undefined') {
|
| 85 |
+
console.log(`%c${level.toUpperCase()}${scope ? `%c %c${scope}` : ''}`, ...styles, allMessages);
|
| 86 |
+
} else {
|
| 87 |
+
console.log(`${labelText}`, allMessages);
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
function formatText(text: string, color: string, bg: string) {
|
| 92 |
+
return chalk.bgHex(bg)(chalk.hex(color)(text));
|
| 93 |
}
|
| 94 |
|
| 95 |
function getLabelStyles(color: string, textColor: string) {
|
|
|
|
| 112 |
return '#EE4744';
|
| 113 |
}
|
| 114 |
default: {
|
| 115 |
+
return '#000000';
|
| 116 |
}
|
| 117 |
}
|
| 118 |
}
|
app/utils/selectStarterTemplate.ts
CHANGED
|
@@ -27,7 +27,7 @@ ${templates
|
|
| 27 |
Response Format:
|
| 28 |
<selection>
|
| 29 |
<templateName>{selected template name}</templateName>
|
| 30 |
-
<
|
| 31 |
</selection>
|
| 32 |
|
| 33 |
Examples:
|
|
@@ -37,7 +37,7 @@ User: I need to build a todo app
|
|
| 37 |
Response:
|
| 38 |
<selection>
|
| 39 |
<templateName>react-basic-starter</templateName>
|
| 40 |
-
<
|
| 41 |
</selection>
|
| 42 |
</example>
|
| 43 |
|
|
@@ -46,7 +46,7 @@ User: Write a script to generate numbers from 1 to 100
|
|
| 46 |
Response:
|
| 47 |
<selection>
|
| 48 |
<templateName>blank</templateName>
|
| 49 |
-
<
|
| 50 |
</selection>
|
| 51 |
</example>
|
| 52 |
|
|
@@ -62,16 +62,17 @@ Important: Provide only the selection tags in your response, no additional text.
|
|
| 62 |
|
| 63 |
const templates: Template[] = STARTER_TEMPLATES.filter((t) => !t.name.includes('shadcn'));
|
| 64 |
|
| 65 |
-
const parseSelectedTemplate = (llmOutput: string): string | null => {
|
| 66 |
try {
|
| 67 |
// Extract content between <templateName> tags
|
| 68 |
const templateNameMatch = llmOutput.match(/<templateName>(.*?)<\/templateName>/);
|
|
|
|
| 69 |
|
| 70 |
if (!templateNameMatch) {
|
| 71 |
return null;
|
| 72 |
}
|
| 73 |
|
| 74 |
-
return templateNameMatch[1].trim();
|
| 75 |
} catch (error) {
|
| 76 |
console.error('Error parsing template selection:', error);
|
| 77 |
return null;
|
|
@@ -101,7 +102,10 @@ export const selectStarterTemplate = async (options: { message: string; model: s
|
|
| 101 |
} else {
|
| 102 |
console.log('No template selected, using blank template');
|
| 103 |
|
| 104 |
-
return
|
|
|
|
|
|
|
|
|
|
| 105 |
}
|
| 106 |
};
|
| 107 |
|
|
@@ -181,7 +185,7 @@ const getGitHubRepoContent = async (
|
|
| 181 |
}
|
| 182 |
};
|
| 183 |
|
| 184 |
-
export async function getTemplates(templateName: string) {
|
| 185 |
const template = STARTER_TEMPLATES.find((t) => t.name == templateName);
|
| 186 |
|
| 187 |
if (!template) {
|
|
@@ -211,7 +215,7 @@ export async function getTemplates(templateName: string) {
|
|
| 211 |
|
| 212 |
const filesToImport = {
|
| 213 |
files: filteredFiles,
|
| 214 |
-
ignoreFile: filteredFiles,
|
| 215 |
};
|
| 216 |
|
| 217 |
if (templateIgnoreFile) {
|
|
@@ -227,7 +231,7 @@ export async function getTemplates(templateName: string) {
|
|
| 227 |
}
|
| 228 |
|
| 229 |
const assistantMessage = `
|
| 230 |
-
<boltArtifact id="imported-files" title="Importing Starter Files" type="bundled">
|
| 231 |
${filesToImport.files
|
| 232 |
.map(
|
| 233 |
(file) =>
|
|
@@ -278,10 +282,16 @@ Any attempt to modify these protected files will result in immediate termination
|
|
| 278 |
If you need to make changes to functionality, create new files instead of modifying the protected ones listed above.
|
| 279 |
---
|
| 280 |
`;
|
| 281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
Now that the Template is imported please continue with my original request
|
| 283 |
`;
|
| 284 |
-
}
|
| 285 |
|
| 286 |
return {
|
| 287 |
assistantMessage,
|
|
|
|
| 27 |
Response Format:
|
| 28 |
<selection>
|
| 29 |
<templateName>{selected template name}</templateName>
|
| 30 |
+
<title>{a proper title for the project}</title>
|
| 31 |
</selection>
|
| 32 |
|
| 33 |
Examples:
|
|
|
|
| 37 |
Response:
|
| 38 |
<selection>
|
| 39 |
<templateName>react-basic-starter</templateName>
|
| 40 |
+
<title>Simple React todo application</title>
|
| 41 |
</selection>
|
| 42 |
</example>
|
| 43 |
|
|
|
|
| 46 |
Response:
|
| 47 |
<selection>
|
| 48 |
<templateName>blank</templateName>
|
| 49 |
+
<title>script to generate numbers from 1 to 100</title>
|
| 50 |
</selection>
|
| 51 |
</example>
|
| 52 |
|
|
|
|
| 62 |
|
| 63 |
const templates: Template[] = STARTER_TEMPLATES.filter((t) => !t.name.includes('shadcn'));
|
| 64 |
|
| 65 |
+
const parseSelectedTemplate = (llmOutput: string): { template: string; title: string } | null => {
|
| 66 |
try {
|
| 67 |
// Extract content between <templateName> tags
|
| 68 |
const templateNameMatch = llmOutput.match(/<templateName>(.*?)<\/templateName>/);
|
| 69 |
+
const titleMatch = llmOutput.match(/<title>(.*?)<\/title>/);
|
| 70 |
|
| 71 |
if (!templateNameMatch) {
|
| 72 |
return null;
|
| 73 |
}
|
| 74 |
|
| 75 |
+
return { template: templateNameMatch[1].trim(), title: titleMatch?.[1].trim() || 'Untitled Project' };
|
| 76 |
} catch (error) {
|
| 77 |
console.error('Error parsing template selection:', error);
|
| 78 |
return null;
|
|
|
|
| 102 |
} else {
|
| 103 |
console.log('No template selected, using blank template');
|
| 104 |
|
| 105 |
+
return {
|
| 106 |
+
template: 'blank',
|
| 107 |
+
title: '',
|
| 108 |
+
};
|
| 109 |
}
|
| 110 |
};
|
| 111 |
|
|
|
|
| 185 |
}
|
| 186 |
};
|
| 187 |
|
| 188 |
+
export async function getTemplates(templateName: string, title?: string) {
|
| 189 |
const template = STARTER_TEMPLATES.find((t) => t.name == templateName);
|
| 190 |
|
| 191 |
if (!template) {
|
|
|
|
| 215 |
|
| 216 |
const filesToImport = {
|
| 217 |
files: filteredFiles,
|
| 218 |
+
ignoreFile: [] as typeof filteredFiles,
|
| 219 |
};
|
| 220 |
|
| 221 |
if (templateIgnoreFile) {
|
|
|
|
| 231 |
}
|
| 232 |
|
| 233 |
const assistantMessage = `
|
| 234 |
+
<boltArtifact id="imported-files" title="${title || 'Importing Starter Files'}" type="bundled">
|
| 235 |
${filesToImport.files
|
| 236 |
.map(
|
| 237 |
(file) =>
|
|
|
|
| 282 |
If you need to make changes to functionality, create new files instead of modifying the protected ones listed above.
|
| 283 |
---
|
| 284 |
`;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
userMessage += `
|
| 288 |
+
---
|
| 289 |
+
template import is done, and you can now use the imported files,
|
| 290 |
+
edit only the files that need to be changed, and you can create new files as needed.
|
| 291 |
+
NO NOT EDIT/WRITE ANY FILES THAT ALREADY EXIST IN THE PROJECT AND DOES NOT NEED TO BE MODIFIED
|
| 292 |
+
---
|
| 293 |
Now that the Template is imported please continue with my original request
|
| 294 |
`;
|
|
|
|
| 295 |
|
| 296 |
return {
|
| 297 |
assistantMessage,
|
package.json
CHANGED
|
@@ -74,6 +74,7 @@
|
|
| 74 |
"@xterm/addon-web-links": "^0.11.0",
|
| 75 |
"@xterm/xterm": "^5.5.0",
|
| 76 |
"ai": "^4.0.13",
|
|
|
|
| 77 |
"date-fns": "^3.6.0",
|
| 78 |
"diff": "^5.2.0",
|
| 79 |
"dotenv": "^16.4.7",
|
|
|
|
| 74 |
"@xterm/addon-web-links": "^0.11.0",
|
| 75 |
"@xterm/xterm": "^5.5.0",
|
| 76 |
"ai": "^4.0.13",
|
| 77 |
+
"chalk": "^5.4.1",
|
| 78 |
"date-fns": "^3.6.0",
|
| 79 |
"diff": "^5.2.0",
|
| 80 |
"dotenv": "^16.4.7",
|
pnpm-lock.yaml
CHANGED
|
@@ -143,6 +143,9 @@ importers:
|
|
| 143 |
ai:
|
| 144 |
specifier: ^4.0.13
|
| 145 |
version: 4.0.18([email protected])([email protected])
|
|
|
|
|
|
|
|
|
|
| 146 |
date-fns:
|
| 147 |
specifier: ^3.6.0
|
| 148 |
version: 3.6.0
|
|
@@ -2604,8 +2607,8 @@ packages:
|
|
| 2604 |
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
| 2605 |
engines: {node: '>=10'}
|
| 2606 |
|
| 2607 |
-
chalk@5.
|
| 2608 |
-
resolution: {integrity: sha512-
|
| 2609 |
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
| 2610 |
|
| 2611 | |
|
@@ -8207,7 +8210,7 @@ snapshots:
|
|
| 8207 |
ansi-styles: 4.3.0
|
| 8208 |
supports-color: 7.2.0
|
| 8209 |
|
| 8210 |
-
chalk@5.
|
| 8211 |
|
| 8212 | |
| 8213 |
|
|
@@ -9415,7 +9418,7 @@ snapshots:
|
|
| 9415 | |
| 9416 |
dependencies:
|
| 9417 |
'@types/diff-match-patch': 1.0.36
|
| 9418 |
-
chalk: 5.
|
| 9419 |
diff-match-patch: 1.0.5
|
| 9420 |
|
| 9421 |
|
|
|
| 143 |
ai:
|
| 144 |
specifier: ^4.0.13
|
| 145 |
version: 4.0.18([email protected])([email protected])
|
| 146 |
+
chalk:
|
| 147 |
+
specifier: ^5.4.1
|
| 148 |
+
version: 5.4.1
|
| 149 |
date-fns:
|
| 150 |
specifier: ^3.6.0
|
| 151 |
version: 3.6.0
|
|
|
|
| 2607 |
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
| 2608 |
engines: {node: '>=10'}
|
| 2609 |
|
| 2610 |
+
chalk@5.4.1:
|
| 2611 |
+
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
|
| 2612 |
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
| 2613 |
|
| 2614 | |
|
|
|
| 8210 |
ansi-styles: 4.3.0
|
| 8211 |
supports-color: 7.2.0
|
| 8212 |
|
| 8213 |
+
chalk@5.4.1: {}
|
| 8214 |
|
| 8215 | |
| 8216 |
|
|
|
|
| 9418 | |
| 9419 |
dependencies:
|
| 9420 |
'@types/diff-match-patch': 1.0.36
|
| 9421 |
+
chalk: 5.4.1
|
| 9422 |
diff-match-patch: 1.0.5
|
| 9423 |
|
| 9424 |