Compare commits

..

24 Commits

Author SHA1 Message Date
Enrico Ros 6ae440d252 1.7.3: Patch release for Mistral support 2023-12-12 17:01:40 -08:00
Enrico Ros c0c724afc1 Mistral Platform: full support
Closes #273.
2023-12-12 16:39:06 -08:00
Enrico Ros a265112ce1 Mistral Platform: backend-configurable support (#273) 2023-12-12 16:39:06 -08:00
Enrico Ros 75605ed408 Dropdown: support model vendor icons 2023-12-12 16:39:06 -08:00
Enrico Ros ad38ff4157 LLMs: safer and smarter access 2023-12-12 16:39:06 -08:00
Enrico Ros 08c60e53b1 LLMs: reorder template params 2023-12-12 16:39:06 -08:00
Enrico Ros d0dcb2ac02 LLMs: getTransportAccess 2023-12-12 16:39:06 -08:00
Enrico Ros fbeb604b26 Update README.md 2023-12-12 03:42:05 -08:00
Enrico Ros c4f3b1df77 Update README.md 2023-12-12 03:40:44 -08:00
Enrico Ros 5a1f9caaac Roll rest 2023-12-12 03:16:35 -08:00
Enrico Ros 2fc70d5e95 Roll other dev deps 2023-12-12 03:12:43 -08:00
Enrico Ros 43adadef78 Roll Material/Joy/Next 2023-12-12 03:11:14 -08:00
Enrico Ros 96f6e7628b Roll Prisma 2023-12-12 03:08:10 -08:00
Enrico Ros 32ad82bcee Drag/Drop: do not remove the text from the source 2023-12-12 03:07:31 -08:00
Enrico Ros 3d72aec369 Roll pdfjs-dist 2023-12-12 02:58:06 -08:00
Enrico Ros d244ee2cca Update Docker image workflow.
Assume the vX.Y.Z is the latest (and will have the latest tag). Removing this to remove the 'stable' tag, as
latest is better.

The 'main' branch keeps the development tag.
2023-12-12 01:38:57 -08:00
Enrico Ros cc8a235ae3 Bits 2023-12-12 01:21:43 -08:00
Enrico Ros ae348812de OpenRouter: improve showing of discounted models 2023-12-12 01:14:33 -08:00
Enrico Ros 6053636f66 OpenRouter: OAuth login support 2023-12-11 22:35:40 -08:00
Enrico Ros f2e2aee672 1.7.2: Stable Patch Version 2023-12-11 21:22:31 -08:00
Enrico Ros 11cbb2bbf0 OpenRouter: update models 2023-12-11 21:21:22 -08:00
Enrico Ros 30bd19d6ce HTML Table to Markdown Table: improve reliability and ignore hidden data 2023-12-11 20:46:34 -08:00
Enrico Ros d0b5c02062 Improve how Stream errors are shown 2023-12-11 18:22:15 -08:00
Enrico Ros 771192e406 Ollama: support ollama errors via API 2023-12-11 18:19:38 -08:00
53 changed files with 1226 additions and 447 deletions
+1 -1
View File
@@ -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)
+6 -4
View File
@@ -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
View File
@@ -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
+2
View File
@@ -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 |
+485 -251
View File
File diff suppressed because it is too large Load Diff
+15 -15
View File
@@ -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"
+98
View File
@@ -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>
);
}
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);
+1 -1
View File
@@ -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';
+2 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
),
};
+3 -1
View File
@@ -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
View File
@@ -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> =>
+1 -1
View File
@@ -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>;
}
+14 -8
View File
@@ -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 />}
+40 -5
View File
@@ -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;
}
+1 -1
View File
@@ -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
+20 -1
View File
@@ -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');
}),
});
+2
View File
@@ -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,
+31 -10
View File
@@ -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 -1
View File
@@ -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();
}
};
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
},
};
+61
View File
@@ -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} />}
</>;
}
+57
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 &amp; 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 &amp; 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} />}
+5 -4
View File
@@ -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);
},
};
@@ -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,
+3
View File
@@ -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(),