Showing preview only (389K chars total). Download the full file or copy to clipboard to get everything.
Repository: nat/openplayground
Branch: main
Commit: 7f3f79035b32
Files: 74
Total size: 367.5 KB
Directory structure:
gitextract_ydrkwoag/
├── .dockerignore
├── .gitignore
├── LICENSE
├── README.md
├── app/
│ ├── .parcelrc
│ ├── .postcssrc
│ ├── .prettierrc
│ ├── package.json
│ ├── src/
│ │ ├── app.tsx
│ │ ├── components/
│ │ │ ├── inputarea.tsx
│ │ │ ├── multi-select.tsx
│ │ │ ├── navbar.tsx
│ │ │ ├── parameter-slider.tsx
│ │ │ ├── parameters-side-panel.tsx
│ │ │ └── ui/
│ │ │ ├── alert-dialog.tsx
│ │ │ ├── button.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── dialog-sheet-base.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── navigation-menu.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── right-sheet.tsx
│ │ │ ├── scroll-area.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── sheet.tsx
│ │ │ ├── slider.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toaster.tsx
│ │ │ └── tooltip.tsx
│ │ ├── error-page.tsx
│ │ ├── hooks/
│ │ │ ├── ui/
│ │ │ │ └── use-toast.tsx
│ │ │ └── use-breakpoint.ts
│ │ ├── index.css
│ │ ├── index.html
│ │ ├── index.tsx
│ │ ├── lib/
│ │ │ ├── ctrl-meta-keypress.tsx
│ │ │ ├── editor-styles.tsx
│ │ │ ├── keypress.tsx
│ │ │ ├── meta-keypress.tsx
│ │ │ └── utils.ts
│ │ └── pages/
│ │ ├── compare.tsx
│ │ ├── index.tsx
│ │ ├── playground.tsx
│ │ └── settings.tsx
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── build.py
├── dockerfile
├── pyproject.toml
└── server/
├── __init__.py
├── app.py
├── lib/
│ ├── __init__.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── inference.py
│ │ ├── provider.py
│ │ └── response_utils.py
│ ├── entities.py
│ ├── event_emitter.py
│ ├── inference/
│ │ ├── __init__.py
│ │ └── huggingface/
│ │ ├── __init__.py
│ │ ├── generator.py
│ │ ├── helpers.py
│ │ └── hf.py
│ ├── sse.py
│ ├── sseserver.py
│ └── storage.py
├── models.json
└── requirements.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
.env
================================================
FILE: .gitignore
================================================
# These files should be generated during dev/publishing
server/static/*
#mISC
.DS_Store
.vscode/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock*
package-lock.json
Cargo.lock
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# Parcel related files
dist/
.cache/
.parcel-cache/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
bin/
# build/
develop-eggs/
dist/
eggs/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
# Sphinx documentation
docs/_build/
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) [2023] [OpenPlayground]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# openplayground
An LLM playground you can run on your laptop.
https://user-images.githubusercontent.com/111631/227399583-39b23f48-9823-4571-a906-985dbe282b20.mp4
#### Features
- Use any model from [OpenAI](https://openai.com), [Anthropic](https://anthropic.com), [Cohere](https://cohere.com), [Forefront](https://forefront.ai), [HuggingFace](https://huggingface.co), [Aleph Alpha](https://aleph-alpha.com), [Replicate](https://replicate.com), [Banana](https://banana.dev) and [llama.cpp](https://github.com/ggerganov/llama.cpp).
- Full playground UI, including history, parameter tuning, keyboard shortcuts, and logprops.
- Compare models side-by-side with the same prompt, individually tune model parameters, and retry with different parameters.
- Automatically detects local models in your HuggingFace cache, and lets you install new ones.
- Works OK on your phone.
- Probably won't kill everyone.
## Try on nat.dev
Try the hosted version: [nat.dev](https://nat.dev).
## How to install and run
```sh
pip install openplayground
openplayground run
```
Alternatively, run it as a docker container:
```sh
docker run --name openplayground -p 5432:5432 -d --volume openplayground:/web/config natorg/openplayground
```
This runs a Flask process, so you can add the typical flags such as setting a different port `openplayground run -p 1235` and others.
## How to run for development
```sh
git clone https://github.com/nat/openplayground
cd app && npm install && npx parcel watch src/index.html --no-cache
cd server && pip3 install -r requirements.txt && cd .. && python3 -m server.app
```
## Docker
```sh
docker build . --tag "openplayground"
docker run --name openplayground -p 5432:5432 -d --volume openplayground:/web/config openplayground
```
First volume is optional. It's used to store API keys, models settings.
## Ideas for contributions
- Add a token counter to the playground
- Add a cost counter to the playground and the compare page
- Measure and display time to first token
- Setup automatic builds with GitHub Actions
- The default parameters for each model are configured in the `server/models.json` file. If you find better default parameters for a model, please submit a pull request!
- Someone can help us make a homebrew package, and a dockerfile
- Easier way to install open source models directly from openplayground, with `openplayground install <model>` or in the UI.
- Find and fix bugs
- ChatGPT UI, with turn-by-turn, markdown rendering, chatgpt plugin support, etc.
- We will probably need multimodal inputs and outputs at some point in 2023
### llama.cpp
## Adding models to openplayground
Models and providers have three types in openplayground:
- Searchable
- Local inference
- API
You can add models in `server/models.json` with the following schema:
#### Local inference
For models running locally on your device you can add them to openplayground like the following (a minimal example):
```json
"llama": {
"api_key" : false,
"models" : {
"llama-70b": {
"parameters": {
"temperature": {
"value": 0.5,
"range": [
0.1,
1.0
]
},
}
}
}
}
```
Keep in mind you will need to add a generation method for your model in `server/app.py`. Take a look at `local_text_generation()` as an example.
#### API Provider Inference
This is for model providers like OpenAI, cohere, forefront, and more. You can connect them easily into openplayground (a minimal example):
```json
"cohere": {
"api_key" : true,
"models" : {
"xlarge": {
"parameters": {
"temperature": {
"value": 0.5,
"range": [
0.1,
1.0
]
},
}
}
}
}
```
Keep in mind you will need to add a generation method for your model in `server/app.py`. Take a look at `openai_text_generation()` or `cohere_text_generation()` as an example.
#### Searchable models
We use this for Huggingface Remote Inference models, the search endpoint is useful for scaling to N models in the settings page.
```json
"provider_name": {
"api_key": true,
"search": {
"endpoint": "ENDPOINT_URL"
},
"parameters": {
"parameter": {
"value": 1.0,
"range": [
0.1,
1.0
]
},
}
}
```
#### Credits
Instigated by Nat Friedman. Initial implementation by [Zain Huda](https://github.com/zainhuda) as a repl.it bounty. Many features and extensive refactoring by [Alex Lourenco](https://github.com/AlexanderLourenco).
================================================
FILE: app/.parcelrc
================================================
{
"extends": "@parcel/config-default",
"compressors": {
"*.{html,css,js,svg,map}": [
"...",
"@parcel/compressor-gzip",
"@parcel/compressor-brotli"
]
}
}
================================================
FILE: app/.postcssrc
================================================
{
"plugins": {
"tailwindcss": {}
}
}
================================================
FILE: app/.prettierrc
================================================
{
"trailingComma": "es5",
"semi": false
}
================================================
FILE: app/package.json
================================================
{
"devDependencies": {
"@parcel/compressor-brotli": "^2.8.3",
"@parcel/compressor-gzip": "^2.8.3",
"@parcel/config-default": "^2.8.3",
"@types/chroma-js": "^2.4.0",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"autoprefixer": "^10.4.13",
"buffer": "^5.7.1",
"events": "^3.3.0",
"https-browserify": "^1.0.0",
"parcel": "^2.8.3",
"postcss": "^8.4.21",
"process": "^0.11.10",
"punycode": "^1.4.1",
"querystring-es3": "^0.2.1",
"stream-http": "^3.2.0",
"tailwindcss": "^3.2.4",
"url": "^0.11.0",
"util": "^0.12.5"
},
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.0.2",
"@radix-ui/react-checkbox": "^1.0.1",
"@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-label": "^2.0.0",
"@radix-ui/react-navigation-menu": "^1.1.1",
"@radix-ui/react-popover": "^1.0.4",
"@radix-ui/react-scroll-area": "^1.0.2",
"@radix-ui/react-select": "^1.2.0",
"@radix-ui/react-separator": "^1.0.1",
"@radix-ui/react-slider": "^1.1.0",
"@radix-ui/react-switch": "^1.0.1",
"@radix-ui/react-toast": "^1.1.2",
"@radix-ui/react-tooltip": "^1.0.3",
"@types/draft-js": "^0.11.10",
"chroma-js": "^2.4.2",
"class-variance-authority": "^0.4.0",
"clsx": "^1.2.1",
"draft-js": "^0.11.7",
"event-source-polyfill": "^1.0.31",
"eventsource": "^2.0.2",
"immutable": "^4.2.3",
"localforage": "^1.10.0",
"lucide-react": "^0.108.0",
"match-sorter": "^6.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-responsive": "^9.0.2",
"react-router-dom": "^6.8.1",
"react-scroll": "^1.8.9",
"react-select": "^5.7.0",
"react-tiny-popover": "^7.2.3",
"react-transition-group": "^4.4.5",
"sort-by": "^1.2.0",
"sse.js": "^0.6.1",
"tailwind-merge": "^1.9.0",
"tailwindcss-animate": "^1.0.5",
"uuidv4": "^6.2.13"
}
}
================================================
FILE: app/src/app.tsx
================================================
import React, { useEffect } from "react"
import {Playground, Compare, Settings} from "./pages"
import {SSE} from "sse.js"
import {
EditorState,
convertFromRaw,
} from "draft-js"
import {
BrowserRouter,
Route,
Routes,
} from "react-router-dom"
import { Toaster } from "./components/ui/toaster"
import { useToast } from "./hooks/ui/use-toast"
const DEFAULT_PARAMETERS_STATE = {
temperature: 1.0,
maximumLength: 200,
topP: 0.9,
topK: 0,
repetitionPenalty: 1.0,
frequencyPenalty: 0.0,
presencePenalty: 0.0,
stopSequences: [],
highlightModels: true,
showProbabilities: false
}
const DEFAULT_EDITOR_STATE = {
prompt: "",
prePrompt: "",
internalState: null
}
const DEFAULT_HISTORY_STATE = {
show: false,
entries: [],
current: null
}
const DEFAULT_CONTEXTS = {
PAGES: {
playground:{
history: DEFAULT_HISTORY_STATE,
editor: {...DEFAULT_EDITOR_STATE, previousInternalState: null },
modelsState: [],
parameters: DEFAULT_PARAMETERS_STATE
},
compare:{
history: DEFAULT_HISTORY_STATE,
editor: DEFAULT_EDITOR_STATE,
modelsState: [],
parameters: {
...DEFAULT_PARAMETERS_STATE,
selectAllModels: false,
showParametersTable: false
}
},
},
MODELS: [],
}
let SETTINGS = null;
try {
SETTINGS = JSON.parse(localStorage.getItem("openplayground_settings"));
if (!SETTINGS) throw new Error("no settings")
} catch (e) {
localStorage.clear();
SETTINGS = {};
} finally {
if (!SETTINGS.pages) {
SETTINGS.pages = DEFAULT_CONTEXTS.PAGES;
}
if (!SETTINGS.models) {
SETTINGS.models = DEFAULT_CONTEXTS.MODELS;
}
}
DEFAULT_CONTEXTS.PAGES = SETTINGS.pages;
DEFAULT_CONTEXTS.MODELS = SETTINGS.models;
export const APIContext = React.createContext({});
export const EditorContext = React.createContext({});
export const ModelsStateContext = React.createContext([]);
export const ParametersContext = React.createContext({});
export const HistoryContext = React.createContext({});
export const ModelsContext = React.createContext(DEFAULT_CONTEXTS.MODELS);
const saveSettings = () => {
let _settings = JSON.stringify(SETTINGS)
let SETTINGS_SIZE = _settings.length * 2 / 1024 / 1024;
if (SETTINGS_SIZE >= 5) {
const shouldDownloadHistory = confirm("Local Storage is full. Do you wish to download your history prior to clearing storage?");
const first_entry = SETTINGS.pages["playground"].history.entries.shift()
if (shouldDownloadHistory) {
const element = document.createElement("a")
const history_json = SETTINGS.pages["playground"].history.entries.map((entry: any) => {
const model = entry.modelsState.find(({selected}) => selected)
const text = EditorState.createWithContent(convertFromRaw(entry.editor.internalState)).getCurrentContent().getPlainText()
return {
model: model.name,
date: entry.date,
timestamp: entry.timestamp,
text: text,
parameters: entry.parameters
}
})
const file = new Blob([JSON.stringify(history_json)], {
type: "application/json",
})
element.href = URL.createObjectURL(file)
element.download = "history.json"
document.body.appendChild(element)
element.click()
}
SETTINGS.pages["playground"].history.entries = [first_entry]
SETTINGS.pages["playground"].history.current = first_entry
_settings = JSON.stringify(SETTINGS)
}
localStorage.setItem("openplayground_settings", _settings)
}
function useDebounce(func, delay) {
const timeoutRef = React.useRef(null);
useEffect(() => {
return () => {
clearTimeout(timeoutRef.current);
};
}, []);
function debouncedFunction(...args) {
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
func(...args);
}, delay);
}
return debouncedFunction;
}
const APIContextWrapper = ({children}) => {
const pendingCompletionRequest = React.useRef(false);
const textCompletionSubscribers = React.useRef([]);
const chatCompletionSubscribers = React.useRef([]);
const notificationSubscribers = React.useRef([]);
useEffect(() => {
const sse_request = new SSE("/api/notifications")
sse_request.addEventListener("notification", (event: any) => {
const parsedEvent = JSON.parse(event.data);
notificationSubscribers.current.forEach((callback) => {
callback(parsedEvent.message);
})
});
sse_request.stream();
}, [])
const Model = {
getAll: async () => (await fetch("/api/models")).json(),
getAllEnabled: async () => (await fetch("/api/models-enabled")).json(),
toggle: async (provider, model) => (await fetch(`/api/provider/${provider}/model/${encodeURIComponent(model)}/toggle-status`)).json(),
search: async (provider, query) => (await fetch(`/api/provider/${provider}/models/search?query=${query}`)).json(),
};
const Notifications = {
subscribe: (callback) => {
notificationSubscribers.current.push(callback);
},
unsubscribe: (callback) => {
notificationSubscribers.current = notificationSubscribers.current.filter((cb) => cb !== callback);
},
};
const Provider = {
setAPIKey: async (provider, apiKey) => (await fetch(`/api/provider/${provider}/api-key`, {method: "PUT", headers: {"Content-Type": "application/json"},
body: JSON.stringify({apiKey: apiKey})}
)).json(),
getAll: async () => (await fetch("/api/providers")).json(),
getAllWithModels: async () => (await fetch("/api/providers-with-key-and-models")).json(),
};
const Inference = {
subscribeTextCompletion: (callback) => {
textCompletionSubscribers.current.push(callback);
},
unsubscribeTextCompletion: (callback) => {
textCompletionSubscribers.current = textCompletionSubscribers.current.filter((cb) => cb !== callback);
},
textCompletionRequest: createTextCompletionRequest,
subscribeChatCompletion: (callback) => {
chatCompletionSubscribers.current.push(callback);
},
unsubscribeChatCompletion: (callback) => {
chatCompletionSubscribers.current = chatCompletionSubscribers.current.filter((cb) => cb !== callback);
},
chatCompletion: createChatCompletionRequest,
};
const [apiContext, _] = React.useState({
Model,
Notifications,
Provider,
Inference,
});
function createTextCompletionRequest({prompt, models}) {
const url = "/api/inference/text/stream";
const payload = {
prompt: prompt,
models: models,
};
return createCompletionRequest(url, payload, textCompletionSubscribers);
}
function createChatCompletionRequest(prompt, model) {
const url = "/api/inference/chat/stream";
const payload = {prompt, model};
return createCompletionRequest(url, payload, chatCompletionSubscribers);
}
function createCompletionRequest(url, payload, subscribers) {
pendingCompletionRequest.current = true;
let sse_request = null;
function beforeUnloadHandler() {
if (sse_request) sse_request.close();
}
window.addEventListener("beforeunload", beforeUnloadHandler);
const completionsBuffer = createCompletionsBuffer(payload.models);
let error_occured = false;
let request_complete = false;
sse_request = new SSE(url, {payload: JSON.stringify(payload)});
bindSSEEvents(sse_request, completionsBuffer, {error_occured, request_complete}, beforeUnloadHandler, subscribers);
return () => {
if (sse_request) sse_request.close();
};
}
function createCompletionsBuffer(models) {
const buffer = {};
models.forEach((model) => {
buffer[model.tag] = [];
});
return buffer;
}
function bindSSEEvents(sse_request, completionsBuffer, requestState, beforeUnloadHandler, subscribers) {
sse_request.onopen = async () => {
bulkWrite(completionsBuffer, requestState, subscribers);
};
sse_request.addEventListener("infer", (event) => {
let resp = JSON.parse(event.data);
completionsBuffer[resp.modelTag].push(resp);
});
sse_request.addEventListener("status", (event) => {
subscribers.current.forEach((callback) => callback({
event: "status",
data: JSON.parse(event.data)
}));
});
sse_request.addEventListener("error", (event) => {
requestState.error_occured = true;
try {
const message = JSON.parse(event.data);
subscribers.current.forEach((callback) => callback({
"event": "error",
"data": message.status
}));
} catch (e) {
subscribers.current.forEach((callback) => callback({
"event": "error",
"data": "Unknown error"
}));
}
close_sse(sse_request, requestState, beforeUnloadHandler, subscribers);
});
sse_request.addEventListener("abort", () => {
requestState.error_occured = true;
close_sse(sse_request, requestState, beforeUnloadHandler, subscribers);
});
sse_request.addEventListener("readystatechange", (event) => {
if (event.readyState === 2) close_sse(sse_request, requestState, beforeUnloadHandler, subscribers);
});
sse_request.stream();
}
function close_sse(sse_request, requestState, beforeUnloadHandler, subscribers) {
requestState.request_complete = true;
subscribers.current.forEach((callback) => callback({
"event": "close",
"meta": {error: requestState.error_occured},
}));
window.removeEventListener("beforeunload", beforeUnloadHandler);
}
function bulkWrite(completionsBuffer, requestState, subscribers) {
setTimeout(() => {
let newTokens = false;
let batchUpdate = {};
for (let modelTag in completionsBuffer) {
if (completionsBuffer[modelTag].length > 0) {
newTokens = true;
batchUpdate[modelTag] = completionsBuffer[modelTag].splice(0, completionsBuffer[modelTag].length);
}
}
if (newTokens) {
subscribers.current.forEach((callback) => callback({
event: "completion",
data: batchUpdate,
}));
}
if (!requestState.request_complete) bulkWrite(completionsBuffer, requestState, subscribers);
}, 20);
}
return (
<APIContext.Provider value={apiContext}>
{children}
</APIContext.Provider>
)
}
const PlaygroundContextWrapper = ({page, children}) => {
const apiContext = React.useContext(APIContext)
const [editorContext, _setEditorContext] = React.useState(DEFAULT_CONTEXTS.PAGES[page].editor);
const [parametersContext, _setParametersContext] = React.useState(DEFAULT_CONTEXTS.PAGES[page].parameters);
let [modelsStateContext, _setModelsStateContext] = React.useState(DEFAULT_CONTEXTS.PAGES[page].modelsState);
const [modelsContext, _setModelsContext] = React.useState(DEFAULT_CONTEXTS.MODELS);
const [historyContext, _setHistoryContext] = React.useState(DEFAULT_CONTEXTS.PAGES[page].history);
/* Temporary fix for models that have been purged remotely but are still cached locally */
for(const {name} of modelsStateContext) {
if (!modelsContext[name]) {
modelsStateContext = modelsStateContext.filter(({name: _name}) => _name !== name)
}
}
const editorContextRef = React.useRef(editorContext);
const historyContextRef = React.useRef(historyContext);
React.useEffect(() => {
historyContextRef.current = historyContext;
editorContextRef.current = editorContext;
}, [historyContext, editorContext]);
const {toast} = useToast()
useEffect(() => {
const notificationCallback = ({event, data, meta}) => {
console.warn("NOTIFICATION CALLBACK", event, data)
switch (event) {
case "modelAdded":
toast({
title: "New Model is available!",
description: `${data.provider}'s model ${data.model} has been added to the playground!`,
})
updateModelsData().catch(console.error)
break;
case "modelRemoved":
toast({
title: "Model removed!",
description: `${data.provider}'s model ${data.model} has been removed from the playground!`,
})
updateModelsData().catch(console.error)
break;
default:
console.log("Unknown event????", event, data);
break;
}
}
apiContext.Notifications.subscribe(notificationCallback)
return () => {
apiContext.Notifications.unsubscribe(notificationCallback);
};
}, []);
const updateModelsData = async () => {
const json_params = await apiContext.Model.getAllEnabled()
const models = {};
const PAGE_MODELS_STATE = SETTINGS.pages[page].modelsState;
for (const [model_key, modelDetails] of Object.entries(json_params)) {
const existingModelEntry = (PAGE_MODELS_STATE.find((model) => model.name === model_key));
if (!existingModelEntry) {
PAGE_MODELS_STATE.push({
name: model_key,
tag: model_key,
capabilities: modelDetails.capabilities,
provider: modelDetails.provider,
parameters: Object.entries(modelDetails.parameters).reduce((acc, [key, fields]) => {
acc[key] = fields.value;
return acc;
}, {}),
enabled: (page === "compare") ? false : true,
selected: false
})
} else {
if (!existingModelEntry.parameters) {
existingModelEntry.capabilites = modelDetails.capabilities,
existingModelEntry.provider = modelDetails.provider,
existingModelEntry.tag = model_key;
existingModelEntry.parameters = Object.entries(modelDetails.parameters).reduce((acc, [key, fields]) => {
acc[key] = fields.value;
return acc;
}, {});
}
}
models[model_key] = {
name: model_key,
capabilities: modelDetails.capabilities,
defaultParameters: modelDetails.parameters,
provider: modelDetails.provider,
}
}
const SERVER_SIDE_MODELS = Object.keys(json_params);
for (const {name} of PAGE_MODELS_STATE) {
if (!SERVER_SIDE_MODELS.includes(name)) {
PAGE_MODELS_STATE.splice(PAGE_MODELS_STATE.findIndex((model) => model.name === name), 1)
}
}
setModelsContext(models)
setModelsStateContext(PAGE_MODELS_STATE)
}
const debouncedSettingsSave = useDebounce(saveSettings, 3000);
const setEditorContext = (newEditorContext, immediate=false) => {
SETTINGS.pages[page].editor = {...SETTINGS.pages[page].editor, ...newEditorContext};
const _editor = {...SETTINGS.pages[page].editor, internalState: null };
_setEditorContext(_editor);
if (immediate) {
saveSettings()
} else {
debouncedSettingsSave()
}
}
const setParametersContext = (newParameters) => {
const parameters = { ...DEFAULT_PARAMETERS_STATE, ...newParameters}
SETTINGS.pages[page].parameters = parameters;
debouncedSettingsSave()
_setParametersContext(parameters);
}
const setModelsContext = (newModels) => {
SETTINGS.models = newModels;
debouncedSettingsSave()
_setModelsContext(newModels);
}
const setModelsStateContext = (newModelsState) => {
SETTINGS.pages[page].modelsState = newModelsState;
debouncedSettingsSave()
_setModelsStateContext(newModelsState);
}
const toggleShowHistory = (value) => {
const _newHistory = {
...SETTINGS.pages[page].history,
show: (value === undefined || value === null) ? !SETTINGS.pages[page].history.show : value
}
_setHistoryContext(_newHistory);
SETTINGS.pages[page].history = _newHistory;
debouncedSettingsSave()
}
const addHistoryEntry = (editorState) => {
//check if device is mobile by navigator
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (isMobile) return;
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
const day = String(currentDate.getDate()).padStart(2, '0');
const newEntry = {
timestamp: currentDate.getTime(),
date: `${year}-${month}-${day}`,
editor: {
...editorContextRef.current,
internalState: editorState
},
parameters: SETTINGS.pages[page].parameters,
modelsState: SETTINGS.pages[page].modelsState,
}
const _newHistory = {
...SETTINGS.pages[page].history,
entries: [newEntry, ...SETTINGS.pages[page].history.entries],
current: newEntry
}
_setHistoryContext(_newHistory);
//console.warn("Adding to history", _newHistory)
SETTINGS.pages[page].history = _newHistory;
debouncedSettingsSave()
}
const removeHistoryEntry = (entry) => {
const _newHistory = {
...SETTINGS.pages[page].history,
entries: SETTINGS.pages[page].history.entries.filter((historyEntry) => historyEntry !== entry)
}
_setHistoryContext(_newHistory);
SETTINGS.pages[page].history = _newHistory;
debouncedSettingsSave()
}
const clearHistory = () => {
const _newHistory = {
entries: [],
show: false,
current: null
}
_setHistoryContext(_newHistory);
SETTINGS.pages[page].history = _newHistory;
debouncedSettingsSave()
}
const selectHistoryItem = (entry) => {
SETTINGS.pages[page].history.current = entry;
_setEditorContext(entry.editor);
_setHistoryContext(SETTINGS.pages[page].history);
setParametersContext(entry.parameters);
setModelsStateContext(entry.modelsState);
}
React.useEffect(() => {
updateModelsData().catch(console.error)
}, [])
return (
<HistoryContext.Provider value = {{
historyContext, selectHistoryItem,
addHistoryEntry, removeHistoryEntry, clearHistory, toggleShowHistory
}}>
<EditorContext.Provider value = {{editorContext, setEditorContext}}>
<ParametersContext.Provider value = {{parametersContext, setParametersContext}}>
<ModelsContext.Provider value = {{modelsContext, setModelsContext}}>
<ModelsStateContext.Provider value = {{modelsStateContext, setModelsStateContext}}>
{children}
</ModelsStateContext.Provider>
</ModelsContext.Provider>
</ParametersContext.Provider>
</EditorContext.Provider>
</HistoryContext.Provider>
)
}
function ProviderWithRoutes() {
return (
<Routes>
<Route
path="/"
element={
<APIContextWrapper>
<PlaygroundContextWrapper key = "playground" page = "playground">
<Playground/>
<Toaster />
</PlaygroundContextWrapper>
</APIContextWrapper>
}
/>
<Route
path="/compare"
element={
<APIContextWrapper>
<PlaygroundContextWrapper key = "compare" page = "compare">
<Compare/>
<Toaster />
</PlaygroundContextWrapper>
</APIContextWrapper>
}
/>
<Route
path="/settings"
element={
<APIContextWrapper>
<Settings />
<Toaster />
</APIContextWrapper>
}
/>
</Routes>
);
}
export default function App() {
return (
<BrowserRouter>
<ProviderWithRoutes />
</BrowserRouter>
)
}
================================================
FILE: app/src/components/inputarea.tsx
================================================
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
export function InputArea() {
return (
<div className="grid h-96">
<Textarea placeholder="Write tagline for a ice cream shop." />
<Button className="inline-flex items-center px-5 py-8 text-sm font-medium text-center">
Submit
</Button>
</div>
)
}
================================================
FILE: app/src/components/multi-select.tsx
================================================
import React from "react"
import CreatableSelect from "react-select/creatable"
import { useBreakpoint } from "../hooks/use-breakpoint"
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"
interface MultiSelectProps {
maxOptions?: number
tooltipContent: React.ReactElement
defaultOptions: any,
disabled?: boolean
onValueChange: (value: any) => void
}
const MultiSelect: React.FC<MultiSelectProps> = ({
maxOptions,
tooltipContent,
defaultOptions,
disabled = false,
onValueChange,
}) => {
const { isLg } = useBreakpoint("lg")
const formattedOptions = defaultOptions.map((option: any) => {
return { label: option, value: option }
})
return (
<div>
<Tooltip delayDuration={300} skipDelayDuration={150}>
<TooltipTrigger asChild>
<div>
<span className="cursor-default flow-root inline-block align-middle mb-1">
<p className="text-sm font-normal float-left align-text-top">
Stop Sequences
</p>
</span>
<span className="cursor-default flow-root inline-block align-middle mb-3">
<p className="text-xs font-normal float-left align-text-top text-gray-400">
Enter sequence and press Tab
</p>
</span>
<CreatableSelect
isDisabled={disabled}
isMulti
placeholder=""
noOptionsMessage={({ inputValue }) => "Enter a sequence"}
formatCreateLabel={(userInput) => `Add ${userInput}`}
value={formattedOptions}
options={formattedOptions}
defaultValue={formattedOptions}
components={{
DropdownIndicator: () => null,
IndicatorSeparator: () => null,
}}
isValidNewOption={(inputValue, options) => {
if (inputValue.length < 1) return false
if (maxOptions == null) return true
if (options.length >= maxOptions) return false
return true
}}
onChange={(e) => {
console.log(e)
const values = e.map((i: any) => i.value)
if (onValueChange) onValueChange(values)
}}
/>
</div>
</TooltipTrigger>
<TooltipContent side={isLg ? "left" : "bottom"}>
{tooltipContent}
</TooltipContent>
</Tooltip>
</div>
)
}
export default MultiSelect
================================================
FILE: app/src/components/navbar.tsx
================================================
import React from "react"
import { Link } from "react-router-dom"
export default function NavBar({ tab, children }: any) {
const menu = ["playground", "compare", "settings"].map((menuName, index) => (
<div key = {menuName} className="align-middle mt-1 flex items-center">
<Link
to={`/${index > 0 ? menuName: ''}`}
className={
tab === menuName
? "cursor-default"
: "cursor-pointer"
}>
<p
className={
tab === menuName
? "text-xl font-semibold"
: "text-xl font-medium text-gray-500 hover:text-gray-900"
}
>
{menuName.charAt(0).toUpperCase() + menuName.slice(1)}
</p>
</Link>
</div>
))
return (
<div className="flex flex-col font-display mb-3 border">
<div className="flex inline-block mx-5 my-4 gap-x-4 flex-wrap">
{menu}
<div className ="flex-1" />
<div
className = "ml-4 mt-1 cursor-pointer flex justify-end items-center self-flex-end"
onClick={() => {
window.open("https://discord.gg/J8sFfUK2N2", "_blank")
}}
>
<img
className = "h-[20px]"
src= "https://assets-global.website-files.com/6257adef93867e50d84d30e2/636e0a6a49cf127bf92de1e2_icon_clyde_blurple_RGB.png"
/>
</div>
<div
className = "ml-4 mt-1 cursor-pointer flex justify-end items-center self-flex-end"
onClick={() => {
window.open("https://github.com/nat/openplayground", "_blank")
}}
>
<img
className = "h-[35px]"
src= "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
/>
</div>
{children}
</div>
</div>
)
}
================================================
FILE: app/src/components/parameter-slider.tsx
================================================
import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip"
import React, {
HTMLInputTypeAttribute,
ReactElement,
useEffect,
useState,
useEffect
} from "react"
import { Input } from "./ui/input"
import { Slider } from "./ui/slider"
import { useBreakpoint } from "../hooks/use-breakpoint"
import { AlertTriangle } from "lucide-react"
interface ParameterSliderProps {
title: string
type: HTMLInputTypeAttribute | undefined
min: number
max: number
step: number
defaultValue: number
disabled: boolean
normalizeInputData: (value: string) => any
normalizeSliderData?: Function
onChangeValue: ((value: number) => void) | undefined
tooltipContent: ReactElement
}
const ParamaterSlider: React.FC<ParameterSliderProps> = ({
title,
defaultValue,
disabled,
min,
max,
step,
normalizeInputData,
normalizeSliderData,
type = "number",
tooltipContent,
onChangeValue,
}) => {
// TODO: deprecate this
const [value, setValue] = useState(defaultValue)
const { isLg } = useBreakpoint("lg")
useEffect(() => {
setValue(defaultValue)
}, [defaultValue])
// prevents slider from overflowing if value is > max val (can happen if user types in big number in input before unfocusing)
const sliderValue = Math.max(
Number(min),
Math.min(Number(max), Number(value))
)
return (
<div className="">
<Tooltip delayDuration={300} skipDelayDuration={150}>
<TooltipTrigger asChild>
<div>
<span className="cursor-default flow-root inline-block align-middle mb-3">
<p className={`text-sm font-normal float-left align-text-top ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}>
{(disabled) && (
<AlertTriangle className="w-4 h-4 text-gray-500 inline mr-1 mb-0.5" />
)}
{title}
</p>
<Input
inputMode="decimal"
className="float-right h-6 p-0 w-14"
type={type}
value={[value.toString()]}
step={step}
autoFocus={false}
onChange={(e) => {
if (normalizeInputData) {
let normalized = normalizeInputData(e.target.value)
setValue(normalized)
if (!isNaN(normalized) && onChangeValue) {
onChangeValue(normalized)
}
}
}}
onBlur={(e) => {
let normalized = Math.max(
Number(min),
Math.min(Number(max), Number(value))
)
if (isNaN(normalized)) {
normalized = defaultValue
}
setValue(normalized)
if (onChangeValue) {
onChangeValue(normalized)
}
}}
disabled={disabled}
min={min}
max={max}
/>
</span>
<Slider
disabled={disabled}
defaultValue={[value]}
value={[sliderValue]}
min={min}
max={max}
step={step}
onValueChange={(e) => {
setValue(e[0])
}}
onValueCommit={(e) => {
if (onChangeValue) onChangeValue(e[0])
}}
/>
</div>
</TooltipTrigger>
<TooltipContent side={isLg ? "left" : "bottom"}>
{tooltipContent}
</TooltipContent>
</Tooltip>
</div>
)
}
export default ParamaterSlider
================================================
FILE: app/src/components/parameters-side-panel.tsx
================================================
import React, { useContext } from "react"
import { Checkbox } from "./ui/checkbox"
import { useBreakpoint } from "../hooks/use-breakpoint"
import ParameterSlider from "./parameter-slider"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "./ui/select"
import MultiSelect from "./multi-select"
import { uuid } from "uuidv4"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "./ui/tooltip"
import { ParametersContext, ModelsContext, ModelsStateContext } from "../app"
import { BarChart2, Copy, Trash2, Filter } from "lucide-react"
import {handleSelectModel} from "../lib/utils"
const modelProviders = {
forefront: "Forefront",
"huggingface-local": "Hugging Face (Local)",
huggingface: "Hugging Face",
"aleph-alpha": "Aleph Alpha",
anthropic: "Anthropic",
cohere: "co:here",
openai: "OpenAI",
}
const ParametersSidePanel = ({ showModelDropdown, showModelList }) => {
const { isLg } = useBreakpoint("lg")
const { parametersContext, setParametersContext } = useContext(ParametersContext)
const { modelsContext, setModelsContext } = useContext(ModelsContext)
const { modelsStateContext, setModelsStateContext } = useContext(ModelsStateContext)
const [modelSearchValue, setModelSearchValue] = React.useState<string>("")
const number_of_models_selected = modelsStateContext.filter(
(modelState) => modelState.selected
).length
const number_of_models_enabled = modelsStateContext.filter(
(modelState) => modelState.enabled
).length
const models_shared_keys = Object.keys(modelsContext).length === 0 ? {} : modelsStateContext
.filter(
(modelState) =>
modelState.enabled &&
(number_of_models_selected >= 1 ? modelState.selected : true)
)
.map((modelState) => (modelsContext[modelState.name].defaultParameters))
.flatMap((parameter) =>
Object.entries(parameter).map(([key, parameter]) => ({
key,
range: parameter["range"],
}))
)
.reduce((acc, { key, range }) => {
acc[key] = acc[key] || { range: [] }
acc[key].range = [...new Set([...acc[key].range, ...range])]
return acc
}, {})
const generate_parameters_sliders = () => {
return [
{
title: "Maximum Length",
name: "maximumLength",
type: "number",
step: 1,
tooltipContent: (
<p>
Maximum number of tokens to generate. <br /> Responses are not
guaranted to fill up <br /> to the maximum desired length. <br />
</p>
),
normalizeFn: (value) => parseInt(value),
},
{
title: "Temperature",
name: "temperature",
type: "number",
step: 0.01,
tooltipContent: (
<p>
A non-negative float that tunes the degree <br /> of randomness in
generation. Lower <br />
temperatures mean less random generations.
<br />
</p>
),
normalizeFn: (value) => parseFloat(value),
},
{
title: "Top P",
name: "topP",
type: "number",
step: 0.01,
tooltipContent: (
<p>
If set to float less than 1, only the smallest <br /> set of most
probable tokens with probabilities <br /> that add up to top_p or
higher are kept for
<br /> generation. <br />
</p>
),
normalizeFn: (value) => parseFloat(value),
},
{
title: "Top K",
name: "topK",
type: "number",
step: 1,
tooltipContent: (
<p>
Can be used to reduce repetitiveness of <br />
generated tokens. The higher the value,
<br /> the stronger a penalty is applied to
<br />
previously present tokens, proportional
<br /> to how many times they have already
<br /> appeared in the prompt or prior generation. <br />
</p>
),
normalizeFn: (value) => parseInt(value),
},
{
title: "Frequency Penalty",
name: "frequencyPenalty",
type: "number",
step: 0.01,
tooltipContent: (
<p>
Can be used to reduce repetitiveness of <br />
generated tokens. The higher the value,
<br /> the stronger a penalty is applied to
<br />
previously present tokens, proportional
<br /> to how many times they have already
<br /> appeared in the prompt or prior generation.
</p>
),
normalizeFn: (value) => parseFloat(value),
},
{
title: "Presence Penalty",
name: "presencePenalty",
type: "number",
step: 0.01,
tooltipContent: (
<p>
Can be used to reduce repetitiveness of <br />
generated tokens. Similar to Frequency Penalty,
<br /> except that this penalty is applied equally <br /> to all
tokens that have already appeared,
<br />
regardless of their <br /> exact frequencies. <br />
</p>
),
normalizeFn: (value) => parseFloat(value),
},
{
title: "Repetition Penalty",
name: "repetitionPenalty",
type: "number",
step: 0.01,
tooltipContent: (
<p>
Akin to presence penalty. The repetition penalty is meant <br /> to
avoid sentences that repeat themselves without <br /> anything
really interesting.{" "}
</p>
),
normalizeFn: (value) => parseFloat(value),
},
]
.filter((parameter) => parameter.name in models_shared_keys)
.map((parameter) => ({
...parameter,
value: parametersContext[parameter.name],
min: models_shared_keys[parameter.name].range[0],
max: models_shared_keys[parameter.name].range[1],
disabled:
number_of_models_enabled === 0 ||
models_shared_keys[parameter.name].range.length > 2,
}))
.map((parameter) => {
return (
<ParameterSlider
key={parameter.name}
title={parameter.title}
type={parameter.type}
defaultValue={parameter.value}
disabled={parameter.disabled}
onChangeValue={(value: number) => {
setModelsStateContext(
modelsStateContext.map((modelState: any) => {
if (
modelState.parameters[parameter.name] &&
(number_of_models_selected === 0 || modelState.selected)
) {
modelState.parameters[parameter.name] = value
}
return modelState
})
)
setParametersContext({
...parametersContext,
[parameter.name]: value,
})
}}
min={parameter.min}
max={parameter.max}
step={parameter.step}
normalizeInputData={parameter.normalizeFn}
tooltipContent={
<>
{parameter.tooltipContent}
{number_of_models_enabled === 0 ? (
<p>
<b>Disabled:</b> no models have been enabled.
</p>
) : parameter.disabled ? (
<p>
<b>Disabled:</b> the range of values for this parameter
<br /> <b>is not</b> uniform across all models.
<br />
<b>Tip:</b> to edit similar models, tap the models on
<br /> the list or select them by clicking their name
<br /> above their respective editor.
</p>
) : null}
</>
}
/>
)
})
}
const generate_card = (modelState: any) => {
return (
<div
key={`selected_${modelState.tag}`}
className={`relative select-none my-2 flex justify-center items-center rounded-md border border-slate-200 font-mono text-sm dark:border-slate-700 overflow-hidden ${
modelState.selected ? "bg-slate-200 dark:bg-slate-200" : ""
} ${
modelState.enabled
? "cursor-pointer hover:bg-slate-200 dark:hover:bg-slate-200"
: ""
}`}
>
<div
className={`pl-4 py-3 flex-1 overflow-hidden ${
!modelState.enabled ? "text-zinc-400" : ""
}`}
onClick={(event) => {
if (modelState.enabled)
handleSelectModel(
modelState,
modelsStateContext,
setModelsStateContext,
parametersContext,
setParametersContext,
event.ctrlKey || event.metaKey
)
}}
>
{modelState.name.split(":")[1]}
<br />
<span style={{ fontSize: "12px" }}>
Provider: <i>{modelState.provider}</i>
</span>
<br />
</div>
<Copy
size={10}
className="absolute top-2 right-2"
onClick={() => {
const index_of_model = modelsStateContext.findIndex(
(m: any) => m.name === modelState.name
)
const name_fragments = modelState.name.split(":")
setModelsStateContext([
...modelsStateContext.slice(0, index_of_model + 1),
{
...modelState,
is_clone: true,
tag: `${name_fragments[0]}:${name_fragments[1]}:${uuid()}`,
},
...modelsStateContext.slice(index_of_model + 1),
])
}}
/>
<Checkbox
className="mr-6"
key={modelState.tag}
checked={modelState.enabled}
onCheckedChange={(val: boolean) => {
setModelsStateContext(
modelsStateContext.map((m: any) => {
if (m.tag === modelState.tag) {
return {
...m,
enabled: val,
selected: false
}
}
return m
})
)
}}
/>
{modelState.is_clone ? (
<Trash2
size={10}
className="absolute bottom-2 right-2"
onClick={() => {
setModelsStateContext(
modelsStateContext.filter((m: any) => m.tag !== modelState.tag)
)
}}
/>
) : null}
</div>
)
}
const generate_list = () => {
if (!showModelList) return null
return (
<>
<div>
<div className="my-2 flex cursor-default flex mb-1">
<p className="flex-1 text-sm font-normal float-left align-text-top">
Enable All
</p>
<Checkbox
checked={parametersContext.selectAllModels}
onCheckedChange={(val: boolean) => {
setModelsStateContext(
modelsStateContext.map((modelState: any) => {
modelState.enabled = val
modelState.selected = false
return modelState
})
)
setParametersContext({
...parametersContext,
selectAllModels: val,
})
}}
className="float-right"
/>
</div>
</div>
<div className="my-2 flex flex-row border-slate-300 border p-2 rounded">
<div className="flex items-center">
<Filter size={18} />
</div>
<div className="ml-2 flex-1 mr-2">
<input
className="outline-0 w-[100%]"
value={modelSearchValue}
onChange={(event) => {
setModelSearchValue(event.target.value)
}}
placeholder="Model Name"
/>
</div>
</div>
<div>
<ul>
{modelsStateContext
.filter((modelState: any) =>
!modelState.tag ? false :
modelSearchValue !== ""
? modelState.name
.toLowerCase()
.indexOf(modelSearchValue.toLowerCase()) !== -1
: true
)
.map(generate_card)}
</ul>
</div>
</>
)
}
const generate_header = () => {
if (!showModelDropdown)
return (
<div className="flex mb-2">
<span className="cursor-default flex-1 flow-root inline-block align-middle">
<p className="text-sm font-medium float-left align-text-top">
Parameters
</p>
</span>
<Tooltip delayDuration={300} skipDelayDuration={150}>
<TooltipTrigger asChild>
<div
onClick={() => {
setParametersContext({
...parametersContext,
showParametersTable: !parametersContext.showParametersTable,
})
}}
className={`mx-1 cursor-pointer flex justify-center items-center w-[24px] h-[24px] rounded-full border-[1px] border-slate-200 select-none ${
parametersContext.showParametersTable
? "text-white bg-slate-700"
: "hover:text-white hover:bg-slate-700 text-slate-600 bg-white"
}`}
>
<BarChart2 size={18} />
</div>
</TooltipTrigger>
<TooltipContent side={"bottom"}>
<p>Show Parameters for all models</p>
</TooltipContent>
</Tooltip>
</div>
)
const selectedModel = modelsStateContext.find((modelState) => modelState.selected)
return (
<div className="">
<div className="mb-2">
<span className="flow-root inline-block align-middle">
<p className="text-sm font-medium float-left align-text-top">
Model
</p>
</span>
<Select
value={selectedModel?.name || null}
onValueChange={(value) => {
setModelsStateContext(
modelsStateContext.map((model) => ({...model, selected: (model.name === value) ? true : false}))
)
const modelParameters = modelsStateContext.find((model) => model.name === value).parameters
setParametersContext({
temperature: modelParameters.temperature || parametersContext.temperature,
maximumLength: modelParameters.maximumLength || parametersContext.maximumLength,
topP: modelParameters.topP || parametersContext.topP,
topK: modelParameters.topK || parametersContext.topK,
frequencyPenalty: modelParameters.frequencyPenalty || parametersContext.frequencyPenalty,
presencePenalty: modelParameters.presencePenalty || parametersContext.presencePenalty,
repetitionPenalty: modelParameters.repetitionPenalty || parametersContext.repetitionPenalty,
stopSequences: modelParameters.stopSequences || parametersContext.stopSequences
})
}}
>
<SelectTrigger
className="w-full"
onKeyDown={(e) => {
if (e.code === "Enter" && e.metaKey) {
e.preventDefault()
}
}}
>
<SelectValue placeholder="Select a Model" />
</SelectTrigger>
<SelectContent
onKeyDown={(e) => {
if (e.code === "Enter" && e.metaKey) {
e.preventDefault()
}
}}
>
{Object.entries(modelProviders).map(([provider, prettyName]) => (
<SelectGroup key={provider}>
{Object.entries(modelsContext)
.filter(([key]) => key.split(":")[0] === provider)
.map(([model_key, _], index) => {
if (modelsContext[model_key]) {
return (
<div key={model_key}>
<SelectLabel hidden={index != 0}>
{prettyName}
</SelectLabel>
<SelectItem
value={model_key}
onKeyDown={(e) => {
if (e.code === "Enter" && e.metaKey) {
e.preventDefault()
}
}}
>
{model_key.split(":")[1]}
</SelectItem>
</div>
)
}
})}
</SelectGroup>
))}
</SelectContent>
</Select>
</div>
</div>
)
}
const generate_show_probabilities = () => {
const selectedModel = modelsStateContext.find((modelState) => modelState.selected)
if (!selectedModel || !selectedModel.capabilities || !selectedModel.capabilities.includes("logprobs"))
return null
return (
<Tooltip delayDuration={300} skipDelayDuration={150}>
<TooltipTrigger asChild>
<div className="cursor-default flex justify-between align-middle inline-block align-middle mb-1">
<p className="text-sm font-normal float-left align-text-top">
Show Probabilities
</p>
<Checkbox
name="show-probabilities"
className="float-right self-center"
checked={parametersContext.showProbabilities}
onCheckedChange={(val: boolean) => {
setParametersContext({
...parametersContext,
showProbabilities: val,
})
}}
/>
</div>
</TooltipTrigger>
<TooltipContent side={isLg ? "left" : "bottom"}>
<p>
When enabled hover over generated words <br /> to see how likely a
token was to be generated,
<br /> if the model supports it.
</p>
</TooltipContent>
</Tooltip>
)
}
return (
<div className="flex flex-col max-h-[100%] pt-4 sm:pt-4 md:pt-[0px] lg:pt-[0px]">
<div className="mb-2">
{generate_header()}
</div>
<div className="flex flex-col gap-y-3">
{generate_parameters_sliders()}
<MultiSelect
onValueChange={(value: any) => {
setModelsStateContext(
modelsStateContext.map((modelState: any) => {
if (
modelState.parameters.stopSequences &&
(number_of_models_selected === 0 || modelState.selected)
)
modelState.parameters.stopSequences = value
return modelState
})
)
setParametersContext({
...parametersContext,
["stopSequences"]: value,
})
}}
defaultOptions={parametersContext.stopSequences}
tooltipContent={
<>
<p>
Up to four sequences where the API will stop <br /> generating
further tokens. The returned text <br />
will not contain the stop sequence.
</p>
</>
}
/>
{generate_show_probabilities()}
<Tooltip delayDuration={300} skipDelayDuration={150}>
<TooltipTrigger asChild>
<div className="cursor-default flex justify-between align-middle inline-block align-middle mb-1">
<p className="text-sm font-normal float-left align-text-top">
Highlight Models
</p>
<Checkbox
name="highlight-models"
className="float-right self-center"
checked={parametersContext.highlightModels}
onCheckedChange={(val: boolean) => {
setParametersContext({
...parametersContext,
highlightModels: val,
})
}}
/>
</div>
</TooltipTrigger>
<TooltipContent side={isLg ? "left" : "bottom"}>
<p>Disable model specific text highlights</p>
</TooltipContent>
</Tooltip>
</div>
{generate_list()}
</div>
)
}
export default ParametersSidePanel
================================================
FILE: app/src/components/ui/alert-dialog.tsx
================================================
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "../../lib/utils"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = ({
className,
children,
...props
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
<AlertDialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
{children}
</div>
</AlertDialogPrimitive.Portal>
)
AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed z-50 grid w-full max-w-lg scale-100 gap-4 bg-white p-6 opacity-100 animate-in fade-in-90 slide-in-from-bottom-10 sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0 md:w-full",
"dark:bg-slate-900 font-display",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold text-slate-900",
"dark:text-slate-50",
className
)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn(className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-slate-900 py-2 px-4 text-sm font-semibold text-white transition-colors hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
"mt-2 inline-flex h-10 items-center justify-center rounded-md border border-slate-200 bg-transparent py-2 px-4 text-sm font-semibold text-slate-900 transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-700 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}
================================================
FILE: app/src/components/ui/button.tsx
================================================
import * as React from "react"
import { VariantProps, cva } from "class-variance-authority"
import { cn } from "../../lib/utils"
const buttonVariants = cva(
"active:scale-95 inline-flex items-center justify-center rounded text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800",
{
variants: {
variant: {
default:
"bg-slate-900 text-white hover:bg-slate-700 dark:bg-slate-50 dark:text-slate-900",
destructive:
"bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600",
outline:
"bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-100",
subtle:
"bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-100",
ghost:
"bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent",
link: "bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent",
},
size: {
default: "h-8 px-3",
sm: "h-9 px-3 rounded",
lg: "h-11 px-8 rounded",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
================================================
FILE: app/src/components/ui/checkbox.tsx
================================================
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "../../lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-slate-300 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }
================================================
FILE: app/src/components/ui/dialog-sheet-base.tsx
================================================
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "../../lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = ({
className,
children,
...props
}: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
{children}
</div>
</DialogPrimitive.Portal>
)
DialogPortal.displayName = DialogPrimitive.Portal.displayName
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<DialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in",
className
)}
{...props}
ref={ref}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed z-50 grid w-full scale-100 gap-4 bg-white p-6 opacity-100 animate-in fade-in-90 slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0",
"dark:bg-slate-900",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold text-slate-900",
"dark:text-slate-50",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-slate-500", "dark:text-slate-400", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
================================================
FILE: app/src/components/ui/dialog.tsx
================================================
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "../../lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = ({
className,
children,
...props
}: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
{children}
</div>
</DialogPrimitive.Portal>
)
DialogPortal.displayName = DialogPrimitive.Portal.displayName
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<DialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in",
className
)}
{...props}
ref={ref}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"font-display fixed z-50 grid w-full scale-100 gap-4 bg-white p-6 opacity-100 animate-in fade-in-90 slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0",
"dark:bg-slate-900",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold text-slate-900",
"dark:text-slate-50",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-slate-500", "dark:text-slate-400", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
================================================
FILE: app/src/components/ui/input.tsx
================================================
import * as React from "react"
import { cn } from "../../lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }, ref) => {
return (
<input
className={cn(
"h-5 border border-slate-50 text-right bg-transparent py-2 text-base placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
)}
ref={ref}
{...props}
lang="en-150"
/>
)
}
)
Input.displayName = "Input"
export { Input }
================================================
FILE: app/src/components/ui/label.tsx
================================================
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cn } from "../../lib/utils"
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }
================================================
FILE: app/src/components/ui/navigation-menu.tsx
================================================
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"
import { cn } from "../../lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:bg-slate-100 disabled:opacity-50 dark:focus:bg-slate-800 disabled:pointer-events-none bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-slate-50 dark:data-[state=open]:bg-slate-800 h-10 py-2 px-4 group"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"absolute top-0 left-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=to-start]:slide-out-to-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=from-end]:slide-in-from-right-52 md:w-auto",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border border-slate-200 bg-white shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:zoom-in-90 data-[state=closed]:zoom-out-95 dark:border-slate-700 dark:bg-slate-800 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=visible]:fade-in data-[state=hidden]:fade-out",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-slate-200 shadow-md dark:bg-slate-800" />
</NavigationMenuPrimitive.Indicator>
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}
================================================
FILE: app/src/components/ui/popover.tsx
================================================
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "../../lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-sm border border-slate-100 bg-white shadow-lg outline-none animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2 data-[side=right]:slide-in-from-left-2 data-[side=left]:slide-in-from-right-2 dark:border-slate-800 dark:bg-slate-800",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent }
================================================
FILE: app/src/components/ui/right-sheet.tsx
================================================
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "../../lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = ({
className,
children,
...props
}: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex justify-end">{children}</div>
</DialogPrimitive.Portal>
)
DialogPortal.displayName = DialogPrimitive.Portal.displayName
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<DialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in",
className
)}
{...props}
ref={ref}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"font-display fixed z-50 grid w-full scale-100 gap-4 bg-white p-6 opacity-100 sm:max-w-lg",
"fixed z-50 scale-100 gap-4 bg-white p-6 opacity-100 dark:bg-slate-900 animate-in slide-in-from-right h-full duration-300",
"dark:bg-slate-900",
className
)}
{...props}
>
{children}
{props.hideClose ? null : (
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
<X className="h-5 w-5" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>)
}
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold text-slate-900",
"dark:text-slate-50",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-slate-500", "dark:text-slate-400", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog as Sheet,
DialogTrigger as SheetTrigger,
DialogContent as SheetContent,
DialogHeader as SheetHeader,
DialogFooter as SheetFooter,
DialogTitle as SheetTitle,
DialogDescription as SheetDesription,
}
================================================
FILE: app/src/components/ui/scroll-area.tsx
================================================
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "../../lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-slate-300 dark:bg-slate-700" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }
================================================
FILE: app/src/components/ui/select.tsx
================================================
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown } from "lucide-react"
import { cn } from "../../lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-sm border border-slate-400 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"font-display relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white text-slate-700 shadow-md animate-in fade-in-80 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400",
className
)}
{...props}
>
<SelectPrimitive.Viewport className="p-1">
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn(
"py-1.5 pr-2 pl-8 text-sm font-semibold text-slate-900 dark:text-slate-300",
className
)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-slate-100 dark:bg-slate-700", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
}
================================================
FILE: app/src/components/ui/separator.tsx
================================================
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "../../lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"bg-slate-200 dark:bg-slate-700",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }
================================================
FILE: app/src/components/ui/sheet.tsx
================================================
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cva, VariantProps } from "class-variance-authority"
import { cn } from "../../lib/utils"
const dialogVariants = cva(
"fixed z-50 scale-100 gap-4 bg-white p-6 opacity-100 dark:bg-slate-900",
{
variants: {
type: {
modal:
"grid w-full animate-in fade-in-90 slide-in-from-bottom-10 sm:max-w-lg sm:zoom-in-90 sm:slide-in-from-bottom-0",
"drawer-top": "animate-in slide-in-from-top w-full duration-300",
"drawer-bottom": "animate-in slide-in-from-bottom w-full duration-300",
"drawer-left": "animate-in slide-in-from-left h-full duration-300",
"drawer-right": "animate-in slide-in-from-right h-full duration-300",
},
},
defaultVariants: {
type: "modal",
},
}
)
const portalVariants = cva("fixed inset-0 z-50 flex", {
variants: {
type: {
modal: "justify-center items-start sm:items-center",
"drawer-top": "items-start",
"drawer-bottom": "items-end",
"drawer-left": "justify-start",
"drawer-right": "justify-end",
},
},
defaultVariants: { type: "modal" },
})
interface DialogPortalProps
extends DialogPrimitive.DialogPortalProps,
VariantProps<typeof portalVariants> {}
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = ({
type,
className,
children,
...props
}: DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
<div className={portalVariants({ type })}>{children}</div>
</DialogPrimitive.Portal>
)
DialogPortal.displayName = DialogPrimitive.Portal.displayName
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
<DialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in",
className
)}
{...props}
ref={ref}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
export interface DialogContentProps
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
VariantProps<typeof dialogVariants> {}
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
DialogContentProps
>(({ className, type, children, ...props }, ref) => (
<DialogPortal type={type}>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(dialogVariants({ type }), className)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute top-4 right-4 opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold text-slate-900",
"dark:text-slate-50",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-slate-500", "dark:text-slate-400", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
================================================
FILE: app/src/components/ui/slider.tsx
================================================
"use client"
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { cn } from "../../lib/utils"
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => {
const [isDragging, setIsDragging] = React.useState(false);
const { onValueChange, onValueCommit, ...rest } = props;
const _onValueChange = (value) => {
//remove classes ease-in duration-500
if (thumbRef.current) thumbRef.current.parentElement.classList.remove("ease-in", "duration-500")
if (!isDragging) setIsDragging(true);
if (onValueChange) onValueChange(value)
}
const _onValueCommit = (value) => {
setIsDragging(false);
if(onValueCommit) onValueCommit(value)
}
const thumbRef = React.useRef(null);
React.useEffect(() => {
setTimeout(() => {
//prevent animation on initial render
thumbRef.current.parentElement.classList.add("ease-in", "duration-500");
}, 0)
}, []);
return (
<SliderPrimitive.Root
onValueChange={_onValueChange}
onValueCommit={_onValueCommit}
ref={ref}
className={cn(
"relative flex w-full touch-none select-none items-center",
className
)}
{...rest}
>
<SliderPrimitive.Track className="relative h-1 w-full grow overflow-hidden rounded-full bg-slate-200 dark:bg-slate-800">
<SliderPrimitive.Range className={`absolute h-full bg-slate-600 dark:bg-slate-800 ${isDragging ? "" : "ease-in duration-500" }`} />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb ref={thumbRef}
className={`block h-4 w-4 rounded-full border-2 border-slate-800 bg-white transition-colors focus:outline-none focus:ring-1 focus:ring-slate-400 disabled:pointer-events-none disabled:bg-slate-200 disabled:opacity-50 dark:border-slate-100 dark:bg-slate-400 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 ${isDragging ? "cursor-grabbing" : "cursor-grab ease-in duration-500" } disabled:cursor-default`} />
</SliderPrimitive.Root>
)})
Slider.displayName = SliderPrimitive.Root.displayName
export { Slider }
================================================
FILE: app/src/components/ui/switch.tsx
================================================
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "../../lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=unchecked]:bg-slate-200 data-[state=checked]:bg-slate-900 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=unchecked]:bg-slate-700 dark:data-[state=checked]:bg-slate-400",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=unchecked]:translate-x-0 data-[state=checked]:translate-x-5"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }
================================================
FILE: app/src/components/ui/textarea.tsx
================================================
import * as React from "react"
import { cn } from "../../lib/utils"
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"resize-none rounded-md border border-slate-300 bg-transparent py-2 px-3 text-base placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
export { Textarea }
================================================
FILE: app/src/components/ui/toast.tsx
================================================
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { VariantProps, cva } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "../../lib/utils"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:bottom-0 sm:right-0 sm:flex-col md:max-w-[420px]",
className
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"data-[swipe=move]:transition-none grow-1 group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full mt-4 data-[state=closed]:slide-out-to-right-full dark:border-slate-700 last:mt-0 sm:last:mt-4",
{
variants: {
variant: {
default:
"bg-white border-slate-200 dark:bg-slate-800 dark:border-slate-700",
destructive:
"group destructive bg-red-600 text-white border-red-600 dark:border-red-600",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border border-slate-200 bg-transparent px-3 text-sm font-medium transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-red-100 group-[.destructive]:hover:border-slate-50 group-[.destructive]:hover:bg-red-100 group-[.destructive]:hover:text-red-600 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-700 dark:hover:text-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800",
className
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute top-2 right-2 rounded-md p-1 text-slate-500 opacity-0 transition-opacity hover:text-slate-900 focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:hover:text-slate-50",
className
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-base font-semibold font-display", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90 font-display", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}
================================================
FILE: app/src/components/ui/toaster.tsx
================================================
"use client"
import { useToast } from "../../hooks/ui/use-toast"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "./toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}
================================================
FILE: app/src/components/ui/tooltip.tsx
================================================
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "../../lib/utils"
const Tooltip = ({ ...props }) => (
<TooltipPrimitive.Provider>
<TooltipPrimitive.Root {...props} />
</TooltipPrimitive.Provider>
)
Tooltip.displayName = TooltipPrimitive.Tooltip.displayName
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border border-slate-100 bg-white px-3 py-1.5 text-sm text-slate-700 shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=top]:slide-in-from-bottom-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400",
className
)}
{...props}
/>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent }
================================================
FILE: app/src/error-page.tsx
================================================
import { useRouteError } from "react-router-dom"
export default function ErrorPage() {
const error = useRouteError()
console.error(error)
return (
<div id="error-page">
<h1>Oops!</h1>
<p>Sorry, an unexpected error has occurred.</p>
<p>
<i>{error.statusText || error.message}</i>
</p>
</div>
)
}
================================================
FILE: app/src/hooks/ui/use-toast.tsx
================================================
// Inspired by react-hot-toast library
import * as React from "react"
import { ToastActionElement, type ToastProps } from "../../components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_VALUE
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST":
const { toastId } = action
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
interface Toast extends Omit<ToasterToast, "id"> {}
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }
================================================
FILE: app/src/hooks/use-breakpoint.ts
================================================
// @ts-ignore
// https://stackoverflow.com/a/71098593/2865073
import { useMediaQuery } from "react-responsive"
import resolveConfig from "tailwindcss/resolveConfig"
import tailwindConfig from "../../tailwind.config"
const fullConfig = resolveConfig(tailwindConfig)
const breakpoints = fullConfig.theme?.screens
type BreakpointKey = keyof typeof breakpoints
export function useBreakpoint<K extends BreakpointKey>(breakpointKey: any) {
const bool = useMediaQuery({
query: `(min-width: ${breakpoints[breakpointKey]})`,
})
const capitalizedKey =
breakpointKey[0].toUpperCase() + breakpointKey.substring(1)
type Key = `is${Capitalize<K>}`
return {
[`is${capitalizedKey}`]: bool,
} as Record<any, boolean>
}
================================================
FILE: app/src/index.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
html, body, #app {
height: 100%;
max-height: 100%;
font-family: "Quicksand", sans-serif;
}
/* fixes overflowing on certain devices */
.public-DraftEditor-content {
overflow-wrap: anywhere !important;
}
.DraftEditor-root {
flex: 1 1 0;
overflow: auto;
}
.loading_border {
padding: 11px;
outline:none;
border: none;
border-radius: 1px;
box-shadow: none;
background-image:
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155),
linear-gradient(rgba(226 232 240), rgba(226 232 240)),
linear-gradient(rgba(226 232 240), rgba(226 232 240)),
linear-gradient(rgba(226 232 240), rgba(226 232 240)),
linear-gradient(rgba(226 232 240), rgba(226 232 240));
background-position: 0 0, 0 0, 0 100%, 0 100%,
0 0, 0 0, 0 100%, 100% 0;
background-size: 1px 0%, 0% 1px, 0% 1px, 1px 0%,
1px 100%, 100% 1px, 100% 1px,1px 100%;
background-color:transparent;
background-repeat:no-repeat;
transition:0.4s linear;
}
.border_inference_animate {
animation: animate 4s linear infinite;
}
.border_inference_pending {
background-image:
linear-gradient(#8d8e8f, #8d8e8f),
linear-gradient(#8d8e8f, #8d8e8f),
linear-gradient(#8d8e8f, #8d8e8f),
linear-gradient(#8d8e8f, #8d8e8f),
linear-gradient(rgba(226 232 240), rgba(226 232 240)),
linear-gradient(rgba(226 232 240), rgba(226 232 240)),
linear-gradient(rgba(226 232 240), rgba(226 232 240)),
linear-gradient(rgba(226 232 240), rgba(226 232 240));
}
.border_inference_complete {
background-image:
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155),
linear-gradient(#334155, #334155);
}
.border_inference_error {
background-image:
linear-gradient(#f56760, #f56760),
linear-gradient(#f56760, #f56760),
linear-gradient(#f56760, #f56760),
linear-gradient(#f56760, #f56760),
linear-gradient(#f56760, #f56760),
linear-gradient(#f56760, #f56760),
linear-gradient(#f56760, #f56760),
linear-gradient(#f56760, #f56760);
}
@keyframes animate {
0% {
background-position: 0 100%, 0 0, 0 100%, 100% 0,
0 0, 0 0, 0 100%, 100% 0;
background-size: 1px 0%, 100% 1px, 0% 1px,1px 0%,
1px 100%, 100% 1px, 100% 1px,1px 100%;
}
40% {
background-position: 0 100%, 100% 0, 100% 100%, 100% 0,
0 0, 0 0, 0 100%, 100% 0;
background-size: 1px 0%, 100% 1px, 0% 1px,1px 100%,
1px 100%, 100% 1px, 100% 1px,1px 100%;
}
60% {
background-position: 0 100%, 100% 0, 100% 100%, 100% 100%,
0 0, 0 0, 0 100%, 100% 0;
background-size: 1px 0%, 0% 1px, 100% 1px,1px 100%,
1px 100%, 100% 1px, 100% 1px,1px 100%;
}
70% {
background-position: 0 100%, 100% 0, 0% 100%, 100% 100%,
0 0, 0 0, 0 100%, 100% 0;
background-size: 1px 100%, 0% 1px, 100% 1px,1px 0%,
1px 100%, 100% 1px, 100% 1px,1px 100%;
}
80% {
background-position: 0% 0%, 0% 0, 0% 100%, 100% 100%,
0 0, 0 0, 0 100%, 100% 0;
background-size: 1px 100%, 0% 1px, 0% 1px,1px 0%,
1px 100%, 100% 1px, 100% 1px,1px 100%;
}
100% {
background-position: 0% 0%, 0 0, 0 100%, 100% 100%,
0 0, 0 0, 0 100%, 100% 0;
background-size: 1px 0%, 100% 1px, 0% 1px,1px 0%,
1px 100%, 100% 1px, 100% 1px,1px 100%;
}
}
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 500ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 500ms;
}
::-webkit-scrollbar-track {
background-color: transparent;
border-radius: 9999px;
}
::-webkit-scrollbar-thumb {
--tw-border-opacity: 1;
background-color: rgba(217,217,227,.8);
border-color: rgba(255,255,255,var(--tw-border-opacity));
border-radius: 9999px;
border-width: 1px;
}
/* Customize the track */
::-webkit-scrollbar-track {
background-color: transparent;
border-radius: 9999px;
}
/* Customize the thumb */
::-webkit-scrollbar-thumb {
background-color: #a1a1a1;
border-radius: 9999px;
border: 1px solid #a1a1a1;
}
/* Set the scrollbar size */
::-webkit-scrollbar {
height: 0.3rem;
width: 0.3rem;
}
/* Set the scrollbar's corners to round */
::-webkit-scrollbar-corner {
background: transparent;
}
.item-enter {
opacity: 0;
}
.item-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.item-exit {
opacity: 1;
}
.item-exit-active {
opacity: 0;
transition: opacity 500ms ease-in;
}
================================================
FILE: app/src/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>OpenPlayground</title>
<meta name="description" content="Open source playground to test LLMs" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<style>
@import url("https://fonts.googleapis.com/css2?family=Gloock&family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Manrope:wght@300;400;500;600;700;800&display=swap");
</style>
<style>
@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&display=swap");
</style>
<link href="./index.css" rel="stylesheet" />
</head>
<body>
<div id="app"></div>
<script type="module" src="index.tsx"></script>
</body>
</html>
================================================
FILE: app/src/index.tsx
================================================
import React from "react"
import { createRoot } from "react-dom/client"
import App from "./app"
const container = document.getElementById("app")!
const root = createRoot(container)
root.render(<App />)
================================================
FILE: app/src/lib/ctrl-meta-keypress.tsx
================================================
import { useCallback, useEffect, useLayoutEffect, useRef } from "react"
export const useCtrlMetaKeyPress = (keys: any, callback: any, node = null) => {
const callbackRef = useRef(callback)
useLayoutEffect(() => {
callbackRef.current = callback
})
const handleKeyPress = useCallback(
(event: any) => {
if (event.metaKey && event.ctrlKey && keys.some((key: any) => event.key === key)) {
callbackRef.current(event)
}
},
[keys]
)
useEffect(() => {
const targetNode = node ?? document
targetNode && targetNode.addEventListener("keydown", handleKeyPress)
return () =>
targetNode && targetNode.removeEventListener("keydown", handleKeyPress)
}, [handleKeyPress, node])
}
================================================
FILE: app/src/lib/editor-styles.tsx
================================================
export const styleMap = {
HIGHLIGHT: {
backgroundColor: "#faed27",
},
NORMAL: {
backgroundColor: "transparent",
},
BOLD: {
fontWeight: "bold",
},
};
export const styles = {
openai: {
transition: "background-color 0.2s ease-in-out",
backgroundColor: "#b9eebc",
padding: "2px 0",
},
huggingface_local: {
transition: "background-color 0.2s ease-in-out",
backgroundColor: "#f6b2b3",
padding: "2px 0",
},
cohere: {
transition: "background-color 0.2s ease-in-out",
backgroundColor: "#a198e6",
padding: "2px 0",
},
huggingface: {
transition: "background-color 0.2s ease-in-out",
backgroundColor: "#D7BCE8",
padding: "2px 0",
},
forefront: {
backgroundColor: "#BCCAE8",
padding: "2px 0",
},
anthropic: {
backgroundColor: "#cc785c80",
padding: "2px 0",
},
aleph_alpha: {
backgroundColor: "#e3ff00",
padding: "2px 0",
},
default: {
backgroundColor: "transparent",
transition: "background-color 0.2s ease-in-out",
padding: "2px 0",
},
};
export function getDecoratedStyle(provider: string, showHighlights: boolean) {
if (showHighlights === false) return styles.default;
switch (provider) {
case "openai":
return styles.openai;
case "huggingface-local":
return styles.huggingface_local;
case "cohere":
return styles.cohere;
case "huggingface":
return styles.huggingface;
case "forefront":
return styles.forefront;
case "anthropic":
return styles.anthropic;
case "aleph-alpha":
return styles.aleph_alpha;
default:
return styles.default;
}
}
================================================
FILE: app/src/lib/keypress.tsx
================================================
import { useCallback, useEffect, useLayoutEffect, useRef } from "react"
export const useKeyPress = (keys: any, callback: any, node = null) => {
const callbackRef = useRef(callback)
useLayoutEffect(() => {
callbackRef.current = callback
})
const handleKeyPress = useCallback(
(event: any) => {
if (keys.some((key: any) => event.key === key)) {
callbackRef.current(event)
}
},
[keys]
)
useEffect(() => {
const targetNode = node ?? document
targetNode && targetNode.addEventListener("keydown", handleKeyPress)
return () =>
targetNode && targetNode.removeEventListener("keydown", handleKeyPress)
}, [handleKeyPress, node])
}
================================================
FILE: app/src/lib/meta-keypress.tsx
================================================
import { useCallback, useEffect, useLayoutEffect, useRef } from "react"
export const useMetaKeyPress = (keys: any, callback: any, node = null) => {
const callbackRef = useRef(callback)
useLayoutEffect(() => {
callbackRef.current = callback
})
const handleKeyPress = useCallback(
(event: any) => {
if ((event.ctrlKey || event.metaKey) && keys.some((key: any) => event.key === key)) {
callbackRef.current(event)
}
},
[keys]
)
useEffect(() => {
const targetNode = node ?? document
targetNode && targetNode.addEventListener("keydown", handleKeyPress)
return () =>
targetNode && targetNode.removeEventListener("keydown", handleKeyPress)
}, [handleKeyPress, node])
}
================================================
FILE: app/src/lib/utils.ts
================================================
import { ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function handleSelectModel(modelState, modelsStateContext, setModelsStateContext, parametersContext, setParametersContext, multi_select: boolean) {
const number_of_models_selected = modelsStateContext.filter(modelState => modelState.selected).length
const selected = (!multi_select && number_of_models_selected > 1) ? true : !modelState.selected
if (selected && !multi_select) {
const parameters = modelState.parameters
setParametersContext({
...parametersContext,
temperature: parameters.temperature?.value || parametersContext.temperature,
maximumLength: parameters.maximumLength?.value || parametersContext.maximumLength,
topP: parameters.topP?.value || parametersContext.topP,
topK: parameters.topK?.value || parametersContext.topK,
frequencyPenalty: parameters.frequencyPenalty?.value || parametersContext.frequencyPenalty,
presencePenalty: parameters.presencePenalty?.value || parametersContext.presencePenalty,
repetitionPenalty: parameters.repetitionPenalty?.value || parametersContext.repetitionPenalty,
stopSequences: parameters.stopSequences?.value || parametersContext.stopSequences
})
}
setModelsStateContext(
modelsStateContext.map((m) => {
if (!multi_select && m.tag !== modelState.tag) {
m.selected = false
} else if (m.tag === modelState.tag) {
m.selected = selected
}
return m
})
)
}
================================================
FILE: app/src/pages/compare.tsx
================================================
import React, {
useCallback,
useContext,
useEffect,
useRef,
useState,
forwardRef,
useImperativeHandle,
} from "react"
import {
Editor,
EditorState,
convertFromRaw,
convertToRaw,
CompositeDecorator,
SelectionState,
Modifier,
ContentState,
RichUtils,
getDefaultKeyBinding,
} from "draft-js"
import { Button } from "../components/ui/button"
import { Popover } from "react-tiny-popover"
import NavBar from "../components/navbar"
import {
Loader2,
Settings2,
AlertTriangle,
} from "lucide-react"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "../components/ui/tooltip"
import {
AlertDialog,
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "../components/ui/alert-dialog"
import chroma from "chroma-js"
import {useMetaKeyPress} from "../lib/meta-keypress"
import {useKeyPress} from "../lib/keypress"
import "draft-js/dist/Draft.css"
import {Sheet, SheetContent, SheetTrigger} from "../components/ui/right-sheet"
import {CSSTransition, TransitionGroup} from "react-transition-group"
import {APIContext, EditorContext, ParametersContext, ModelsContext, ModelsStateContext} from "../app"
import {styleMap, getDecoratedStyle} from "../lib/editor-styles"
import ParameterSidePanel from "../components/parameters-side-panel"
import {handleSelectModel} from "../lib/utils"
const normalize_parameter = (parameter: number) => {
if (parameter > 1) return parameter
else return parameter.toFixed(1)
}
const ModelCardStats = (props: any) => {
const {errorMessage, is_running, totalCharacters} = props
const [isTimerRunning, setIsTimerRunning] = useState<boolean>(false);
const intervalRef = useRef(null);
const [time, setTime] = useState(0);
useEffect(() => {
if (is_running && isTimerRunning === false) {
startTimer()
} else if (!is_running && isTimerRunning === true) {
stopTimer()
}
}, [is_running])
const startTimer = () => {
setIsTimerRunning(true)
setTime(0)
intervalRef.current = setInterval(() => {
setTime((prevTime) => prevTime + 1)
}, 1000)
}
const stopTimer = () => {
clearInterval(intervalRef.current)
setIsTimerRunning(false)
}
function insertLineBreaks(str) {
if (str === undefined || str === null) return [];
const words = str.split(" ");
const result = [];
let accumulator = "";
for (let i = 0; i < words.length; i++) {
accumulator += `${words[i]} `;
if ((i + 1) % 4 === 0 || i === words.length - 1) {
result.push(accumulator);
accumulator = "";
}
}
return result;
}
const paragraphs = insertLineBreaks(errorMessage).map((words, index) => (
<span className = "block text-center" key={index}>{words}</span>
));
const token_per_second =
totalCharacters > 0 ? Math.floor(totalCharacters / Math.max(time, 1)) : 0
const formatTime = (time) => {
const minutes = Math.floor(time / 60)
.toString()
.padStart(2, "0")
const seconds = (time % 60).toString().padStart(2, "0")
return `${minutes}:${seconds}`
}
return (
<div className="flex font-medium">
<span>{formatTime(time)}</span>
<span className="flex-1"></span>
<span>
<Tooltip delayDuration={300} skipDelayDuration={150} open={errorMessage ? true : false}>
<TooltipTrigger asChild>
<div style = {{display: (errorMessage ) ? "block" : "none"}}>
<AlertTriangle color = "#f56760"/>
</div>
</TooltipTrigger>
<TooltipContent side={"top"}>
<>{paragraphs}</>
</TooltipContent>
</Tooltip>
{token_per_second === 0 || errorMessage ? "" : `${token_per_second} chars/s`}{" "}
</span>
<span className="flex-1"></span>
<span>{totalCharacters} chars</span>
</div>
)
}
const ModelEditor = React.memo((props: any) => {
const {
editorState,
setEditorState
} = props
return (
<Editor
customStyleMap={styleMap}
editorState={editorState}
onChange={(editorState) => {
setEditorState(editorState)
}}
/>
)
})
const ModelCard = forwardRef((props, ref) => {
const token_index = useRef(0)
const {model, showHighlights, showProbabilities, completion} = props
const [serverModelState, setServerModelState] = React.useState<string>("IDLE")
const [errorMessage, setErrorMessage] = useState(null);
const [totalCharacters, setTotalCharacters] = useState(0);
const [output, setOutput] = React.useState<string[]>([])
const [status, setStatus] = React.useState<string[]>([])
const {modelsStateContext, setModelsStateContext} = useContext(ModelsStateContext)
const {parametersContext, setParametersContext} = useContext(ParametersContext)
const showProbabilitiesRef = useRef(showProbabilities)
const showHighlightsRef = useRef(showHighlights)
useEffect(() => {
setEditorState(
EditorState.forceSelection(editorState, editorState.getSelection())
)
}, [showProbabilities, showHighlights])
useEffect(() => {
showProbabilitiesRef.current = showProbabilities
showHighlightsRef.current = showHighlights
})
if (completion.length > token_index.current) {
let completion_slice = completion.slice(token_index.current, completion.length)
token_index.current = completion.length;
setOutput(completion_slice)
}
const Decorated = (props: any) => {
const children = props.children
const entity = props.contentState.getEntity(props.entityKey)
const entityData = entity.getData()
const style = getDecoratedStyle(entityData.modelProvider, showHighlightsRef.current)
const probabilitiesMap = entityData.topNDistribution
const tokensMap = probabilitiesMap ? probabilitiesMap["tokens"] : []
const [popoverOpen, setPopoverOpen] = React.useState<boolean>(false)
if (entityData.message === props.decoratedText) {
let content = (
<span style={style} key={children[0].key} data-offset-key={children[0].key}>
{children}
</span>
)
if (probabilitiesMap && (tokensMap[props.decoratedText] != undefined && tokensMap[props.decoratedText].length > 0)) {
let percentage = Math.min(tokensMap[props.decoratedText][1] / probabilitiesMap.simpleProbSum, 1.0)
let f = chroma.scale(["#ff8886", "ffff00", "#96f29b"])
let highlight_color = f(percentage)
let custom_style = showProbabilitiesRef.current ? {
backgroundColor: highlight_color,
padding: "2px 0",
} : style
let popoverContent =
(
<div className="shadow-xl shadow-inner rounded-sm bg-white mb-2" data-container="body">
<ul key={children[0].key} className="grid pt-4">
{
Object.entries(tokensMap).map((item, index) => {
return (
<li key={item + "-" + index + "-" + children[0].key} className={item[0] === entityData.message ? "bg-highlight-tokens w-full font-base text-white pl-4" : "pl-4 text-bg-slate-800"}>
{item[0]} = {tokensMap[item[0]][1]}%
</li>
)
})
}
</ul>
<div className="m-4 pb-4">
<div className="text-base">Total: {probabilitiesMap.logProbSum} logprob on 1 tokens</div>
<div className="text-xs">({probabilitiesMap.simpleProbSum}% probability covered in top {Object.keys(probabilitiesMap.tokens).length} logits)</div>
</div>
</div>
)
content = (
<Popover
isOpen={popoverOpen}
onClickOutside={() => setPopoverOpen(false)}
positions={["bottom", "top", "left", "right"]}
content={popoverContent}
containerStyle={{zIndex: "1000"}}
>
<span style={custom_style} className={popoverOpen ? "font-bold" : ""} id={children[0].key} key={children[0].key} data-offset-key={children[0].key} onClick={() => {showProbabilitiesRef.current ? setPopoverOpen(!popoverOpen) : null}}>
{children}
</span>
</Popover>
)
}
return content
} else {
return <span data-offset-key={children[0].key}>{children}</span>
}
}
const getEditorState = useCallback((): EditorState => editorStateRef.current, [])
const createDecorator = () => new CompositeDecorator([{
strategy: findEntityRangesByType("HIGHLIGHTED_WORD"),
component: Decorated,
props: {
getEditorState,
}
}])
const [editorState, setEditorState] = React.useState(EditorState.createEmpty(createDecorator()))
const editorStateRef = useRef<EditorState>(editorState)
function findEntityRangesByType(entityType: any) {
return (contentBlock: any, callback: any, contentState: any) => {
contentBlock.findEntityRanges((character: any) => {
const entityKey = character.getEntity()
if (entityKey === null) {
return false
}
return contentState.getEntity(entityKey).getType() === entityType
}, callback)
}
}
useEffect(() => {
let current_editor_state = editorState;
let aggregate_new_chars = 0;
try {
for(const output_entry of output) {
aggregate_new_chars += output_entry.message.split("").length
const currentContent = current_editor_state.getCurrentContent()
const blockMap = currentContent.getBlockMap()
const key = blockMap.last().getKey()
const length = blockMap.last().getLength()
const selection = new SelectionState({
anchorKey: key,
anchorOffset: length,
focusKey: key,
focusOffset: length,
})
currentContent.createEntity("HIGHLIGHTED_WORD", "MUTABLE", output_entry)
const entityKey = currentContent.getLastCreatedEntityKey()
const textWithInsert = Modifier.insertText(
currentContent,
selection,
output_entry.message,
null,
entityKey
)
const editorWithInsert = EditorState.push(
editorState,
textWithInsert,
"insert-characters"
)
current_editor_state = editorWithInsert
}
} catch (e) {
}
setTotalCharacters(totalCharacters + aggregate_new_chars)
setEditorState(current_editor_state)
}, [output])
useEffect(() => {
if (status.message === "[INITIALIZING]") {
setServerModelState("INITIALIZED")
setTotalCharacters(0)
setErrorMessage(null)
return
}
if (status.message && status.message.indexOf("[ERROR] ") === 0 && (serverModelState !== "COMPLETED" && serverModelState !== "IDLE")) {
setServerModelState("ERROR")
setErrorMessage(status.message.replace("[ERROR] ", ""))
return
}
if (status.message === "[COMPLETED]") {
setServerModelState("COMPLETED")
return
}
}, [status])
const handleNotification = (output: any) => {
setOutput(output)
}
const handleNotificationStatus = (status: any) => {
setStatus(status)
}
const handleUndo = (output: any) => {
setEditorState(
EditorState.createWithContent(
ContentState.createFromText(""),
createDecorator()
)
)
}
useImperativeHandle(ref, () => ({
handleNotification,
handleUndo,
handleNotificationStatus
}))
let border_class = ""
switch (serverModelState) {
case "INITIALIZED":
border_class = "border_inference_pending border_inference_animate"
break
case "RUNNING":
border_class = "border_inference_animate"
break
case "COMPLETED":
border_class = "border_inference_complete"
break
case "ERROR":
border_class = "border_inference_error"
break
default:
break
}
return (
<div className={`flex flex-col items-center text-gray-600 text-lg font-bold h-96`}
style = {model.selected? {
transition: "all 0.3s ease",
backgroundColor: "#f5f5f5",
borderRadius: 4,
padding: 6
} : {
transition: "all 0.3s ease",
backgroundColor: "#ffffff",
borderRadius: 0,
padding: 0
} }>
<div className="flex justify max-w-[100%]">
<h2
onClick={(event) => {
handleSelectModel(
model,
modelsStateContext,
setModelsStateContext,
parametersContext,
setParametersContext,
event.ctrlKey || event.metaKey
)
}}
className={
`select-none cursor-pointer text-ellipsis overflow-hidden max-w-full whitespace-nowrap overflow-hidden ${model.selected ? "font-medium" : "font-normal"}`
}>
{model.name}
</h2>
</div>
<div className="relative editor-container h-full w-full text-base flex mt-2" style = {{clipPath: "inset(-1px)"}}>
<div
className={`font-medium relative p-3 overflow-hidden flex-1 flex flex-col loading_border ${border_class}`}
>
<ModelEditor {...props} editorState ={editorState} setEditorState ={setEditorState} />
<ModelCardStats
errorMessage={errorMessage}
totalCharacters={totalCharacters}
is_running = {serverModelState !== "ERROR" && serverModelState !== "COMPLETED" && serverModelState !== "IDLE"}
/>
</div>
</div>
</div>
)
})
function PromptEditor({editorState, setEditorState, ...props}: any) {
const scrollRef = useRef(null)
const editorRef = React.useRef(null)
const {modelsStateContext} = useContext(ModelsStateContext)
const {parametersContext} = useContext(ParametersContext)
const number_of_models_enabled = modelsStateContext.filter((modelState) => modelState.enabled).length
function focusEditor() {
editorRef.current.focus()
}
const handleKeyCommand = (command: any, editorState: any) => {
if (command === "bold") {
setEditorState(RichUtils.toggleInlineStyle(editorState, "BOLD"))
return "handled"
}
if (command === "ignore_enter") {
return "handled"
}
return "not-handled"
}
const keyBindingFn = (event: any) => {
if (event.code === "Enter" && event.metaKey) {
return "ignore_enter"
}
if (event.metaKey && event.keyCode === 66) {
return "bold"
} else if (event.ctrlKey && event.keyCode === 66) {
return "bold"
}
return getDefaultKeyBinding(event)
}
/*useEffect(() => {
setEditorContext({
internalState: convertToRaw(editorState.getCurrentContent())
})
}, [
editorState
])*/
useEffect(() => {
setEditorState(
EditorState.forceSelection(editorState, editorState.getSelection())
)
}, [parametersContext.showProbabilities])
return (
<div
ref={scrollRef}
onClick={focusEditor}
className="overflow-y-auto editor-container h-[100%] w-full text-base"
>
<Editor
placeholder={
number_of_models_enabled >= 1
? "Type your prompt here..."
: "No models have been enabled..."
}
ref={editorRef}
keyBindingFn={keyBindingFn}
handleKeyCommand={handleKeyCommand}
customStyleMap={styleMap}
editorState={editorState}
onChange={(editorState: any) => {
setEditorState(editorState)
}}
/>
</div>
)
}
function PromptArea({showDialog}) {
const is_mac_os = navigator.platform.toUpperCase().indexOf("MAC") >= 0
const cancel_callback = React.useRef<any>(null)
const [generating, setGenerating] = React.useState<boolean>(false)
const apiContext = useContext(APIContext)
const {modelsStateContext} = useContext(ModelsStateContext)
const {editorContext, setEditorContext} = useContext(EditorContext)
React.useEffect(() => {
return () => {
setEditorContext({
...editorContext,
internalState: convertToRaw(editorStateRef.current.getCurrentContent()),
prompt: editorStateRef.current.getCurrentContent().getPlainText()
}, true)
}
}, []);
const [editorState, setEditorState] = React.useState(
EditorState.moveFocusToEnd(EditorState.createWithContent(
editorContext.internalState !== null ? convertFromRaw(editorContext.internalState): ContentState.createFromText(editorContext.prompt)
))
)
const editorStateRef = useRef(editorState)
useEffect(() => {
editorStateRef.current = editorState
}, [editorState])
const generatingRef = useRef(generating);
const number_of_models_enabled = modelsStateContext.filter((modelState) => modelState.enabled).length
const abortCompletion = () => {
if (cancel_callback.current) {
cancel_callback.current()
}
}
useKeyPress(["Escape"], (event: any) => {
abortCompletion()
})
useEffect(() => {
const completion_callback = ({event}) => {
switch (event) {
case "close":
setEditorContext({
prompt: editorStateRef.current.getCurrentContent().getPlainText()
})
setGenerating(false)
break;
default:
break;
}
}
apiContext.Inference.subscribeTextCompletion(completion_callback)
return () => {
apiContext.Inference.unsubscribeTextCompletion(completion_callback);
};
}, []);
useMetaKeyPress(["Enter"], (event: any) => {
handleSubmit()
})
const handleStreamingSubmit = async (
regenerate = false,
passedInPrompt = "",
) => {
const prompt = regenerate ? passedInPrompt : editorState.getCurrentContent().getPlainText();
setGenerating(true)
setEditorContext({
prePrompt: prompt
})
const _cancel_callback = apiContext.Inference.textCompletionRequest({
prompt: regenerate ? passedInPrompt : prompt,
models: modelsStateContext.map((modelState) => {
if(modelState.enabled) {
return modelState
}
}).filter(Boolean)
})
cancel_callback.current = _cancel_callback
}
useEffect(() => {
generatingRef.current = generating
})
const handleSubmit = async (regenerate = false, passedInPrompt = "") => {
return handleStreamingSubmit(regenerate, passedInPrompt)
}
return (
<form
className="flex flex-col grow basis-auto min-h-[25%] max-h-[75%] overflow-auto"
onSubmit={(e) => {
e.preventDefault()
handleSubmit()
}}
>
<div className="flex-1 flex border border-slate-400">
<div className={`relative overflow-hidden flex-1 flex flex-col p-2`}>
<PromptEditor editorState = {editorState} setEditorState = {setEditorState}/>
<div className="absolute bottom-[.5em] right-[1em] z-[2]">
{generating && (
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<Button
variant="default"
className="inline-flex items-center ml-1 text-sm font-medium text-center"
onClick={(e) => {
e.stopPropagation()
abortCompletion()
}}
>
{" "}
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Cancel
</Button>
</TooltipTrigger>
<TooltipContent
side="top"
align="center"
className="bg-slate-600 text-white hidden md:block"
>
Cancel
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
Escape
</kbd>
</TooltipContent>
</Tooltip>)
}
{!generating && (
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<Button
disabled={number_of_models_enabled === 0}
variant="default"
className="bg-emerald-500 hover:bg-emerald-700 inline-flex items-center ml-1 text-sm font-medium text-center"
type="submit"
value="submit"
>
Submit
</Button>
</TooltipTrigger>
<TooltipContent
side="top"
align="center"
className="bg-slate-600 text-white hidden md:block"
>
Submit
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
{is_mac_os ? "⌘" : "Control"}
</kbd>
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
Enter
</kbd>
</TooltipContent>
</Tooltip>
)}
</div>
</div>
</div>
</form>
)
}
const ModelsCompletion = ({showDialog}) => {
const modelEditorRefs = useRef({})
const [modelsCompletionState, setModelsCompletionState] = React.useState({});
const [_, signalRender] = React.useState(0);
const apiContext = React.useContext(APIContext);
const {modelsStateContext} = useContext(ModelsStateContext)
const {parametersContext} = React.useContext(ParametersContext);
const number_of_models_enabled = modelsStateContext.filter((modelState) => modelState.enabled).length
const notifyEditorStatus = (model_tag, message) => {
if (model_tag === "*") {
for(const model_tag in modelEditorRefs.current) {
if (modelEditorRefs.current[model_tag])
modelEditorRefs.current[model_tag].handleNotificationStatus(message)
}
return
}
if (modelEditorRefs.current[model_tag])
modelEditorRefs.current[model_tag].handleNotificationStatus(message)
}
const handleUndoLast = () => {
for (const editor_model_tag of Object.keys(modelEditorRefs.current)) {
if (modelEditorRefs.current[editor_model_tag])
modelEditorRefs.current[editor_model_tag].handleUndo()
}
}
useMetaKeyPress(["O"], (event: any) => {
if (editorContext.prePrompt === "") {
return
}
handleUndoLast()
})
useEffect(() => {
const completion_callback = ({event, data}) => {
switch (event) {
case "open":
handleUndoLast()
break;
case "status":
notifyEditorStatus(data.modelTag, data)
break;
case "cancel":
notifyEditorStatus("*", {
message: "[ERROR] Cancelled by user"
})
break;
case "completion":
for (let model_tag in data) {
modelsCompletionState[model_tag] = [
...(modelsCompletionState[model_tag] || []),
...data[model_tag]
]
}
setModelsCompletionState(modelsCompletionState)
signalRender((x) => x + 1)
break;
case "error":
switch(data) {
case "Too many pending requests":
showDialog({
title: "Too many pending requests",
message: "Please wait a few seconds before trying again.",
})
break;
case "Too many daily completions":
showDialog({
title: "Daily limit reached",
message: "It seems you've reached your daily limit. Please try again tomorrow.",
})
break;
case "Unauthorized":
showDialog({
title: "Unauthorized",
message: "Please log in to use this feature.",
})
break;
default:
showDialog({
title: "Error",
message: data,
})
break;
}
break;
default:
console.log("Unknown event", event, data);
break;
}
}
apiContext.Inference.subscribeTextCompletion(completion_callback)
return () => {
apiContext.Inference.unsubscribeTextCompletion(completion_callback);
};
}, []);
return (
<TransitionGroup
className={`grid h-full mt-3 pr-1 overflow-auto
${
number_of_models_enabled === 1
? "grid-cols-1 gap-1 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2"
: number_of_models_enabled === 2
? "grid-cols-1 gap-3 sm:grid-cols-1 md:grid-cols-2"
: number_of_models_enabled === 3
? "grid-cols-1 gap-3 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-3"
: number_of_models_enabled === 4
? "grid-cols-1 gap-3 gap-3 sm:grid-cols-1 md:grid-cols-2"
: "grid-cols-1 gap-2 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 3xl:grid-cols-4 5xl:grid-cols-5 6xl:grid-cols-6 8xl:grid-cols-8"
}
`}
> {
modelsStateContext
.filter((modelState) => modelState.enabled && modelState.tag)
.map((modelState) => (
<CSSTransition
key ={modelState.tag}
timeout={500}
classNames="fade"
unmountOnExit>
<ModelCard
key={modelState.tag}
ref={(ref) => (modelEditorRefs.current[modelState.tag] = ref) }
completion = {modelsCompletionState[modelState.tag] || []}
model={modelState}
showProbabilities={parametersContext.showProbabilities}
showHighlights={parametersContext.highlightModels}
/>
</CSSTransition>
)
)
}
</TransitionGroup>
)
}
const ParametersTable = () => {
const {parametersContext, setParametersContext} = React.useContext(ParametersContext);
const {modelsContext} = React.useContext(ModelsContext);
const {modelsStateContext, setModelsStateContext} = useContext(ModelsStateContext)
const number_of_models_enabled = modelsStateContext.filter((modelState) => modelState.enabled).length
const generate_headers = () => (
["Model", "ML", "T", "TP", "TK", "FP", "PP", "RP"].map((header, index) => (
<div className={`text-center font-normal ${index === 0 ? "min-w-[150px] sticky top-[0] left-[0] z-[1] bg-white" : "min-w-[150px] flex-1"}`}>
{header}
</div>
)
))
const generate_table_line_graph = (parameterValue, parameterDefaults) => {
if (!parameterValue) return (
<div className="flex-1">
<span className="cursor-default text-xs">N/A</span>
</div>
)
return (
<div className="flex-1">
<div className = "mx-4 relative flx items-center">
<div className="rounded-md bg-slate-200 w-[100%] h-[3px]"/>
<div
className="rounded-md bg-slate-600 absolute h-[3px] top-[0px] ease-in duration-300"
style={{ width: `${100 * (parameterValue / parameterDefaults.range[1])}%`}}
/>
</div>
<span className= "cursor-default absolute top-[-1px] right-[16px] text-[12px]">
{normalize_parameter(parameterValue)}
</span>
</div>
)
}
const generate_row = (modelState, model) => (
[
"name", "maximumLength", "temperature", "topP", "topK", "frequencyPenalty", "presencePenalty", "repetitionPenalty"
].map((parameter, index) => (
(parameter === "name") ?
<div className={`py-1 bg-inherit text-center font-light min-w-[150px] max-w-[150px] left-[0px] z-[2] sticky whitespace-nowrap text-ellipsis overflow-hidden ${modelState.selected ? "font-medium" : ""}`}>
<span>{modelState.name.split(":")[1]}</span>
</div>
:
<div className="bg-inherit text-center font-light relative min-w-[150px] flex flex-1 items-center">
{generate_table_line_graph(modelState.parameters[parameter], model.defaultParameters[parameter])}
</div>
))
)
const generate_rows = () => {
return modelsStateContext
.filter((modelState) => modelState.enabled)
.map((modelState) => {
return (<div
key={modelState.tag}
className={`cursor-pointer flex flex-row hover:bg-slate-100 ${modelState.selected ? "bg-slate-100" : "bg-white"}`}
onClick={(event) => {
handleSelectModel(
modelState,
modelsStateContext,
setModelsStateContext,
parametersContext,
setParametersContext,
event.ctrlKey || event.metaKey
)
}}>
{generate_row(modelState, modelsContext[modelState.name])}
</div>)
})
}
return (
<div>
{!parametersContext.showParametersTable || number_of_models_enabled === 0 ? null :
<div className="flex mt-4 justify-center">
<div className="rounded-sm border border-slate-200 max-h-[350px] max-w-[1400px] overflow-auto">
<div className="flex flex-col m-2 min-w-[900px]">
<div className="flex flex-row sticky top-[0] z-[3] bg-white">
{generate_headers()}
</div>
{generate_rows()}
</div>
</div>
</div>
}
</div>
)
}
const CustomAlertDialogue = ({dialog}) => {
const [openDialog, setOpenDialog] = React.useState<boolean>(false)
const [_dialogue, _setDialogue] = React.useState<any>({
title: "",
message: ""
})
useEffect(() => {
if (!openDialog && dialog.title !== "" && dialog.message !== "") {
_setDialogue({
title: dialog.title,
message: dialog.message
})
setOpenDialog(true)
}
}, [dialog])
return (
<AlertDialog open={openDialog} onOpenChange={setOpenDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{_dialogue.title}</AlertDialogTitle>
<AlertDialogDescription className="text-base text-slate-700 dark:text-slate-400">
{_dialogue.message}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction>Ok</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
export default function Compare() {
const [openParameterSheet, setSaveOpenParameterSheet] = React.useState<boolean>(false)
const parametersSidePanel = (<ParameterSidePanel showModelList = {true} />)
const [dialog, showDialog] = React.useState({
title: "",
message: ""
})
return (
<div className="flex flex-col h-full">
<NavBar tab="compare">
<div className="align-middle mt-1">
<div className="flex basis-full mb-2 lg:mb-0 space-x-2">
<Sheet open={openParameterSheet} onOpenChange={setSaveOpenParameterSheet}>
<SheetTrigger asChild>
<Button variant="subtle" className="lg:hidden">
<Settings2 className="h-6 w-6" />
</Button>
</SheetTrigger>
<SheetContent className="w-[80vw] p-4 pt-10 overflow-auto">
{parametersSidePanel}
</SheetContent>
</Sheet>
</div>
</div>
</NavBar>
<CustomAlertDialogue dialog = {dialog} />
<div className="flex flex-grow flex-col font-display min-h-0 min-w-0">
<div className="flex flex-row space-x-4 flex-grow mx-2 md:ml-5 lg:ml-5 min-h-0 min-w-0">
<div className="flex-1 flex flex-col lg:max-w-[calc(100%-266px)] max-w-[100%]">
<PromptArea showDialog = {showDialog}/>
<ParametersTable showDialog = {showDialog}/>
<ModelsCompletion showDialog = {showDialog}/>
</div>
<div className="hidden p-1 grow-0 shrink-0 basis-auto lg:w-[250px] overflow-auto lg:block">
{parametersSidePanel}
</div>
</div>
</div>
</div>
)
}
================================================
FILE: app/src/pages/index.tsx
================================================
import Playground from "./playground"
import Compare from "./compare"
import Settings from "./settings"
export { Playground, Compare, Settings };
================================================
FILE: app/src/pages/playground.tsx
================================================
import React, {
useCallback, useContext, useEffect, useRef
} from "react"
import {
Editor,
EditorState,
CompositeDecorator,
SelectionState,
Modifier,
ContentState,
RichUtils,
getDefaultKeyBinding,
convertToRaw,
convertFromRaw,
} from "draft-js"
import { Button } from "../components/ui/button"
import NavBar from "../components/navbar"
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
import {
X,
HistoryIcon,
Loader2,
Settings2,
} from "lucide-react"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "../components/ui/tooltip"
import { Popover } from "react-tiny-popover"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "../components/ui/alert-dialog"
import { useMetaKeyPress } from "../lib/meta-keypress"
import { useKeyPress } from "../lib/keypress"
import "draft-js/dist/Draft.css"
import { Sheet, SheetContent, SheetTrigger } from "../components/ui/right-sheet"
import chroma from "chroma-js"
import { useToast } from "../hooks/ui/use-toast"
import {styleMap, getDecoratedStyle} from "../lib/editor-styles"
import { APIContext, EditorContext, ModelsStateContext, ParametersContext, HistoryContext} from "../app"
import ParameterSidePanel from "../components/parameters-side-panel"
import { TooltipProvider } from "@radix-ui/react-tooltip"
const HistorySidePanel = () => {
const {
historyContext, toggleShowHistory, clearHistory, selectHistoryItem
} = useContext(HistoryContext)
const handleDeleteAllHistory = () => {
clearHistory()
}
if (!historyContext.show) return null;
const downloadHistory = () => {
const element = document.createElement("a")
const history_json = historyContext.entries.map((entry: any) => {
const model = entry.modelsState.find(({selected}) => selected)
const text = EditorState.createWithContent(convertFromRaw(entry.editor.internalState)).getCurrentContent().getPlainText()
return {
model: model.name,
date: entry.date,
timestamp: entry.timestamp,
text: text,
parameters: entry.parameters
}
})
const file = new Blob([JSON.stringify(history_json)], {
type: "application/json",
})
element.href = URL.createObjectURL(file)
element.download = "history.json"
document.body.appendChild(element) // Required for this to work in FireFox
element.click()
}
return (
<div className="flex flex-col h-full relative overflow-auto">
<div
className="text-lg tracking-tight font-semibold text-slate-900 flex sticky top-[0] right-[0]"
style={{ justifyContent: "flex-end" }}
>
<div>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button
type="button"
variant="subtle"
className="inline-flex text-sm font-medium outline-0"
onClick={(e) => {
setShowHistory((e) => !e)
}}
disabled={history.length == 0}
>
...
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="outline-0 cursor-default min-w-[150px] bg-white rounded-md shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade z-10"
sideOffset={5}
>
<DropdownMenu.Item
className="cursor-pointer outline-0 hover:bg-slate-200 text-sm p-2 text-center"
onClick={() => {
downloadHistory()
}}
>
Download as JSON
</DropdownMenu.Item>
<DropdownMenu.Separator className="h-[1px] bg-slate-200" />
<DropdownMenu.Item
className="cursor-pointer outline-0 hover:bg-slate-200 text-sm p-2 text-center"
onClick={() => {
handleDeleteAllHistory()
}}
>
Clear History
</DropdownMenu.Item>
<DropdownMenu.Arrow className="fill-white" />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</div>
<div className="cursor-pointer inline m-2 align-middle lg:inline align-middle mb-1" style = {{height: 20, width: 20}}>
<X
size={20}
onClick={(e) => {
toggleShowHistory()
}}
/>
</div>
</div>
<div className="overflow-y-auto max-h-[100%] mt-2">
{historyContext.entries
.reduce((accumulator: any, value: any) => {
let val = value.date
if (!accumulator.includes(val)) {
accumulator.push(val)
}
return accumulator.sort((a, b) => (new Date(b) - new Date(a)))
}, [])
.map((unique_date: any, main_index) => {
return (
<div key = {unique_date}>
<div className="text-xs tracking-tight mb-4 mt-2 font-semibold uppercase text-slate-900">
{new Date(unique_date).toLocaleDateString(
["en-GB", "en-us"],
{
weekday: "long",
year: "numeric",
month: "short",
day: "numeric",
}
)}
</div>
{historyContext.entries
.filter((value: any) => (value.date === unique_date))
.sort((a: any, b: any) => (new Date(b.timestamp) - new Date(a.timestamp)))
.map((historyItem: any, index: number) => {
const isSelectedHistoryItem = historyContext.current.timestamp === historyItem.timestamp && historyContext.current.editor.prompt === historyItem.editor.prompt;
return (
<div key={historyItem.timestamp}>
<div
onClick={() => {
selectHistoryItem(historyItem)
}}
className={`[&>div:nth-child(2)]:hover:w-[7px]
[&>div:nth-child(2)]:hover:h-[7px]
[&>div:nth-child(2)]:hover:left-[77px]
[&>div:nth-child(2)]:hover:border-slate-800
[&>div:nth-child(2)]:hover:border-2
rounded-sm rounded-sm relative flex flex-row p-4 font-bold text-sm cursor-pointer click:bg-slate-300 dark:hover:bg-slate-200 ${
isSelectedHistoryItem
? "bg-slate-200"
: "hover:bg-slate-100"
}`}
>
<div
className={`bg-slate-300 w-[1px] absolute left-[80px] ${
main_index === 0 && index === 0
? "h-[75%] top-[25%]"
: "h-[100%] top-[0]"
}`}
/>
<div
className={`ease-in duration-100 border rounded-full bg-white absolute top-[22px] ${
isSelectedHistoryItem
? "border-slate-800 w-[7px] h-[7px] border-2 left-[77px]"
: "border-slate-500 w-[5px] h-[5px] left-[78px] "
}
`}
/>
<div className="text-xs pl-4 pr-10">
{main_index === 0 && index === 0 ? (
<span style = {{marginRight: 6}}>Now</span>
) : (
new Date(historyItem.timestamp)
.toTimeString()
.split(":")
.slice(0, 2)
.join(":")
)}
</div>
<div className="text-xs overflow-hidden ">
<p className="truncate tracking-wide">
{main_index === 0 && index === 0
? "Current"
: historyItem.editor.prompt}
</p>
<div
className="mt font-medium"
style={{
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
{historyItem.modelsState.find(({selected})=> selected).name}
</div>
</div>
</div>
</div>
)
})}
</div>
)
})}
</div>
</div>
)
}
class EditorWrapper extends React.Component {
componentDidCatch() {
const {resetEditorState} = this.props
resetEditorState()
}
keyBindingFn(event: any) {
if (event.code === "Enter" && event.metaKey) {
return "ignore_enter"
}
if (event.metaKey && event.keyCode === 66) {
return "bold"
} else if (event.ctrlKey && event.keyCode === 66) {
return "bold"
}
return getDefaultKeyBinding(event)
}
handleKeyCommand(command: any, editorState: any) {
const {setEditorState} = this.props
if (command === "bold") {
setEditorState(RichUtils.toggleInlineStyle(editorState, "BOLD"))
return "handled"
}
if (command === "ignore_enter") {
return "handled"
}
return "not-handled"
}
render() {
const {editorState, setEditorState} = this.props
return (
<Editor
keyBindingFn={this.keyBindingFn.bind(this)}
handleKeyCommand={this.handleKeyCommand.bind(this)}
customStyleMap={styleMap}
editorState={editorState}
onChange={(editorState: any) => {
setEditorState(editorState)
}}
stripPastedStyles
/>
)
}
}
const PromptCompletionEditor = ({showDialog}) => {
const {editorContext, setEditorContext} = useContext(EditorContext)
const {parametersContext} = useContext(ParametersContext)
const {modelsStateContext} = useContext(ModelsStateContext)
const {
historyContext, addHistoryEntry, toggleShowHistory
} = useContext(HistoryContext)
const number_of_models_selected = modelsStateContext.filter(({selected}) => selected).length
const [status, setStatus] = React.useState<string[]>([])
const [output, setOutput] = React.useState<string[]>([])
const apiContext = useContext(APIContext)
const scrollRef = useRef(null)
const is_mac_os = navigator.platform.toUpperCase().indexOf("MAC") >= 0
const [_, signalRender] = React.useState(0);
const [generating, setGenerating] = React.useState<boolean>(false);
const cancel_callback = React.useRef<any>(null)
const { toast } = useToast()
const showProbabilitiesRef = useRef(parametersContext.showProbabilities)
const highlightModelsRef = useRef(parametersContext.highlightModels)
useEffect(() => {
showProbabilitiesRef.current = parametersContext.showProbabilities
highlightModelsRef.current = parametersContext.highlightModels
})
React.useEffect(() => {
return () => {
setEditorContext({
...editorContext,
internalState: convertToRaw(editorStateRef.current.getCurrentContent()),
prompt: editorStateRef.current.getCurrentContent().getPlainText()
}, true)
}
}, []);
useEffect(() => {
if (editorContext.internalState) {
setEditorState(
EditorState.createWithContent(convertFromRaw(editorContext.internalState),
createDecorator())
)
}
}, [editorContext.internalState])
const handleStreamingSubmit = async (
regenerate = false,
passedInPrompt = ""
) => {
const prompt = regenerate ? passedInPrompt : editorState.getCurrentContent().getPlainText();
setGenerating(true)
setEditorContext({
prePrompt: prompt,
previousInternalState: convertToRaw(editorState.getCurrentContent())
})
const _cancel_callback = apiContext.Inference.textCompletionRequest({
prompt: regenerate ? passedInPrompt : prompt,
models: modelsStateContext.map((modelState) => {
if(modelState.selected) {
return modelState
}
}).filter(Boolean)
})
cancel_callback.current = _cancel_callback
}
useEffect(() => {
const completionCallback = ({event, data, meta}) => {
switch (event) {
case "cancel":
setGenerating(false)
break;
case "close":
if (!meta.error)
addHistoryEntry(convertToRaw(editorStateRef.current.getCurrentContent()))
setEditorContext({
prompt: editorStateRef.current.getCurrentContent().getPlainText(),
internalState: convertToRaw(editorStateRef.current.getCurrentContent()),
})
setGenerating(false)
break;
case "completion":
setOutput(data[Object.keys(data)[0]])
signalRender((x) => x + 1)
break;
case "status":
const {message} = data
if (message.indexOf("[ERROR] ") === 0) {
showDialog({
title: "Model Error",
message: message.replace("[ERROR] ", ""),
})
}
break;
case "error":
switch(data) {
case "Too many pending requests":
showDialog({
title: "Too many pending requests",
message: "Please wait a few seconds before trying again.",
})
break;
case "Too many daily completions":
showDialog({
title: "Daily limit reached",
message: "It seems you've reached your daily limit of completions. Please try again tomorrow.",
})
break;
case "Unauthorized":
showDialog({
title: "Unauthorized",
message: "Please log in to use this feature.",
})
break;
default:
console.log("default error handling?")
showDialog({
title: "Error",
message: data,
})
break;
}
break;
default:
console.log("Unknown event", event, data);
break;
}
}
apiContext.Inference.subscribeTextCompletion(completionCallback)
return () => {
apiContext.Inference.unsubscribeTextCompletion(completionCallback);
};
}, []);
const handleSubmit = async (regenerate = false, passedInPrompt = "") => {
return handleStreamingSubmit(regenerate, passedInPrompt)
}
useMetaKeyPress(["Enter"], (event: any) => {
handleSubmit()
})
const abortCompletion = () => {
if (cancel_callback.current) {
cancel_callback.current()
}
}
useKeyPress(["Escape"], (event: any) => {
abortCompletion()
})
useMetaKeyPress(["u"], (event: any) => {
if (editorContext.prePrompt === "") {
return
} else {
handleUndoLast()
}
})
const regenerateKeyPress = (event: any) => {
event.preventDefault()
if (editorContext.prePrompt === "") {
return
} else {
handleUndoLast()
handleSubmit(true, editorContext.prePrompt)
}
}
useMetaKeyPress(["alt", "r"], regenerateKeyPress)
useMetaKeyPress(["alt", "®"], regenerateKeyPress)
const Decorated = (props: any) => {
const children = props.children
const entity = props.contentState.getEntity(props.entityKey)
const entityData = entity.getData()
const style = getDecoratedStyle(entityData.modelProvider, highlightModelsRef.current)
const probabilitiesMap = entityData.topNDistribution
const tokensMap = probabilitiesMap ? probabilitiesMap["tokens"] : []
const [popoverOpen, setPopoverOpen] = React.useState<boolean>(false)
if (entityData.message === props.decoratedText) {
let content = (
<span style={style} key={children[0].key} data-offset-key={children[0].key}>
{children}
</span>
)
if (probabilitiesMap && (tokensMap[props.decoratedText] != undefined && tokensMap[props.decoratedText].length > 0)) {
let percentage = Math.min(tokensMap[props.decoratedText][1] / probabilitiesMap.simpleProbSum, 1.0)
let f = chroma.scale(["#ff8886", "ffff00", "#96f29b"])
let highlight_color = f(percentage)
let custom_style = showProbabilitiesRef.current ? {
backgroundColor: highlight_color,
padding: "2px 0",
} : style
let popoverContent =
(
<div className="shadow-xl shadow-inner rounded-sm bg-white mb-2" data-container="body">
<ul key={children[0].key} className="grid pt-4">
{
Object.entries(tokensMap).map((item, index) => {
return (
<li key={item + "-" + index + "-" + children[0].key} className={item[0] === entityData.message ? "bg-highlight-tokens w-full font-base text-white pl-4" : "pl-4 text-bg-slate-800"}>
{item[0]} = {tokensMap[item[0]][1]}%
</li>
)
})
}
</ul>
<div className="m-4 pb-4">
<div className="text-base">Total: {probabilitiesMap.logProbSum} logprob on 1 tokens</div>
<div className="text-xs">({probabilitiesMap.simpleProbSum}% probability covered in top {Object.keys(probabilitiesMap.tokens).length} logits)</div>
</div>
</div>
)
content = (
<Popover
isOpen={popoverOpen}
onClickOutside={() => setPopoverOpen(false)}
positions={["bottom", "top", "left", "right"]}
content={popoverContent}
containerStyle={{zIndex: "1000"}}
>
<span style={custom_style} className={popoverOpen ? "font-bold" : ""} id={children[0].key} key={children[0].key} data-offset-key={children[0].key} onClick={() => {showProbabilitiesRef.current ? setPopoverOpen(!popoverOpen) : null}}>
{children}
</span>
</Popover>
)
}
return content
} else {
return <span data-offset-key={children[0].key}>{children}</span>
}
}
function findEntityRangesByType(entityType: any) {
return (contentBlock: any, callback: any, contentState: any) => {
contentBlock.findEntityRanges((character: any) => {
const entityKey = character.getEntity()
if (entityKey === null) {
return false
}
return contentState.getEntity(entityKey).getType() === entityType
}, callback)
}
}
const getEditorState = useCallback((): EditorState => {
return editorStateRef.current
}, [])
const createDecorator = () => {
return new CompositeDecorator([
{
strategy: findEntityRangesByType("HIGHLIGHTED_WORD"),
component: Decorated,
props: {
getEditorState,
},
},
])
}
const [editorState, setEditorState] = React.useState(
EditorState.moveFocusToEnd(EditorState.createWithContent(
editorContext.internalState !== null ? convertFromRaw(editorContext.internalState): ContentState.createFromText(editorContext.prompt),
createDecorator()
))
)
const editorStateRef = useRef<EditorState>(editorState)
useEffect(() => {
editorStateRef.current = editorState;
}, [editorState]);
useEffect(() => {
setEditorState(
EditorState.forceSelection(editorState, editorState.getSelection())
)
}, [parametersContext.showProbabilities, parametersContext.highlightModels])
const resetEditorState = () => {
setEditorState(
EditorState.moveFocusToEnd(EditorState.createWithContent(
ContentState.createFromText(""),
createDecorator()
))
)
}
useEffect(() => {
let current_editor_state = editorState;
try {
for(const output_entry of output) {
const currentContent = current_editor_state.getCurrentContent()
const blockMap = currentContent.getBlockMap()
const key = blockMap.last().getKey()
const length = blockMap.last().getLength()
const selection = new SelectionState({
anchorKey: key,
anchorOffset: length,
focusKey: key,
focusOffset: length,
})
currentContent.createEntity("HIGHLIGHTED_WORD", "MUTABLE", output_entry)
const entityKey = currentContent.getLastCreatedEntityKey()
const textWithInsert = Modifier.insertText(
currentContent,
selection,
output_entry.message,
null,
entityKey
)
const editorWithInsert = EditorState.push(
current_editor_state,
textWithInsert,
"insert-characters"
)
const newEditorState = EditorState.moveSelectionToEnd(editorWithInsert)
const finalEditorState = EditorState.forceSelection(
newEditorState,
newEditorState.getSelection()
)
current_editor_state = finalEditorState
if (scrollRef.current) {
const scrollEl = scrollRef.current
scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.clientHeight
}
}
} catch (e) {
console.log("Error in editor update", e)
}
setEditorState(current_editor_state)
editorStateRef.current = current_editor_state
}, [output])
useEffect(() => {
if (status.message && status.message.indexOf("[QUEUE] ") === 0) {
toast({
title: "Inference request queued",
description: "We're currently experiencing high load, your compeletion request is in a queue and will be compeleted shortly"
})
return
}
if (status.message && status.message.indexOf("[ERROR] ") === 0) {
showDialog({
title: "An error occured!",
description: status.message.replace("[ERROR] ", "")
})
return
}
}, [status])
const handleUndoLast = () => {
setEditorState(
EditorState.moveFocusToEnd(
EditorState.createWithContent(
convertFromRaw(editorContext.previousInternalState),
createDecorator()
)
)
)
setEditorContext({
prompt: editorContext.prePrompt,
prePrompt: "",
previousInternalState: null,
})
}
return (
<form
onSubmit={(e) => {
e.preventDefault()
handleSubmit()
}}
className="flex flex-col grow basis-auto lg:max-w-[calc(100%-266px)]"
>
<div
id="editor"
ref={scrollRef}
className="overflow-y-auto editor-container h-full w-full py-3 px-3 text-base rounded-md border border-slate-300"
>
<EditorWrapper
editorState = {editorState}
setEditorState= {setEditorState}
resetEditorState = {resetEditorState}
/>
</div>
<div className="flex space-x-2 mb-8">
{generating && (
<TooltipProvider>
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<div>
<Button
type="button"
variant="subtle"
className="hidden lg:inline-flex md:inline-flex items-center mt-4 text-sm font-medium text-center"
onClick={(e) => {
e.stopPropagation()
abortCompletion()
}}
>
{" "}
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Cancel Generation
</Button>
<Button
type="button"
variant="subtle"
className="inline-flex lg:hidden md:hidden items-center mt-4 text-sm font-medium text-center"
onClick={(e) => {
e.stopPropagation()
abortCompletion()
}}
>
{" "}
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Cancel
</Button>
</div>
</TooltipTrigger>
<TooltipContent
side="top"
align="center"
className="bg-slate-600 text-white hidden hidden md:block"
>
Cancel Generation
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
Esc
</kbd>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<TooltipProvider>
{!generating && (
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<Button
variant="default"
className="bg-emerald-500 hover:bg-emerald-700 inline-flex items-center mt-4 text-sm font-medium text-center"
type="submit"
value="submit"
disabled={number_of_models_selected === 0}
>
Submit
</Button>
</TooltipTrigger>
<TooltipContent
side="top"
align="center"
className="bg-slate-600 text-white hidden md:block"
>
Submit
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
{is_mac_os ? "⌘" : "Control"}
</kbd>
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
Enter
</kbd>
</TooltipContent>
</Tooltip>
)}
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<div>
<Button
type="button"
variant="subtle"
className="inline-flex items-center mt-4 text-sm font-medium text-center"
onClick={handleUndoLast}
disabled={editorContext.prePrompt === ""}
>
Undo
</Button>
</div>
</TooltipTrigger>
<TooltipContent
side="top"
align="center"
className="bg-slate-600 text-white hidden md:block"
>
Undo Last
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
{is_mac_os ? "⌘" : "Control"}
</kbd>
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
U
</kbd>
</TooltipContent>
</Tooltip>
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<div>
<Button
type="button"
variant="subtle"
className="inline-flex items-center mt-4 text-sm font-medium text-center"
onClick={(e) => {
e.stopPropagation()
handleUndoLast()
handleSubmit(true, editorContext.prePrompt)
}}
disabled={editorContext.prePrompt === ""}
>
Regenerate
</Button>
</div>
</TooltipTrigger>
<TooltipContent
side="top"
align="center"
className="bg-slate-600 text-white hidden md:block"
>
Regenerate
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
{is_mac_os ? "⌘" : "Control"}
</kbd>
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
{is_mac_os ? "Option" : "Alt"}
</kbd>
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
R
</kbd>
</TooltipContent>
</Tooltip>
<Tooltip delayDuration={100}>
<TooltipTrigger asChild>
<Button
type="button"
variant="subtle"
className="inline-flex items-center py-2.5 mt-4 text-sm font-medium text-center hidden lg:inline-flex"
onClick={(e) => {
e.stopPropagation()
toggleShowHistory()
}}
disabled={historyContext.entries.length == 0}
>
<HistoryIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent
side="top"
align="center"
className="bg-slate-600 text-white"
>
Show History
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
{is_mac_os ? "⌘" : "Control"}
</kbd>
<kbd className="align-top pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-slate-100 bg-slate-100 px-1.5 font-mono text-[10px] font-medium text-slate-600 opacity-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400">
H
</kbd>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</form>
)
}
const CustomAlertDialogue = ({dialog}) => {
const [openDialog, setOpenDialog] = React.useState<boolean>(false)
const [_dialogue, _setDialogue] = React.useState<any>({
title: "",
message: ""
})
useEffect(() => {
if (!openDialog && dialog.title !== "" && dialog.message !== "") {
_setDialogue({
title: dialog.title,
message: dialog.message
})
setOpenDialog(true)
}
}, [dialog])
return (
<AlertDialog open={openDialog} onOpenChange={setOpenDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{_dialogue.title}</AlertDialogTitle>
<AlertDialogDescription className="text-base text-slate-700 dark:text-slate-400">
{_dialogue.message}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction>Ok</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
export default function Playground() {
const apiContext = useContext(APIContext)
const {historyContext, toggleShowHistory} = useContext(HistoryContext)
const [openHistorySheet, setOpenHistorySheet] = React.useState<boolean>(false)
const [openParameterSheet, setSaveOpenParameterSheet] = React.useState<boolean>(false)
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const [deleteHistoryDialog, setDeleteHistoryDialog] = React.useState<boolean>(false)
const [dialog, showDialog] = React.useState({
title: "",
message: ""
})
const historySidebar = (<HistorySidePanel />)
const parameterSidebar = (<ParameterSidePanel showModelDropdown={true} showModelList ={false} />)
useMetaKeyPress(["h"], (event: any) => {
event.preventDefault()
if (historyContext.entries.length > 0 && !isMobile) toggleShowHistory()
})
const mobileOpenParametersButton = (
<Sheet open={openParameterSheet} onOpenChange={setSaveOpenParameterSheet}>
<SheetTrigger asChild>
<Button variant="subtle" className="lg:hidden">
<Settings2 className="h-6 w-6" />
</Button>
</SheetTrigger>
<SheetContent className="w-[80vw] p-4 pt-8">
{parameterSidebar}
</SheetContent>
</Sheet>
)
const mobileOpenHistoryButton = (
<Sheet open={openHistorySheet} onOpenChange={() => {
if (historyContext.entries.length == 0) {
alert("No history to show!")
} else {
toggleShowHistory(!openHistorySheet)
}
setOpenHistorySheet(!openHistorySheet)
}}>
<SheetTrigger asChild>
<Button variant="subtle" className="lg:hidden">
<HistoryIcon className="h-4 w-4" />
</Button>
</SheetTrigger>
<SheetContent className="w-[80vw]">{historySidebar}</SheetContent>
</Sheet>
)
return (
<div className="flex flex-col h-full">
<NavBar tab="playground">
gitextract_ydrkwoag/
├── .dockerignore
├── .gitignore
├── LICENSE
├── README.md
├── app/
│ ├── .parcelrc
│ ├── .postcssrc
│ ├── .prettierrc
│ ├── package.json
│ ├── src/
│ │ ├── app.tsx
│ │ ├── components/
│ │ │ ├── inputarea.tsx
│ │ │ ├── multi-select.tsx
│ │ │ ├── navbar.tsx
│ │ │ ├── parameter-slider.tsx
│ │ │ ├── parameters-side-panel.tsx
│ │ │ └── ui/
│ │ │ ├── alert-dialog.tsx
│ │ │ ├── button.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── dialog-sheet-base.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── navigation-menu.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── right-sheet.tsx
│ │ │ ├── scroll-area.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── sheet.tsx
│ │ │ ├── slider.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toaster.tsx
│ │ │ └── tooltip.tsx
│ │ ├── error-page.tsx
│ │ ├── hooks/
│ │ │ ├── ui/
│ │ │ │ └── use-toast.tsx
│ │ │ └── use-breakpoint.ts
│ │ ├── index.css
│ │ ├── index.html
│ │ ├── index.tsx
│ │ ├── lib/
│ │ │ ├── ctrl-meta-keypress.tsx
│ │ │ ├── editor-styles.tsx
│ │ │ ├── keypress.tsx
│ │ │ ├── meta-keypress.tsx
│ │ │ └── utils.ts
│ │ └── pages/
│ │ ├── compare.tsx
│ │ ├── index.tsx
│ │ ├── playground.tsx
│ │ └── settings.tsx
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── build.py
├── dockerfile
├── pyproject.toml
└── server/
├── __init__.py
├── app.py
├── lib/
│ ├── __init__.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── inference.py
│ │ ├── provider.py
│ │ └── response_utils.py
│ ├── entities.py
│ ├── event_emitter.py
│ ├── inference/
│ │ ├── __init__.py
│ │ └── huggingface/
│ │ ├── __init__.py
│ │ ├── generator.py
│ │ ├── helpers.py
│ │ └── hf.py
│ ├── sse.py
│ ├── sseserver.py
│ └── storage.py
├── models.json
└── requirements.txt
SYMBOL INDEX (223 symbols across 33 files)
FILE: app/src/app.tsx
constant DEFAULT_PARAMETERS_STATE (line 16) | const DEFAULT_PARAMETERS_STATE = {
constant DEFAULT_EDITOR_STATE (line 29) | const DEFAULT_EDITOR_STATE = {
constant DEFAULT_HISTORY_STATE (line 35) | const DEFAULT_HISTORY_STATE = {
constant DEFAULT_CONTEXTS (line 41) | const DEFAULT_CONTEXTS = {
constant SETTINGS (line 63) | let SETTINGS = null;
function useDebounce (line 130) | function useDebounce(func, delay) {
function createTextCompletionRequest (line 216) | function createTextCompletionRequest({prompt, models}) {
function createChatCompletionRequest (line 225) | function createChatCompletionRequest(prompt, model) {
function createCompletionRequest (line 231) | function createCompletionRequest(url, payload, subscribers) {
function createCompletionsBuffer (line 253) | function createCompletionsBuffer(models) {
function bindSSEEvents (line 261) | function bindSSEEvents(sse_request, completionsBuffer, requestState, bef...
function close_sse (line 309) | function close_sse(sse_request, requestState, beforeUnloadHandler, subsc...
function bulkWrite (line 318) | function bulkWrite(completionsBuffer, requestState, subscribers) {
function ProviderWithRoutes (line 602) | function ProviderWithRoutes() {
function App (line 642) | function App() {
FILE: app/src/components/inputarea.tsx
function InputArea (line 4) | function InputArea() {
FILE: app/src/components/multi-select.tsx
type MultiSelectProps (line 7) | interface MultiSelectProps {
FILE: app/src/components/navbar.tsx
function NavBar (line 4) | function NavBar({ tab, children }: any) {
FILE: app/src/components/parameter-slider.tsx
type ParameterSliderProps (line 14) | interface ParameterSliderProps {
FILE: app/src/components/ui/button.tsx
type ButtonProps (line 36) | interface ButtonProps
FILE: app/src/components/ui/input.tsx
type InputProps (line 5) | interface InputProps
FILE: app/src/components/ui/sheet.tsx
type DialogPortalProps (line 41) | interface DialogPortalProps
type DialogContentProps (line 76) | interface DialogContentProps
FILE: app/src/components/ui/textarea.tsx
type TextareaProps (line 5) | interface TextareaProps
FILE: app/src/components/ui/toast.tsx
type ToastProps (line 114) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement (line 116) | type ToastActionElement = React.ReactElement<typeof ToastAction>
FILE: app/src/components/ui/toaster.tsx
function Toaster (line 14) | function Toaster() {
FILE: app/src/error-page.tsx
function ErrorPage (line 3) | function ErrorPage() {
FILE: app/src/hooks/ui/use-toast.tsx
constant TOAST_LIMIT (line 6) | const TOAST_LIMIT = 1
constant TOAST_REMOVE_DELAY (line 7) | const TOAST_REMOVE_DELAY = 1000
type ToasterToast (line 9) | type ToasterToast = ToastProps & {
function genId (line 25) | function genId() {
type ActionType (line 30) | type ActionType = typeof actionTypes
type Action (line 32) | type Action =
type State (line 50) | interface State {
function dispatch (line 127) | function dispatch(action: Action) {
type Toast (line 134) | interface Toast extends Omit<ToasterToast, "id"> {}
function toast (line 136) | function toast({ ...props }: Toast) {
function useToast (line 165) | function useToast() {
FILE: app/src/hooks/use-breakpoint.ts
type BreakpointKey (line 10) | type BreakpointKey = keyof typeof breakpoints
function useBreakpoint (line 12) | function useBreakpoint<K extends BreakpointKey>(breakpointKey: any) {
FILE: app/src/lib/editor-styles.tsx
function getDecoratedStyle (line 53) | function getDecoratedStyle(provider: string, showHighlights: boolean) {
FILE: app/src/lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
function handleSelectModel (line 8) | function handleSelectModel(modelState, modelsStateContext, setModelsStat...
FILE: app/src/pages/compare.tsx
function insertLineBreaks (line 89) | function insertLineBreaks(str) {
function findEntityRangesByType (line 275) | function findEntityRangesByType(entityType: any) {
function PromptEditor (line 436) | function PromptEditor({editorState, setEditorState, ...props}: any) {
function PromptArea (line 512) | function PromptArea({showDialog}) {
function Compare (line 968) | function Compare() {
FILE: app/src/pages/playground.tsx
class EditorWrapper (line 246) | class EditorWrapper extends React.Component {
method componentDidCatch (line 247) | componentDidCatch() {
method keyBindingFn (line 252) | keyBindingFn(event: any) {
method handleKeyCommand (line 265) | handleKeyCommand(command: any, editorState: any) {
method render (line 278) | render() {
function findEntityRangesByType (line 552) | function findEntityRangesByType(entityType: any) {
function Playground (line 926) | function Playground() {
FILE: app/src/pages/settings.tsx
type Provider (line 12) | interface Provider {
type Model (line 21) | interface Model {
type ProviderProps (line 27) | interface ProviderProps {
type AllSelectedModelsProps (line 250) | interface AllSelectedModelsProps {
function Settings (line 303) | function Settings() {
FILE: server/app.py
function warning_on_one_line (line 30) | def warning_on_one_line(message, category, filename, lineno, file=None, ...
function serve (line 42) | def serve(path):
function page_not_found (line 49) | def page_not_found(i):
function before_request (line 54) | def before_request():
class RedirectStderr (line 62) | class RedirectStderr:
method __init__ (line 63) | def __init__(self, new_stderr):
method __enter__ (line 67) | def __enter__(self):
method __exit__ (line 71) | def __exit__(self, exc_type, exc_val, exc_tb):
function redirect_stderr (line 75) | def redirect_stderr(new_stderr):
class MonitorThread (line 79) | class MonitorThread(threading.Thread):
method __init__ (line 80) | def __init__(self, model, output_buffer):
method run (line 87) | def run(self):
method stop (line 134) | def stop(self):
class NotificationManager (line 137) | class NotificationManager:
method __init__ (line 138) | def __init__(self, sse_queue: SSEQueueWithTopic):
method __model_added_callback__ (line 146) | def __model_added_callback__(self, model_name, model):
method __model_updated_callback__ (line 161) | def __model_updated_callback__(self, model_name, model):
method __model_download_update_callback__ (line 176) | def __model_download_update_callback__(self, _, model, progress):
class DownloadManager (line 193) | class DownloadManager:
method __init__ (line 194) | def __init__(self, storage: Storage):
method __initialization_check__ (line 203) | def __initialization_check__(self):
method __model_added_callback__ (line 236) | def __model_added_callback__(self, model_name, model):
method __download_loop__ (line 240) | def __download_loop__(self):
class GlobalStateManager (line 271) | class GlobalStateManager:
method __init__ (line 272) | def __init__(self, storage):
method get_storage (line 285) | def get_storage(self):
method get_sse_manager (line 288) | def get_sse_manager(self):
method text_generation (line 291) | def text_generation(self, inference_request: InferenceRequest):
method get_announcer (line 319) | def get_announcer(self):
function cli (line 323) | def cli():
function run (line 334) | def run(host, port, debug, env, models, log_level):
function import_config (line 361) | def import_config(input):
function export_config (line 380) | def export_config(ctx, output):
FILE: server/lib/api/__init__.py
function add_cors_header (line 18) | def add_cors_header(response):
function set_app_context (line 23) | def set_app_context():
function all_models (line 27) | def all_models():
function enabled_models_names (line 42) | def enabled_models_names():
function enabled_models (line 55) | def enabled_models():
function providers (line 71) | def providers():
function providers_with_models (line 85) | def providers_with_models():
function providers_check_api_keys (line 104) | def providers_check_api_keys():
function notifications (line 120) | def notifications():
FILE: server/lib/api/inference.py
function set_app_context (line 20) | def set_app_context():
function stream_inference (line 24) | def stream_inference():
function is_valid_request_data (line 48) | def is_valid_request_data(data):
function create_inference_request (line 51) | def create_inference_request(model, storage, prompt, request_uuid):
function extract_model_data (line 65) | def extract_model_data(model):
function validate_parameters (line 68) | def validate_parameters(model, parameters):
function stream_response (line 82) | def stream_response(global_state, uuid):
function bulk_completions (line 101) | def bulk_completions(global_state, tasks: List[InferenceRequest]):
function split_tasks_by_provider (line 123) | def split_tasks_by_provider(tasks: List[InferenceRequest]) -> Tuple[List...
FILE: server/lib/api/provider.py
function set_app_context (line 16) | def set_app_context():
function verify_provider (line 20) | def verify_provider():
function provider_models (line 36) | def provider_models(provider_name):
function provider_model (line 50) | def provider_model(provider_name, model_name):
function provider_toggle_model (line 64) | def provider_toggle_model(provider_name, model_name):
function provider_models_search (line 95) | def provider_models_search(provider_name):
function provider_update_api_key (line 123) | def provider_update_api_key(provider_name):
FILE: server/lib/api/response_utils.py
function create_response_message (line 3) | def create_response_message(message: str, status_code: int) -> Response:
FILE: server/lib/entities.py
class Model (line 5) | class Model:
method __init__ (line 6) | def __init__(
method copy (line 16) | def copy(self):
method __repr__ (line 26) | def __repr__(self):
class ModelEncoder (line 29) | class ModelEncoder(json.JSONEncoder):
method __init__ (line 30) | def __init__(self, *args, serialize_as_list=True, **kwargs):
method default (line 34) | def default(self, obj):
class Provider (line 46) | class Provider:
method __init__ (line 47) | def __init__(
method has_model (line 63) | def has_model(self, model_name: str) -> bool:
method get_model (line 66) | def get_model(self, model_name: str) -> Model:
method update_model (line 71) | def update_model(self, model_name: str, model: Model) -> None:
method add_model (line 78) | def add_model(self, model: Model) -> None:
method remove_model (line 83) | def remove_model(self, model_name: str) -> None:
method copy (line 90) | def copy(self):
method __repr__ (line 102) | def __repr__(self):
class ProviderEncoder (line 105) | class ProviderEncoder(json.JSONEncoder):
method __init__ (line 106) | def __init__(self, *args, serialize_models_as_list=True, **kwargs):
method default (line 110) | def default(self, obj):
method to_camel_case (line 125) | def to_camel_case(snake_str):
FILE: server/lib/event_emitter.py
class EVENTS (line 4) | class EVENTS(Enum):
class Singleton (line 13) | class Singleton(type):
method __call__ (line 17) | def __call__(cls, *args, **kwargs):
class EventEmitter (line 23) | class EventEmitter(metaclass=Singleton):
method __init__ (line 24) | def __init__(self):
method on (line 28) | def on(self, event: EVENTS, listener):
method off (line 35) | def off(self, event, listener):
method emit (line 40) | def emit(self, event: EVENTS, *args, **kwargs):
FILE: server/lib/inference/__init__.py
class ProviderDetails (line 23) | class ProviderDetails:
class InferenceRequest (line 33) | class InferenceRequest:
class ProablityDistribution (line 51) | class ProablityDistribution:
class InferenceResult (line 63) | class InferenceResult:
class InferenceAnnouncer (line 84) | class InferenceAnnouncer:
method __init__ (line 85) | def __init__(self, sse_topic):
method __format_message__ (line 89) | def __format_message__(self, event: str, infer_result: InferenceResult...
method announce (line 110) | def announce(self, infer_result: InferenceResult, event: str):
method cancel_callback (line 125) | def cancel_callback(self, message):
class InferenceManager (line 132) | class InferenceManager:
method __init__ (line 133) | def __init__(self, sse_topic):
method __error_handler__ (line 136) | def __error_handler__(self, inference_fn: InferenceFunction, provider_...
method __openai_chat_generation__ (line 201) | def __openai_chat_generation__(self, provider_details: ProviderDetails...
method __openai_text_generation__ (line 258) | def __openai_text_generation__(self, provider_details: ProviderDetails...
method openai_text_generation (line 327) | def openai_text_generation(self, provider_details: ProviderDetails, in...
method __cohere_text_generation__ (line 334) | def __cohere_text_generation__(self, provider_details: ProviderDetails...
method cohere_text_generation (line 378) | def cohere_text_generation(self, provider_details: ProviderDetails, in...
method __huggingface_text_generation__ (line 381) | def __huggingface_text_generation__(self, provider_details: ProviderDe...
method huggingface_text_generation (line 460) | def huggingface_text_generation(self, provider_details: ProviderDetail...
method __forefront_text_generation__ (line 463) | def __forefront_text_generation__(self, provider_details: ProviderDeta...
method forefront_text_generation (line 566) | def forefront_text_generation(self, provider_details: ProviderDetails,...
method __local_text_generation__ (line 569) | def __local_text_generation__(self, provider_details: ProviderDetails,...
method local_text_generation (line 601) | def local_text_generation(self, provider_details: ProviderDetails, inf...
method __anthropic_text_generation__ (line 604) | def __anthropic_text_generation__(self, provider_details: ProviderDeta...
method anthropic_text_generation (line 639) | def anthropic_text_generation(self, provider_details: ProviderDetails,...
method __aleph_alpha_text_generation__ (line 642) | def __aleph_alpha_text_generation__(self, provider_details: ProviderDe...
method aleph_alpha_text_generation (line 667) | def aleph_alpha_text_generation(self, provider_details: ProviderDetail...
method get_announcer (line 670) | def get_announcer(self):
FILE: server/lib/inference/huggingface/generator.py
function greedy_search_generator (line 15) | def greedy_search_generator(
FILE: server/lib/inference/huggingface/helpers.py
class StoppingCriteriaSub (line 4) | class StoppingCriteriaSub(StoppingCriteria):
method __init__ (line 5) | def __init__(self, stops = []):
method __call__ (line 8) | def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTen...
FILE: server/lib/inference/huggingface/hf.py
class HFInference (line 23) | class HFInference:
method __init__ (line 27) | def __init__(self, model_name: str):
method load_model (line 32) | def load_model(self, model_name: str) -> (PreTrainedModel, PreTrainedT...
method generate (line 68) | def generate(self,
FILE: server/lib/sse.py
class Message (line 16) | class Message(object):
method __init__ (line 20) | def __init__(self, data, type=None, id=None, retry=None):
method to_dict (line 37) | def to_dict(self):
method __str__ (line 51) | def __str__(self):
method __repr__ (line 69) | def __repr__(self):
method __eq__ (line 87) | def __eq__(self, other):
class ServerSentEventsBlueprint (line 97) | class ServerSentEventsBlueprint(Blueprint):
method sse_server (line 103) | def sse_server(self):
method publish (line 112) | def publish(self, data, type=None, id=None, retry=None, channel='sse'):
method messages (line 133) | def messages(self, channel='sse'):
method stream (line 158) | def stream(self):
FILE: server/lib/sseserver.py
class SSEQueue (line 6) | class SSEQueue:
method __init__ (line 7) | def __init__(self):
method listen (line 10) | def listen(self):
method publish (line 16) | def publish(self, message: str):
class SSEQueueWithTopic (line 24) | class SSEQueueWithTopic:
method __init__ (line 25) | def __init__(self):
method listen (line 28) | def listen(self, topic: str):
method publish (line 34) | def publish(self, topic: str, message: str):
method add_topic (line 40) | def add_topic(self, topic: str):
method get_topic (line 46) | def get_topic(self, topic: str):
method remove_topic (line 52) | def remove_topic(self, topic: str):
FILE: server/lib/storage.py
class Storage (line 24) | class Storage:
method __init__ (line 25) | def __init__(self, models_json_path: str = None, env_file_path: str = ...
method __initialize_config__ (line 73) | def __initialize_config__(self, models_json_path: str = None):
method get_models (line 134) | def get_models(self) -> List[Model]:
method get_enabled_models (line 137) | def get_enabled_models(self) -> List[Model]:
method get_enabled_models_names (line 140) | def get_enabled_models_names(self) -> List[str]:
method get_enabled_models_by_provider (line 143) | def get_enabled_models_by_provider(self) -> Dict[str, List[Model]]:
method get_model (line 152) | def get_model(self, model_name: str) -> Model:
method get_providers (line 155) | def get_providers(self) -> List[Provider]:
method get_provider_names (line 158) | def get_provider_names(self) -> List[str]:
method get_provider (line 161) | def get_provider(self, provider_name: str) -> Provider:
method update_provider_api_key (line 171) | def update_provider_api_key(self, provider_name: str, api_key: str):
method __update___ (line 185) | def __update___(self, event: str, *args, **kwargs):
method update_model (line 195) | def update_model(self, model_name: str, model: Model):
method __save__ (line 210) | def __save__(self):
method import_config (line 238) | def import_config(config_path: str):
method export_config (line 249) | def export_config(output_path: str):
Condensed preview — 74 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (397K chars).
[
{
"path": ".dockerignore",
"chars": 4,
"preview": ".env"
},
{
"path": ".gitignore",
"chars": 1591,
"preview": "# These files should be generated during dev/publishing\nserver/static/*\n#mISC\n.DS_Store\n.vscode/\n# Logs\nlogs\n*.log\nnpm-d"
},
{
"path": "LICENSE",
"chars": 1074,
"preview": "MIT License\n\nCopyright (c) [2023] [OpenPlayground]\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "README.md",
"chars": 4776,
"preview": "# openplayground\n\nAn LLM playground you can run on your laptop.\n\nhttps://user-images.githubusercontent.com/111631/227399"
},
{
"path": "app/.parcelrc",
"chars": 198,
"preview": "{\n \"extends\": \"@parcel/config-default\",\n \"compressors\": {\n \"*.{html,css,js,svg,map}\": [\n \"...\",\n "
},
{
"path": "app/.postcssrc",
"chars": 44,
"preview": "{\n \"plugins\": {\n \"tailwindcss\": {}\n }\n}"
},
{
"path": "app/.prettierrc",
"chars": 46,
"preview": "{\n \"trailingComma\": \"es5\",\n \"semi\": false\n}\n"
},
{
"path": "app/package.json",
"chars": 1965,
"preview": "{\n \"devDependencies\": {\n \"@parcel/compressor-brotli\": \"^2.8.3\",\n \"@parcel/compressor-gzip\": \"^2.8.3\",\n \"@parce"
},
{
"path": "app/src/app.tsx",
"chars": 19506,
"preview": "import React, { useEffect } from \"react\"\nimport {Playground, Compare, Settings} from \"./pages\"\nimport {SSE} from \"sse.js"
},
{
"path": "app/src/components/inputarea.tsx",
"chars": 385,
"preview": "import { Button } from \"@/components/ui/button\"\nimport { Textarea } from \"@/components/ui/textarea\"\n\nexport function Inp"
},
{
"path": "app/src/components/multi-select.tsx",
"chars": 2532,
"preview": "import React from \"react\"\n\nimport CreatableSelect from \"react-select/creatable\"\nimport { useBreakpoint } from \"../hooks/"
},
{
"path": "app/src/components/navbar.tsx",
"chars": 1871,
"preview": "import React from \"react\"\nimport { Link } from \"react-router-dom\"\n\nexport default function NavBar({ tab, children }: any"
},
{
"path": "app/src/components/parameter-slider.tsx",
"chars": 3743,
"preview": "import { Tooltip, TooltipTrigger, TooltipContent } from \"./ui/tooltip\"\nimport React, {\n HTMLInputTypeAttribute,\n React"
},
{
"path": "app/src/components/parameters-side-panel.tsx",
"chars": 21039,
"preview": "import React, { useContext } from \"react\"\nimport { Checkbox } from \"./ui/checkbox\"\nimport { useBreakpoint } from \"../hoo"
},
{
"path": "app/src/components/ui/alert-dialog.tsx",
"chars": 5156,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\"\n\nimpor"
},
{
"path": "app/src/components/ui/button.tsx",
"chars": 2088,
"preview": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"../../l"
},
{
"path": "app/src/components/ui/checkbox.tsx",
"chars": 1036,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Chec"
},
{
"path": "app/src/components/ui/dialog-sheet-base.tsx",
"chars": 3790,
"preview": "import * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from \"lucide-react"
},
{
"path": "app/src/components/ui/dialog.tsx",
"chars": 3817,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from"
},
{
"path": "app/src/components/ui/input.tsx",
"chars": 791,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"../../lib/utils\"\n\nexport interface InputProps\n extends React.InputH"
},
{
"path": "app/src/components/ui/label.tsx",
"chars": 591,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\n\nimport { cn } from"
},
{
"path": "app/src/components/ui/navigation-menu.tsx",
"chars": 5122,
"preview": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva }"
},
{
"path": "app/src/components/ui/popover.tsx",
"chars": 1104,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\n\nimport { cn } "
},
{
"path": "app/src/components/ui/right-sheet.tsx",
"chars": 3967,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from"
},
{
"path": "app/src/components/ui/scroll-area.tsx",
"chars": 1672,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\n\nimport "
},
{
"path": "app/src/components/ui/select.tsx",
"chars": 3829,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { Check, C"
},
{
"path": "app/src/components/ui/separator.tsx",
"chars": 786,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { c"
},
{
"path": "app/src/components/ui/sheet.tsx",
"chars": 4828,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from"
},
{
"path": "app/src/components/ui/slider.tsx",
"chars": 2174,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SliderPrimitive from \"@radix-ui/react-slider\"\n\nimport { cn } fr"
},
{
"path": "app/src/components/ui/switch.tsx",
"chars": 1242,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\"\n\nimport { cn } f"
},
{
"path": "app/src/components/ui/textarea.tsx",
"chars": 813,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"../../lib/utils\"\n\nexport interface TextareaProps\n extends React.Tex"
},
{
"path": "app/src/components/ui/toast.tsx",
"chars": 5186,
"preview": "import * as React from \"react\"\nimport * as ToastPrimitives from \"@radix-ui/react-toast\"\nimport { VariantProps, cva } fro"
},
{
"path": "app/src/components/ui/toaster.tsx",
"chars": 780,
"preview": "\"use client\"\n\nimport { useToast } from \"../../hooks/ui/use-toast\"\n\nimport {\n Toast,\n ToastClose,\n ToastDescription,\n "
},
{
"path": "app/src/components/ui/tooltip.tsx",
"chars": 1217,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } "
},
{
"path": "app/src/error-page.tsx",
"chars": 345,
"preview": "import { useRouteError } from \"react-router-dom\"\n\nexport default function ErrorPage() {\n const error = useRouteError()\n"
},
{
"path": "app/src/hooks/ui/use-toast.tsx",
"chars": 3800,
"preview": "// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport { ToastActionElement, type ToastProps } fr"
},
{
"path": "app/src/hooks/use-breakpoint.ts",
"chars": 729,
"preview": "// @ts-ignore\n// https://stackoverflow.com/a/71098593/2865073\nimport { useMediaQuery } from \"react-responsive\"\nimport re"
},
{
"path": "app/src/index.css",
"chars": 4992,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml, body, #app {\n height: 100%;\n max-height: 100%;\n fon"
},
{
"path": "app/src/index.html",
"chars": 912,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>OpenPlayground</title>\n <meta name=\"description\" content=\"Open s"
},
{
"path": "app/src/index.tsx",
"chars": 205,
"preview": "import React from \"react\"\nimport { createRoot } from \"react-dom/client\"\nimport App from \"./app\"\n \nconst container = doc"
},
{
"path": "app/src/lib/ctrl-meta-keypress.tsx",
"chars": 734,
"preview": "import { useCallback, useEffect, useLayoutEffect, useRef } from \"react\"\n\nexport const useCtrlMetaKeyPress = (keys: any, "
},
{
"path": "app/src/lib/editor-styles.tsx",
"chars": 1805,
"preview": "export const styleMap = {\n HIGHLIGHT: {\n backgroundColor: \"#faed27\",\n },\n NORMAL: {\n backgroundColor:"
},
{
"path": "app/src/lib/keypress.tsx",
"chars": 693,
"preview": "import { useCallback, useEffect, useLayoutEffect, useRef } from \"react\"\n\nexport const useKeyPress = (keys: any, callback"
},
{
"path": "app/src/lib/meta-keypress.tsx",
"chars": 733,
"preview": "import { useCallback, useEffect, useLayoutEffect, useRef } from \"react\"\n\nexport const useMetaKeyPress = (keys: any, call"
},
{
"path": "app/src/lib/utils.ts",
"chars": 1599,
"preview": "import { ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassVal"
},
{
"path": "app/src/pages/compare.tsx",
"chars": 32473,
"preview": "\nimport React, {\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n forwardRef,\n useImperativeHandle,\n} "
},
{
"path": "app/src/pages/index.tsx",
"chars": 146,
"preview": "import Playground from \"./playground\"\nimport Compare from \"./compare\"\nimport Settings from \"./settings\"\n\nexport { Playgr"
},
{
"path": "app/src/pages/playground.tsx",
"chars": 37578,
"preview": "import React, {\n useCallback, useContext, useEffect, useRef\n} from \"react\"\nimport {\n Editor,\n EditorState,\n Composit"
},
{
"path": "app/src/pages/settings.tsx",
"chars": 15913,
"preview": "import React, { useContext, useEffect, useState } from \"react\"\nimport NavBar from \"../components/navbar\"\nimport { Button"
},
{
"path": "app/tailwind.config.js",
"chars": 1095,
"preview": "const { fontFamily } = require(\"tailwindcss/defaultTheme\")\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports ="
},
{
"path": "app/tsconfig.json",
"chars": 555,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"sk"
},
{
"path": "app/tsconfig.tsbuildinfo",
"chars": 45535,
"preview": "{\"program\":{\"fileNames\":[\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es5.d.ts\",\"../../../.n"
},
{
"path": "build.py",
"chars": 330,
"preview": "import os\nimport shutil\nimport subprocess\n\nos.environ['NODE_ENV'] = 'production'\n\nos.chdir('./app')\n\nsubprocess.run(['pa"
},
{
"path": "dockerfile",
"chars": 996,
"preview": "# ==== FRONTEND ====\nFROM node:19-alpine AS builder\n\nWORKDIR /frontend\n\n# Copy the package.json and install dependencies"
},
{
"path": "pyproject.toml",
"chars": 977,
"preview": "[tool.poetry]\nname = \"openplayground\"\nversion = \"0.1.5\"\ndescription = \"An LLM playground you can run on your laptop.\"\nau"
},
{
"path": "server/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "server/app.py",
"chars": 15398,
"preview": "import click\nimport logging\nimport os\nimport warnings\nimport io\nimport queue\nimport sys\nfrom pathlib import Path\nimport "
},
{
"path": "server/lib/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "server/lib/api/__init__.py",
"chars": 4473,
"preview": "import logging\nimport json\n\nfrom ..entities import ProviderEncoder, ModelEncoder\nfrom ..sse import Message\nfrom .inferen"
},
{
"path": "server/lib/api/inference.py",
"chars": 4632,
"preview": "import logging\nimport json\nimport time\nimport threading\n\nfrom .response_utils import create_response_message\nfrom ..infe"
},
{
"path": "server/lib/api/provider.py",
"chars": 4538,
"preview": "import logging\nimport json\nimport requests\n\nfrom .response_utils import create_response_message\nfrom ..entities import M"
},
{
"path": "server/lib/api/response_utils.py",
"chars": 273,
"preview": "from flask import Response, jsonify\n\ndef create_response_message(message: str, status_code: int) -> Response:\n respon"
},
{
"path": "server/lib/entities.py",
"chars": 5034,
"preview": "import json\nfrom typing import List\nfrom .event_emitter import EventEmitter, EVENTS\n\nclass Model:\n def __init__(\n "
},
{
"path": "server/lib/event_emitter.py",
"chars": 1636,
"preview": "import threading\nfrom enum import Enum\n\nclass EVENTS(Enum):\n MODEL_ADDED = 'update_model_added'\n MODEL_REMOVED = '"
},
{
"path": "server/lib/inference/__init__.py",
"chars": 30430,
"preview": "import anthropic\nimport cachetools\nimport math\nimport openai\nimport os\nimport json\nimport requests\nimport sseclient\nimpo"
},
{
"path": "server/lib/inference/huggingface/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "server/lib/inference/huggingface/generator.py",
"chars": 7943,
"preview": "from typing import Optional, Union, List, Dict, Tuple\nimport warnings\nimport torch\nimport torch.distributed as dist\nimpo"
},
{
"path": "server/lib/inference/huggingface/helpers.py",
"chars": 363,
"preview": "from transformers import StoppingCriteria\nimport torch\n\nclass StoppingCriteriaSub(StoppingCriteria):\n def __init__(se"
},
{
"path": "server/lib/inference/huggingface/hf.py",
"chars": 5495,
"preview": "import os\nimport transformers\nimport psutil\nimport torch\nimport importlib\nimport logging\nimport warnings\n\nfrom transform"
},
{
"path": "server/lib/sse.py",
"chars": 6856,
"preview": "# coding=utf-8\n# credit to https://github.com/singingwolfboy/flask-sse\nfrom __future__ import unicode_literals\nimport si"
},
{
"path": "server/lib/sseserver.py",
"chars": 1864,
"preview": "# Thread Safe and Singular Global Instance of SSE Server\nimport queue\nimport logging\n\nlogger = logging.getLogger(__name_"
},
{
"path": "server/lib/storage.py",
"chars": 10480,
"preview": "import os\nimport importlib.resources as pkg_resources\nimport json\nimport logging\n\nfrom .event_emitter import EventEmitte"
},
{
"path": "server/models.json",
"chars": 15753,
"preview": "{\n \"openai\": {\n \"requiresAPIKey\": true,\n \"remoteInference\": true,\n \"models\": {\n \"text-ada-001\": {\n "
},
{
"path": "server/requirements.txt",
"chars": 284,
"preview": "aleph_alpha_client==2.16.1\nanthropic==0.2.3\ncachetools==5.3.0\nclick==8.1.3\nFlask==2.2.3\nFlask_Cors==3.0.10\nhuggingface_h"
}
]
About this extraction
This page contains the full source code of the nat/openplayground GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 74 files (367.5 KB), approximately 94.6k tokens, and a symbol index with 223 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.