mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ae440d252 | |||
| c0c724afc1 | |||
| a265112ce1 | |||
| 75605ed408 | |||
| ad38ff4157 | |||
| 08c60e53b1 | |||
| d0dcb2ac02 | |||
| fbeb604b26 | |||
| c4f3b1df77 | |||
| 5a1f9caaac | |||
| 2fc70d5e95 | |||
| 43adadef78 | |||
| 96f6e7628b | |||
| 32ad82bcee | |||
| 3d72aec369 | |||
| d244ee2cca | |||
| cc8a235ae3 | |||
| ae348812de | |||
| 6053636f66 | |||
| f2e2aee672 | |||
| 11cbb2bbf0 | |||
| 30bd19d6ce | |||
| d0b5c02062 | |||
| 771192e406 |
@@ -13,7 +13,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- main-stable # Trigger on pushes to the main-stable branch
|
||||
#- main-stable # Disabled as the v* tag is used for stable releases
|
||||
tags:
|
||||
- 'v*' # Trigger on version tags (e.g., v1.7.0)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# BIG-AGI 🧠✨
|
||||
|
||||
Welcome to big-AGI 👋, the GPT application for professionals that need form, function,
|
||||
simplicity, and speed. Powered by the latest models from 7 vendors, including
|
||||
open-source, `big-AGI` offers best-in-class Voice and Chat with AI Personas,
|
||||
Welcome to big-AGI 👋, the GPT application for professionals that need function, form,
|
||||
simplicity, and speed. Powered by the latest models from 7 vendors and
|
||||
open-source model servers, `big-AGI` offers best-in-class Voice and Chat with AI Personas,
|
||||
visualizations, coding, drawing, calling, and quite more -- all in a polished UX.
|
||||
|
||||
Pros use big-AGI. 🚀 Developers love big-AGI. 🤖
|
||||
@@ -21,7 +21,7 @@ shows the current developments and future ideas.
|
||||
- Got a suggestion? [_Add your roadmap ideas_](https://github.com/enricoros/big-agi/issues/new?&template=roadmap-request.md)
|
||||
- Want to contribute? [_Pick up a task!_](https://github.com/users/enricoros/projects/4/views/4) - _easy_ to _pro_
|
||||
|
||||
### What's New in 1.7.1 · Dec 11, 2023 · Attachment Theory 🌟
|
||||
### What's New in 1.7.3 · Dec 13, 2023 · Attachment Theory 🌟
|
||||
|
||||
- **Attachments System Overhaul**: Drag, paste, link, snap, text, images, PDFs and more. [#251](https://github.com/enricoros/big-agi/issues/251)
|
||||
- **Desktop Webcam Capture**: Image capture now available as Labs feature. [#253](https://github.com/enricoros/big-agi/issues/253)
|
||||
@@ -32,6 +32,8 @@ shows the current developments and future ideas.
|
||||
- Latest Ollama and Oobabooga models
|
||||
- For developers: **Password Protection**: HTTP Basic Auth. [Learn How](https://github.com/enricoros/big-agi/blob/main/docs/deploy-authentication.md)
|
||||
- [1.7.1]: Improved Ollama chats. [#270](https://github.com/enricoros/big-agi/issues/270)
|
||||
- [1.7.2]: OpenRouter login & free models 🎁
|
||||
- [1.7.3]: Mistral Platform support. [#273](https://github.com/enricoros/big-agi/issues/273)
|
||||
|
||||
### What's New in 1.6.0 - Nov 28, 2023
|
||||
|
||||
|
||||
+3
-1
@@ -10,7 +10,7 @@ by release.
|
||||
- work in progress: [big-AGI open roadmap](https://github.com/users/enricoros/projects/4/views/2), [help here](https://github.com/users/enricoros/projects/4/views/4)
|
||||
- milestone: [1.8.0](https://github.com/enricoros/big-agi/milestone/8)
|
||||
|
||||
### What's New in 1.7.1 · Dec 11, 2023 · Attachment Theory 🌟
|
||||
### What's New in 1.7.3 · Dec 13, 2023 · Attachment Theory 🌟
|
||||
|
||||
- **Attachments System Overhaul**: Drag, paste, link, snap, text, images, PDFs and more. [#251](https://github.com/enricoros/big-agi/issues/251)
|
||||
- **Desktop Webcam Capture**: Image capture now available as Labs feature. [#253](https://github.com/enricoros/big-agi/issues/253)
|
||||
@@ -21,6 +21,8 @@ by release.
|
||||
- Latest Ollama and Oobabooga models
|
||||
- For developers: **Password Protection**: HTTP Basic Auth. [Learn How](https://github.com/enricoros/big-agi/blob/main/docs/deploy-authentication.md)
|
||||
- [1.7.1]: Improved Ollama chats. [#270](https://github.com/enricoros/big-agi/issues/270)
|
||||
- [1.7.2]: OpenRouter login & free models 🎁
|
||||
- [1.7.3]: Mistral Platform support. [#273](https://github.com/enricoros/big-agi/issues/273)
|
||||
|
||||
### What's New in 1.6.0 - Nov 28, 2023 · Surf's Up
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ AZURE_OPENAI_API_ENDPOINT=
|
||||
AZURE_OPENAI_API_KEY=
|
||||
ANTHROPIC_API_KEY=
|
||||
ANTHROPIC_API_HOST=
|
||||
MISTRAL_API_KEY=
|
||||
OLLAMA_API_HOST=
|
||||
OPENROUTER_API_KEY=
|
||||
|
||||
@@ -79,6 +80,7 @@ requiring the user to enter an API key
|
||||
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API key, see [config-azure-openai.md](config-azure-openai.md) | Optional, but if set `AZURE_OPENAI_API_ENDPOINT` must also be set |
|
||||
| `ANTHROPIC_API_KEY` | The API key for Anthropic | Optional |
|
||||
| `ANTHROPIC_API_HOST` | Changes the backend host for the Anthropic vendor, to enable platforms such as [config-aws-bedrock.md](config-aws-bedrock.md) | Optional |
|
||||
| `MISTRAL_API_KEY` | The API key for Mistral | Optional |
|
||||
| `OLLAMA_API_HOST` | Changes the backend host for the Ollama vendor. See [config-ollama.md](config-ollama.md) | |
|
||||
| `OPENROUTER_API_KEY` | The API key for OpenRouter | Optional |
|
||||
|
||||
|
||||
Generated
+485
-251
File diff suppressed because it is too large
Load Diff
+15
-15
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "big-agi",
|
||||
"version": "1.7.1",
|
||||
"version": "1.7.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -18,10 +18,10 @@
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-material": "^5.14.18",
|
||||
"@mui/joy": "^5.0.0-beta.15",
|
||||
"@next/bundle-analyzer": "^14.0.3",
|
||||
"@prisma/client": "^5.6.0",
|
||||
"@mui/icons-material": "^5.14.19",
|
||||
"@mui/joy": "^5.0.0-beta.17",
|
||||
"@next/bundle-analyzer": "^14.0.4",
|
||||
"@prisma/client": "^5.7.0",
|
||||
"@sanity/diff-match-patch": "^3.1.1",
|
||||
"@t3-oss/env-nextjs": "^0.7.1",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
@@ -33,8 +33,8 @@
|
||||
"browser-fs-access": "^0.35.0",
|
||||
"eventsource-parser": "^1.1.1",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"next": "^14.0.3",
|
||||
"pdfjs-dist": "4.0.189",
|
||||
"next": "^14.0.4",
|
||||
"pdfjs-dist": "4.0.269",
|
||||
"plantuml-encoder": "^1.4.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^18.2.0",
|
||||
@@ -51,19 +51,19 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/puppeteer": "^0.0.5",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/plantuml-encoder": "^1.4.2",
|
||||
"@types/prismjs": "^1.26.3",
|
||||
"@types/react": "^18.2.38",
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@types/react-katex": "^3.0.3",
|
||||
"@types/react-katex": "^3.0.4",
|
||||
"@types/react-timeago": "^4.1.6",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-next": "^14.0.3",
|
||||
"prettier": "^3.1.0",
|
||||
"prisma": "^5.6.0",
|
||||
"typescript": "^5.3.2"
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-next": "^14.0.4",
|
||||
"prettier": "^3.1.1",
|
||||
"prisma": "^5.7.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.0.0 || ^18.0.0"
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import * as React from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { Box, Typography } from '@mui/joy';
|
||||
|
||||
import { useModelsStore } from '~/modules/llms/store-llms';
|
||||
|
||||
import { AppLayout } from '~/common/layout/AppLayout';
|
||||
import { InlineError } from '~/common/components/InlineError';
|
||||
import { apiQuery } from '~/common/util/trpc.client';
|
||||
import { navigateToIndex } from '~/common/app.routes';
|
||||
import { openLayoutModelsSetup } from '~/common/layout/store-applayout';
|
||||
|
||||
|
||||
function CallbackOpenRouterPage(props: { openRouterCode: string | undefined }) {
|
||||
|
||||
// external state
|
||||
const { data, isError, error, isLoading } = apiQuery.backend.exchangeOpenRouterKey.useQuery({ code: props.openRouterCode || '' }, {
|
||||
enabled: !!props.openRouterCode,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
// derived state
|
||||
const isErrorInput = !props.openRouterCode;
|
||||
const openRouterKey = data?.key ?? undefined;
|
||||
const isSuccess = !!openRouterKey;
|
||||
|
||||
|
||||
// Success: save the key and redirect to the chat app
|
||||
React.useEffect(() => {
|
||||
if (!isSuccess)
|
||||
return;
|
||||
|
||||
// 1. Save the key as the client key
|
||||
useModelsStore.getState().setOpenRoutersKey(openRouterKey);
|
||||
|
||||
// 2. Navigate to the chat app
|
||||
navigateToIndex(true).then(() => openLayoutModelsSetup());
|
||||
|
||||
}, [isSuccess, openRouterKey]);
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
backgroundColor: 'background.level1',
|
||||
overflowY: 'auto',
|
||||
display: 'flex', justifyContent: 'center',
|
||||
p: { xs: 3, md: 6 },
|
||||
}}>
|
||||
|
||||
<Box sx={{
|
||||
// my: 'auto',
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center',
|
||||
gap: 4,
|
||||
}}>
|
||||
|
||||
<Typography level='title-lg'>
|
||||
Welcome Back
|
||||
</Typography>
|
||||
|
||||
{isLoading && <Typography level='body-sm'>Loading...</Typography>}
|
||||
|
||||
{isErrorInput && <InlineError error='There was an issue retrieving the code from OpenRouter.' />}
|
||||
|
||||
{isError && <InlineError error={error} />}
|
||||
|
||||
{data && (
|
||||
<Typography level='body-md'>
|
||||
Success! You can now close this window.
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This page will be invoked by OpenRouter as a Callback
|
||||
*
|
||||
* Docs: https://openrouter.ai/docs#oauth
|
||||
* Example URL: https://localhost:3000/link/callback_openrouter?code=SomeCode
|
||||
*/
|
||||
export default function Page() {
|
||||
|
||||
// get the 'code=...' from the URL
|
||||
const { query } = useRouter();
|
||||
const { code: openRouterCode } = query;
|
||||
|
||||
return (
|
||||
<AppLayout suspendAutoModelsSetup>
|
||||
<CallbackOpenRouterPage openRouterCode={openRouterCode as (string | undefined)} />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
Vendored
-21
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -23,14 +23,26 @@ function AppBarLLMDropdown(props: {
|
||||
const llmItems: DropdownItems = {};
|
||||
let prevSourceId: DModelSourceId | null = null;
|
||||
for (const llm of props.llms) {
|
||||
if (!llm.hidden || llm.id === props.chatLlmId) {
|
||||
if (!prevSourceId || llm.sId !== prevSourceId) {
|
||||
if (prevSourceId)
|
||||
llmItems[`sep-${llm.id}`] = { type: 'separator', title: llm.sId };
|
||||
prevSourceId = llm.sId;
|
||||
}
|
||||
llmItems[llm.id] = { title: llm.label };
|
||||
|
||||
// filter-out hidden models
|
||||
if (!(!llm.hidden || llm.id === props.chatLlmId))
|
||||
continue;
|
||||
|
||||
// add separators when changing sources
|
||||
if (!prevSourceId || llm.sId !== prevSourceId) {
|
||||
if (prevSourceId)
|
||||
llmItems[`sep-${llm.id}`] = {
|
||||
type: 'separator',
|
||||
title: llm.sId,
|
||||
};
|
||||
prevSourceId = llm.sId;
|
||||
}
|
||||
|
||||
// add the model item
|
||||
llmItems[llm.id] = {
|
||||
title: llm.label,
|
||||
// icon: llm.id.startsWith('some vendor') ? <VendorIcon /> : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const handleChatLLMChange = (_event: any, value: DLLMId | null) => value && props.setChatLlmId(value);
|
||||
|
||||
@@ -331,7 +331,8 @@ export function Composer(props: {
|
||||
|
||||
const handleOverlayDragOver = React.useCallback((e: React.DragEvent) => {
|
||||
eatDragEvent(e);
|
||||
// e.dataTransfer.dropEffect = 'copy';
|
||||
// this makes sure we don't "transfer" (or move) the attachment, but we tell the sender we'll copy it
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
}, [eatDragEvent]);
|
||||
|
||||
const handleOverlayDrop = React.useCallback(async (event: React.DragEvent) => {
|
||||
|
||||
@@ -254,7 +254,7 @@ export async function attachmentPerformConversion(attachment: Readonly<Attachmen
|
||||
case 'rich-text-table':
|
||||
let mdTable: string;
|
||||
try {
|
||||
mdTable = htmlTableToMarkdown(input.altData!);
|
||||
mdTable = htmlTableToMarkdown(input.altData!, false);
|
||||
} catch (error) {
|
||||
// fallback to text/plain
|
||||
mdTable = inputDataToString(input.data);
|
||||
|
||||
@@ -7,7 +7,7 @@ import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
|
||||
import { DLLMId, useModelsStore } from '~/modules/llms/store-llms';
|
||||
import { findVendorById } from '~/modules/llms/vendors/vendor.registry';
|
||||
import { findVendorById } from '~/modules/llms/vendors/vendors.registry';
|
||||
|
||||
import { FormLabelStart } from '~/common/components/forms/FormLabelStart';
|
||||
import { GoodModal } from '~/common/components/GoodModal';
|
||||
|
||||
@@ -7,7 +7,7 @@ import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined
|
||||
|
||||
import { DLLM, DModelSourceId, useModelsStore } from '~/modules/llms/store-llms';
|
||||
import { IModelVendor } from '~/modules/llms/vendors/IModelVendor';
|
||||
import { findVendorById } from '~/modules/llms/vendors/vendor.registry';
|
||||
import { findVendorById } from '~/modules/llms/vendors/vendors.registry';
|
||||
|
||||
import { GoodTooltip } from '~/common/components/GoodTooltip';
|
||||
import { openLayoutLLMOptions } from '~/common/layout/store-applayout';
|
||||
@@ -109,6 +109,7 @@ export function ModelsList(props: {
|
||||
<List variant='soft' size='sm' sx={{
|
||||
borderRadius: 'sm',
|
||||
pl: { xs: 0, md: 1 },
|
||||
overflowY: 'auto',
|
||||
}}>
|
||||
{items}
|
||||
</List>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { shallow } from 'zustand/shallow';
|
||||
import { Box, Checkbox, Divider } from '@mui/joy';
|
||||
|
||||
import { DModelSource, DModelSourceId, useModelsStore } from '~/modules/llms/store-llms';
|
||||
import { createModelSourceForDefaultVendor, findVendorById } from '~/modules/llms/vendors/vendor.registry';
|
||||
import { createModelSourceForDefaultVendor, findVendorById } from '~/modules/llms/vendors/vendors.registry';
|
||||
|
||||
import { GoodModal } from '~/common/components/GoodModal';
|
||||
import { closeLayoutModelsSetup, openLayoutModelsSetup, useLayoutModelsSetup } from '~/common/layout/store-applayout';
|
||||
|
||||
@@ -7,7 +7,7 @@ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||
|
||||
import { type DModelSourceId, useModelsStore } from '~/modules/llms/store-llms';
|
||||
import { type IModelVendor, type ModelVendorId } from '~/modules/llms/vendors/IModelVendor';
|
||||
import { createModelSourceForVendor, findAllVendors, findVendorById } from '~/modules/llms/vendors/vendor.registry';
|
||||
import { createModelSourceForVendor, findAllVendors, findVendorById } from '~/modules/llms/vendors/vendors.registry';
|
||||
|
||||
import { CloseableMenu } from '~/common/components/CloseableMenu';
|
||||
import { ConfirmationModal } from '~/common/components/ConfirmationModal';
|
||||
@@ -29,7 +29,7 @@ function vendorIcon(vendor: IModelVendor | null, greenMark: boolean) {
|
||||
icon = <vendor.Icon />;
|
||||
}
|
||||
return (greenMark && icon)
|
||||
? <Badge color='primary' size='sm' badgeContent=''>{icon}</Badge>
|
||||
? <Badge color='success' size='sm' badgeContent=''>{icon}</Badge>
|
||||
: icon;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,11 @@ export function ModelsSourceSelector(props: {
|
||||
<ListItemDecorator>
|
||||
{vendorIcon(vendor, !!vendor.hasBackendCap && vendor.hasBackendCap())}
|
||||
</ListItemDecorator>
|
||||
{vendor.name}{/*{sourceCount > 0 && ` (added)`}*/}
|
||||
{vendor.name}
|
||||
{/*{sourceCount > 0 && ` (added)`}*/}
|
||||
{!!vendor.hasFreeModels && ` 🎁`}
|
||||
{/*{!!vendor.instanceLimit && ` (${sourceCount}/${vendor.instanceLimit})`}*/}
|
||||
{vendor.location === 'local' && <span style={{ opacity: 0.5 }}>local</span>}
|
||||
</MenuItem>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ export const NewsItems: NewsItem[] = [
|
||||
],
|
||||
},*/
|
||||
{
|
||||
versionCode: '1.7.1',
|
||||
versionCode: '1.7.3',
|
||||
versionName: 'Attachment Theory',
|
||||
versionDate: new Date('2023-12-11T06:00:00Z'), // new Date().toISOString()
|
||||
// versionDate: new Date('2023-12-10T12:00:00Z'), // 1.7.0
|
||||
@@ -81,6 +81,8 @@ export const NewsItems: NewsItem[] = [
|
||||
{ text: <>Optimized voice input and performance</> },
|
||||
{ text: <>Latest Ollama and Oobabooga models</> },
|
||||
{ text: <>1.7.1: Improved <B href={RIssues + '/270'}>Ollama chats</B></> },
|
||||
{ text: <>1.7.2: Updated OpenRouter models 🎁</> },
|
||||
{ text: <>1.7.3: <B href={RIssues + '/273'}>Mistral Platform</B> support</> },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,9 +13,22 @@ export const ROUTE_INDEX = '/';
|
||||
export const ROUTE_APP_CHAT = '/';
|
||||
export const ROUTE_APP_LINK_CHAT = '/link/chat/:linkId';
|
||||
export const ROUTE_APP_NEWS = '/news';
|
||||
const ROUTE_CALLBACK_OPENROUTER = '/link/callback_openrouter';
|
||||
|
||||
export const getIndexLink = () => ROUTE_INDEX;
|
||||
|
||||
export const getCallbackUrl = (source: 'openrouter') => {
|
||||
const callbackUrl = new URL(window.location.href);
|
||||
switch (source) {
|
||||
case 'openrouter':
|
||||
callbackUrl.pathname = ROUTE_CALLBACK_OPENROUTER;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown source: ${source}`);
|
||||
}
|
||||
return callbackUrl.toString();
|
||||
};
|
||||
|
||||
export const getChatLinkRelativePath = (chatLinkId: string) => ROUTE_APP_LINK_CHAT.replace(':linkId', chatLinkId);
|
||||
|
||||
const navigateFn = (path: string) => (replace?: boolean): Promise<boolean> =>
|
||||
|
||||
@@ -23,7 +23,7 @@ export function GoodModal(props: {
|
||||
const showBottomClose = !!props.onClose && props.hideBottomClose !== true;
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<ModalOverflow>
|
||||
<ModalOverflow sx={{p:1}}>
|
||||
<ModalDialog
|
||||
sx={{
|
||||
minWidth: { xs: 360, sm: 500, md: 600, lg: 700 },
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { SvgIcon } from '@mui/joy';
|
||||
import { SxProps } from '@mui/joy/styles/types';
|
||||
|
||||
export function MistralIcon(props: { sx?: SxProps }) {
|
||||
return <SvgIcon viewBox='0 0 24 24' width='24' height='24' strokeWidth={0} stroke='none' fill='currentColor' strokeLinecap='butt' strokeLinejoin='miter' {...props}>
|
||||
<path d='m 2,2 v 4 4 V 14 v 4 4 h 4 v -4 -4 h 4 v 4 h 4 v -4 h 4 v 4 4 h 4 v -4 -4 -4 -4 V 2 h -4 v 4 h -4 v 4 h -4 v -4 H 6 V 2 Z' />
|
||||
</SvgIcon>;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ export type DropdownItems = Record<string, {
|
||||
title: string,
|
||||
symbol?: string,
|
||||
type?: 'separator'
|
||||
icon?: React.ReactNode,
|
||||
}>;
|
||||
|
||||
|
||||
@@ -71,20 +72,25 @@ export function AppBarDropdown<TValue extends string>(props: {
|
||||
{!!props.prependOption && Object.keys(props.items).length >= 1 && <Divider />}
|
||||
|
||||
<Box sx={{ overflowY: 'auto' }}>
|
||||
{Object.keys(props.items).map((key: string, idx: number) => <React.Fragment key={'key-' + idx}>
|
||||
{props.items[key].type === 'separator'
|
||||
? <ListDivider />
|
||||
: <Option value={key} sx={{ whiteSpace: 'nowrap' }}>
|
||||
{props.showSymbols && <ListItemDecorator sx={{ fontSize: 'xl' }}>{props.items[key]?.symbol + ' '}</ListItemDecorator>}
|
||||
{props.items[key].title}
|
||||
{Object.keys(props.items).map((key: string, idx: number) => {
|
||||
const item = props.items[key];
|
||||
|
||||
if (item.type === 'separator')
|
||||
return <ListDivider key={'key-' + idx} />;
|
||||
|
||||
return (
|
||||
<Option key={'key-' + idx} value={key} sx={{ whiteSpace: 'nowrap' }}>
|
||||
{props.showSymbols && <ListItemDecorator sx={{ fontSize: 'xl' }}>{item?.symbol + ' '}</ListItemDecorator>}
|
||||
{props.showSymbols && !!item.icon && <ListItemDecorator>{item?.icon}</ListItemDecorator>}
|
||||
{item.title}
|
||||
{/*{key === props.value && (*/}
|
||||
{/* <IconButton variant='soft' onClick={() => alert('aa')} sx={{ ml: 'auto' }}>*/}
|
||||
{/* <SettingsIcon color='success' />*/}
|
||||
{/* </IconButton>*/}
|
||||
{/*)}*/}
|
||||
</Option>
|
||||
}
|
||||
</React.Fragment>)}
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
{!!props.appendOption && Object.keys(props.items).length >= 1 && <ListDivider />}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
* @fileoverview Utility functions for Markdown.
|
||||
*/
|
||||
|
||||
import { isBrowser } from '~/common/util/pwaUtils';
|
||||
|
||||
/**
|
||||
* Quick and dirty conversion of HTML tables to Markdown tables.
|
||||
* Big plus: doesn't require any dependencies.
|
||||
*/
|
||||
export function htmlTableToMarkdown(html: string): string {
|
||||
export function htmlTableToMarkdown(html: string, includeInvisible: boolean): string {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
const table = doc.querySelector('table');
|
||||
@@ -16,20 +18,53 @@ export function htmlTableToMarkdown(html: string): string {
|
||||
const headerCells = table.querySelectorAll('thead th');
|
||||
if (headerCells.length > 0) {
|
||||
const headerRow = '| ' + Array.from(headerCells)
|
||||
.map(cell => cell.textContent?.trim() || '')
|
||||
.join(' | ') + '| ';
|
||||
.map(cell => getTextWithSpaces(cell, includeInvisible).trim())
|
||||
.join(' | ') + ' |';
|
||||
markdownRows.push(headerRow);
|
||||
markdownRows.push('|:' + Array(headerCells.length).fill('-').join('|:') + '|');
|
||||
markdownRows.push('|:' + Array(headerCells.length).fill('---').join('|:') + '|');
|
||||
}
|
||||
|
||||
const bodyRows = table.querySelectorAll('tbody tr');
|
||||
for (const row of Array.from(bodyRows)) {
|
||||
const rowCells = row.querySelectorAll('td');
|
||||
const markdownRow = '| ' + Array.from(rowCells)
|
||||
.map(cell => cell.textContent?.trim() || '')
|
||||
.map(cell => getTextWithSpaces(cell, includeInvisible).trim())
|
||||
.join(' | ') + ' |';
|
||||
markdownRows.push(markdownRow);
|
||||
}
|
||||
|
||||
return markdownRows.join('\n');
|
||||
}
|
||||
|
||||
// Helper function to get text with spaces, ignoring hidden elements
|
||||
function getTextWithSpaces(node: Node, includeInvisible: boolean): string {
|
||||
let text = '';
|
||||
node.childNodes.forEach(child => {
|
||||
if (child.nodeType === Node.TEXT_NODE)
|
||||
text += child.textContent;
|
||||
else if (child.nodeType === Node.ELEMENT_NODE)
|
||||
if (includeInvisible || isVisible(child as Element))
|
||||
text += ' ' + getTextWithSpaces(child, includeInvisible) + ' ';
|
||||
});
|
||||
return text;
|
||||
}
|
||||
|
||||
// Helper function to determine if an element is visible
|
||||
function isVisible(element: Element): boolean {
|
||||
if (!isBrowser) return true;
|
||||
|
||||
// if the cell is hidden, don't include it
|
||||
const style = window.getComputedStyle(element);
|
||||
if (style.display === 'none' || style.visibility === 'hidden')
|
||||
return false;
|
||||
|
||||
// Check for common classes used to hide content or indicate tooltip/popover content.
|
||||
// You may need to add more classes here based on your actual HTML/CSS.
|
||||
const ignoredClasses = ['hidden', 'group-hover', 'tooltip', 'pointer-events-none', 'opacity-0'];
|
||||
for (const ignoredClass of ignoredClasses)
|
||||
if (element.classList.contains(ignoredClass))
|
||||
return false;
|
||||
|
||||
// Otherwise, the element is considered visible
|
||||
return true;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export async function pdfToText(pdfBuffer: ArrayBuffer): Promise<string> {
|
||||
const { getDocument, GlobalWorkerOptions } = await import('pdfjs-dist');
|
||||
|
||||
// Set the worker script path
|
||||
GlobalWorkerOptions.workerSrc = '/workers/pdf.worker.min.js';
|
||||
GlobalWorkerOptions.workerSrc = '/workers/pdf.worker.min.mjs';
|
||||
|
||||
const pdf = await getDocument(pdfBuffer).promise;
|
||||
const textPages: string[] = []; // Initialize an array to hold text from all pages
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { BackendCapabilities } from '~/modules/backend/state-backend';
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from '~/server/api/trpc.server';
|
||||
import { env } from '~/server/env.mjs';
|
||||
import { fetchJsonOrTRPCError } from '~/server/api/trpc.serverutils';
|
||||
|
||||
import { analyticsListCapabilities } from './backend.analytics';
|
||||
|
||||
@@ -23,11 +28,25 @@ export const backendRouter = createTRPCRouter({
|
||||
hasImagingProdia: !!env.PRODIA_API_KEY,
|
||||
hasLlmAnthropic: !!env.ANTHROPIC_API_KEY,
|
||||
hasLlmAzureOpenAI: !!env.AZURE_OPENAI_API_KEY && !!env.AZURE_OPENAI_API_ENDPOINT,
|
||||
hasLlmMistral: !!env.MISTRAL_API_KEY,
|
||||
hasLlmOllama: !!env.OLLAMA_API_HOST,
|
||||
hasLlmOpenAI: !!env.OPENAI_API_KEY || !!env.OPENAI_API_HOST,
|
||||
hasLlmOpenRouter: !!env.OPENROUTER_API_KEY,
|
||||
hasVoiceElevenLabs: !!env.ELEVENLABS_API_KEY,
|
||||
};
|
||||
} satisfies BackendCapabilities;
|
||||
}),
|
||||
|
||||
|
||||
// The following are used for various OAuth integrations
|
||||
|
||||
/* Exchange the OpenrRouter 'code' (from PKCS) for an OpenRouter API Key */
|
||||
exchangeOpenRouterKey: publicProcedure
|
||||
.input(z.object({ code: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
// Documented here: https://openrouter.ai/docs#oauth
|
||||
return await fetchJsonOrTRPCError<{ key: string }, { code: string }>('https://openrouter.ai/api/v1/auth/keys', 'POST', {}, {
|
||||
code: input.code,
|
||||
}, 'Backend.exchangeOpenRouterKey');
|
||||
}),
|
||||
|
||||
});
|
||||
@@ -9,6 +9,7 @@ export interface BackendCapabilities {
|
||||
hasImagingProdia: boolean;
|
||||
hasLlmAnthropic: boolean;
|
||||
hasLlmAzureOpenAI: boolean;
|
||||
hasLlmMistral: boolean;
|
||||
hasLlmOllama: boolean;
|
||||
hasLlmOpenAI: boolean;
|
||||
hasLlmOpenRouter: boolean;
|
||||
@@ -30,6 +31,7 @@ const useBackendStore = create<BackendStore>()(
|
||||
hasImagingProdia: false,
|
||||
hasLlmAnthropic: false,
|
||||
hasLlmAzureOpenAI: false,
|
||||
hasLlmMistral: false,
|
||||
hasLlmOllama: false,
|
||||
hasLlmOpenAI: false,
|
||||
hasLlmOpenRouter: false,
|
||||
|
||||
@@ -2,7 +2,8 @@ import { create } from 'zustand';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import { ModelVendorId } from './vendors/IModelVendor';
|
||||
import type { IModelVendor, ModelVendorId } from './vendors/IModelVendor';
|
||||
import type { SourceSetupOpenRouter } from './vendors/openrouter/openrouter.vendor';
|
||||
|
||||
|
||||
/**
|
||||
@@ -76,6 +77,9 @@ interface ModelsActions {
|
||||
setChatLLMId: (id: DLLMId | null) => void;
|
||||
setFastLLMId: (id: DLLMId | null) => void;
|
||||
setFuncLLMId: (id: DLLMId | null) => void;
|
||||
|
||||
// special
|
||||
setOpenRoutersKey: (key: string) => void;
|
||||
}
|
||||
|
||||
type LlmsStore = ModelsData & ModelsActions;
|
||||
@@ -162,13 +166,22 @@ export const useModelsStore = create<LlmsStore>()(
|
||||
set(state => ({
|
||||
sources: state.sources.map((source: DModelSource): DModelSource =>
|
||||
source.id === id
|
||||
? {
|
||||
...source,
|
||||
setup: { ...source.setup, ...partialSetup },
|
||||
} : source,
|
||||
? { ...source, setup: { ...source.setup, ...partialSetup } }
|
||||
: source,
|
||||
),
|
||||
})),
|
||||
|
||||
setOpenRoutersKey: (key: string) =>
|
||||
set(state => {
|
||||
const openRouterSource = state.sources.find(source => source.vId === 'openrouter');
|
||||
if (!openRouterSource) return state;
|
||||
return {
|
||||
sources: state.sources.map(source => source.id === openRouterSource.id
|
||||
? { ...source, setup: { ...source.setup, oaiKey: key satisfies SourceSetupOpenRouter['oaiKey'] } }
|
||||
: source),
|
||||
};
|
||||
}),
|
||||
|
||||
}),
|
||||
{
|
||||
name: 'app-models',
|
||||
@@ -259,16 +272,24 @@ export function useChatLLM() {
|
||||
/**
|
||||
* Source-specific read/write - great time saver
|
||||
*/
|
||||
export function useSourceSetup<TSourceSetup, TAccess>(sourceId: DModelSourceId, getAccess: (partialSetup?: Partial<TSourceSetup>) => TAccess) {
|
||||
// invalidate when the setup changes
|
||||
export function useSourceSetup<TSourceSetup, TAccess>(sourceId: DModelSourceId, vendor: IModelVendor<TSourceSetup, TAccess>) {
|
||||
|
||||
// invalidates only when the setup changes
|
||||
const { updateSourceSetup, ...rest } = useModelsStore(state => {
|
||||
const source: DModelSource<TSourceSetup> | null = state.sources.find(source => source.id === sourceId) ?? null;
|
||||
|
||||
// find the source (or null)
|
||||
const source: DModelSource<TSourceSetup> | null = state.sources.find(source => source.id === sourceId) as DModelSource<TSourceSetup> ?? null;
|
||||
|
||||
// (safe) source-derived properties
|
||||
const sourceSetupValid = (source?.setup && vendor?.validateSetup) ? vendor.validateSetup(source.setup as TSourceSetup) : false;
|
||||
const sourceLLMs = source ? state.llms.filter(llm => llm._source === source) : [];
|
||||
const access = vendor.getTransportAccess(source?.setup);
|
||||
|
||||
return {
|
||||
source,
|
||||
sourceLLMs,
|
||||
access,
|
||||
sourceHasLLMs: !!sourceLLMs.length,
|
||||
access: getAccess(source?.setup),
|
||||
sourceSetupValid,
|
||||
updateSourceSetup: state.updateSourceSetup,
|
||||
};
|
||||
}, shallow);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DLLMId } from '../store-llms';
|
||||
import type { OpenAIWire } from './server/openai/openai.wiretypes';
|
||||
import { findVendorForLlmOrThrow } from '../vendors/vendor.registry';
|
||||
import { findVendorForLlmOrThrow } from '../vendors/vendors.registry';
|
||||
|
||||
|
||||
export interface VChatMessageIn {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from '~/server/api/trpc.server';
|
||||
import { env } from '~/server/env.mjs';
|
||||
@@ -246,8 +247,17 @@ export const llmOllamaRouter = createTRPCRouter({
|
||||
const wireGeneration = await ollamaPOST(access, ollamaChatCompletionPayload(model, history, false), OLLAMA_PATH_CHAT);
|
||||
const generation = wireOllamaChunkedOutputSchema.parse(wireGeneration);
|
||||
|
||||
if ('error' in generation)
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: `Ollama chat-generation issue: ${generation.error}`,
|
||||
});
|
||||
|
||||
if (!generation.message?.content)
|
||||
throw new Error('Ollama chat generation (non-stream) issue: ' + JSON.stringify(wireGeneration));
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: `Ollama chat-generation API issue: ${JSON.stringify(wireGeneration)}`,
|
||||
});
|
||||
|
||||
return {
|
||||
role: 'assistant',
|
||||
|
||||
@@ -43,27 +43,34 @@ export type WireOllamaChatCompletionInput = z.infer<typeof wireOllamaChatComplet
|
||||
/**
|
||||
* Chat Completion or Generation APIs - Streaming Response
|
||||
*/
|
||||
export const wireOllamaChunkedOutputSchema = z.object({
|
||||
model: z.string(),
|
||||
// created_at: z.string(), // commented because unused
|
||||
export const wireOllamaChunkedOutputSchema = z.union([
|
||||
// Chat Completion Chunk
|
||||
z.object({
|
||||
model: z.string(),
|
||||
// created_at: z.string(), // commented because unused
|
||||
|
||||
// [Chat Completion] (exclusive with 'response')
|
||||
message: z.object({
|
||||
role: z.enum(['assistant' /*, 'system', 'user' Disabled on purpose, to validate the response */]),
|
||||
content: z.string(),
|
||||
}).optional(), // optional on the last message
|
||||
// [Chat Completion] (exclusive with 'response')
|
||||
message: z.object({
|
||||
role: z.enum(['assistant' /*, 'system', 'user' Disabled on purpose, to validate the response */]),
|
||||
content: z.string(),
|
||||
}).optional(), // optional on the last message
|
||||
|
||||
// [Generation] (non-chat, exclusive with 'message')
|
||||
//response: z.string().optional(),
|
||||
// [Generation] (non-chat, exclusive with 'message')
|
||||
//response: z.string().optional(),
|
||||
|
||||
done: z.boolean(),
|
||||
done: z.boolean(),
|
||||
|
||||
// only on the last message
|
||||
// context: z.array(z.number()), // non-chat endpoint
|
||||
// total_duration: z.number(),
|
||||
// prompt_eval_count: z.number(),
|
||||
// prompt_eval_duration: z.number(),
|
||||
// eval_count: z.number(),
|
||||
// eval_duration: z.number(),
|
||||
// only on the last message
|
||||
// context: z.array(z.number()), // non-chat endpoint
|
||||
// total_duration: z.number(),
|
||||
// prompt_eval_count: z.number(),
|
||||
// prompt_eval_duration: z.number(),
|
||||
// eval_count: z.number(),
|
||||
// eval_duration: z.number(),
|
||||
|
||||
});
|
||||
}),
|
||||
// Possible Error
|
||||
z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
]);
|
||||
@@ -0,0 +1,33 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
|
||||
// [Mistral] Models List API - Response
|
||||
|
||||
export const wireMistralModelsListOutputSchema = z.object({
|
||||
id: z.string(),
|
||||
object: z.literal('model'),
|
||||
created: z.number(),
|
||||
owned_by: z.string(),
|
||||
root: z.null().optional(),
|
||||
parent: z.null().optional(),
|
||||
// permission: z.array(wireMistralModelsListPermissionsSchema)
|
||||
});
|
||||
|
||||
// export type WireMistralModelsListOutput = z.infer<typeof wireMistralModelsListOutputSchema>;
|
||||
|
||||
/*
|
||||
const wireMistralModelsListPermissionsSchema = z.object({
|
||||
id: z.string(),
|
||||
object: z.literal('model_permission'),
|
||||
created: z.number(),
|
||||
allow_create_engine: z.boolean(),
|
||||
allow_sampling: z.boolean(),
|
||||
allow_logprobs: z.boolean(),
|
||||
allow_search_indices: z.boolean(),
|
||||
allow_view: z.boolean(),
|
||||
allow_fine_tuning: z.boolean(),
|
||||
organization: z.string(),
|
||||
group: z.null().optional(),
|
||||
is_blocking: z.boolean()
|
||||
});
|
||||
*/
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { ModelDescriptionSchema } from '../server.schemas';
|
||||
import { SERVER_DEBUG_WIRE } from '~/server/wire';
|
||||
|
||||
import { LLM_IF_OAI_Chat, LLM_IF_OAI_Complete, LLM_IF_OAI_Fn, LLM_IF_OAI_Vision } from '../../../store-llms';
|
||||
|
||||
import type { ModelDescriptionSchema } from '../server.schemas';
|
||||
import { wireMistralModelsListOutputSchema } from './mistral.wiretypes';
|
||||
|
||||
|
||||
// [Azure] / [OpenAI]
|
||||
const _knownOpenAIChatModels: ManualMappings = [
|
||||
@@ -203,6 +207,63 @@ export function localAIModelToModelDescription(modelId: string): ModelDescriptio
|
||||
}
|
||||
|
||||
|
||||
// [Mistral]
|
||||
|
||||
const _knownMistralChatModels: ManualMappings = [
|
||||
{
|
||||
idPrefix: 'mistral-medium',
|
||||
label: 'Mistral Medium',
|
||||
description: 'Mistral internal prototype model.',
|
||||
contextWindow: 32768,
|
||||
interfaces: [LLM_IF_OAI_Chat],
|
||||
},
|
||||
{
|
||||
idPrefix: 'mistral-small',
|
||||
label: 'Mistral Small',
|
||||
description: 'Higher reasoning capabilities and more capabilities (English, French, German, Italian, Spanish, and Code)',
|
||||
contextWindow: 32768,
|
||||
interfaces: [LLM_IF_OAI_Chat],
|
||||
},
|
||||
{
|
||||
idPrefix: 'mistral-tiny',
|
||||
label: 'Mistral Tiny',
|
||||
description: 'Used for large batch processing tasks where cost is a significant factor but reasoning capabilities are not crucial',
|
||||
contextWindow: 32768,
|
||||
interfaces: [LLM_IF_OAI_Chat],
|
||||
},
|
||||
{
|
||||
idPrefix: 'mistral-embed',
|
||||
label: 'Mistral Embed',
|
||||
description: 'Mistral Medium on Mistral',
|
||||
// output: 1024 dimensions
|
||||
maxCompletionTokens: 1024, // HACK - it's 1024 dimensions, but those are not 'completion tokens'
|
||||
contextWindow: 32768, // actually unknown, assumed from the other models
|
||||
interfaces: [],
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function mistralModelToModelDescription(_model: unknown): ModelDescriptionSchema {
|
||||
const model = wireMistralModelsListOutputSchema.parse(_model);
|
||||
return fromManualMapping(_knownMistralChatModels, model.id, model.created, undefined, {
|
||||
idPrefix: model.id,
|
||||
label: model.id.replaceAll(/[_-]/g, ' '),
|
||||
description: 'New Mistral Model',
|
||||
contextWindow: 32768,
|
||||
interfaces: [LLM_IF_OAI_Chat], // assume..
|
||||
hidden: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function mistralModelsSort(a: ModelDescriptionSchema, b: ModelDescriptionSchema): number {
|
||||
if (a.hidden && !b.hidden)
|
||||
return 1;
|
||||
if (!a.hidden && b.hidden)
|
||||
return -1;
|
||||
return a.id.localeCompare(b.id);
|
||||
}
|
||||
|
||||
|
||||
// [Oobabooga]
|
||||
const _knownOobaboogaChatModels: ManualMappings = [];
|
||||
|
||||
@@ -236,8 +297,8 @@ export function oobaboogaModelToModelDescription(modelId: string, created: numbe
|
||||
/**
|
||||
* Created to reflect the doc page: https://openrouter.ai/docs
|
||||
*
|
||||
* Update prompt:
|
||||
* "Please update the typescript object below (do not change the definition, just the object), based on the updated upstream documentation:"
|
||||
* Update prompt (last updated 2023-12-12)
|
||||
* "Please update the following typescript object (do not change the definition, just values, and do not miss any rows), based on the information provided thereafter:"
|
||||
*
|
||||
* fields:
|
||||
* - cw: context window size (max tokens, total)
|
||||
@@ -247,19 +308,24 @@ export function oobaboogaModelToModelDescription(modelId: string, created: numbe
|
||||
*/
|
||||
const orModelMap: { [id: string]: { name: string; cw: number; cp?: number; cc?: number; old?: boolean; unfilt?: boolean; } } = {
|
||||
// 'openrouter/auto': { name: 'Auto (best for prompt)', cw: 128000, cp: undefined, cc: undefined, unfilt: undefined },
|
||||
'mistralai/mistral-7b-instruct': { name: 'Mistral 7B Instruct (beta)', cw: 8192, cp: 0, cc: 0, unfilt: true },
|
||||
'huggingfaceh4/zephyr-7b-beta': { name: 'Hugging Face: Zephyr 7B (beta)', cw: 4096, cp: 0, cc: 0, unfilt: true },
|
||||
'openchat/openchat-7b': { name: 'OpenChat 7B (beta)', cw: 8192, cp: 0, cc: 0, unfilt: true },
|
||||
'undi95/toppy-m-7b': { name: 'Toppy M 7B (beta)', cw: 32768, cp: 0, cc: 0, unfilt: true },
|
||||
'gryphe/mythomist-7b': { name: 'MythoMist 7B (beta)', cw: 32768, cp: 0, cc: 0, unfilt: true },
|
||||
'nousresearch/nous-hermes-llama2-13b': { name: 'Nous: Hermes 13B (beta)', cw: 4096, cp: 0.000155, cc: 0.000155, unfilt: true },
|
||||
'meta-llama/codellama-34b-instruct': { name: 'Meta: CodeLlama 34B Instruct (beta)', cw: 8192, cp: 0.00045, cc: 0.00045, unfilt: true },
|
||||
'phind/phind-codellama-34b': { name: 'Phind: CodeLlama 34B v2 (beta)', cw: 4096, cp: 0.00045, cc: 0.00045, unfilt: true },
|
||||
'intel/neural-chat-7b': { name: 'Neural Chat 7B v3.1 (beta)', cw: 32768, cp: 0.005, cc: 0.005, unfilt: true },
|
||||
'haotian-liu/llava-13b': { name: 'Llava 13B (beta)', cw: 2048, cp: 0.005, cc: 0.005, unfilt: true },
|
||||
'meta-llama/llama-2-13b-chat': { name: 'Meta: Llama v2 13B Chat (beta)', cw: 4096, cp: 0.000234533, cc: 0.000234533, unfilt: true },
|
||||
'alpindale/goliath-120b': { name: 'Goliath 120B (beta)', cw: 6144, cp: 0.00703125, cc: 0.00703125, unfilt: true },
|
||||
'lizpreciatior/lzlv-70b-fp16-hf': { name: 'lzlv 70B (beta)', cw: 4096, cp: 0.000562, cc: 0.000762, unfilt: true },
|
||||
'nousresearch/nous-capybara-7b': { name: 'Nous: Capybara 7B', cw: 4096, cp: 0, cc: 0, unfilt: true },
|
||||
'mistralai/mistral-7b-instruct': { name: 'Mistral 7B Instruct', cw: 8192, cp: 0, cc: 0, unfilt: true },
|
||||
'huggingfaceh4/zephyr-7b-beta': { name: 'Hugging Face: Zephyr 7B', cw: 4096, cp: 0, cc: 0, unfilt: true },
|
||||
'openchat/openchat-7b': { name: 'OpenChat 3.5', cw: 8192, cp: 0, cc: 0, unfilt: true },
|
||||
'gryphe/mythomist-7b': { name: 'MythoMist 7B', cw: 32768, cp: 0, cc: 0, unfilt: true },
|
||||
'openrouter/cinematika-7b': { name: 'Cinematika 7B (alpha)', cw: 32000, cp: 0, cc: 0, unfilt: true },
|
||||
'mistralai/mixtral-8x7b-instruct': { name: 'Mistral: Mixtral 8x7B Instruct (beta)', cw: 32000, cp: 0, cc: 0, unfilt: true },
|
||||
'rwkv/rwkv-5-world-3b': { name: 'RWKV v5 World 3B (beta)', cw: 10000, cp: 0, cc: 0, unfilt: true },
|
||||
'recursal/rwkv-5-3b-ai-town': { name: 'RWKV v5 3B AI Town (beta)', cw: 10000, cp: 0, cc: 0, unfilt: true },
|
||||
'jebcarter/psyfighter-13b': { name: 'Psyfighter 13B', cw: 4096, cp: 0.001, cc: 0.001, unfilt: true },
|
||||
'koboldai/psyfighter-13b-2': { name: 'Psyfighter v2 13B', cw: 4096, cp: 0.001, cc: 0.001, unfilt: true },
|
||||
'nousresearch/nous-hermes-llama2-13b': { name: 'Nous: Hermes 13B', cw: 4096, cp: 0.000075, cc: 0.000075, unfilt: true },
|
||||
'meta-llama/codellama-34b-instruct': { name: 'Meta: CodeLlama 34B Instruct', cw: 8192, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'phind/phind-codellama-34b': { name: 'Phind: CodeLlama 34B v2', cw: 4096, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'intel/neural-chat-7b': { name: 'Neural Chat 7B v3.1', cw: 4096, cp: 0.0025, cc: 0.0025, unfilt: true },
|
||||
'haotian-liu/llava-13b': { name: 'Llava 13B', cw: 2048, cp: 0.0025, cc: 0.0025, unfilt: true },
|
||||
'nousresearch/nous-hermes-2-vision-7b': { name: 'Nous: Hermes 2 Vision 7B (alpha)', cw: 4096, cp: 0.0025, cc: 0.0025, unfilt: true },
|
||||
'meta-llama/llama-2-13b-chat': { name: 'Meta: Llama v2 13B Chat', cw: 4096, cp: 0.000156755, cc: 0.000156755, unfilt: true },
|
||||
'openai/gpt-3.5-turbo': { name: 'OpenAI: GPT-3.5 Turbo', cw: 4095, cp: 0.001, cc: 0.002, unfilt: false },
|
||||
'openai/gpt-3.5-turbo-1106': { name: 'OpenAI: GPT-3.5 Turbo 16k (preview)', cw: 16385, cp: 0.001, cc: 0.002, unfilt: false },
|
||||
'openai/gpt-3.5-turbo-16k': { name: 'OpenAI: GPT-3.5 Turbo 16k', cw: 16385, cp: 0.003, cc: 0.004, unfilt: false },
|
||||
@@ -272,24 +338,38 @@ const orModelMap: { [id: string]: { name: string; cw: number; cp?: number; cc?:
|
||||
'google/palm-2-codechat-bison': { name: 'Google: PaLM 2 Code Chat', cw: 7168, cp: 0.0005, cc: 0.0005, unfilt: true },
|
||||
'google/palm-2-chat-bison-32k': { name: 'Google: PaLM 2 Chat 32k', cw: 32000, cp: 0.0005, cc: 0.0005, unfilt: true },
|
||||
'google/palm-2-codechat-bison-32k': { name: 'Google: PaLM 2 Code Chat 32k', cw: 32000, cp: 0.0005, cc: 0.0005, unfilt: true },
|
||||
'meta-llama/llama-2-70b-chat': { name: 'Meta: Llama v2 70B Chat (beta)', cw: 4096, cp: 0.0007, cc: 0.00095, unfilt: true },
|
||||
'nousresearch/nous-hermes-llama2-70b': { name: 'Nous: Hermes 70B (beta)', cw: 4096, cp: 0.0009, cc: 0.0009, unfilt: true },
|
||||
'nousresearch/nous-capybara-34b': { name: 'Nous: Capybara 34B (beta)', cw: 32000, cp: 0.02, cc: 0.02, unfilt: true },
|
||||
'jondurbin/airoboros-l2-70b': { name: 'Airoboros 70B (beta)', cw: 4096, cp: 0.0007, cc: 0.00095, unfilt: true },
|
||||
'migtissera/synthia-70b': { name: 'Synthia 70B (beta)', cw: 8192, cp: 0.009375, cc: 0.009375, unfilt: true },
|
||||
'open-orca/mistral-7b-openorca': { name: 'Mistral OpenOrca 7B (beta)', cw: 8192, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'teknium/openhermes-2-mistral-7b': { name: 'OpenHermes 2 Mistral 7B (beta)', cw: 4096, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'teknium/openhermes-2.5-mistral-7b': { name: 'OpenHermes 2.5 Mistral 7B (beta)', cw: 4096, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'pygmalionai/mythalion-13b': { name: 'Pygmalion: Mythalion 13B (beta)', cw: 8192, cp: 0.001125, cc: 0.001125, unfilt: true },
|
||||
'undi95/remm-slerp-l2-13b': { name: 'ReMM SLERP 13B (beta)', cw: 6144, cp: 0.001125, cc: 0.001125, unfilt: true },
|
||||
'xwin-lm/xwin-lm-70b': { name: 'Xwin 70B (beta)', cw: 8192, cp: 0.009375, cc: 0.009375, unfilt: true },
|
||||
'gryphe/mythomax-l2-13b-8k': { name: 'MythoMax 13B 8k (beta)', cw: 8192, cp: 0.001125, cc: 0.001125, unfilt: true },
|
||||
'neversleep/noromaid-20b': { name: 'Noromaid 20B (beta)', cw: 8192, cp: 0.00225, cc: 0.00225, unfilt: true },
|
||||
'perplexity/pplx-70b-online': { name: 'Perplexity: PPLX 70B Online', cw: 4096, cp: 0, cc: 0.0028, unfilt: true },
|
||||
'perplexity/pplx-7b-online': { name: 'Perplexity: PPLX 7B Online', cw: 4096, cp: 0, cc: 0.00028, unfilt: true },
|
||||
'perplexity/pplx-7b-chat': { name: 'Perplexity: PPLX 7B Chat', cw: 8192, cp: 0.00007, cc: 0.00028, unfilt: true },
|
||||
'perplexity/pplx-70b-chat': { name: 'Perplexity: PPLX 70B Chat', cw: 4096, cp: 0.0007, cc: 0.0028, unfilt: true },
|
||||
'meta-llama/llama-2-70b-chat': { name: 'Meta: Llama v2 70B Chat', cw: 4096, cp: 0.0007, cc: 0.00095, unfilt: true },
|
||||
'nousresearch/nous-hermes-llama2-70b': { name: 'Nous: Hermes 70B', cw: 4096, cp: 0.0009, cc: 0.0009, unfilt: true },
|
||||
'nousresearch/nous-capybara-34b': { name: 'Nous: Capybara 34B', cw: 32000, cp: 0.0007, cc: 0.0028, unfilt: true },
|
||||
'jondurbin/airoboros-l2-70b': { name: 'Airoboros 70B', cw: 4096, cp: 0.0007, cc: 0.00095, unfilt: true },
|
||||
'migtissera/synthia-70b': { name: 'Synthia 70B', cw: 8192, cp: 0.00375, cc: 0.00375, unfilt: true },
|
||||
'open-orca/mistral-7b-openorca': { name: 'Mistral OpenOrca 7B', cw: 8192, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'teknium/openhermes-2-mistral-7b': { name: 'OpenHermes 2 Mistral 7B', cw: 4096, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'teknium/openhermes-2.5-mistral-7b': { name: 'OpenHermes 2.5 Mistral 7B', cw: 4096, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'pygmalionai/mythalion-13b': { name: 'Pygmalion: Mythalion 13B', cw: 8192, cp: 0.001125, cc: 0.001125, unfilt: true },
|
||||
'undi95/remm-slerp-l2-13b': { name: 'ReMM SLERP 13B', cw: 6144, cp: 0.001125, cc: 0.001125, unfilt: true },
|
||||
'xwin-lm/xwin-lm-70b': { name: 'Xwin 70B', cw: 8192, cp: 0.00375, cc: 0.00375, unfilt: true },
|
||||
'gryphe/mythomax-l2-13b-8k': { name: 'MythoMax 13B 8k', cw: 8192, cp: 0.001125, cc: 0.001125, unfilt: true },
|
||||
'undi95/toppy-m-7b': { name: 'Toppy M 7B', cw: 32768, cp: 0.000375, cc: 0.000375, unfilt: true },
|
||||
'alpindale/goliath-120b': { name: 'Goliath 120B', cw: 6144, cp: 0.009375, cc: 0.009375, unfilt: true },
|
||||
'lizpreciatior/lzlv-70b-fp16-hf': { name: 'lzlv 70B', cw: 4096, cp: 0.0007, cc: 0.00095, unfilt: true },
|
||||
'neversleep/noromaid-20b': { name: 'Noromaid 20B', cw: 8192, cp: 0.00225, cc: 0.00225, unfilt: true },
|
||||
'01-ai/yi-34b-chat': { name: 'Yi 34B Chat', cw: 4096, cp: 0.0008, cc: 0.0008, unfilt: true },
|
||||
'01-ai/yi-34b': { name: 'Yi 34B (base)', cw: 4096, cp: 0.0008, cc: 0.0008, unfilt: true },
|
||||
'01-ai/yi-6b': { name: 'Yi 6B (base)', cw: 4096, cp: 0.00014, cc: 0.00014, unfilt: true },
|
||||
'togethercomputer/stripedhyena-nous-7b': { name: 'StripedHyena Nous 7B', cw: 32000, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'togethercomputer/stripedhyena-hessian-7b': { name: 'StripedHyena Hessian 7B (base)', cw: 32000, cp: 0.0002, cc: 0.0002, unfilt: true },
|
||||
'mistralai/mixtral-8x7b': { name: 'Mistral: Mixtral 8x7B (base) (beta)', cw: 32000, cp: 0.0006, cc: 0.0006, unfilt: true },
|
||||
'anthropic/claude-2': { name: 'Anthropic: Claude v2.1', cw: 200000, cp: 0.008, cc: 0.024, unfilt: false },
|
||||
'anthropic/claude-2.0': { name: 'Anthropic: Claude v2.0', cw: 100000, cp: 0.008, cc: 0.024, unfilt: false },
|
||||
'anthropic/claude-instant-v1': { name: 'Anthropic: Claude Instant v1', cw: 100000, cp: 0.00163, cc: 0.00551, unfilt: false },
|
||||
'mancer/weaver': { name: 'Mancer: Weaver (alpha)', cw: 8000, cp: 0.0045, cc: 0.0045, unfilt: true },
|
||||
'mancer/weaver': { name: 'Mancer: Weaver (alpha)', cw: 8000, cp: 0.003375, cc: 0.003375, unfilt: true },
|
||||
'gryphe/mythomax-l2-13b': { name: 'MythoMax 13B', cw: 4096, cp: 0.0006, cc: 0.0006, unfilt: true },
|
||||
// Old models (maintained for reference)
|
||||
'openai/gpt-3.5-turbo-0301': { name: 'OpenAI: GPT-3.5 Turbo (older v0301)', cw: 4095, cp: 0.001, cc: 0.002, old: true },
|
||||
'openai/gpt-4-0314': { name: 'OpenAI: GPT-4 (older v0314)', cw: 8191, cp: 0.03, cc: 0.06, old: true },
|
||||
'openai/gpt-4-32k-0314': { name: 'OpenAI: GPT-4 32k (older v0314)', cw: 32767, cp: 0.06, cc: 0.12, old: true },
|
||||
@@ -301,7 +381,12 @@ const orModelMap: { [id: string]: { name: string; cw: number; cp?: number; cc?:
|
||||
'anthropic/claude-instant-1.0': { name: 'Anthropic: Claude Instant (older v1)', cw: 9000, cp: 0.00163, cc: 0.00551, old: true },
|
||||
};
|
||||
|
||||
const orModelFamilyOrder = ['mistralai/', 'huggingfaceh4/', 'undi95/', 'openchat/', 'anthropic/', 'google/', 'openai/', 'meta-llama/', 'phind/', 'openrouter/'];
|
||||
const orModelFamilyOrder = [
|
||||
// great models
|
||||
'mistralai/mixtral-8x7b-instruct', 'mistralai/mistral-7b-instruct', 'nousresearch/nous-capybara-7b',
|
||||
// great orgs
|
||||
'huggingfaceh4/', 'openchat/', 'anthropic/', 'google/', 'openai/', 'meta-llama/', 'phind/',
|
||||
];
|
||||
|
||||
export function openRouterModelFamilySortFn(a: { id: string }, b: { id: string }): number {
|
||||
const aPrefixIndex = orModelFamilyOrder.findIndex(prefix => a.id.startsWith(prefix));
|
||||
@@ -321,10 +406,10 @@ export function openRouterModelToModelDescription(modelId: string, created: numb
|
||||
const orModel = orModelMap[modelId] ?? null;
|
||||
let label = orModel?.name || modelId.replace('/', ' · ');
|
||||
if (orModel?.cp === 0 && orModel?.cc === 0)
|
||||
label += ' - 🎁 Free';
|
||||
label += ' · 🎁'; // Free? Discounted?
|
||||
|
||||
// if (!orModel)
|
||||
// console.log('openRouterModelToModelDescription: unknown model id:', modelId);
|
||||
if (SERVER_DEBUG_WIRE && !orModel)
|
||||
console.log(' - openRouterModelToModelDescription: non-mapped model id:', modelId);
|
||||
|
||||
// context: use the known size if available, otherwise fallback to the (undocumneted) provided length or fallback again to 4096
|
||||
const contextWindow = orModel?.cw || context_length || 4096;
|
||||
|
||||
@@ -9,12 +9,12 @@ import { Brand } from '~/common/app.config';
|
||||
|
||||
import type { OpenAIWire } from './openai.wiretypes';
|
||||
import { listModelsOutputSchema, ModelDescriptionSchema } from '../server.schemas';
|
||||
import { localAIModelToModelDescription, oobaboogaModelToModelDescription, openAIModelToModelDescription, openRouterModelFamilySortFn, openRouterModelToModelDescription } from './models.data';
|
||||
import { localAIModelToModelDescription, mistralModelsSort, mistralModelToModelDescription, oobaboogaModelToModelDescription, openAIModelToModelDescription, openRouterModelFamilySortFn, openRouterModelToModelDescription } from './models.data';
|
||||
|
||||
|
||||
// Input Schemas
|
||||
|
||||
const openAIDialects = z.enum(['azure', 'localai', 'oobabooga', 'openai', 'openrouter']);
|
||||
const openAIDialects = z.enum(['azure', 'localai', 'mistral', 'oobabooga', 'openai', 'openrouter']);
|
||||
|
||||
export const openAIAccessSchema = z.object({
|
||||
dialect: openAIDialects,
|
||||
@@ -186,12 +186,18 @@ export const llmOpenAIRouter = createTRPCRouter({
|
||||
.map((model): ModelDescriptionSchema => openAIModelToModelDescription(model.id, model.created));
|
||||
break;
|
||||
|
||||
case 'mistral':
|
||||
models = openAIModels
|
||||
.map(mistralModelToModelDescription)
|
||||
.sort(mistralModelsSort);
|
||||
break;
|
||||
|
||||
case 'openrouter':
|
||||
models = openAIModels
|
||||
.sort(openRouterModelFamilySortFn)
|
||||
.map(model => openRouterModelToModelDescription(model.id, model.created, (model as any)?.['context_length']));
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return { models };
|
||||
@@ -267,9 +273,10 @@ async function openaiPOST<TOut extends object, TPostBody extends object>(access:
|
||||
}
|
||||
|
||||
|
||||
const DEFAULT_HELICONE_OPENAI_HOST = 'oai.hconeai.com';
|
||||
const DEFAULT_MISTRAL_HOST = 'https://api.mistral.ai';
|
||||
const DEFAULT_OPENAI_HOST = 'api.openai.com';
|
||||
const DEFAULT_OPENROUTER_HOST = 'https://openrouter.ai/api';
|
||||
const DEFAULT_HELICONE_OPENAI_HOST = 'oai.hconeai.com';
|
||||
|
||||
export function fixupHost(host: string, apiPath: string): string {
|
||||
if (!host.startsWith('http'))
|
||||
@@ -361,6 +368,20 @@ export function openAIAccess(access: OpenAIAccessSchema, modelRefId: string | nu
|
||||
};
|
||||
|
||||
|
||||
case 'mistral':
|
||||
// https://docs.mistral.ai/platform/client
|
||||
const mistralKey = access.oaiKey || env.MISTRAL_API_KEY || '';
|
||||
const mistralHost = fixupHost(access.oaiHost || DEFAULT_MISTRAL_HOST, apiPath);
|
||||
return {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${mistralKey}`,
|
||||
},
|
||||
url: mistralHost + apiPath,
|
||||
};
|
||||
|
||||
|
||||
case 'openrouter':
|
||||
const orKey = access.oaiKey || env.OPENROUTER_API_KEY || '';
|
||||
const orHost = fixupHost(access.oaiHost || DEFAULT_OPENROUTER_HOST, apiPath);
|
||||
|
||||
@@ -67,6 +67,7 @@ export async function openaiStreamingRelayHandler(req: NextRequest): Promise<Res
|
||||
|
||||
case 'azure':
|
||||
case 'localai':
|
||||
case 'mistral':
|
||||
case 'oobabooga':
|
||||
case 'openai':
|
||||
case 'openrouter':
|
||||
@@ -153,6 +154,10 @@ function createOllamaChatCompletionStreamParser(): AIStreamParser {
|
||||
// validate chunk
|
||||
const chunk = wireOllamaChunkedOutputSchema.parse(wireJsonChunk);
|
||||
|
||||
// pass through errors from Ollama
|
||||
if ('error' in chunk)
|
||||
throw new Error(chunk.error);
|
||||
|
||||
// process output
|
||||
let text = chunk.message?.content || /*chunk.response ||*/ '';
|
||||
|
||||
@@ -255,7 +260,7 @@ function createEventStreamTransformer(vendorTextParser: AIStreamParser, inputFor
|
||||
} catch (error: any) {
|
||||
if (SERVER_DEBUG_WIRE)
|
||||
console.log(' - E: parse issue:', event.data, error?.message || error);
|
||||
controller.enqueue(textEncoder.encode(`[Stream Issue] ${dialectLabel}: ${safeErrorString(error) || 'Unknown stream parsing error'}`));
|
||||
controller.enqueue(textEncoder.encode(` **[Stream Issue] ${dialectLabel}: ${safeErrorString(error) || 'Unknown stream parsing error'}**`));
|
||||
controller.terminate();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { apiAsync } from '~/common/util/trpc.client';
|
||||
|
||||
import type { DLLM, DLLMId } from '../store-llms';
|
||||
import { findVendorForLlmOrThrow } from '../vendors/vendor.registry';
|
||||
import { findVendorForLlmOrThrow } from '../vendors/vendors.registry';
|
||||
|
||||
import type { ChatStreamFirstPacketSchema, ChatStreamInputSchema } from './server/openai/openai.streaming';
|
||||
import type { OpenAIWire } from './server/openai/openai.wiretypes';
|
||||
@@ -27,7 +27,7 @@ export async function streamChat(
|
||||
onUpdate: (update: Partial<{ text: string, typing: boolean, originLLM: string }>, done: boolean) => void,
|
||||
): Promise<void> {
|
||||
const { llm, vendor } = findVendorForLlmOrThrow(llmId);
|
||||
const access = vendor.getAccess(llm._source.setup) as ChatStreamInputSchema['access'];
|
||||
const access = vendor.getTransportAccess(llm._source.setup) as ChatStreamInputSchema['access'];
|
||||
return await vendorStreamChat(access, llm, messages, abortSignal, onUpdate);
|
||||
}
|
||||
|
||||
|
||||
+11
-6
@@ -1,18 +1,20 @@
|
||||
import type React from 'react';
|
||||
|
||||
import type { DLLM, DModelSourceId } from '../store-llms';
|
||||
import { VChatFunctionIn, VChatMessageIn, VChatMessageOrFunctionCallOut, VChatMessageOut } from '../transports/chatGenerate';
|
||||
import type { VChatFunctionIn, VChatMessageIn, VChatMessageOrFunctionCallOut, VChatMessageOut } from '../transports/chatGenerate';
|
||||
|
||||
|
||||
export type ModelVendorId = 'anthropic' | 'azure' | 'localai' | 'ollama' | 'oobabooga' | 'openai' | 'openrouter';
|
||||
export type ModelVendorId = 'anthropic' | 'azure' | 'localai' | 'mistral' | 'ollama' | 'oobabooga' | 'openai' | 'openrouter';
|
||||
|
||||
export type ModelVendorRegistryType = Record<ModelVendorId, IModelVendor>;
|
||||
|
||||
export interface IModelVendor<TSourceSetup = unknown, TLLMOptions = unknown, TAccess = unknown, TDLLM = DLLM<TSourceSetup, TLLMOptions>> {
|
||||
export interface IModelVendor<TSourceSetup = unknown, TAccess = unknown, TLLMOptions = unknown, TDLLM = DLLM<TSourceSetup, TLLMOptions>> {
|
||||
readonly id: ModelVendorId;
|
||||
readonly name: string;
|
||||
readonly rank: number;
|
||||
readonly location: 'local' | 'cloud';
|
||||
readonly instanceLimit: number;
|
||||
readonly hasFreeModels?: boolean;
|
||||
readonly hasBackendCap?: () => boolean;
|
||||
|
||||
// components
|
||||
@@ -20,10 +22,13 @@ export interface IModelVendor<TSourceSetup = unknown, TLLMOptions = unknown, TAc
|
||||
readonly SourceSetupComponent: React.ComponentType<{ sourceId: DModelSourceId }>;
|
||||
readonly LLMOptionsComponent: React.ComponentType<{ llm: TDLLM }>;
|
||||
|
||||
// functions
|
||||
readonly initializeSetup?: () => TSourceSetup;
|
||||
/// abstraction interface ///
|
||||
|
||||
getAccess(setup?: Partial<TSourceSetup>): TAccess;
|
||||
initializeSetup?(): TSourceSetup;
|
||||
|
||||
validateSetup?(setup: TSourceSetup): boolean;
|
||||
|
||||
getTransportAccess(setup?: Partial<TSourceSetup>): TAccess;
|
||||
|
||||
callChatGenerate(llm: TDLLM, messages: VChatMessageIn[], maxTokens?: number): Promise<VChatMessageOut>;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export function AnthropicSourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
|
||||
// external state
|
||||
const { source, sourceHasLLMs, access, updateSetup } =
|
||||
useSourceSetup(props.sourceId, ModelVendorAnthropic.getAccess);
|
||||
useSourceSetup(props.sourceId, ModelVendorAnthropic);
|
||||
|
||||
// derived state
|
||||
const { anthropicKey, anthropicHost, heliconeKey } = access;
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ export interface SourceSetupAnthropic {
|
||||
heliconeKey: string;
|
||||
}
|
||||
|
||||
export const ModelVendorAnthropic: IModelVendor<SourceSetupAnthropic, LLMOptionsOpenAI, AnthropicAccessSchema> = {
|
||||
export const ModelVendorAnthropic: IModelVendor<SourceSetupAnthropic, AnthropicAccessSchema, LLMOptionsOpenAI> = {
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
rank: 13,
|
||||
@@ -36,14 +36,14 @@ export const ModelVendorAnthropic: IModelVendor<SourceSetupAnthropic, LLMOptions
|
||||
LLMOptionsComponent: OpenAILLMOptions,
|
||||
|
||||
// functions
|
||||
getAccess: (partialSetup): AnthropicAccessSchema => ({
|
||||
getTransportAccess: (partialSetup): AnthropicAccessSchema => ({
|
||||
dialect: 'anthropic',
|
||||
anthropicKey: partialSetup?.anthropicKey || '',
|
||||
anthropicHost: partialSetup?.anthropicHost || null,
|
||||
heliconeKey: partialSetup?.heliconeKey || null,
|
||||
}),
|
||||
callChatGenerate(llm, messages: VChatMessageIn[], maxTokens?: number): Promise<VChatMessageOut> {
|
||||
return anthropicCallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, /*null, null,*/ maxTokens);
|
||||
return anthropicCallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, /*null, null,*/ maxTokens);
|
||||
},
|
||||
callChatGenerateWF(): Promise<VChatMessageOrFunctionCallOut> {
|
||||
throw new Error('Anthropic does not support "Functions" yet');
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ export function AzureSourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
|
||||
// external state
|
||||
const { source, sourceHasLLMs, access, updateSetup } =
|
||||
useSourceSetup(props.sourceId, ModelVendorAzure.getAccess);
|
||||
useSourceSetup(props.sourceId, ModelVendorAzure);
|
||||
|
||||
// derived state
|
||||
const { oaiKey: azureKey, oaiHost: azureEndpoint } = access;
|
||||
|
||||
+4
-4
@@ -36,7 +36,7 @@ export interface SourceSetupAzure {
|
||||
*
|
||||
* Work in progress...
|
||||
*/
|
||||
export const ModelVendorAzure: IModelVendor<SourceSetupAzure, LLMOptionsOpenAI, OpenAIAccessSchema> = {
|
||||
export const ModelVendorAzure: IModelVendor<SourceSetupAzure, OpenAIAccessSchema, LLMOptionsOpenAI> = {
|
||||
id: 'azure',
|
||||
name: 'Azure',
|
||||
rank: 14,
|
||||
@@ -50,7 +50,7 @@ export const ModelVendorAzure: IModelVendor<SourceSetupAzure, LLMOptionsOpenAI,
|
||||
LLMOptionsComponent: OpenAILLMOptions,
|
||||
|
||||
// functions
|
||||
getAccess: (partialSetup): OpenAIAccessSchema => ({
|
||||
getTransportAccess: (partialSetup): OpenAIAccessSchema => ({
|
||||
dialect: 'azure',
|
||||
oaiKey: partialSetup?.azureKey || '',
|
||||
oaiOrg: '',
|
||||
@@ -59,9 +59,9 @@ export const ModelVendorAzure: IModelVendor<SourceSetupAzure, LLMOptionsOpenAI,
|
||||
moderationCheck: false,
|
||||
}),
|
||||
callChatGenerate(llm, messages: VChatMessageIn[], maxTokens?: number): Promise<VChatMessageOut> {
|
||||
return openAICallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, null, null, maxTokens);
|
||||
return openAICallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, null, null, maxTokens);
|
||||
},
|
||||
callChatGenerateWF(llm, messages: VChatMessageIn[], functions: VChatFunctionIn[] | null, forceFunctionName: string | null, maxTokens?: number): Promise<VChatMessageOrFunctionCallOut> {
|
||||
return openAICallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, functions, forceFunctionName, maxTokens);
|
||||
return openAICallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, functions, forceFunctionName, maxTokens);
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -19,7 +19,7 @@ export function LocalAISourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
|
||||
// external state
|
||||
const { source, access, updateSetup } =
|
||||
useSourceSetup(props.sourceId, ModelVendorLocalAI.getAccess);
|
||||
useSourceSetup(props.sourceId, ModelVendorLocalAI);
|
||||
|
||||
// derived state
|
||||
const { oaiHost } = access;
|
||||
|
||||
+4
-4
@@ -14,7 +14,7 @@ export interface SourceSetupLocalAI {
|
||||
oaiHost: string; // use OpenAI-compatible non-default hosts (full origin path)
|
||||
}
|
||||
|
||||
export const ModelVendorLocalAI: IModelVendor<SourceSetupLocalAI, LLMOptionsOpenAI, OpenAIAccessSchema> = {
|
||||
export const ModelVendorLocalAI: IModelVendor<SourceSetupLocalAI, OpenAIAccessSchema, LLMOptionsOpenAI> = {
|
||||
id: 'localai',
|
||||
name: 'LocalAI',
|
||||
rank: 20,
|
||||
@@ -30,7 +30,7 @@ export const ModelVendorLocalAI: IModelVendor<SourceSetupLocalAI, LLMOptionsOpen
|
||||
initializeSetup: () => ({
|
||||
oaiHost: 'http://localhost:8080',
|
||||
}),
|
||||
getAccess: (partialSetup) => ({
|
||||
getTransportAccess: (partialSetup) => ({
|
||||
dialect: 'localai',
|
||||
oaiKey: '',
|
||||
oaiOrg: '',
|
||||
@@ -39,9 +39,9 @@ export const ModelVendorLocalAI: IModelVendor<SourceSetupLocalAI, LLMOptionsOpen
|
||||
moderationCheck: false,
|
||||
}),
|
||||
callChatGenerate(llm, messages: VChatMessageIn[], maxTokens?: number): Promise<VChatMessageOut> {
|
||||
return openAICallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, null, null, maxTokens);
|
||||
return openAICallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, null, null, maxTokens);
|
||||
},
|
||||
callChatGenerateWF(llm, messages: VChatMessageIn[], functions: VChatFunctionIn[] | null, forceFunctionName: string | null, maxTokens?: number): Promise<VChatMessageOrFunctionCallOut> {
|
||||
return openAICallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, functions, forceFunctionName, maxTokens);
|
||||
return openAICallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, functions, forceFunctionName, maxTokens);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { FormInputKey } from '~/common/components/forms/FormInputKey';
|
||||
import { InlineError } from '~/common/components/InlineError';
|
||||
import { Link } from '~/common/components/Link';
|
||||
import { SetupFormRefetchButton } from '~/common/components/forms/SetupFormRefetchButton';
|
||||
import { apiQuery } from '~/common/util/trpc.client';
|
||||
|
||||
import { DModelSourceId, useModelsStore, useSourceSetup } from '../../store-llms';
|
||||
import { modelDescriptionToDLLM } from '../openai/OpenAISourceSetup';
|
||||
|
||||
import { ModelVendorMistral } from './mistral.vendor';
|
||||
|
||||
|
||||
const MISTRAL_REG_LINK = 'https://console.mistral.ai/';
|
||||
|
||||
|
||||
export function MistralSourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
|
||||
// external state
|
||||
const { source, sourceSetupValid, sourceHasLLMs, access, updateSetup } =
|
||||
useSourceSetup(props.sourceId, ModelVendorMistral);
|
||||
|
||||
// derived state
|
||||
const { oaiKey: mistralKey } = access;
|
||||
|
||||
const needsUserKey = !ModelVendorMistral.hasBackendCap?.();
|
||||
const shallFetchSucceed = !needsUserKey || (!!mistralKey && sourceSetupValid);
|
||||
const showKeyError = !!mistralKey && !sourceSetupValid;
|
||||
|
||||
// fetch models
|
||||
const { isFetching, refetch, isError, error } = apiQuery.llmOpenAI.listModels.useQuery({ access }, {
|
||||
enabled: false,
|
||||
onSuccess: models => source && useModelsStore.getState().setLLMs(
|
||||
models.models.map(model => modelDescriptionToDLLM(model, source)),
|
||||
props.sourceId,
|
||||
),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
return <>
|
||||
|
||||
<FormInputKey
|
||||
id='mistral-key' label='Mistral Key'
|
||||
rightLabel={<>{needsUserKey
|
||||
? !mistralKey && <Link level='body-sm' href={MISTRAL_REG_LINK} target='_blank'>request Key</Link>
|
||||
: '✔️ already set in server'}
|
||||
</>}
|
||||
value={mistralKey} onChange={value => updateSetup({ oaiKey: value })}
|
||||
required={needsUserKey} isError={showKeyError}
|
||||
placeholder='...'
|
||||
/>
|
||||
|
||||
<SetupFormRefetchButton
|
||||
refetch={refetch} disabled={/*!shallFetchSucceed ||*/ isFetching} error={isError}
|
||||
/>
|
||||
|
||||
{isError && <InlineError error={error} />}
|
||||
|
||||
</>;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { backendCaps } from '~/modules/backend/state-backend';
|
||||
|
||||
import { MistralIcon } from '~/common/components/icons/MistralIcon';
|
||||
|
||||
import type { IModelVendor } from '../IModelVendor';
|
||||
import type { OpenAIAccessSchema } from '../../transports/server/openai/openai.router';
|
||||
import type { VChatMessageIn, VChatMessageOut } from '../../transports/chatGenerate';
|
||||
|
||||
import { LLMOptionsOpenAI, openAICallChatGenerate, SourceSetupOpenAI } from '../openai/openai.vendor';
|
||||
import { OpenAILLMOptions } from '../openai/OpenAILLMOptions';
|
||||
|
||||
import { MistralSourceSetup } from './MistralSourceSetup';
|
||||
|
||||
|
||||
// special symbols
|
||||
|
||||
export type SourceSetupMistral = Pick<SourceSetupOpenAI, 'oaiKey' | 'oaiHost'>;
|
||||
|
||||
|
||||
/** Implementation Notes for the Mistral vendor
|
||||
*/
|
||||
export const ModelVendorMistral: IModelVendor<SourceSetupMistral, OpenAIAccessSchema, LLMOptionsOpenAI> = {
|
||||
id: 'mistral',
|
||||
name: 'Mistral',
|
||||
rank: 15,
|
||||
location: 'cloud',
|
||||
instanceLimit: 1,
|
||||
hasBackendCap: () => backendCaps().hasLlmMistral,
|
||||
|
||||
// components
|
||||
Icon: MistralIcon,
|
||||
SourceSetupComponent: MistralSourceSetup,
|
||||
LLMOptionsComponent: OpenAILLMOptions,
|
||||
|
||||
// functions
|
||||
initializeSetup: () => ({
|
||||
oaiHost: 'https://api.mistral.ai/',
|
||||
oaiKey: '',
|
||||
}),
|
||||
validateSetup: (setup) => {
|
||||
return setup.oaiKey?.length >= 32;
|
||||
},
|
||||
getTransportAccess: (partialSetup): OpenAIAccessSchema => ({
|
||||
dialect: 'mistral',
|
||||
oaiKey: partialSetup?.oaiKey || '',
|
||||
oaiOrg: '',
|
||||
oaiHost: partialSetup?.oaiHost || '',
|
||||
heliKey: '',
|
||||
moderationCheck: false,
|
||||
}),
|
||||
callChatGenerate(llm, messages: VChatMessageIn[], maxTokens?: number): Promise<VChatMessageOut> {
|
||||
return openAICallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, null, null, maxTokens);
|
||||
},
|
||||
callChatGenerateWF() {
|
||||
throw new Error('Mistral does not support "Functions" yet');
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -22,7 +22,7 @@ export function OllamaSourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
|
||||
// external state
|
||||
const { source, access, updateSetup } =
|
||||
useSourceSetup(props.sourceId, ModelVendorOllama.getAccess);
|
||||
useSourceSetup(props.sourceId, ModelVendorOllama);
|
||||
|
||||
// derived state
|
||||
const { ollamaHost } = access;
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@ export interface SourceSetupOllama {
|
||||
}
|
||||
|
||||
|
||||
export const ModelVendorOllama: IModelVendor<SourceSetupOllama, LLMOptionsOpenAI, OllamaAccessSchema> = {
|
||||
export const ModelVendorOllama: IModelVendor<SourceSetupOllama, OllamaAccessSchema, LLMOptionsOpenAI> = {
|
||||
id: 'ollama',
|
||||
name: 'Ollama',
|
||||
rank: 22,
|
||||
@@ -32,12 +32,12 @@ export const ModelVendorOllama: IModelVendor<SourceSetupOllama, LLMOptionsOpenAI
|
||||
LLMOptionsComponent: OpenAILLMOptions,
|
||||
|
||||
// functions
|
||||
getAccess: (partialSetup): OllamaAccessSchema => ({
|
||||
getTransportAccess: (partialSetup): OllamaAccessSchema => ({
|
||||
dialect: 'ollama',
|
||||
ollamaHost: partialSetup?.ollamaHost || '',
|
||||
}),
|
||||
callChatGenerate(llm, messages: VChatMessageIn[], maxTokens?: number): Promise<VChatMessageOut> {
|
||||
return ollamaCallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, maxTokens);
|
||||
return ollamaCallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, maxTokens);
|
||||
},
|
||||
callChatGenerateWF(): Promise<VChatMessageOrFunctionCallOut> {
|
||||
throw new Error('Ollama does not support "Functions" yet');
|
||||
|
||||
@@ -18,7 +18,7 @@ export function OobaboogaSourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
|
||||
// external state
|
||||
const { source, sourceHasLLMs, access, updateSetup } =
|
||||
useSourceSetup(props.sourceId, ModelVendorOoobabooga.getAccess);
|
||||
useSourceSetup(props.sourceId, ModelVendorOoobabooga);
|
||||
|
||||
// derived state
|
||||
const { oaiHost } = access;
|
||||
|
||||
+4
-4
@@ -14,7 +14,7 @@ export interface SourceSetupOobabooga {
|
||||
oaiHost: string; // use OpenAI-compatible non-default hosts (full origin path)
|
||||
}
|
||||
|
||||
export const ModelVendorOoobabooga: IModelVendor<SourceSetupOobabooga, LLMOptionsOpenAI, OpenAIAccessSchema> = {
|
||||
export const ModelVendorOoobabooga: IModelVendor<SourceSetupOobabooga, OpenAIAccessSchema, LLMOptionsOpenAI> = {
|
||||
id: 'oobabooga',
|
||||
name: 'Oobabooga',
|
||||
rank: 25,
|
||||
@@ -30,7 +30,7 @@ export const ModelVendorOoobabooga: IModelVendor<SourceSetupOobabooga, LLMOption
|
||||
initializeSetup: (): SourceSetupOobabooga => ({
|
||||
oaiHost: 'http://127.0.0.1:5000',
|
||||
}),
|
||||
getAccess: (partialSetup): OpenAIAccessSchema => ({
|
||||
getTransportAccess: (partialSetup): OpenAIAccessSchema => ({
|
||||
dialect: 'oobabooga',
|
||||
oaiKey: '',
|
||||
oaiOrg: '',
|
||||
@@ -39,9 +39,9 @@ export const ModelVendorOoobabooga: IModelVendor<SourceSetupOobabooga, LLMOption
|
||||
moderationCheck: false,
|
||||
}),
|
||||
callChatGenerate(llm, messages: VChatMessageIn[], maxTokens?: number): Promise<VChatMessageOut> {
|
||||
return openAICallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, null, null, maxTokens);
|
||||
return openAICallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, null, null, maxTokens);
|
||||
},
|
||||
callChatGenerateWF(llm, messages: VChatMessageIn[], functions: VChatFunctionIn[] | null, forceFunctionName: string | null, maxTokens?: number): Promise<VChatMessageOrFunctionCallOut> {
|
||||
return openAICallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, functions, forceFunctionName, maxTokens);
|
||||
return openAICallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, functions, forceFunctionName, maxTokens);
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -29,7 +29,7 @@ export function OpenAISourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
|
||||
// external state
|
||||
const { source, sourceHasLLMs, access, updateSetup } =
|
||||
useSourceSetup(props.sourceId, ModelVendorOpenAI.getAccess);
|
||||
useSourceSetup(props.sourceId, ModelVendorOpenAI);
|
||||
|
||||
// derived state
|
||||
const { oaiKey, oaiOrg, oaiHost, heliKey, moderationCheck } = access;
|
||||
|
||||
+4
-4
@@ -28,7 +28,7 @@ export interface LLMOptionsOpenAI {
|
||||
llmResponseTokens: number;
|
||||
}
|
||||
|
||||
export const ModelVendorOpenAI: IModelVendor<SourceSetupOpenAI, LLMOptionsOpenAI, OpenAIAccessSchema> = {
|
||||
export const ModelVendorOpenAI: IModelVendor<SourceSetupOpenAI, OpenAIAccessSchema, LLMOptionsOpenAI> = {
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
rank: 10,
|
||||
@@ -42,7 +42,7 @@ export const ModelVendorOpenAI: IModelVendor<SourceSetupOpenAI, LLMOptionsOpenAI
|
||||
LLMOptionsComponent: OpenAILLMOptions,
|
||||
|
||||
// functions
|
||||
getAccess: (partialSetup): OpenAIAccessSchema => ({
|
||||
getTransportAccess: (partialSetup): OpenAIAccessSchema => ({
|
||||
dialect: 'openai',
|
||||
oaiKey: '',
|
||||
oaiOrg: '',
|
||||
@@ -52,11 +52,11 @@ export const ModelVendorOpenAI: IModelVendor<SourceSetupOpenAI, LLMOptionsOpenAI
|
||||
...partialSetup,
|
||||
}),
|
||||
callChatGenerate(llm, messages: VChatMessageIn[], maxTokens?: number): Promise<VChatMessageOut> {
|
||||
const access = this.getAccess(llm._source.setup);
|
||||
const access = this.getTransportAccess(llm._source.setup);
|
||||
return openAICallChatGenerate(access, llm.options, messages, null, null, maxTokens);
|
||||
},
|
||||
callChatGenerateWF(llm, messages: VChatMessageIn[], functions: VChatFunctionIn[] | null, forceFunctionName: string | null, maxTokens?: number): Promise<VChatMessageOrFunctionCallOut> {
|
||||
const access = this.getAccess(llm._source.setup);
|
||||
const access = this.getTransportAccess(llm._source.setup);
|
||||
return openAICallChatGenerate(access, llm.options, messages, functions, forceFunctionName, maxTokens);
|
||||
},
|
||||
};
|
||||
|
||||
+35
-10
@@ -1,12 +1,13 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Typography } from '@mui/joy';
|
||||
import { Button, Typography } from '@mui/joy';
|
||||
|
||||
import { FormInputKey } from '~/common/components/forms/FormInputKey';
|
||||
import { InlineError } from '~/common/components/InlineError';
|
||||
import { Link } from '~/common/components/Link';
|
||||
import { SetupFormRefetchButton } from '~/common/components/forms/SetupFormRefetchButton';
|
||||
import { apiQuery } from '~/common/util/trpc.client';
|
||||
import { getCallbackUrl } from '~/common/app.routes';
|
||||
|
||||
import { DModelSourceId, useModelsStore, useSourceSetup } from '../../store-llms';
|
||||
import { modelDescriptionToDLLM } from '../openai/OpenAISourceSetup';
|
||||
@@ -18,7 +19,7 @@ export function OpenRouterSourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
|
||||
// external state
|
||||
const { source, sourceHasLLMs, access, updateSetup } =
|
||||
useSourceSetup(props.sourceId, ModelVendorOpenRouter.getAccess);
|
||||
useSourceSetup(props.sourceId, ModelVendorOpenRouter);
|
||||
|
||||
// derived state
|
||||
const { oaiKey } = access;
|
||||
@@ -38,22 +39,30 @@ export function OpenRouterSourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
|
||||
const handleOpenRouterLogin = () => {
|
||||
// replace the current page with the OAuth page
|
||||
const callbackUrl = getCallbackUrl('openrouter');
|
||||
const oauthUrl = 'https://openrouter.ai/auth?callback_url=' + encodeURIComponent(callbackUrl);
|
||||
window.open(oauthUrl, '_self');
|
||||
// ...bye / see you soon at the callback location...
|
||||
};
|
||||
|
||||
|
||||
return <>
|
||||
|
||||
{/*<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>*/}
|
||||
{/*<OpenRouterIcon />*/}
|
||||
<Typography level='body-sm'>
|
||||
<Link href='https://openrouter.ai/keys' target='_blank'>OpenRouter</Link> is an independent, premium service
|
||||
<Link href='https://openrouter.ai/keys' target='_blank'>OpenRouter</Link> is an independent service
|
||||
granting access to <Link href='https://openrouter.ai/docs#models' target='_blank'>exclusive models</Link> such
|
||||
as GPT-4 32k, Claude, and more, typically unavailable to the public. <Link
|
||||
href='https://github.com/enricoros/big-agi/blob/main/docs/config-openrouter.md'>Configuration & documentation</Link>.
|
||||
as GPT-4 32k, Claude, and more. <Link
|
||||
href='https://github.com/enricoros/big-agi/blob/main/docs/config-openrouter.md' target='_blank'>
|
||||
Configuration & documentation</Link>.
|
||||
</Typography>
|
||||
{/*</Box>*/}
|
||||
|
||||
<FormInputKey
|
||||
id='openrouter-key' label='OpenRouter API Key'
|
||||
rightLabel={<>{needsUserKey
|
||||
? !oaiKey && <Link level='body-sm' href='https://openrouter.ai/keys' target='_blank'>create key</Link>
|
||||
? !oaiKey && <Link level='body-sm' href='https://openrouter.ai/keys' target='_blank'>your keys</Link>
|
||||
: '✔️ already set in server'
|
||||
} {oaiKey && keyValid && <Link level='body-sm' href='https://openrouter.ai/activity' target='_blank'>check usage</Link>}
|
||||
</>}
|
||||
@@ -62,7 +71,23 @@ export function OpenRouterSourceSetup(props: { sourceId: DModelSourceId }) {
|
||||
placeholder='sk-or-...'
|
||||
/>
|
||||
|
||||
<SetupFormRefetchButton refetch={refetch} disabled={!shallFetchSucceed || isFetching} error={isError} />
|
||||
<Typography level='body-sm'>
|
||||
🎁 A selection of <Link href='https://openrouter.ai/docs#models' target='_blank'>OpenRouter models</Link> are
|
||||
made available without charge. You can get an API key by using the Login button below.
|
||||
</Typography>
|
||||
|
||||
<SetupFormRefetchButton
|
||||
refetch={refetch} disabled={!shallFetchSucceed || isFetching} error={isError}
|
||||
leftButton={
|
||||
<Button
|
||||
color='neutral' variant={(needsUserKey && !keyValid) ? 'solid' : 'outlined'}
|
||||
onClick={handleOpenRouterLogin}
|
||||
endDecorator={(needsUserKey && !keyValid) ? '🎁' : undefined}
|
||||
>
|
||||
OpenRouter Login
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
{isError && <InlineError error={error} />}
|
||||
|
||||
|
||||
@@ -32,12 +32,13 @@ export interface SourceSetupOpenRouter {
|
||||
* [x] decide whether to do UI work to improve the appearance - prioritized models
|
||||
* [x] works!
|
||||
*/
|
||||
export const ModelVendorOpenRouter: IModelVendor<SourceSetupOpenRouter, LLMOptionsOpenAI, OpenAIAccessSchema> = {
|
||||
export const ModelVendorOpenRouter: IModelVendor<SourceSetupOpenRouter, OpenAIAccessSchema, LLMOptionsOpenAI> = {
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
rank: 12,
|
||||
location: 'cloud',
|
||||
instanceLimit: 1,
|
||||
hasFreeModels: true,
|
||||
hasBackendCap: () => backendCaps().hasLlmOpenRouter,
|
||||
|
||||
// components
|
||||
@@ -50,7 +51,7 @@ export const ModelVendorOpenRouter: IModelVendor<SourceSetupOpenRouter, LLMOptio
|
||||
oaiHost: 'https://openrouter.ai/api',
|
||||
oaiKey: '',
|
||||
}),
|
||||
getAccess: (partialSetup): OpenAIAccessSchema => ({
|
||||
getTransportAccess: (partialSetup): OpenAIAccessSchema => ({
|
||||
dialect: 'openrouter',
|
||||
oaiKey: partialSetup?.oaiKey || '',
|
||||
oaiOrg: '',
|
||||
@@ -59,9 +60,9 @@ export const ModelVendorOpenRouter: IModelVendor<SourceSetupOpenRouter, LLMOptio
|
||||
moderationCheck: false,
|
||||
}),
|
||||
callChatGenerate(llm, messages: VChatMessageIn[], maxTokens?: number): Promise<VChatMessageOut> {
|
||||
return openAICallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, null, null, maxTokens);
|
||||
return openAICallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, null, null, maxTokens);
|
||||
},
|
||||
callChatGenerateWF(llm, messages: VChatMessageIn[], functions: VChatFunctionIn[] | null, forceFunctionName: string | null, maxTokens?: number): Promise<VChatMessageOrFunctionCallOut> {
|
||||
return openAICallChatGenerate(this.getAccess(llm._source.setup), llm.options, messages, functions, forceFunctionName, maxTokens);
|
||||
return openAICallChatGenerate(this.getTransportAccess(llm._source.setup), llm.options, messages, functions, forceFunctionName, maxTokens);
|
||||
},
|
||||
};
|
||||
+5
-3
@@ -1,19 +1,21 @@
|
||||
import { ModelVendorAnthropic } from './anthropic/anthropic.vendor';
|
||||
import { ModelVendorAzure } from './azure/azure.vendor';
|
||||
import { ModelVendorLocalAI } from './localai/localai.vendor';
|
||||
import { ModelVendorMistral } from './mistral/mistral.vendor';
|
||||
import { ModelVendorOllama } from './ollama/ollama.vendor';
|
||||
import { ModelVendorOoobabooga } from './oobabooga/oobabooga.vendor';
|
||||
import { ModelVendorOpenAI } from './openai/openai.vendor';
|
||||
import { ModelVendorOpenRouter } from './openrouter/openrouter.vendor';
|
||||
|
||||
import type { IModelVendor, ModelVendorId, ModelVendorRegistryType } from './IModelVendor';
|
||||
import { DLLMId, DModelSource, DModelSourceId, findLLMOrThrow } from '../store-llms';
|
||||
import { IModelVendor, ModelVendorId } from './IModelVendor';
|
||||
|
||||
/** Vendor Instances Registry **/
|
||||
const MODEL_VENDOR_REGISTRY: Record<ModelVendorId, IModelVendor> = {
|
||||
/** Global: Vendor Instances Registry **/
|
||||
const MODEL_VENDOR_REGISTRY: ModelVendorRegistryType = {
|
||||
anthropic: ModelVendorAnthropic,
|
||||
azure: ModelVendorAzure,
|
||||
localai: ModelVendorLocalAI,
|
||||
mistral: ModelVendorMistral,
|
||||
ollama: ModelVendorOllama,
|
||||
oobabooga: ModelVendorOoobabooga,
|
||||
openai: ModelVendorOpenAI,
|
||||
@@ -21,6 +21,9 @@ export const env = createEnv({
|
||||
ANTHROPIC_API_KEY: z.string().optional(),
|
||||
ANTHROPIC_API_HOST: z.string().url().optional(),
|
||||
|
||||
// LLM: Mistral
|
||||
MISTRAL_API_KEY: z.string().optional(),
|
||||
|
||||
// LLM: Ollama
|
||||
OLLAMA_API_HOST: z.string().url().optional(),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user