[
  {
    "path": ".dockerignore",
    "content": ".env"
  },
  {
    "path": ".gitignore",
    "content": "# These files should be generated during dev/publishing\nserver/static/*\n#mISC\n.DS_Store\n.vscode/\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nyarn.lock*\npackage-lock.json\nCargo.lock\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Typescript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# Parcel related files\ndist/\n.cache/\n.parcel-cache/\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# C extensions\n*.so\n\n# Distribution / packaging\nbin/\n# build/\ndevelop-eggs/\ndist/\neggs/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\n.tox/\n.coverage\n.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\n# Rope\n.ropeproject\n\n# Django stuff:\n*.log\n*.pot\n\n# Sphinx documentation\ndocs/_build/"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) [2023] [OpenPlayground]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# openplayground\n\nAn LLM playground you can run on your laptop.\n\nhttps://user-images.githubusercontent.com/111631/227399583-39b23f48-9823-4571-a906-985dbe282b20.mp4\n\n#### Features\n\n- 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).\n- Full playground UI, including history, parameter tuning, keyboard shortcuts, and logprops.\n- Compare models side-by-side with the same prompt, individually tune model parameters, and retry with different parameters.\n- Automatically detects local models in your HuggingFace cache, and lets you install new ones.\n- Works OK on your phone.\n- Probably won't kill everyone.\n\n## Try on nat.dev\n\nTry the hosted version: [nat.dev](https://nat.dev).\n\n## How to install and run\n\n```sh\npip install openplayground\nopenplayground run\n```\n\nAlternatively, run it as a docker container:\n```sh\ndocker run --name openplayground -p 5432:5432 -d --volume openplayground:/web/config natorg/openplayground\n```\n\nThis runs a Flask process, so you can add the typical flags such as setting a different port `openplayground run -p 1235` and others.\n\n## How to run for development\n\n```sh\ngit clone https://github.com/nat/openplayground\ncd app && npm install && npx parcel watch src/index.html --no-cache\ncd server && pip3 install -r requirements.txt && cd .. && python3 -m server.app\n```\n\n## Docker\n\n```sh\ndocker build . --tag \"openplayground\"\ndocker run --name openplayground -p 5432:5432 -d --volume openplayground:/web/config openplayground\n```\n\nFirst volume is optional. It's used to store API keys, models settings.\n\n## Ideas for contributions\n\n- Add a token counter to the playground\n- Add a cost counter to the playground and the compare page\n- Measure and display time to first token\n- Setup automatic builds with GitHub Actions\n- 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!\n- Someone can help us make a homebrew package, and a dockerfile\n- Easier way to install open source models directly from openplayground, with `openplayground install <model>` or in the UI.\n- Find and fix bugs\n- ChatGPT UI, with turn-by-turn, markdown rendering, chatgpt plugin support, etc.\n- We will probably need multimodal inputs and outputs at some point in 2023\n\n### llama.cpp\n\n## Adding models to openplayground\n\nModels and providers have three types in openplayground:\n\n- Searchable\n- Local inference\n- API\n\nYou can add models in `server/models.json` with the following schema:\n\n#### Local inference\n\nFor models running locally on your device you can add them to openplayground like the following (a minimal example):\n\n```json\n\"llama\": {\n    \"api_key\" : false,\n    \"models\" : {\n        \"llama-70b\": {\n            \"parameters\": {\n                \"temperature\": {\n                    \"value\": 0.5,\n                    \"range\": [\n                        0.1,\n                        1.0\n                    ]\n                },\n            }\n        }\n    }\n}\n```\n\nKeep 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.\n\n#### API Provider Inference\n\nThis is for model providers like OpenAI, cohere, forefront, and more. You can connect them easily into openplayground (a minimal example):\n\n```json\n\"cohere\": {\n    \"api_key\" : true,\n    \"models\" : {\n        \"xlarge\": {\n            \"parameters\": {\n                \"temperature\": {\n                    \"value\": 0.5,\n                    \"range\": [\n                        0.1,\n                        1.0\n                    ]\n                },\n            }\n        }\n    }\n}\n```\n\nKeep 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.\n\n#### Searchable models\n\nWe use this for Huggingface Remote Inference models, the search endpoint is useful for scaling to N models in the settings page.\n\n```json\n\"provider_name\": {\n    \"api_key\": true,\n    \"search\": {\n        \"endpoint\": \"ENDPOINT_URL\"\n    },\n    \"parameters\": {\n        \"parameter\": {\n            \"value\": 1.0,\n            \"range\": [\n                0.1,\n                1.0\n            ]\n        },\n    }\n}\n```\n\n#### Credits\n\nInstigated 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).\n"
  },
  {
    "path": "app/.parcelrc",
    "content": "{\n    \"extends\": \"@parcel/config-default\",\n    \"compressors\": {\n        \"*.{html,css,js,svg,map}\": [\n        \"...\",\n        \"@parcel/compressor-gzip\",\n        \"@parcel/compressor-brotli\"\n    ]\n  }\n}"
  },
  {
    "path": "app/.postcssrc",
    "content": "{\n  \"plugins\": {\n    \"tailwindcss\": {}\n  }\n}"
  },
  {
    "path": "app/.prettierrc",
    "content": "{\n  \"trailingComma\": \"es5\",\n  \"semi\": false\n}\n"
  },
  {
    "path": "app/package.json",
    "content": "{\n  \"devDependencies\": {\n    \"@parcel/compressor-brotli\": \"^2.8.3\",\n    \"@parcel/compressor-gzip\": \"^2.8.3\",\n    \"@parcel/config-default\": \"^2.8.3\",\n    \"@types/chroma-js\": \"^2.4.0\",\n    \"@types/react\": \"^18.0.27\",\n    \"@types/react-dom\": \"^18.0.10\",\n    \"autoprefixer\": \"^10.4.13\",\n    \"buffer\": \"^5.7.1\",\n    \"events\": \"^3.3.0\",\n    \"https-browserify\": \"^1.0.0\",\n    \"parcel\": \"^2.8.3\",\n    \"postcss\": \"^8.4.21\",\n    \"process\": \"^0.11.10\",\n    \"punycode\": \"^1.4.1\",\n    \"querystring-es3\": \"^0.2.1\",\n    \"stream-http\": \"^3.2.0\",\n    \"tailwindcss\": \"^3.2.4\",\n    \"url\": \"^0.11.0\",\n    \"util\": \"^0.12.5\"\n  },\n  \"dependencies\": {\n    \"@radix-ui/react-alert-dialog\": \"^1.0.2\",\n    \"@radix-ui/react-checkbox\": \"^1.0.1\",\n    \"@radix-ui/react-dialog\": \"^1.0.3\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.0.2\",\n    \"@radix-ui/react-label\": \"^2.0.0\",\n    \"@radix-ui/react-navigation-menu\": \"^1.1.1\",\n    \"@radix-ui/react-popover\": \"^1.0.4\",\n    \"@radix-ui/react-scroll-area\": \"^1.0.2\",\n    \"@radix-ui/react-select\": \"^1.2.0\",\n    \"@radix-ui/react-separator\": \"^1.0.1\",\n    \"@radix-ui/react-slider\": \"^1.1.0\",\n    \"@radix-ui/react-switch\": \"^1.0.1\",\n    \"@radix-ui/react-toast\": \"^1.1.2\",\n    \"@radix-ui/react-tooltip\": \"^1.0.3\",\n    \"@types/draft-js\": \"^0.11.10\",\n    \"chroma-js\": \"^2.4.2\",\n    \"class-variance-authority\": \"^0.4.0\",\n    \"clsx\": \"^1.2.1\",\n    \"draft-js\": \"^0.11.7\",\n    \"event-source-polyfill\": \"^1.0.31\",\n    \"eventsource\": \"^2.0.2\",\n    \"immutable\": \"^4.2.3\",\n    \"localforage\": \"^1.10.0\",\n    \"lucide-react\": \"^0.108.0\",\n    \"match-sorter\": \"^6.3.1\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-responsive\": \"^9.0.2\",\n    \"react-router-dom\": \"^6.8.1\",\n    \"react-scroll\": \"^1.8.9\",\n    \"react-select\": \"^5.7.0\",\n    \"react-tiny-popover\": \"^7.2.3\",\n    \"react-transition-group\": \"^4.4.5\",\n    \"sort-by\": \"^1.2.0\",\n    \"sse.js\": \"^0.6.1\",\n    \"tailwind-merge\": \"^1.9.0\",\n    \"tailwindcss-animate\": \"^1.0.5\",\n    \"uuidv4\": \"^6.2.13\"\n  }\n}\n"
  },
  {
    "path": "app/src/app.tsx",
    "content": "import React, { useEffect } from \"react\"\nimport {Playground, Compare, Settings} from \"./pages\"\nimport {SSE} from \"sse.js\"\nimport {\n  EditorState,\n  convertFromRaw,\n} from \"draft-js\"\nimport {\n  BrowserRouter,\n  Route,\n  Routes,\n} from \"react-router-dom\"\nimport { Toaster } from \"./components/ui/toaster\"\nimport { useToast } from \"./hooks/ui/use-toast\"\n\nconst DEFAULT_PARAMETERS_STATE = {\n  temperature: 1.0,\n  maximumLength: 200,\n  topP: 0.9,\n  topK: 0,\n  repetitionPenalty: 1.0,\n  frequencyPenalty: 0.0,\n  presencePenalty: 0.0,\n  stopSequences: [],\n  highlightModels: true,\n  showProbabilities: false\n}\n\nconst DEFAULT_EDITOR_STATE = {\n  prompt: \"\",\n  prePrompt: \"\",\n  internalState: null\n}\n\nconst DEFAULT_HISTORY_STATE = {\n  show: false,\n  entries: [],\n  current: null\n}\n\nconst DEFAULT_CONTEXTS = {\n  PAGES: {\n    playground:{\n      history: DEFAULT_HISTORY_STATE,\n      editor: {...DEFAULT_EDITOR_STATE, previousInternalState: null  },\n      modelsState: [],\n      parameters: DEFAULT_PARAMETERS_STATE\n    },\n    compare:{\n      history: DEFAULT_HISTORY_STATE,\n      editor: DEFAULT_EDITOR_STATE,\n      modelsState: [],\n      parameters: {\n        ...DEFAULT_PARAMETERS_STATE,\n        selectAllModels: false,\n        showParametersTable: false\n      }\n    },\n  },\n  MODELS: [],\n}\n\nlet SETTINGS = null;\n\ntry {\n  SETTINGS = JSON.parse(localStorage.getItem(\"openplayground_settings\"));\n  if (!SETTINGS) throw new Error(\"no settings\")\n} catch (e) {\n  localStorage.clear();\n  SETTINGS = {};\n} finally {\n  if (!SETTINGS.pages) {\n    SETTINGS.pages = DEFAULT_CONTEXTS.PAGES;\n  }\n  if (!SETTINGS.models) {\n    SETTINGS.models = DEFAULT_CONTEXTS.MODELS;\n  }\n}\n\nDEFAULT_CONTEXTS.PAGES = SETTINGS.pages;\nDEFAULT_CONTEXTS.MODELS = SETTINGS.models;\n\nexport const APIContext = React.createContext({});\nexport const EditorContext = React.createContext({});\nexport const ModelsStateContext = React.createContext([]);\nexport const ParametersContext = React.createContext({});\nexport const HistoryContext = React.createContext({});\nexport const ModelsContext = React.createContext(DEFAULT_CONTEXTS.MODELS);\n\nconst saveSettings = () => {\n  let _settings = JSON.stringify(SETTINGS)\n  let SETTINGS_SIZE = _settings.length * 2 / 1024 / 1024;\n\n  if (SETTINGS_SIZE >= 5) {\n    const shouldDownloadHistory = confirm(\"Local Storage is full. Do you wish to download your history prior to clearing storage?\");\n\n    const first_entry = SETTINGS.pages[\"playground\"].history.entries.shift()\n\n    if (shouldDownloadHistory) {\n      const element = document.createElement(\"a\")\n      const history_json = SETTINGS.pages[\"playground\"].history.entries.map((entry: any) => {\n        const model = entry.modelsState.find(({selected}) => selected)\n        const text = EditorState.createWithContent(convertFromRaw(entry.editor.internalState)).getCurrentContent().getPlainText()\n        return {\n          model: model.name,\n          date: entry.date,\n          timestamp: entry.timestamp,\n          text: text,\n          parameters: entry.parameters\n        }\n      })\n\n      const file = new Blob([JSON.stringify(history_json)], {\n        type: \"application/json\",\n      })\n      element.href = URL.createObjectURL(file)\n      element.download = \"history.json\"\n      document.body.appendChild(element)\n      element.click()\n    }\n\n    SETTINGS.pages[\"playground\"].history.entries = [first_entry]\n    SETTINGS.pages[\"playground\"].history.current = first_entry\n    _settings = JSON.stringify(SETTINGS)\n  }\n\n  localStorage.setItem(\"openplayground_settings\", _settings)\n}\n\nfunction useDebounce(func, delay) {\n  const timeoutRef = React.useRef(null);\n\n  useEffect(() => {\n    return () => {\n      clearTimeout(timeoutRef.current);\n    };\n  }, []);\n\n  function debouncedFunction(...args) {\n    clearTimeout(timeoutRef.current);\n\n    timeoutRef.current = setTimeout(() => {\n      func(...args);\n    }, delay);\n  }\n\n  return debouncedFunction;\n}\n\nconst APIContextWrapper = ({children}) => {\n  const pendingCompletionRequest = React.useRef(false);\n  const textCompletionSubscribers = React.useRef([]);\n  const chatCompletionSubscribers = React.useRef([]);\n  const notificationSubscribers = React.useRef([]);\n\n  useEffect(() => {\n    const sse_request = new SSE(\"/api/notifications\")\n    \n    sse_request.addEventListener(\"notification\", (event: any) => {\n      const parsedEvent = JSON.parse(event.data);\n      notificationSubscribers.current.forEach((callback) => {\n        callback(parsedEvent.message);\n      })\n    });\n    sse_request.stream();\n  }, [])\n\n  const Model = {\n    getAll: async () => (await fetch(\"/api/models\")).json(),\n    getAllEnabled: async () => (await fetch(\"/api/models-enabled\")).json(),\n    toggle: async (provider, model) => (await fetch(`/api/provider/${provider}/model/${encodeURIComponent(model)}/toggle-status`)).json(),\n    search: async (provider, query) => (await fetch(`/api/provider/${provider}/models/search?query=${query}`)).json(),\n  };\n\n  const Notifications = {\n    subscribe: (callback) => {\n      notificationSubscribers.current.push(callback);\n    },\n    unsubscribe: (callback) => {\n      notificationSubscribers.current = notificationSubscribers.current.filter((cb) => cb !== callback);\n    },\n  };\n  \n  const Provider = {\n    setAPIKey: async (provider, apiKey) => (await fetch(`/api/provider/${provider}/api-key`, {method: \"PUT\", headers: {\"Content-Type\": \"application/json\"}, \n      body: JSON.stringify({apiKey: apiKey})}\n    )).json(),\n    getAll: async () => (await fetch(\"/api/providers\")).json(),\n    getAllWithModels: async () => (await fetch(\"/api/providers-with-key-and-models\")).json(),\n  };\n  \n  const Inference = {\n    subscribeTextCompletion: (callback) => {\n      textCompletionSubscribers.current.push(callback);\n    },\n    unsubscribeTextCompletion: (callback) => {\n      textCompletionSubscribers.current = textCompletionSubscribers.current.filter((cb) => cb !== callback);\n    },\n    textCompletionRequest: createTextCompletionRequest,\n    subscribeChatCompletion: (callback) => {\n      chatCompletionSubscribers.current.push(callback);\n    },\n    unsubscribeChatCompletion: (callback) => {\n      chatCompletionSubscribers.current = chatCompletionSubscribers.current.filter((cb) => cb !== callback);\n    },\n    chatCompletion: createChatCompletionRequest,\n  };\n  \n  const [apiContext, _] = React.useState({\n    Model,\n    Notifications,\n    Provider,\n    Inference,\n  });\n  \n  function createTextCompletionRequest({prompt, models}) {\n    const url = \"/api/inference/text/stream\";\n    const payload = {\n      prompt: prompt,\n      models: models,\n    };\n    return createCompletionRequest(url, payload, textCompletionSubscribers);\n  }\n  \n  function createChatCompletionRequest(prompt, model) {\n    const url = \"/api/inference/chat/stream\";\n    const payload = {prompt, model};\n    return createCompletionRequest(url, payload, chatCompletionSubscribers);\n  }\n  \n  function createCompletionRequest(url, payload, subscribers) {\n    pendingCompletionRequest.current = true;\n    let sse_request = null;\n  \n    function beforeUnloadHandler() {\n      if (sse_request) sse_request.close();\n    }\n  \n    window.addEventListener(\"beforeunload\", beforeUnloadHandler);\n    const completionsBuffer = createCompletionsBuffer(payload.models);\n    let error_occured = false;\n    let request_complete = false;\n  \n    sse_request = new SSE(url, {payload: JSON.stringify(payload)});\n  \n    bindSSEEvents(sse_request, completionsBuffer, {error_occured, request_complete}, beforeUnloadHandler, subscribers);\n  \n    return () => {\n      if (sse_request) sse_request.close();\n    };\n  }\n\n  function createCompletionsBuffer(models) {\n    const buffer = {};\n    models.forEach((model) => {\n      buffer[model.tag] = [];\n    });\n    return buffer;\n  }\n  \n  function bindSSEEvents(sse_request, completionsBuffer, requestState, beforeUnloadHandler, subscribers) {\n    sse_request.onopen = async () => {\n      bulkWrite(completionsBuffer, requestState, subscribers);\n    };\n  \n    sse_request.addEventListener(\"infer\", (event) => {\n      let resp = JSON.parse(event.data);\n      completionsBuffer[resp.modelTag].push(resp);\n    });\n  \n    sse_request.addEventListener(\"status\", (event) => {\n      subscribers.current.forEach((callback) => callback({\n        event: \"status\",\n        data: JSON.parse(event.data)\n      }));\n    });\n  \n    sse_request.addEventListener(\"error\", (event) => {\n      requestState.error_occured = true;\n      try {\n        const message = JSON.parse(event.data);\n  \n        subscribers.current.forEach((callback) => callback({\n          \"event\": \"error\",\n          \"data\": message.status \n        }));\n      } catch (e) {\n        subscribers.current.forEach((callback) => callback({\n          \"event\": \"error\",\n          \"data\": \"Unknown error\"\n        }));\n      }\n  \n      close_sse(sse_request, requestState, beforeUnloadHandler, subscribers);\n    });\n  \n    sse_request.addEventListener(\"abort\", () => {\n      requestState.error_occured = true;\n      close_sse(sse_request, requestState, beforeUnloadHandler, subscribers);\n    });\n  \n    sse_request.addEventListener(\"readystatechange\", (event) => {\n      if (event.readyState === 2) close_sse(sse_request, requestState, beforeUnloadHandler, subscribers);\n    });\n  \n    sse_request.stream();\n  }\n\n  function close_sse(sse_request, requestState, beforeUnloadHandler, subscribers) {\n    requestState.request_complete = true;\n    subscribers.current.forEach((callback) => callback({\n      \"event\": \"close\",\n      \"meta\": {error: requestState.error_occured},\n    }));\n    window.removeEventListener(\"beforeunload\", beforeUnloadHandler);\n  }  \n  \n  function bulkWrite(completionsBuffer, requestState, subscribers) {\n    setTimeout(() => {\n      let newTokens = false;\n      let batchUpdate = {};\n  \n      for (let modelTag in completionsBuffer) {\n        if (completionsBuffer[modelTag].length > 0) {\n          newTokens = true;\n          batchUpdate[modelTag] = completionsBuffer[modelTag].splice(0, completionsBuffer[modelTag].length);\n        }\n      }\n  \n      if (newTokens) {\n        subscribers.current.forEach((callback) => callback({\n          event: \"completion\",\n          data: batchUpdate,\n        }));\n      }\n  \n      if (!requestState.request_complete) bulkWrite(completionsBuffer, requestState, subscribers);\n    }, 20);\n  }\n\n  return (\n    <APIContext.Provider value={apiContext}>\n      {children}\n    </APIContext.Provider>\n  )\n}\n\nconst PlaygroundContextWrapper = ({page, children}) => {\n  const apiContext = React.useContext(APIContext)\n\n  const [editorContext, _setEditorContext] = React.useState(DEFAULT_CONTEXTS.PAGES[page].editor);\n  const [parametersContext, _setParametersContext] = React.useState(DEFAULT_CONTEXTS.PAGES[page].parameters);\n  let [modelsStateContext, _setModelsStateContext] = React.useState(DEFAULT_CONTEXTS.PAGES[page].modelsState);\n  const [modelsContext, _setModelsContext] = React.useState(DEFAULT_CONTEXTS.MODELS);\n  const [historyContext, _setHistoryContext] = React.useState(DEFAULT_CONTEXTS.PAGES[page].history);\n\n  /* Temporary fix for models that have been purged remotely but are still cached locally */\n  for(const {name} of modelsStateContext) {\n    if (!modelsContext[name]) {\n      modelsStateContext = modelsStateContext.filter(({name: _name}) => _name !== name)\n    }\n  }\n  \n  const editorContextRef = React.useRef(editorContext);\n  const historyContextRef = React.useRef(historyContext);\n\n  React.useEffect(() => {\n    historyContextRef.current = historyContext;\n    editorContextRef.current = editorContext;\n  }, [historyContext, editorContext]);\n\n  const {toast} = useToast()\n\n  useEffect(() => {\n    const notificationCallback = ({event, data, meta}) => {\n      console.warn(\"NOTIFICATION CALLBACK\", event, data)\n\n      switch (event) {\n        case \"modelAdded\":\n          toast({\n            title: \"New Model is available!\",\n            description: `${data.provider}'s model ${data.model} has been added to the playground!`,\n          })\n\n          updateModelsData().catch(console.error)\n        break;\n\n        case \"modelRemoved\":\n          toast({\n            title: \"Model removed!\",\n            description: `${data.provider}'s model ${data.model} has been removed from the playground!`,\n          })\n\n          updateModelsData().catch(console.error)\n        break;\n\n        default:\n          console.log(\"Unknown event????\", event, data);\n        break;\n      }\n    }\n    \n    apiContext.Notifications.subscribe(notificationCallback)\n\n    return () => {\n      apiContext.Notifications.unsubscribe(notificationCallback);\n    };\n  }, []);\n\n  const updateModelsData = async () => {\n    const json_params = await apiContext.Model.getAllEnabled()\n    const models = {};\n    \n    const PAGE_MODELS_STATE = SETTINGS.pages[page].modelsState;\n     \n    for (const [model_key, modelDetails] of Object.entries(json_params)) {\n      const existingModelEntry = (PAGE_MODELS_STATE.find((model) => model.name === model_key));\n\n      if (!existingModelEntry) {\n        PAGE_MODELS_STATE.push({\n          name: model_key,\n          tag: model_key,\n          capabilities: modelDetails.capabilities,\n          provider: modelDetails.provider,\n          parameters: Object.entries(modelDetails.parameters).reduce((acc, [key, fields]) => {\n            acc[key] = fields.value;\n            return acc;\n          }, {}),\n          enabled: (page === \"compare\") ? false : true,\n          selected: false\n          })\n      } else {\n        if (!existingModelEntry.parameters) {\n          existingModelEntry.capabilites = modelDetails.capabilities,\n          existingModelEntry.provider = modelDetails.provider,\n          existingModelEntry.tag = model_key;\n          existingModelEntry.parameters = Object.entries(modelDetails.parameters).reduce((acc, [key, fields]) => {\n            acc[key] = fields.value;\n            return acc;\n          }, {});\n        }\n      }\n\n      models[model_key] = {\n        name: model_key,\n        capabilities: modelDetails.capabilities,\n        defaultParameters: modelDetails.parameters,\n        provider: modelDetails.provider,\n      }\n    }\n      \n    const SERVER_SIDE_MODELS = Object.keys(json_params);\n    for (const {name} of PAGE_MODELS_STATE) {\n      if (!SERVER_SIDE_MODELS.includes(name)) {\n        PAGE_MODELS_STATE.splice(PAGE_MODELS_STATE.findIndex((model) => model.name === name), 1)\n      }\n    }\n\n    setModelsContext(models)\n    setModelsStateContext(PAGE_MODELS_STATE)\n  }\n\n  const debouncedSettingsSave = useDebounce(saveSettings, 3000);\n\n  const setEditorContext = (newEditorContext, immediate=false) => {\n    SETTINGS.pages[page].editor = {...SETTINGS.pages[page].editor, ...newEditorContext};\n\n    const _editor = {...SETTINGS.pages[page].editor, internalState: null };\n\n    _setEditorContext(_editor);\n    if (immediate) {\n      saveSettings()\n    } else {\n      debouncedSettingsSave()\n    }\n  }\n\n  const setParametersContext = (newParameters) => {\n    const parameters = { ...DEFAULT_PARAMETERS_STATE, ...newParameters}\n    SETTINGS.pages[page].parameters = parameters;\n\n    debouncedSettingsSave()\n    _setParametersContext(parameters);\n  }\n\n  const setModelsContext = (newModels) => {\n    SETTINGS.models = newModels;\n    \n    debouncedSettingsSave()\n    _setModelsContext(newModels);\n  }\n\n  const setModelsStateContext = (newModelsState) => {\n    SETTINGS.pages[page].modelsState = newModelsState;\n    \n    debouncedSettingsSave()\n    _setModelsStateContext(newModelsState);\n  }\n\n  const toggleShowHistory = (value) => {\n    const _newHistory = {\n      ...SETTINGS.pages[page].history,\n      show: (value === undefined || value === null) ? !SETTINGS.pages[page].history.show : value\n    }\n\n    _setHistoryContext(_newHistory);\n\n    SETTINGS.pages[page].history = _newHistory;\n    debouncedSettingsSave()\n  }\n\n  const addHistoryEntry = (editorState) => {\n    //check if device is mobile by navigator\n    const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n    if (isMobile) return;\n\n    const currentDate = new Date();\n    const year = currentDate.getFullYear();\n    const month = String(currentDate.getMonth() + 1).padStart(2, '0');\n    const day = String(currentDate.getDate()).padStart(2, '0');\n    \n    const newEntry = {\n      timestamp:  currentDate.getTime(),\n      date:     `${year}-${month}-${day}`,\n      editor:      {\n        ...editorContextRef.current,\n        internalState: editorState\n      },\n      parameters:  SETTINGS.pages[page].parameters,\n      modelsState: SETTINGS.pages[page].modelsState,\n    }\n\n    const _newHistory = {\n      ...SETTINGS.pages[page].history,\n      entries: [newEntry, ...SETTINGS.pages[page].history.entries],\n      current: newEntry\n    }\n\n    _setHistoryContext(_newHistory);\n\n    //console.warn(\"Adding to history\", _newHistory)\n    SETTINGS.pages[page].history = _newHistory;\n    debouncedSettingsSave()\n  }\n\n  const removeHistoryEntry = (entry) => {\n    const _newHistory = {\n      ...SETTINGS.pages[page].history,\n      entries: SETTINGS.pages[page].history.entries.filter((historyEntry) => historyEntry !== entry)\n    }\n\n    _setHistoryContext(_newHistory);\n\n    SETTINGS.pages[page].history = _newHistory;\n    debouncedSettingsSave()\n  }\n\n  const clearHistory = () => {\n    const _newHistory = {\n      entries: [],\n      show: false,\n      current: null\n    }\n\n    _setHistoryContext(_newHistory);\n\n    SETTINGS.pages[page].history = _newHistory;\n    debouncedSettingsSave()\n  }\n\n  const selectHistoryItem = (entry) => {\n    SETTINGS.pages[page].history.current = entry;\n    _setEditorContext(entry.editor);\n\n    _setHistoryContext(SETTINGS.pages[page].history);\n    setParametersContext(entry.parameters);\n    setModelsStateContext(entry.modelsState);\n  }\n\n  React.useEffect(() => {\n    updateModelsData().catch(console.error)\n  }, [])\n\n  return (\n    <HistoryContext.Provider value = {{\n      historyContext, selectHistoryItem,\n      addHistoryEntry, removeHistoryEntry, clearHistory, toggleShowHistory\n    }}>\n      <EditorContext.Provider value = {{editorContext, setEditorContext}}>\n        <ParametersContext.Provider value = {{parametersContext, setParametersContext}}>\n          <ModelsContext.Provider value = {{modelsContext, setModelsContext}}>\n            <ModelsStateContext.Provider value = {{modelsStateContext, setModelsStateContext}}>\n              {children}\n            </ModelsStateContext.Provider>\n          </ModelsContext.Provider>\n        </ParametersContext.Provider>\n      </EditorContext.Provider>\n    </HistoryContext.Provider>\n  )\n}\n\nfunction ProviderWithRoutes() {\n  return (\n    <Routes>\n      <Route\n        path=\"/\"\n        element={\n          <APIContextWrapper>\n            <PlaygroundContextWrapper key = \"playground\" page = \"playground\">\n              <Playground/>\n              <Toaster />\n            </PlaygroundContextWrapper>\n          </APIContextWrapper>\n        }\n      />\n\n      <Route\n        path=\"/compare\"\n        element={\n          <APIContextWrapper>\n            <PlaygroundContextWrapper key = \"compare\" page = \"compare\">\n              <Compare/>\n              <Toaster />\n            </PlaygroundContextWrapper>\n          </APIContextWrapper>\n        }\n      />\n\n      <Route\n        path=\"/settings\"\n        element={\n          <APIContextWrapper>\n            <Settings />\n            <Toaster />\n          </APIContextWrapper>\n        }\n      />\n    </Routes>\n  );\n}\n\nexport default function App() {\n  return (\n    <BrowserRouter>\n      <ProviderWithRoutes />\n    </BrowserRouter>\n  )\n}"
  },
  {
    "path": "app/src/components/inputarea.tsx",
    "content": "import { Button } from \"@/components/ui/button\"\nimport { Textarea } from \"@/components/ui/textarea\"\n\nexport function InputArea() {\n  return (\n    <div className=\"grid h-96\">\n      <Textarea placeholder=\"Write tagline for a ice cream shop.\" />\n      <Button className=\"inline-flex items-center px-5 py-8 text-sm font-medium text-center\">\n        Submit\n      </Button>\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/src/components/multi-select.tsx",
    "content": "import React from \"react\"\n\nimport CreatableSelect from \"react-select/creatable\"\nimport { useBreakpoint } from \"../hooks/use-breakpoint\"\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"./ui/tooltip\"\n\ninterface MultiSelectProps {\n  maxOptions?: number\n  tooltipContent: React.ReactElement\n  defaultOptions: any,\n  disabled?: boolean\n  onValueChange: (value: any) => void\n}\n\nconst MultiSelect: React.FC<MultiSelectProps> = ({\n  maxOptions,\n  tooltipContent,\n  defaultOptions,\n  disabled = false,\n  onValueChange,\n}) => {\n  const { isLg } = useBreakpoint(\"lg\")\n\n  const formattedOptions = defaultOptions.map((option: any) => {\n    return { label: option, value: option }\n  })\n\n  return (\n    <div>\n      <Tooltip delayDuration={300} skipDelayDuration={150}>\n        <TooltipTrigger asChild>\n          <div>\n            <span className=\"cursor-default flow-root inline-block align-middle mb-1\">\n              <p className=\"text-sm font-normal float-left align-text-top\">\n                Stop Sequences\n              </p>\n            </span>\n            <span className=\"cursor-default flow-root inline-block align-middle mb-3\">\n              <p className=\"text-xs font-normal float-left align-text-top text-gray-400\">\n                Enter sequence and press Tab\n              </p>\n            </span>\n            <CreatableSelect\n              isDisabled={disabled}\n              isMulti\n              placeholder=\"\"\n              noOptionsMessage={({ inputValue }) => \"Enter a sequence\"}\n              formatCreateLabel={(userInput) => `Add ${userInput}`}\n              value={formattedOptions}\n              options={formattedOptions}\n              defaultValue={formattedOptions}\n              components={{\n                DropdownIndicator: () => null,\n                IndicatorSeparator: () => null,\n              }}\n              isValidNewOption={(inputValue, options) => {\n                if (inputValue.length < 1) return false\n\n                if (maxOptions == null) return true\n\n                if (options.length >= maxOptions) return false\n\n                return true\n              }}\n              onChange={(e) => {\n                console.log(e)\n                const values = e.map((i: any) => i.value)\n                if (onValueChange) onValueChange(values)\n              }}\n            />\n          </div>\n        </TooltipTrigger>\n        <TooltipContent side={isLg ? \"left\" : \"bottom\"}>\n          {tooltipContent}\n        </TooltipContent>\n      </Tooltip>\n    </div>\n  )\n}\n\nexport default MultiSelect\n"
  },
  {
    "path": "app/src/components/navbar.tsx",
    "content": "import React from \"react\"\nimport { Link } from \"react-router-dom\"\n\nexport default function NavBar({ tab, children }: any) {\n  const menu = [\"playground\", \"compare\", \"settings\"].map((menuName, index) => (\n    <div key = {menuName} className=\"align-middle mt-1 flex items-center\">\n      <Link\n        to={`/${index > 0 ? menuName: ''}`}\n        className={\n          tab === menuName\n          ? \"cursor-default\"\n          : \"cursor-pointer\"\n        }>\n        <p\n          className={\n            tab === menuName\n            ? \"text-xl font-semibold\"\n            : \"text-xl font-medium text-gray-500 hover:text-gray-900\"\n          }\n        >\n          {menuName.charAt(0).toUpperCase() + menuName.slice(1)}\n        </p>\n      </Link>\n    </div>\n  ))\n\n  return (\n    <div className=\"flex flex-col font-display mb-3 border\">\n      <div className=\"flex inline-block mx-5 my-4 gap-x-4 flex-wrap\">\n        {menu}\n        \n        <div className =\"flex-1\" />\n\n        <div\n          className = \"ml-4 mt-1 cursor-pointer flex justify-end items-center self-flex-end\"\n          onClick={() => {\n            window.open(\"https://discord.gg/J8sFfUK2N2\", \"_blank\")\n          }}\n          >\n          <img\n            className = \"h-[20px]\"\n            src= \"https://assets-global.website-files.com/6257adef93867e50d84d30e2/636e0a6a49cf127bf92de1e2_icon_clyde_blurple_RGB.png\"\n          />\n          </div>\n\n          <div\n            className = \"ml-4 mt-1 cursor-pointer flex justify-end items-center self-flex-end\"\n            onClick={() => {\n              window.open(\"https://github.com/nat/openplayground\", \"_blank\")\n            }}\n            >\n            <img\n              className = \"h-[35px]\"\n              src= \"https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png\"\n            />\n          </div>\n        {children}\n      </div>\n    </div>\n  )\n}"
  },
  {
    "path": "app/src/components/parameter-slider.tsx",
    "content": "import { Tooltip, TooltipTrigger, TooltipContent } from \"./ui/tooltip\"\nimport React, {\n  HTMLInputTypeAttribute,\n  ReactElement,\n  useEffect,\n  useState,\n  useEffect\n} from \"react\"\nimport { Input } from \"./ui/input\"\nimport { Slider } from \"./ui/slider\"\nimport { useBreakpoint } from \"../hooks/use-breakpoint\"\nimport { AlertTriangle } from \"lucide-react\"\n\ninterface ParameterSliderProps {\n  title: string\n  type: HTMLInputTypeAttribute | undefined\n  min: number\n  max: number\n  step: number\n  defaultValue: number\n  disabled: boolean\n  normalizeInputData: (value: string) => any\n  normalizeSliderData?: Function\n  onChangeValue: ((value: number) => void) | undefined\n  tooltipContent: ReactElement\n}\n\nconst ParamaterSlider: React.FC<ParameterSliderProps> = ({\n  title,\n  defaultValue,\n  disabled,\n  min,\n  max,\n  step,\n  normalizeInputData,\n  normalizeSliderData,\n  type = \"number\",\n  tooltipContent,\n  onChangeValue,\n}) => {\n  // TODO: deprecate this\n  const [value, setValue] = useState(defaultValue) \n  const { isLg } = useBreakpoint(\"lg\")\n\n  useEffect(() => {\n    setValue(defaultValue)\n  }, [defaultValue])\n\n  // prevents slider from overflowing if value is > max val (can happen if user types in big number in input before unfocusing)\n  const sliderValue = Math.max(\n    Number(min),\n    Math.min(Number(max), Number(value))\n  )\n  return (\n    <div className=\"\">\n      <Tooltip delayDuration={300} skipDelayDuration={150}>\n        <TooltipTrigger asChild>\n          <div>\n            <span className=\"cursor-default flow-root inline-block align-middle mb-3\">\n              <p className={`text-sm font-normal float-left align-text-top ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}>\n                {(disabled) && (\n                  <AlertTriangle className=\"w-4 h-4 text-gray-500 inline mr-1 mb-0.5\" />\n                )}\n                {title}\n              </p>\n              <Input\n                inputMode=\"decimal\"\n                className=\"float-right h-6 p-0 w-14\"\n                type={type}\n                value={[value.toString()]}\n                step={step}\n                autoFocus={false}\n                onChange={(e) => {\n                  if (normalizeInputData) {\n                    let normalized = normalizeInputData(e.target.value)\n                    \n                    setValue(normalized)\n\n                    if (!isNaN(normalized) && onChangeValue) { \n                      onChangeValue(normalized)\n                    }\n                  }\n                }}\n                onBlur={(e) => {\n                  let normalized = Math.max(\n                    Number(min),\n                    Math.min(Number(max), Number(value))\n                  )\n                  \n                  if (isNaN(normalized)) {\n                    normalized = defaultValue\n                  }\n                  setValue(normalized)\n\n                  if (onChangeValue) {\n                    onChangeValue(normalized)\n                  }\n                }}\n                disabled={disabled}\n                min={min}\n                max={max}\n              />\n            </span>\n            <Slider\n              disabled={disabled}\n              defaultValue={[value]}\n              value={[sliderValue]}\n              min={min}\n              max={max}\n              step={step}\n              onValueChange={(e) => {\n                setValue(e[0])\n              }}\n              onValueCommit={(e) => {\n                if (onChangeValue) onChangeValue(e[0])\n              }}\n            />\n          </div>\n        </TooltipTrigger>\n        <TooltipContent side={isLg ? \"left\" : \"bottom\"}>\n          {tooltipContent}\n        </TooltipContent>\n      </Tooltip>\n    </div>\n  )\n}\n\nexport default ParamaterSlider\n"
  },
  {
    "path": "app/src/components/parameters-side-panel.tsx",
    "content": "import React, { useContext } from \"react\"\nimport { Checkbox } from \"./ui/checkbox\"\nimport { useBreakpoint } from \"../hooks/use-breakpoint\"\nimport ParameterSlider from \"./parameter-slider\"\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectTrigger,\n  SelectValue,\n} from \"./ui/select\"\nimport MultiSelect from \"./multi-select\"\nimport { uuid } from \"uuidv4\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"./ui/tooltip\"\nimport { ParametersContext, ModelsContext, ModelsStateContext } from \"../app\"\nimport { BarChart2, Copy, Trash2, Filter } from \"lucide-react\"\nimport {handleSelectModel} from \"../lib/utils\"\n\nconst modelProviders = {\n  forefront: \"Forefront\",\n  \"huggingface-local\": \"Hugging Face (Local)\",\n  huggingface: \"Hugging Face\",\n  \"aleph-alpha\": \"Aleph Alpha\",\n  anthropic: \"Anthropic\",\n  cohere: \"co:here\",\n  openai: \"OpenAI\",\n}\n\nconst ParametersSidePanel = ({ showModelDropdown, showModelList }) => {\n  const { isLg } = useBreakpoint(\"lg\")\n  const { parametersContext, setParametersContext } = useContext(ParametersContext)\n  const { modelsContext, setModelsContext } = useContext(ModelsContext)\n  const { modelsStateContext, setModelsStateContext } = useContext(ModelsStateContext)\n\n  const [modelSearchValue, setModelSearchValue] = React.useState<string>(\"\")\n \n  const number_of_models_selected = modelsStateContext.filter(\n    (modelState) => modelState.selected\n  ).length\n  const number_of_models_enabled = modelsStateContext.filter(\n    (modelState) => modelState.enabled\n  ).length\n  \n  const models_shared_keys = Object.keys(modelsContext).length === 0 ? {} : modelsStateContext\n    .filter(\n      (modelState) =>\n        modelState.enabled &&\n        (number_of_models_selected >= 1 ? modelState.selected : true)\n    )\n    .map((modelState) => (modelsContext[modelState.name].defaultParameters))\n    .flatMap((parameter) =>\n      Object.entries(parameter).map(([key, parameter]) => ({\n        key,\n        range: parameter[\"range\"],\n      }))\n    )\n    .reduce((acc, { key, range }) => {\n      acc[key] = acc[key] || { range: [] }\n      acc[key].range = [...new Set([...acc[key].range, ...range])]\n      return acc\n    }, {})\n  \n  const generate_parameters_sliders = () => {\n    return [\n      {\n        title: \"Maximum Length\",\n        name: \"maximumLength\",\n        type: \"number\",\n        step: 1,\n        tooltipContent: (\n          <p>\n            Maximum number of tokens to generate. <br /> Responses are not\n            guaranted to fill up <br /> to the maximum desired length. <br />\n          </p>\n        ),\n        normalizeFn: (value) => parseInt(value),\n      },\n      {\n        title: \"Temperature\",\n        name: \"temperature\",\n        type: \"number\",\n        step: 0.01,\n        tooltipContent: (\n          <p>\n            A non-negative float that tunes the degree <br /> of randomness in\n            generation. Lower <br />\n            temperatures mean less random generations.\n            <br />\n          </p>\n        ),\n        normalizeFn: (value) => parseFloat(value),\n      },\n      {\n        title: \"Top P\",\n        name: \"topP\",\n        type: \"number\",\n        step: 0.01,\n        tooltipContent: (\n          <p>\n            If set to float less than 1, only the smallest <br /> set of most\n            probable tokens with probabilities <br /> that add up to top_p or\n            higher are kept for\n            <br /> generation. <br />\n          </p>\n        ),\n        normalizeFn: (value) => parseFloat(value),\n      },\n      {\n        title: \"Top K\",\n        name: \"topK\",\n        type: \"number\",\n        step: 1,\n        tooltipContent: (\n          <p>\n            Can be used to reduce repetitiveness of <br />\n            generated tokens. The higher the value,\n            <br /> the stronger a penalty is applied to\n            <br />\n            previously present tokens, proportional\n            <br /> to how many times they have already\n            <br /> appeared in the prompt or prior generation. <br />\n          </p>\n        ),\n        normalizeFn: (value) => parseInt(value),\n      },\n      {\n        title: \"Frequency Penalty\",\n        name: \"frequencyPenalty\",\n        type: \"number\",\n        step: 0.01,\n        tooltipContent: (\n          <p>\n            Can be used to reduce repetitiveness of <br />\n            generated tokens. The higher the value,\n            <br /> the stronger a penalty is applied to\n            <br />\n            previously present tokens, proportional\n            <br /> to how many times they have already\n            <br /> appeared in the prompt or prior generation.\n          </p>\n        ),\n        normalizeFn: (value) => parseFloat(value),\n      },\n      {\n        title: \"Presence Penalty\",\n        name: \"presencePenalty\",\n        type: \"number\",\n        step: 0.01,\n        tooltipContent: (\n          <p>\n            Can be used to reduce repetitiveness of <br />\n            generated tokens. Similar to Frequency Penalty,\n            <br /> except that this penalty is applied equally <br /> to all\n            tokens that have already appeared,\n            <br />\n            regardless of their <br /> exact frequencies. <br />\n          </p>\n        ),\n        normalizeFn: (value) => parseFloat(value),\n      },\n      {\n        title: \"Repetition Penalty\",\n        name: \"repetitionPenalty\",\n        type: \"number\",\n        step: 0.01,\n        tooltipContent: (\n          <p>\n            Akin to presence penalty. The repetition penalty is meant <br /> to\n            avoid sentences that repeat themselves without <br /> anything\n            really interesting.{\" \"}\n          </p>\n        ),\n        normalizeFn: (value) => parseFloat(value),\n      },\n    ]\n      .filter((parameter) => parameter.name in models_shared_keys)\n      .map((parameter) => ({\n        ...parameter,\n        value: parametersContext[parameter.name],\n        min: models_shared_keys[parameter.name].range[0],\n        max: models_shared_keys[parameter.name].range[1],\n        disabled:\n          number_of_models_enabled === 0 ||\n          models_shared_keys[parameter.name].range.length > 2,\n      }))\n      .map((parameter) => {\n        return (\n          <ParameterSlider\n            key={parameter.name}\n            title={parameter.title}\n            type={parameter.type}\n            defaultValue={parameter.value}\n            disabled={parameter.disabled}\n            onChangeValue={(value: number) => {\n              setModelsStateContext(\n                modelsStateContext.map((modelState: any) => {\n                  if (\n                    modelState.parameters[parameter.name] &&\n                    (number_of_models_selected === 0 || modelState.selected)\n                  ) {\n                    modelState.parameters[parameter.name] = value\n                  }\n\n                  return modelState\n                })\n              )\n              setParametersContext({\n                ...parametersContext,\n                [parameter.name]: value,\n              })\n            }}\n            min={parameter.min}\n            max={parameter.max}\n            step={parameter.step}\n            normalizeInputData={parameter.normalizeFn}\n            tooltipContent={\n              <>\n                {parameter.tooltipContent}\n                {number_of_models_enabled === 0 ? (\n                  <p>\n                    <b>Disabled:</b> no models have been enabled.\n                  </p>\n                ) : parameter.disabled ? (\n                  <p>\n                    <b>Disabled:</b> the range of values for this parameter\n                    <br /> <b>is not</b> uniform across all models.\n                    <br />\n                    <b>Tip:</b> to edit similar models, tap the models on\n                    <br /> the list or select them by clicking their name\n                    <br /> above their respective editor.\n                  </p>\n                ) : null}\n              </>\n            }\n          />\n        )\n      })\n  }\n\n  const generate_card = (modelState: any) => {\n    return (\n      <div\n        key={`selected_${modelState.tag}`}\n        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 ${\n          modelState.selected ? \"bg-slate-200 dark:bg-slate-200\" : \"\"\n        } ${\n          modelState.enabled\n            ? \"cursor-pointer hover:bg-slate-200 dark:hover:bg-slate-200\"\n            : \"\"\n        }`}\n      >\n        <div\n          className={`pl-4 py-3 flex-1 overflow-hidden ${\n            !modelState.enabled ? \"text-zinc-400\" : \"\"\n          }`}\n          onClick={(event) => {\n            if (modelState.enabled)\n              handleSelectModel(\n                modelState,\n                modelsStateContext,\n                setModelsStateContext,\n                parametersContext,\n                setParametersContext,\n                event.ctrlKey || event.metaKey\n              )\n          }}\n        >\n          {modelState.name.split(\":\")[1]}\n          <br />\n          <span style={{ fontSize: \"12px\" }}>\n            Provider: <i>{modelState.provider}</i>\n          </span>\n          <br />\n        </div>\n\n        <Copy\n          size={10}\n          className=\"absolute top-2 right-2\"\n          onClick={() => {\n            const index_of_model = modelsStateContext.findIndex(\n              (m: any) => m.name === modelState.name\n            )\n            const name_fragments = modelState.name.split(\":\")\n            setModelsStateContext([\n              ...modelsStateContext.slice(0, index_of_model + 1),\n              {\n                ...modelState,\n                is_clone: true,\n                tag: `${name_fragments[0]}:${name_fragments[1]}:${uuid()}`,\n              },\n              ...modelsStateContext.slice(index_of_model + 1),\n            ])\n          }}\n        />\n\n        <Checkbox\n          className=\"mr-6\"\n          key={modelState.tag}\n          checked={modelState.enabled}\n          onCheckedChange={(val: boolean) => {\n            setModelsStateContext(\n              modelsStateContext.map((m: any) => {\n                if (m.tag === modelState.tag) {\n                  return {\n                    ...m,\n                    enabled: val,\n                    selected: false\n                  }\n                }\n                return m\n              })\n            )\n          }}\n        />\n        {modelState.is_clone ? (\n          <Trash2\n            size={10}\n            className=\"absolute bottom-2 right-2\"\n            onClick={() => {\n              setModelsStateContext(\n                modelsStateContext.filter((m: any) => m.tag !== modelState.tag)\n              )\n            }}\n          />\n        ) : null}\n      </div>\n    )\n  }\n\n  const generate_list = () => {\n    if (!showModelList) return null\n\n    return (\n      <>\n        <div>\n          <div className=\"my-2 flex cursor-default flex mb-1\">\n            <p className=\"flex-1 text-sm font-normal float-left align-text-top\">\n              Enable All\n            </p>\n\n            <Checkbox\n              checked={parametersContext.selectAllModels}\n              onCheckedChange={(val: boolean) => {\n                setModelsStateContext(\n                  modelsStateContext.map((modelState: any) => {\n                    modelState.enabled = val\n                    modelState.selected = false\n                    return modelState\n                  })\n                )\n\n                setParametersContext({\n                  ...parametersContext,\n                  selectAllModels: val,\n                })\n              }}\n              className=\"float-right\"\n            />\n          </div>\n        </div>\n        <div className=\"my-2 flex flex-row border-slate-300 border p-2 rounded\">\n          <div className=\"flex items-center\">\n            <Filter size={18} />\n          </div>\n\n          <div className=\"ml-2 flex-1 mr-2\">\n            <input\n              className=\"outline-0 w-[100%]\"\n              value={modelSearchValue}\n              onChange={(event) => {\n                setModelSearchValue(event.target.value)\n              }}\n              placeholder=\"Model Name\"\n            />\n          </div>\n        </div>\n        <div>\n          <ul>\n            {modelsStateContext\n              .filter((modelState: any) =>\n                !modelState.tag ? false :\n                modelSearchValue !== \"\"\n                  ? modelState.name\n                      .toLowerCase()\n                      .indexOf(modelSearchValue.toLowerCase()) !== -1\n                  : true\n              )\n              .map(generate_card)}\n          </ul>\n        </div>\n      </>\n    )\n  }\n\n  const generate_header = () => {\n    if (!showModelDropdown)\n      return (\n        <div className=\"flex mb-2\">\n          <span className=\"cursor-default flex-1 flow-root inline-block align-middle\">\n            <p className=\"text-sm font-medium float-left align-text-top\">\n              Parameters\n            </p>\n          </span>\n          <Tooltip delayDuration={300} skipDelayDuration={150}>\n          <TooltipTrigger asChild>\n            <div\n              onClick={() => {\n                setParametersContext({\n                  ...parametersContext,\n                  showParametersTable: !parametersContext.showParametersTable,\n                })\n              }}\n              className={`mx-1 cursor-pointer flex justify-center items-center w-[24px] h-[24px] rounded-full border-[1px] border-slate-200 select-none ${\n                parametersContext.showParametersTable\n                  ? \"text-white bg-slate-700\"\n                  : \"hover:text-white hover:bg-slate-700 text-slate-600 bg-white\"\n              }`}\n            >\n              <BarChart2 size={18} />\n            </div>\n          </TooltipTrigger>\n          <TooltipContent side={\"bottom\"}>\n            <p>Show Parameters for all models</p>\n          </TooltipContent>\n        </Tooltip>\n        </div>\n      )\n    \n    const selectedModel = modelsStateContext.find((modelState) => modelState.selected)\n    \n    return (\n      <div className=\"\">\n        <div className=\"mb-2\">\n          <span className=\"flow-root inline-block align-middle\">\n            <p className=\"text-sm font-medium float-left align-text-top\">\n              Model\n            </p>\n          </span>\n          <Select\n            value={selectedModel?.name || null}\n            onValueChange={(value) => {\n              setModelsStateContext(\n                modelsStateContext.map((model) => ({...model, selected: (model.name === value) ? true : false}))\n              )\n              const modelParameters = modelsStateContext.find((model) => model.name === value).parameters\n              \n              setParametersContext({\n                temperature: modelParameters.temperature || parametersContext.temperature,\n                maximumLength: modelParameters.maximumLength || parametersContext.maximumLength,\n                topP: modelParameters.topP || parametersContext.topP,\n                topK: modelParameters.topK || parametersContext.topK,\n                frequencyPenalty: modelParameters.frequencyPenalty || parametersContext.frequencyPenalty,\n                presencePenalty: modelParameters.presencePenalty || parametersContext.presencePenalty,\n                repetitionPenalty: modelParameters.repetitionPenalty || parametersContext.repetitionPenalty,\n                stopSequences: modelParameters.stopSequences || parametersContext.stopSequences\n              })\n            }}\n          >\n            <SelectTrigger\n              className=\"w-full\"\n              onKeyDown={(e) => {\n                if (e.code === \"Enter\" && e.metaKey) {\n                  e.preventDefault()\n                }\n              }}\n            >\n              <SelectValue placeholder=\"Select a Model\" />\n            </SelectTrigger>\n            <SelectContent\n              onKeyDown={(e) => {\n                if (e.code === \"Enter\" && e.metaKey) {\n                  e.preventDefault()\n                }\n              }}\n            >\n              {Object.entries(modelProviders).map(([provider, prettyName]) => (\n                <SelectGroup key={provider}>\n                  {Object.entries(modelsContext)\n                    .filter(([key]) => key.split(\":\")[0] === provider)\n                    .map(([model_key, _], index) => {\n                      if (modelsContext[model_key]) {\n                        return (\n                          <div key={model_key}>\n                            <SelectLabel hidden={index != 0}>\n                              {prettyName}\n                            </SelectLabel>\n                            <SelectItem\n                              value={model_key}\n                              onKeyDown={(e) => {\n                                if (e.code === \"Enter\" && e.metaKey) {\n                                  e.preventDefault()\n                                }\n                              }}\n                            >\n                              {model_key.split(\":\")[1]}\n                            </SelectItem>\n                          </div>\n                        )\n                      }\n                    })}\n                </SelectGroup>\n              ))}\n            </SelectContent>\n          </Select>\n        </div>\n      </div>\n    )\n  }\n\n  const generate_show_probabilities = () => {\n    const selectedModel = modelsStateContext.find((modelState) => modelState.selected)\n    if (!selectedModel || !selectedModel.capabilities || !selectedModel.capabilities.includes(\"logprobs\"))\n      return null\n   \n    return (\n      <Tooltip delayDuration={300} skipDelayDuration={150}>\n        <TooltipTrigger asChild>\n          <div className=\"cursor-default flex justify-between align-middle inline-block align-middle mb-1\">\n            <p className=\"text-sm font-normal float-left align-text-top\">\n              Show Probabilities\n            </p>\n            <Checkbox\n              name=\"show-probabilities\"\n              className=\"float-right self-center\"\n              checked={parametersContext.showProbabilities}\n              onCheckedChange={(val: boolean) => {\n                setParametersContext({\n                  ...parametersContext,\n                  showProbabilities: val,\n                })\n              }}\n            />\n          </div>\n        </TooltipTrigger>\n        <TooltipContent side={isLg ? \"left\" : \"bottom\"}>\n          <p>\n            When enabled hover over generated words <br /> to see how likely a\n            token was to be generated,\n            <br /> if the model supports it.\n          </p>\n        </TooltipContent>\n      </Tooltip>\n    )\n  }\n\n  return (\n    <div className=\"flex flex-col max-h-[100%] pt-4 sm:pt-4 md:pt-[0px] lg:pt-[0px]\">\n      <div className=\"mb-2\">\n        {generate_header()}\n      </div>\n      <div className=\"flex flex-col gap-y-3\">\n        {generate_parameters_sliders()}\n\n        <MultiSelect\n          onValueChange={(value: any) => {\n            setModelsStateContext(\n              modelsStateContext.map((modelState: any) => {\n                if (\n                  modelState.parameters.stopSequences &&\n                  (number_of_models_selected === 0 || modelState.selected)\n                )\n                  modelState.parameters.stopSequences = value\n\n                return modelState\n              })\n            )\n            setParametersContext({\n              ...parametersContext,\n              [\"stopSequences\"]: value,\n            })\n          }}\n          defaultOptions={parametersContext.stopSequences}\n          tooltipContent={\n            <>\n              <p>\n                Up to four sequences where the API will stop <br /> generating\n                further tokens. The returned text <br />\n                will not contain the stop sequence.\n              </p>\n            </>\n          }\n        />\n\n        {generate_show_probabilities()}\n\n        <Tooltip delayDuration={300} skipDelayDuration={150}>\n          <TooltipTrigger asChild>\n            <div className=\"cursor-default flex justify-between align-middle inline-block align-middle mb-1\">\n              <p className=\"text-sm font-normal float-left align-text-top\">\n                Highlight Models\n              </p>\n              <Checkbox\n                name=\"highlight-models\"\n                className=\"float-right self-center\"\n                checked={parametersContext.highlightModels}\n                onCheckedChange={(val: boolean) => {\n                  setParametersContext({\n                    ...parametersContext,\n                    highlightModels: val,\n                  })\n                }}\n              />\n            </div>\n          </TooltipTrigger>\n          <TooltipContent side={isLg ? \"left\" : \"bottom\"}>\n            <p>Disable model specific text highlights</p>\n          </TooltipContent>\n        </Tooltip>\n      </div>\n\n      {generate_list()}\n    </div>\n  )\n}\n\nexport default ParametersSidePanel"
  },
  {
    "path": "app/src/components/ui/alert-dialog.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst AlertDialog = AlertDialogPrimitive.Root\n\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger\n\nconst AlertDialogPortal = ({\n  className,\n  children,\n  ...props\n}: AlertDialogPrimitive.AlertDialogPortalProps) => (\n  <AlertDialogPrimitive.Portal className={cn(className)} {...props}>\n    <div className=\"fixed inset-0 z-50 flex items-end justify-center sm:items-center\">\n      {children}\n    </div>\n  </AlertDialogPrimitive.Portal>\n)\nAlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName\n\nconst AlertDialogOverlay = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, children, ...props }, ref) => (\n  <AlertDialogPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName\n\nconst AlertDialogContent = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPortal>\n    <AlertDialogOverlay />\n    <AlertDialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"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\",\n        \"dark:bg-slate-900 font-display\",\n        className\n      )}\n      {...props}\n    />\n  </AlertDialogPortal>\n))\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName\n\nconst AlertDialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nAlertDialogHeader.displayName = \"AlertDialogHeader\"\n\nconst AlertDialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nAlertDialogFooter.displayName = \"AlertDialogFooter\"\n\nconst AlertDialogTitle = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Title\n    ref={ref}\n    className={cn(\n      \"text-lg font-semibold text-slate-900\",\n      \"dark:text-slate-50\",\n      className\n    )}\n    {...props}\n  />\n))\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName\n\nconst AlertDialogDescription = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Description\n    ref={ref}\n    className={cn(className)}\n    {...props}\n  />\n))\nAlertDialogDescription.displayName =\n  AlertDialogPrimitive.Description.displayName\n\nconst AlertDialogAction = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Action>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Action\n    ref={ref}\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n  />\n))\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName\n\nconst AlertDialogCancel = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Cancel\n    ref={ref}\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n  />\n))\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName\n\nexport {\n  AlertDialog,\n  AlertDialogTrigger,\n  AlertDialogContent,\n  AlertDialogHeader,\n  AlertDialogFooter,\n  AlertDialogTitle,\n  AlertDialogDescription,\n  AlertDialogAction,\n  AlertDialogCancel,\n}\n"
  },
  {
    "path": "app/src/components/ui/button.tsx",
    "content": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst buttonVariants = cva(\n  \"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\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"bg-slate-900 text-white hover:bg-slate-700 dark:bg-slate-50 dark:text-slate-900\",\n        destructive:\n          \"bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600\",\n        outline:\n          \"bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-100\",\n        subtle:\n          \"bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-100\",\n        ghost:\n          \"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\",\n        link: \"bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent\",\n      },\n      size: {\n        default: \"h-8 px-3\",\n        sm: \"h-9 px-3 rounded\",\n        lg: \"h-11 px-8 rounded\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, ...props }, ref) => {\n    return (\n      <button\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "app/src/components/ui/checkbox.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check } from \"lucide-react\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Checkbox = React.forwardRef<\n  React.ElementRef<typeof CheckboxPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <CheckboxPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n  >\n    <CheckboxPrimitive.Indicator\n      className={cn(\"flex items-center justify-center\")}\n    >\n      <Check className=\"h-4 w-4\" />\n    </CheckboxPrimitive.Indicator>\n  </CheckboxPrimitive.Root>\n))\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n"
  },
  {
    "path": "app/src/components/ui/dialog-sheet-base.tsx",
    "content": "import * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Dialog = DialogPrimitive.Root\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = ({\n  className,\n  children,\n  ...props\n}: DialogPrimitive.DialogPortalProps) => (\n  <DialogPrimitive.Portal className={cn(className)} {...props}>\n    <div className=\"fixed inset-0 z-50 flex items-start justify-center sm:items-center\">\n      {children}\n    </div>\n  </DialogPrimitive.Portal>\n)\nDialogPortal.displayName = DialogPrimitive.Portal.displayName\n\nconst DialogOverlay = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, children, ...props }, ref) => (\n  <DialogPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst DialogContent = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <DialogPortal>\n    <DialogOverlay />\n    <DialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"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\",\n        \"dark:bg-slate-900\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <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\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </DialogPrimitive.Close>\n    </DialogPrimitive.Content>\n  </DialogPortal>\n))\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\nconst DialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogHeader.displayName = \"DialogHeader\"\n\nconst DialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogFooter.displayName = \"DialogFooter\"\n\nconst DialogTitle = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Title\n    ref={ref}\n    className={cn(\n      \"text-lg font-semibold text-slate-900\",\n      \"dark:text-slate-50\",\n      className\n    )}\n    {...props}\n  />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-slate-500\", \"dark:text-slate-400\", className)}\n    {...props}\n  />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n  Dialog,\n  DialogTrigger,\n  DialogContent,\n  DialogHeader,\n  DialogFooter,\n  DialogTitle,\n  DialogDescription,\n}\n"
  },
  {
    "path": "app/src/components/ui/dialog.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Dialog = DialogPrimitive.Root\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = ({\n  className,\n  children,\n  ...props\n}: DialogPrimitive.DialogPortalProps) => (\n  <DialogPrimitive.Portal className={cn(className)} {...props}>\n    <div className=\"fixed inset-0 z-50 flex items-start justify-center sm:items-center\">\n      {children}\n    </div>\n  </DialogPrimitive.Portal>\n)\nDialogPortal.displayName = DialogPrimitive.Portal.displayName\n\nconst DialogOverlay = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, children, ...props }, ref) => (\n  <DialogPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst DialogContent = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <DialogPortal>\n    <DialogOverlay />\n    <DialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"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\",\n        \"dark:bg-slate-900\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <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\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </DialogPrimitive.Close>\n    </DialogPrimitive.Content>\n  </DialogPortal>\n))\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\nconst DialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogHeader.displayName = \"DialogHeader\"\n\nconst DialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogFooter.displayName = \"DialogFooter\"\n\nconst DialogTitle = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Title\n    ref={ref}\n    className={cn(\n      \"text-lg font-semibold text-slate-900\",\n      \"dark:text-slate-50\",\n      className\n    )}\n    {...props}\n  />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-slate-500\", \"dark:text-slate-400\", className)}\n    {...props}\n  />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n  Dialog,\n  DialogTrigger,\n  DialogContent,\n  DialogHeader,\n  DialogFooter,\n  DialogTitle,\n  DialogDescription,\n}\n"
  },
  {
    "path": "app/src/components/ui/input.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"../../lib/utils\"\n\nexport interface InputProps\n  extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n  ({ className, ...props }, ref) => {\n    return (\n      <input\n        className={cn(\n          \"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\",\n          className\n        )}\n        ref={ref}\n        {...props}\n        lang=\"en-150\"\n      />\n    )\n  }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n"
  },
  {
    "path": "app/src/components/ui/label.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Label = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <LabelPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\",\n      className\n    )}\n    {...props}\n  />\n))\nLabel.displayName = LabelPrimitive.Root.displayName\n\nexport { Label }\n"
  },
  {
    "path": "app/src/components/ui/navigation-menu.tsx",
    "content": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva } from \"class-variance-authority\"\nimport { ChevronDown } from \"lucide-react\"\nimport { cn } from \"../../lib/utils\"\n\nconst NavigationMenu = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n  <NavigationMenuPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative z-10 flex flex-1 items-center justify-center\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <NavigationMenuViewport />\n  </NavigationMenuPrimitive.Root>\n))\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName\n\nconst NavigationMenuList = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.List\n    ref={ref}\n    className={cn(\n      \"group flex flex-1 list-none items-center justify-center\",\n      className\n    )}\n    {...props}\n  />\n))\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item\n\nconst navigationMenuTriggerStyle = cva(\n  \"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\"\n)\n\nconst NavigationMenuTrigger = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <NavigationMenuPrimitive.Trigger\n    ref={ref}\n    className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n    {...props}\n  >\n    {children}{\" \"}\n    <ChevronDown\n      className=\"relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180\"\n      aria-hidden=\"true\"\n    />\n  </NavigationMenuPrimitive.Trigger>\n))\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName\n\nconst NavigationMenuContent = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.Content\n    ref={ref}\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n  />\n))\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link\n\nconst NavigationMenuViewport = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>\n>(({ className, ...props }, ref) => (\n  <div className={cn(\"absolute left-0 top-full flex justify-center\")}>\n    <NavigationMenuPrimitive.Viewport\n      className={cn(\n        \"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)]\",\n        className\n      )}\n      ref={ref}\n      {...props}\n    />\n  </div>\n))\nNavigationMenuViewport.displayName =\n  NavigationMenuPrimitive.Viewport.displayName\n\nconst NavigationMenuIndicator = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.Indicator\n    ref={ref}\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n  >\n    <div className=\"relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-slate-200 shadow-md dark:bg-slate-800\" />\n  </NavigationMenuPrimitive.Indicator>\n))\nNavigationMenuIndicator.displayName =\n  NavigationMenuPrimitive.Indicator.displayName\n\nexport {\n  navigationMenuTriggerStyle,\n  NavigationMenu,\n  NavigationMenuList,\n  NavigationMenuItem,\n  NavigationMenuContent,\n  NavigationMenuTrigger,\n  NavigationMenuLink,\n  NavigationMenuIndicator,\n  NavigationMenuViewport,\n}\n"
  },
  {
    "path": "app/src/components/ui/popover.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Popover = PopoverPrimitive.Root\n\nconst PopoverTrigger = PopoverPrimitive.Trigger\n\nconst PopoverContent = React.forwardRef<\n  React.ElementRef<typeof PopoverPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = 4, ...props }, ref) => (\n  <PopoverPrimitive.Portal>\n    <PopoverPrimitive.Content\n      ref={ref}\n      align={align}\n      sideOffset={sideOffset}\n      className={cn(\n        \"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\",\n        className\n      )}\n      {...props}\n    />\n  </PopoverPrimitive.Portal>\n))\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\nexport { Popover, PopoverTrigger, PopoverContent }"
  },
  {
    "path": "app/src/components/ui/right-sheet.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Dialog = DialogPrimitive.Root\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = ({\n  className,\n  children,\n  ...props\n}: DialogPrimitive.DialogPortalProps) => (\n  <DialogPrimitive.Portal className={cn(className)} {...props}>\n    <div className=\"fixed inset-0 z-50 flex justify-end\">{children}</div>\n  </DialogPrimitive.Portal>\n)\nDialogPortal.displayName = DialogPrimitive.Portal.displayName\n\nconst DialogOverlay = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, children, ...props }, ref) => (\n  <DialogPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst DialogContent = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <DialogPortal>\n    <DialogOverlay />\n    <DialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"font-display fixed z-50 grid w-full scale-100 gap-4 bg-white p-6 opacity-100 sm:max-w-lg\",\n        \"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\",\n        \"dark:bg-slate-900\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      {props.hideClose ?  null : (\n        <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\">\n    \n          <X className=\"h-5 w-5\" />\n          <span className=\"sr-only\">Close</span>\n        </DialogPrimitive.Close>)\n      }\n    </DialogPrimitive.Content>\n  </DialogPortal>\n))\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\nconst DialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogHeader.displayName = \"DialogHeader\"\n\nconst DialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogFooter.displayName = \"DialogFooter\"\n\nconst DialogTitle = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Title\n    ref={ref}\n    className={cn(\n      \"text-lg font-semibold text-slate-900\",\n      \"dark:text-slate-50\",\n      className\n    )}\n    {...props}\n  />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-slate-500\", \"dark:text-slate-400\", className)}\n    {...props}\n  />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n  Dialog as Sheet,\n  DialogTrigger as SheetTrigger,\n  DialogContent as SheetContent,\n  DialogHeader as SheetHeader,\n  DialogFooter as SheetFooter,\n  DialogTitle as SheetTitle,\n  DialogDescription as SheetDesription,\n}\n"
  },
  {
    "path": "app/src/components/ui/scroll-area.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst ScrollArea = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n  <ScrollAreaPrimitive.Root\n    ref={ref}\n    className={cn(\"relative overflow-hidden\", className)}\n    {...props}\n  >\n    <ScrollAreaPrimitive.Viewport className=\"h-full w-full rounded-[inherit]\">\n      {children}\n    </ScrollAreaPrimitive.Viewport>\n    <ScrollBar />\n    <ScrollAreaPrimitive.Corner />\n  </ScrollAreaPrimitive.Root>\n))\nScrollArea.displayName = ScrollAreaPrimitive.Root.displayName\n\nconst ScrollBar = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>\n>(({ className, orientation = \"vertical\", ...props }, ref) => (\n  <ScrollAreaPrimitive.ScrollAreaScrollbar\n    ref={ref}\n    orientation={orientation}\n    className={cn(\n      \"flex touch-none select-none transition-colors\",\n      orientation === \"vertical\" &&\n        \"h-full w-2.5 border-l border-l-transparent p-[1px]\",\n      orientation === \"horizontal\" &&\n        \"h-2.5 border-t border-t-transparent p-[1px]\",\n      className\n    )}\n    {...props}\n  >\n    <ScrollAreaPrimitive.ScrollAreaThumb className=\"relative flex-1 rounded-full bg-slate-300 dark:bg-slate-700\" />\n  </ScrollAreaPrimitive.ScrollAreaScrollbar>\n))\nScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName\n\nexport { ScrollArea, ScrollBar }\n"
  },
  {
    "path": "app/src/components/ui/select.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { Check, ChevronDown } from \"lucide-react\"\nimport { cn } from \"../../lib/utils\"\n\nconst Select = SelectPrimitive.Root\n\nconst SelectGroup = SelectPrimitive.Group\n\nconst SelectValue = SelectPrimitive.Value\n\nconst SelectTrigger = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronDown className=\"h-4 w-4 opacity-50\" />\n  </SelectPrimitive.Trigger>\n))\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName\n\nconst SelectContent = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Portal>\n    <SelectPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"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\",\n        className\n      )}\n      {...props}\n    >\n      <SelectPrimitive.Viewport className=\"p-1\">\n        {children}\n      </SelectPrimitive.Viewport>\n    </SelectPrimitive.Content>\n  </SelectPrimitive.Portal>\n))\nSelectContent.displayName = SelectPrimitive.Content.displayName\n\nconst SelectLabel = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Label\n    ref={ref}\n    className={cn(\n      \"py-1.5 pr-2 pl-8 text-sm font-semibold text-slate-900 dark:text-slate-300\",\n      className\n    )}\n    {...props}\n  />\n))\nSelectLabel.displayName = SelectPrimitive.Label.displayName\n\nconst SelectItem = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <SelectPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </SelectPrimitive.ItemIndicator>\n    </span>\n\n    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n  </SelectPrimitive.Item>\n))\nSelectItem.displayName = SelectPrimitive.Item.displayName\n\nconst SelectSeparator = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-slate-100 dark:bg-slate-700\", className)}\n    {...props}\n  />\n))\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName\n\nexport {\n  Select,\n  SelectGroup,\n  SelectValue,\n  SelectTrigger,\n  SelectContent,\n  SelectLabel,\n  SelectItem,\n  SelectSeparator,\n}\n"
  },
  {
    "path": "app/src/components/ui/separator.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Separator = React.forwardRef<\n  React.ElementRef<typeof SeparatorPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>\n>(\n  (\n    { className, orientation = \"horizontal\", decorative = true, ...props },\n    ref\n  ) => (\n    <SeparatorPrimitive.Root\n      ref={ref}\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        \"bg-slate-200 dark:bg-slate-700\",\n        orientation === \"horizontal\" ? \"h-[1px] w-full\" : \"h-full w-[1px]\",\n        className\n      )}\n      {...props}\n    />\n  )\n)\nSeparator.displayName = SeparatorPrimitive.Root.displayName\n\nexport { Separator }\n"
  },
  {
    "path": "app/src/components/ui/sheet.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from \"lucide-react\"\n\nimport { cva, VariantProps } from \"class-variance-authority\"\nimport { cn } from \"../../lib/utils\"\n\nconst dialogVariants = cva(\n  \"fixed z-50 scale-100 gap-4 bg-white p-6 opacity-100 dark:bg-slate-900\",\n  {\n    variants: {\n      type: {\n        modal:\n          \"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\",\n        \"drawer-top\": \"animate-in slide-in-from-top w-full duration-300\",\n        \"drawer-bottom\": \"animate-in slide-in-from-bottom w-full duration-300\",\n        \"drawer-left\": \"animate-in slide-in-from-left h-full duration-300\",\n        \"drawer-right\": \"animate-in slide-in-from-right h-full duration-300\",\n      },\n    },\n    defaultVariants: {\n      type: \"modal\",\n    },\n  }\n)\n\nconst portalVariants = cva(\"fixed inset-0 z-50 flex\", {\n  variants: {\n    type: {\n      modal: \"justify-center items-start sm:items-center\",\n      \"drawer-top\": \"items-start\",\n      \"drawer-bottom\": \"items-end\",\n      \"drawer-left\": \"justify-start\",\n      \"drawer-right\": \"justify-end\",\n    },\n  },\n  defaultVariants: { type: \"modal\" },\n})\ninterface DialogPortalProps\n  extends DialogPrimitive.DialogPortalProps,\n    VariantProps<typeof portalVariants> {}\n\nconst Dialog = DialogPrimitive.Root\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = ({\n  type,\n  className,\n  children,\n  ...props\n}: DialogPortalProps) => (\n  <DialogPrimitive.Portal className={cn(className)} {...props}>\n    <div className={portalVariants({ type })}>{children}</div>\n  </DialogPrimitive.Portal>\n)\nDialogPortal.displayName = DialogPrimitive.Portal.displayName\n\nconst DialogOverlay = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, children, ...props }, ref) => (\n  <DialogPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nexport interface DialogContentProps\n  extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,\n    VariantProps<typeof dialogVariants> {}\n\nconst DialogContent = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Content>,\n  DialogContentProps\n>(({ className, type, children, ...props }, ref) => (\n  <DialogPortal type={type}>\n    <DialogOverlay />\n    <DialogPrimitive.Content\n      ref={ref}\n      className={cn(dialogVariants({ type }), className)}\n      {...props}\n    >\n      {children}\n      <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\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </DialogPrimitive.Close>\n    </DialogPrimitive.Content>\n  </DialogPortal>\n))\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\nconst DialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogHeader.displayName = \"DialogHeader\"\n\nconst DialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogFooter.displayName = \"DialogFooter\"\n\nconst DialogTitle = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Title\n    ref={ref}\n    className={cn(\n      \"text-lg font-semibold text-slate-900\",\n      \"dark:text-slate-50\",\n      className\n    )}\n    {...props}\n  />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-slate-500\", \"dark:text-slate-400\", className)}\n    {...props}\n  />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n  Dialog,\n  DialogTrigger,\n  DialogContent,\n  DialogHeader,\n  DialogFooter,\n  DialogTitle,\n  DialogDescription,\n}\n"
  },
  {
    "path": "app/src/components/ui/slider.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SliderPrimitive from \"@radix-ui/react-slider\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Slider = React.forwardRef<\n  React.ElementRef<typeof SliderPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>\n>(({ className, ...props }, ref) => {\n  const [isDragging, setIsDragging] = React.useState(false);\n  const { onValueChange, onValueCommit, ...rest } = props;\n\n  const _onValueChange = (value) => {\n    //remove classes ease-in duration-500\n    if (thumbRef.current) thumbRef.current.parentElement.classList.remove(\"ease-in\", \"duration-500\")\n\n    if (!isDragging) setIsDragging(true);\n    if (onValueChange) onValueChange(value)\n  }\n\n  const _onValueCommit = (value) => {\n    setIsDragging(false);\n    if(onValueCommit) onValueCommit(value)\n  }\n\n  const thumbRef = React.useRef(null);\n\n  React.useEffect(() => {\n    setTimeout(() => {\n      //prevent animation on initial render\n      thumbRef.current.parentElement.classList.add(\"ease-in\", \"duration-500\");\n    }, 0)\n    \n  }, []);\n\n  return (\n  <SliderPrimitive.Root\n    onValueChange={_onValueChange}\n    onValueCommit={_onValueCommit}\n    ref={ref}\n    className={cn(\n      \"relative flex w-full touch-none select-none items-center\",\n      className\n    )}\n    {...rest}\n  >\n    <SliderPrimitive.Track className=\"relative h-1 w-full grow overflow-hidden rounded-full bg-slate-200 dark:bg-slate-800\">\n      <SliderPrimitive.Range className={`absolute h-full bg-slate-600  dark:bg-slate-800 ${isDragging ? \"\" : \"ease-in duration-500\" }`} />\n    </SliderPrimitive.Track>\n    <SliderPrimitive.Thumb ref={thumbRef}\n      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`} />\n  </SliderPrimitive.Root>\n)})\nSlider.displayName = SliderPrimitive.Root.displayName\n\nexport { Slider }\n"
  },
  {
    "path": "app/src/components/ui/switch.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Switch = React.forwardRef<\n  React.ElementRef<typeof SwitchPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n  <SwitchPrimitives.Root\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  >\n    <SwitchPrimitives.Thumb\n      className={cn(\n        \"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\"\n      )}\n    />\n  </SwitchPrimitives.Root>\n))\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"
  },
  {
    "path": "app/src/components/ui/textarea.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"../../lib/utils\"\n\nexport interface TextareaProps\n  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n  ({ className, ...props }, ref) => {\n    return (\n      <textarea\n        className={cn(\n          \"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\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nTextarea.displayName = \"Textarea\"\n\nexport { Textarea }\n"
  },
  {
    "path": "app/src/components/ui/toast.tsx",
    "content": "import * as React from \"react\"\nimport * as ToastPrimitives from \"@radix-ui/react-toast\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst ToastProvider = ToastPrimitives.Provider\n\nconst ToastViewport = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Viewport>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Viewport\n    ref={ref}\n    className={cn(\n      \"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]\",\n      className\n    )}\n    {...props}\n  />\n))\nToastViewport.displayName = ToastPrimitives.Viewport.displayName\n\nconst toastVariants = cva(\n  \"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\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"bg-white border-slate-200 dark:bg-slate-800 dark:border-slate-700\",\n        destructive:\n          \"group destructive bg-red-600 text-white border-red-600 dark:border-red-600\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nconst Toast = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n    VariantProps<typeof toastVariants>\n>(({ className, variant, ...props }, ref) => {\n  return (\n    <ToastPrimitives.Root\n      ref={ref}\n      className={cn(toastVariants({ variant }), className)}\n      {...props}\n    />\n  )\n})\nToast.displayName = ToastPrimitives.Root.displayName\n\nconst ToastAction = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Action>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Action\n    ref={ref}\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n  />\n))\nToastAction.displayName = ToastPrimitives.Action.displayName\n\nconst ToastClose = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Close>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Close\n    ref={ref}\n    className={cn(\n      \"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\",\n      className\n    )}\n    toast-close=\"\"\n    {...props}\n  >\n    <X className=\"h-4 w-4\" />\n  </ToastPrimitives.Close>\n))\nToastClose.displayName = ToastPrimitives.Close.displayName\n\nconst ToastTitle = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Title>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Title\n    ref={ref}\n    className={cn(\"text-base font-semibold font-display\", className)}\n    {...props}\n  />\n))\nToastTitle.displayName = ToastPrimitives.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Description>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Description\n    ref={ref}\n    className={cn(\"text-sm opacity-90 font-display\", className)}\n    {...props}\n  />\n))\nToastDescription.displayName = ToastPrimitives.Description.displayName\n\ntype ToastProps = React.ComponentPropsWithoutRef<typeof Toast>\n\ntype ToastActionElement = React.ReactElement<typeof ToastAction>\n\nexport {\n  type ToastProps,\n  type ToastActionElement,\n  ToastProvider,\n  ToastViewport,\n  Toast,\n  ToastTitle,\n  ToastDescription,\n  ToastClose,\n  ToastAction,\n}\n"
  },
  {
    "path": "app/src/components/ui/toaster.tsx",
    "content": "\"use client\"\n\nimport { useToast } from \"../../hooks/ui/use-toast\"\n\nimport {\n  Toast,\n  ToastClose,\n  ToastDescription,\n  ToastProvider,\n  ToastTitle,\n  ToastViewport,\n} from \"./toast\"\n\nexport function Toaster() {\n  const { toasts } = useToast()\n\n  return (\n    <ToastProvider>\n      {toasts.map(function ({ id, title, description, action, ...props }) {\n        return (\n          <Toast key={id} {...props}>\n            <div className=\"grid gap-1\">\n              {title && <ToastTitle>{title}</ToastTitle>}\n              {description && (\n                <ToastDescription>{description}</ToastDescription>\n              )}\n            </div>\n            {action}\n            <ToastClose />\n          </Toast>\n        )\n      })}\n      <ToastViewport />\n    </ToastProvider>\n  )\n}\n"
  },
  {
    "path": "app/src/components/ui/tooltip.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } from \"../../lib/utils\"\n\nconst Tooltip = ({ ...props }) => (\n  <TooltipPrimitive.Provider>\n    <TooltipPrimitive.Root {...props} />\n  </TooltipPrimitive.Provider>\n)\nTooltip.displayName = TooltipPrimitive.Tooltip.displayName\n\nconst TooltipTrigger = TooltipPrimitive.Trigger\n\nconst TooltipContent = React.forwardRef<\n  React.ElementRef<typeof TooltipPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <TooltipPrimitive.Content\n    ref={ref}\n    sideOffset={sideOffset}\n    className={cn(\n      \"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\",\n      className\n    )}\n    {...props}\n  />\n))\nTooltipContent.displayName = TooltipPrimitive.Content.displayName\n\nexport { Tooltip, TooltipTrigger, TooltipContent }\n"
  },
  {
    "path": "app/src/error-page.tsx",
    "content": "import { useRouteError } from \"react-router-dom\"\n\nexport default function ErrorPage() {\n  const error = useRouteError()\n  console.error(error)\n\n  return (\n    <div id=\"error-page\">\n      <h1>Oops!</h1>\n      <p>Sorry, an unexpected error has occurred.</p>\n      <p>\n        <i>{error.statusText || error.message}</i>\n      </p>\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/src/hooks/ui/use-toast.tsx",
    "content": "// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport { ToastActionElement, type ToastProps } from \"../../components/ui/toast\"\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000\n\ntype ToasterToast = ToastProps & {\n  id: string\n  title?: React.ReactNode\n  description?: React.ReactNode\n  action?: ToastActionElement\n}\n\nconst actionTypes = {\n  ADD_TOAST: \"ADD_TOAST\",\n  UPDATE_TOAST: \"UPDATE_TOAST\",\n  DISMISS_TOAST: \"DISMISS_TOAST\",\n  REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const\n\nlet count = 0\n\nfunction genId() {\n  count = (count + 1) % Number.MAX_VALUE\n  return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n  | {\n      type: ActionType[\"ADD_TOAST\"]\n      toast: ToasterToast\n    }\n  | {\n      type: ActionType[\"UPDATE_TOAST\"]\n      toast: Partial<ToasterToast>\n    }\n  | {\n      type: ActionType[\"DISMISS_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n  | {\n      type: ActionType[\"REMOVE_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n\ninterface State {\n  toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()\n\nconst addToRemoveQueue = (toastId: string) => {\n  if (toastTimeouts.has(toastId)) {\n    return\n  }\n\n  const timeout = setTimeout(() => {\n    toastTimeouts.delete(toastId)\n    dispatch({\n      type: \"REMOVE_TOAST\",\n      toastId: toastId,\n    })\n  }, TOAST_REMOVE_DELAY)\n\n  toastTimeouts.set(toastId, timeout)\n}\n\nexport const reducer = (state: State, action: Action): State => {\n  switch (action.type) {\n    case \"ADD_TOAST\":\n      return {\n        ...state,\n        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n      }\n\n    case \"UPDATE_TOAST\":\n      return {\n        ...state,\n        toasts: state.toasts.map((t) =>\n          t.id === action.toast.id ? { ...t, ...action.toast } : t\n        ),\n      }\n\n    case \"DISMISS_TOAST\":\n      const { toastId } = action\n      if (toastId) {\n        addToRemoveQueue(toastId)\n      } else {\n        state.toasts.forEach((toast) => {\n          addToRemoveQueue(toast.id)\n        })\n      }\n\n      return {\n        ...state,\n        toasts: state.toasts.map((t) =>\n          t.id === toastId || toastId === undefined\n            ? {\n                ...t,\n                open: false,\n              }\n            : t\n        ),\n      }\n    case \"REMOVE_TOAST\":\n      if (action.toastId === undefined) {\n        return {\n          ...state,\n          toasts: [],\n        }\n      }\n      return {\n        ...state,\n        toasts: state.toasts.filter((t) => t.id !== action.toastId),\n      }\n  }\n}\n\nconst listeners: Array<(state: State) => void> = []\n\nlet memoryState: State = { toasts: [] }\n\nfunction dispatch(action: Action) {\n  memoryState = reducer(memoryState, action)\n  listeners.forEach((listener) => {\n    listener(memoryState)\n  })\n}\n\ninterface Toast extends Omit<ToasterToast, \"id\"> {}\n\nfunction toast({ ...props }: Toast) {\n  const id = genId()\n\n  const update = (props: ToasterToast) =>\n    dispatch({\n      type: \"UPDATE_TOAST\",\n      toast: { ...props, id },\n    })\n  const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id })\n\n  dispatch({\n    type: \"ADD_TOAST\",\n    toast: {\n      ...props,\n      id,\n      open: true,\n      onOpenChange: (open) => {\n        if (!open) dismiss()\n      },\n    },\n  })\n\n  return {\n    id: id,\n    dismiss,\n    update,\n  }\n}\n\nfunction useToast() {\n  const [state, setState] = React.useState<State>(memoryState)\n\n  React.useEffect(() => {\n    listeners.push(setState)\n    return () => {\n      const index = listeners.indexOf(setState)\n      if (index > -1) {\n        listeners.splice(index, 1)\n      }\n    }\n  }, [state])\n\n  return {\n    ...state,\n    toast,\n    dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n  }\n}\n\nexport { useToast, toast }\n"
  },
  {
    "path": "app/src/hooks/use-breakpoint.ts",
    "content": "// @ts-ignore\n// https://stackoverflow.com/a/71098593/2865073\nimport { useMediaQuery } from \"react-responsive\"\nimport resolveConfig from \"tailwindcss/resolveConfig\"\nimport tailwindConfig from \"../../tailwind.config\"\nconst fullConfig = resolveConfig(tailwindConfig)\n\nconst breakpoints = fullConfig.theme?.screens\n\ntype BreakpointKey = keyof typeof breakpoints\n\nexport function useBreakpoint<K extends BreakpointKey>(breakpointKey: any) {\n  const bool = useMediaQuery({\n    query: `(min-width: ${breakpoints[breakpointKey]})`,\n  })\n  const capitalizedKey =\n    breakpointKey[0].toUpperCase() + breakpointKey.substring(1)\n  type Key = `is${Capitalize<K>}`\n  return {\n    [`is${capitalizedKey}`]: bool,\n  } as Record<any, boolean>\n}\n"
  },
  {
    "path": "app/src/index.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml, body, #app {\n  height: 100%;\n  max-height: 100%;\n  font-family: \"Quicksand\", sans-serif;\n}\n\n/* fixes overflowing on certain devices */\n.public-DraftEditor-content {\n  overflow-wrap: anywhere !important;\n}\n\n.DraftEditor-root {\n  flex: 1 1 0;\n  overflow: auto;\n}\n.loading_border {\n  padding: 11px;\n  outline:none;\n  border: none;\n  border-radius: 1px;\n  box-shadow: none;\n  background-image: \n    linear-gradient(#334155, #334155), \n    linear-gradient(#334155, #334155), \n    linear-gradient(#334155, #334155), \n    linear-gradient(#334155, #334155), \n    \n    linear-gradient(rgba(226 232 240), rgba(226 232 240)), \n    linear-gradient(rgba(226 232 240), rgba(226 232 240)), \n    linear-gradient(rgba(226 232 240), rgba(226 232 240)), \n    linear-gradient(rgba(226 232 240), rgba(226 232 240));\n  \n  background-position: 0 0, 0 0, 0 100%, 0 100%, \n                       0 0, 0 0, 0 100%, 100% 0;\n  background-size: 1px 0%, 0% 1px, 0% 1px, 1px 0%,  \n                   1px 100%, 100% 1px, 100% 1px,1px 100%;\n  background-color:transparent;\n  background-repeat:no-repeat;\n  transition:0.4s linear;\n}\n\n.border_inference_animate {\n  animation: animate 4s linear infinite;\n}\n\n.border_inference_pending {\n  background-image: \n    linear-gradient(#8d8e8f, #8d8e8f), \n    linear-gradient(#8d8e8f, #8d8e8f), \n    linear-gradient(#8d8e8f, #8d8e8f), \n    linear-gradient(#8d8e8f, #8d8e8f), \n\n    linear-gradient(rgba(226 232 240), rgba(226 232 240)), \n    linear-gradient(rgba(226 232 240), rgba(226 232 240)), \n    linear-gradient(rgba(226 232 240), rgba(226 232 240)), \n    linear-gradient(rgba(226 232 240), rgba(226 232 240));\n}\n\n.border_inference_complete {\n  background-image: \n    linear-gradient(#334155, #334155), \n    linear-gradient(#334155, #334155), \n    linear-gradient(#334155, #334155), \n    linear-gradient(#334155, #334155), \n    \n    linear-gradient(#334155, #334155), \n    linear-gradient(#334155, #334155), \n    linear-gradient(#334155, #334155), \n    linear-gradient(#334155, #334155);\n}\n\n.border_inference_error {\n  background-image: \n    linear-gradient(#f56760, #f56760), \n    linear-gradient(#f56760, #f56760), \n    linear-gradient(#f56760, #f56760), \n    linear-gradient(#f56760, #f56760), \n    \n    linear-gradient(#f56760, #f56760), \n    linear-gradient(#f56760, #f56760), \n    linear-gradient(#f56760, #f56760), \n    linear-gradient(#f56760, #f56760);\n}\n\n@keyframes animate {\n  0% {\n  background-position: 0 100%, 0 0, 0 100%, 100% 0, \n                       0 0, 0 0, 0 100%, 100% 0;\n  background-size: 1px 0%, 100% 1px, 0% 1px,1px 0%,  \n                   1px 100%, 100% 1px, 100% 1px,1px 100%;\n  }\n  40% {\n  background-position: 0 100%, 100% 0, 100% 100%, 100% 0, \n                       0 0, 0 0, 0 100%, 100% 0;\n  background-size: 1px 0%, 100% 1px, 0% 1px,1px 100%,  \n                   1px 100%, 100% 1px, 100% 1px,1px 100%;\n  }\n  60% {\n  background-position: 0 100%, 100% 0, 100% 100%, 100% 100%, \n                       0 0, 0 0, 0 100%, 100% 0;\n  background-size: 1px 0%, 0% 1px, 100% 1px,1px 100%,  \n                   1px 100%, 100% 1px, 100% 1px,1px 100%;\n  }\n  70% {\n  background-position: 0 100%, 100% 0, 0% 100%, 100% 100%, \n                       0 0, 0 0, 0 100%, 100% 0;\n  background-size: 1px 100%, 0% 1px, 100% 1px,1px 0%,  \n                   1px 100%, 100% 1px, 100% 1px,1px 100%;\n  }\n  80% {\n  background-position: 0% 0%, 0% 0, 0% 100%, 100% 100%, \n                       0 0, 0 0, 0 100%, 100% 0;\n  background-size: 1px 100%, 0% 1px, 0% 1px,1px 0%,  \n                   1px 100%, 100% 1px, 100% 1px,1px 100%;\n  }\n  100% {\n  background-position: 0% 0%, 0 0, 0 100%, 100% 100%, \n                       0 0, 0 0, 0 100%, 100% 0;\n  background-size: 1px 0%, 100% 1px, 0% 1px,1px 0%,  \n                   1px 100%, 100% 1px, 100% 1px,1px 100%;  \n  }\n}\n\n.fade-enter {\n  opacity: 0;\n}\n.fade-enter-active {\n  opacity: 1;\n  transition: opacity 500ms;\n}\n.fade-exit {\n  opacity: 1;\n}\n.fade-exit-active {\n  opacity: 0;\n  transition: opacity 500ms;\n}\n::-webkit-scrollbar-track {\n  background-color: transparent;\n  border-radius: 9999px;\n}\n::-webkit-scrollbar-thumb {\n  --tw-border-opacity: 1;\n  background-color: rgba(217,217,227,.8);\n  border-color: rgba(255,255,255,var(--tw-border-opacity));\n  border-radius: 9999px;\n  border-width: 1px;\n}\n\n/* Customize the track */\n::-webkit-scrollbar-track {\n  background-color: transparent;\n  border-radius: 9999px;\n}\n\n/* Customize the thumb */\n::-webkit-scrollbar-thumb {\n  background-color: #a1a1a1;\n  border-radius: 9999px;\n  border: 1px solid #a1a1a1;\n}\n\n/* Set the scrollbar size */\n::-webkit-scrollbar {\n  height: 0.3rem;\n  width: 0.3rem;\n}\n\n/* Set the scrollbar's corners to round */\n::-webkit-scrollbar-corner {\n  background: transparent;\n}\n\n.item-enter {\n  opacity: 0;\n}\n.item-enter-active {\n  opacity: 1;\n  transition: opacity 500ms ease-in;\n}\n.item-exit {\n  opacity: 1;\n}\n.item-exit-active {\n  opacity: 0;\n  transition: opacity 500ms ease-in;\n}\n"
  },
  {
    "path": "app/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>OpenPlayground</title>\n    <meta name=\"description\" content=\"Open source playground to test LLMs\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta charset=\"utf-8\" />\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <style>\n      @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\");\n      </style>\n    <style>\n      @import url(\"https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&display=swap\");\n    </style>\n    <link href=\"./index.css\" rel=\"stylesheet\" />\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "app/src/index.tsx",
    "content": "import React from \"react\"\nimport { createRoot } from \"react-dom/client\"\nimport App from \"./app\"\n  \nconst container = document.getElementById(\"app\")!\nconst root = createRoot(container)\nroot.render(<App />)\n"
  },
  {
    "path": "app/src/lib/ctrl-meta-keypress.tsx",
    "content": "import { useCallback, useEffect, useLayoutEffect, useRef } from \"react\"\n\nexport const useCtrlMetaKeyPress = (keys: any, callback: any, node = null) => {\n  const callbackRef = useRef(callback)\n  useLayoutEffect(() => {\n    callbackRef.current = callback\n  })\n\n  const handleKeyPress = useCallback(\n    (event: any) => {\n      if (event.metaKey && event.ctrlKey && keys.some((key: any) => event.key === key)) {\n        callbackRef.current(event)\n      }\n    },\n    [keys]\n  )\n\n  useEffect(() => {\n    const targetNode = node ?? document\n    targetNode && targetNode.addEventListener(\"keydown\", handleKeyPress)\n\n    return () =>\n      targetNode && targetNode.removeEventListener(\"keydown\", handleKeyPress)\n  }, [handleKeyPress, node])\n}"
  },
  {
    "path": "app/src/lib/editor-styles.tsx",
    "content": "export const styleMap = {\n    HIGHLIGHT: {\n      backgroundColor: \"#faed27\",\n    },\n    NORMAL: {\n      backgroundColor: \"transparent\",\n    },\n    BOLD: {\n      fontWeight: \"bold\",\n    },\n  };\n  \n  export const styles = {\n    openai: {\n      transition: \"background-color 0.2s ease-in-out\",\n      backgroundColor: \"#b9eebc\",\n      padding: \"2px 0\",\n    },\n    huggingface_local: {\n      transition: \"background-color 0.2s ease-in-out\",\n      backgroundColor: \"#f6b2b3\",\n      padding: \"2px 0\",\n    },\n    cohere: {\n      transition: \"background-color 0.2s ease-in-out\",\n      backgroundColor: \"#a198e6\",\n      padding: \"2px 0\",\n    },\n    huggingface: {\n      transition: \"background-color 0.2s ease-in-out\",\n      backgroundColor: \"#D7BCE8\",\n      padding: \"2px 0\",\n    },\n    forefront: {\n      backgroundColor: \"#BCCAE8\",\n      padding: \"2px 0\",\n    },\n    anthropic: {\n      backgroundColor: \"#cc785c80\",\n      padding: \"2px 0\",\n    },\n    aleph_alpha: {\n      backgroundColor: \"#e3ff00\",\n      padding: \"2px 0\",\n    },\n    default: {\n      backgroundColor: \"transparent\",\n      transition: \"background-color 0.2s ease-in-out\",\n      padding: \"2px 0\",\n    },\n  };\n  \n  export function getDecoratedStyle(provider: string, showHighlights: boolean) {\n    if (showHighlights === false) return styles.default;\n    switch (provider) {\n      case \"openai\":\n        return styles.openai;\n      case \"huggingface-local\":\n        return styles.huggingface_local;\n      case \"cohere\":\n        return styles.cohere;\n      case \"huggingface\":\n        return styles.huggingface;\n      case \"forefront\":\n        return styles.forefront;\n      case \"anthropic\":\n        return styles.anthropic;\n      case \"aleph-alpha\":\n        return styles.aleph_alpha;\n  \n      default:\n        return styles.default;\n    }\n  }\n  "
  },
  {
    "path": "app/src/lib/keypress.tsx",
    "content": "import { useCallback, useEffect, useLayoutEffect, useRef } from \"react\"\n\nexport const useKeyPress = (keys: any, callback: any, node = null) => {\n  const callbackRef = useRef(callback)\n  useLayoutEffect(() => {\n    callbackRef.current = callback\n  })\n\n  const handleKeyPress = useCallback(\n    (event: any) => {\n      if (keys.some((key: any) => event.key === key)) {\n        callbackRef.current(event)\n      }\n    },\n    [keys]\n  )\n\n  useEffect(() => {\n    const targetNode = node ?? document\n    targetNode && targetNode.addEventListener(\"keydown\", handleKeyPress)\n\n    return () =>\n      targetNode && targetNode.removeEventListener(\"keydown\", handleKeyPress)\n  }, [handleKeyPress, node])\n}\n"
  },
  {
    "path": "app/src/lib/meta-keypress.tsx",
    "content": "import { useCallback, useEffect, useLayoutEffect, useRef } from \"react\"\n\nexport const useMetaKeyPress = (keys: any, callback: any, node = null) => {\n  const callbackRef = useRef(callback)\n  useLayoutEffect(() => {\n    callbackRef.current = callback\n  })\n\n  const handleKeyPress = useCallback(\n    (event: any) => {\n      if ((event.ctrlKey || event.metaKey) && keys.some((key: any) => event.key === key)) {\n        callbackRef.current(event)\n      }\n    },\n    [keys]\n  )\n\n  useEffect(() => {\n    const targetNode = node ?? document\n    targetNode && targetNode.addEventListener(\"keydown\", handleKeyPress)\n\n    return () =>\n      targetNode && targetNode.removeEventListener(\"keydown\", handleKeyPress)\n  }, [handleKeyPress, node])\n}\n"
  },
  {
    "path": "app/src/lib/utils.ts",
    "content": "import { ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n\nexport function handleSelectModel(modelState, modelsStateContext, setModelsStateContext, parametersContext, setParametersContext, multi_select: boolean) {\n  const number_of_models_selected = modelsStateContext.filter(modelState => modelState.selected).length\n  const selected = (!multi_select && number_of_models_selected > 1) ? true : !modelState.selected\n\n  if (selected && !multi_select) {\n    const parameters = modelState.parameters\n\n    setParametersContext({\n      ...parametersContext,\n      temperature: parameters.temperature?.value || parametersContext.temperature,\n      maximumLength: parameters.maximumLength?.value || parametersContext.maximumLength,\n      topP: parameters.topP?.value || parametersContext.topP,\n      topK: parameters.topK?.value || parametersContext.topK,\n      frequencyPenalty: parameters.frequencyPenalty?.value || parametersContext.frequencyPenalty,\n      presencePenalty: parameters.presencePenalty?.value || parametersContext.presencePenalty,\n      repetitionPenalty: parameters.repetitionPenalty?.value || parametersContext.repetitionPenalty,\n      stopSequences: parameters.stopSequences?.value || parametersContext.stopSequences\n    })\n  }\n\n  setModelsStateContext(\n    modelsStateContext.map((m) => {\n      if (!multi_select && m.tag !== modelState.tag) {\n        m.selected = false\n      } else if (m.tag === modelState.tag) {\n        m.selected = selected\n      }\n      return m\n    })\n  )\n}"
  },
  {
    "path": "app/src/pages/compare.tsx",
    "content": "\nimport React, {\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n  forwardRef,\n  useImperativeHandle,\n} from \"react\"\nimport {\n  Editor,\n  EditorState,\n  convertFromRaw,\n  convertToRaw,\n  CompositeDecorator,\n  SelectionState,\n  Modifier,\n  ContentState,\n  RichUtils,\n  getDefaultKeyBinding,\n} from \"draft-js\"\nimport { Button } from \"../components/ui/button\"\nimport { Popover } from \"react-tiny-popover\"\nimport NavBar from \"../components/navbar\"\nimport {\n  Loader2,\n  Settings2,\n  AlertTriangle,\n} from \"lucide-react\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"../components/ui/tooltip\"\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n} from \"../components/ui/alert-dialog\"\nimport chroma from \"chroma-js\"\nimport {useMetaKeyPress} from \"../lib/meta-keypress\"\nimport {useKeyPress} from \"../lib/keypress\"\nimport \"draft-js/dist/Draft.css\"\nimport {Sheet, SheetContent, SheetTrigger} from \"../components/ui/right-sheet\"\nimport {CSSTransition, TransitionGroup} from \"react-transition-group\"\nimport {APIContext, EditorContext, ParametersContext, ModelsContext, ModelsStateContext} from \"../app\"\nimport {styleMap, getDecoratedStyle} from \"../lib/editor-styles\"\nimport ParameterSidePanel from \"../components/parameters-side-panel\"\nimport {handleSelectModel} from \"../lib/utils\"\n\nconst normalize_parameter = (parameter: number) => {\n  if (parameter > 1) return parameter\n  else return parameter.toFixed(1)\n}\n\nconst ModelCardStats = (props: any) => {\n  const {errorMessage, is_running, totalCharacters} = props\n  const [isTimerRunning, setIsTimerRunning] = useState<boolean>(false);\n  const intervalRef = useRef(null);\n  const [time, setTime] = useState(0);\n  \n  useEffect(() => {\n    if (is_running && isTimerRunning === false) {\n      startTimer()\n    } else if (!is_running && isTimerRunning === true) {\n      stopTimer()\n    }\n  }, [is_running])\n\n\n  const startTimer = () => {\n    setIsTimerRunning(true)\n    setTime(0)\n    intervalRef.current = setInterval(() => {\n      setTime((prevTime) => prevTime + 1)\n    }, 1000)\n  }\n\n  const stopTimer = () => {\n    clearInterval(intervalRef.current)\n    setIsTimerRunning(false)\n  }\n\n  function insertLineBreaks(str) {\n    if (str === undefined || str === null) return [];\n\n    const words = str.split(\" \");\n    const result = [];\n    let accumulator = \"\";\n\n    for (let i = 0; i < words.length; i++) {\n      accumulator += `${words[i]} `;\n      \n      if ((i + 1) % 4 === 0 || i === words.length - 1) {\n        result.push(accumulator);\n        accumulator = \"\";\n      }\n    }\n    return result;\n  }\n\n  const paragraphs = insertLineBreaks(errorMessage).map((words, index) => (\n    <span className = \"block text-center\" key={index}>{words}</span>\n  ));\n\n  const token_per_second =\n    totalCharacters > 0 ? Math.floor(totalCharacters / Math.max(time, 1)) : 0\n\n  const formatTime = (time) => {\n    const minutes = Math.floor(time / 60)\n      .toString()\n      .padStart(2, \"0\")\n    const seconds = (time % 60).toString().padStart(2, \"0\")\n    return `${minutes}:${seconds}`\n  }\n\n  return (\n    <div className=\"flex font-medium\">\n      <span>{formatTime(time)}</span>\n      <span className=\"flex-1\"></span>\n      <span>\n        <Tooltip delayDuration={300} skipDelayDuration={150} open={errorMessage ? true : false}>\n          <TooltipTrigger asChild>\n            <div style = {{display: (errorMessage ) ? \"block\" : \"none\"}}>\n              <AlertTriangle color = \"#f56760\"/>\n            </div>\n          </TooltipTrigger>\n          <TooltipContent side={\"top\"}>\n            <>{paragraphs}</>\n          </TooltipContent>\n        </Tooltip>\n            \n        {token_per_second === 0 || errorMessage ? \"\" : `${token_per_second} chars/s`}{\" \"}\n      </span>\n      <span className=\"flex-1\"></span>\n      <span>{totalCharacters} chars</span>\n    </div>\n  )\n}\n\nconst ModelEditor = React.memo((props: any) => {\n  const {\n    editorState,\n    setEditorState\n  } = props\n\n  return (\n    <Editor\n      customStyleMap={styleMap}\n      editorState={editorState}\n      onChange={(editorState) => {\n        setEditorState(editorState)\n      }}\n    />\n  )\n})\n\nconst ModelCard = forwardRef((props, ref) => {\n  const token_index = useRef(0)\n  const {model, showHighlights, showProbabilities, completion} = props\n  const [serverModelState, setServerModelState] = React.useState<string>(\"IDLE\")\n  const [errorMessage, setErrorMessage] = useState(null);\n  const [totalCharacters, setTotalCharacters] = useState(0);\n  const [output, setOutput] = React.useState<string[]>([])\n  const [status, setStatus] = React.useState<string[]>([])\n\n  const {modelsStateContext, setModelsStateContext} = useContext(ModelsStateContext)\n  const {parametersContext, setParametersContext} = useContext(ParametersContext)\n\n  const showProbabilitiesRef = useRef(showProbabilities)\n  const showHighlightsRef = useRef(showHighlights)\n  \n  useEffect(() => {\n    setEditorState(\n      EditorState.forceSelection(editorState, editorState.getSelection())\n    )\n  }, [showProbabilities, showHighlights])\n  \n  useEffect(() => {\n    showProbabilitiesRef.current = showProbabilities\n    showHighlightsRef.current = showHighlights\n  })\n\n  if (completion.length > token_index.current) {\n    let completion_slice = completion.slice(token_index.current, completion.length)\n    token_index.current = completion.length;\n    setOutput(completion_slice)\n  }\n\n  const Decorated = (props: any) => {\n    const children = props.children\n    const entity = props.contentState.getEntity(props.entityKey)\n    const entityData = entity.getData()\n    const style = getDecoratedStyle(entityData.modelProvider, showHighlightsRef.current)\n    const probabilitiesMap = entityData.topNDistribution\n    const tokensMap = probabilitiesMap ? probabilitiesMap[\"tokens\"] : []\n\n    const [popoverOpen, setPopoverOpen] = React.useState<boolean>(false)\n    if (entityData.message === props.decoratedText) {\n      let content = (\n        <span style={style} key={children[0].key} data-offset-key={children[0].key}>\n          {children}\n        </span>\n      )\n      \n      if (probabilitiesMap && (tokensMap[props.decoratedText] != undefined && tokensMap[props.decoratedText].length > 0)) {\n        let percentage = Math.min(tokensMap[props.decoratedText][1] / probabilitiesMap.simpleProbSum, 1.0)\n        let f = chroma.scale([\"#ff8886\", \"ffff00\", \"#96f29b\"])\n        let highlight_color = f(percentage)\n\n        let custom_style = showProbabilitiesRef.current ? {\n          backgroundColor: highlight_color,\n          padding: \"2px 0\",\n        } : style\n\n        let popoverContent = \n        (\n          <div className=\"shadow-xl shadow-inner rounded-sm bg-white mb-2\" data-container=\"body\">\n            <ul key={children[0].key} className=\"grid pt-4\">\n              {\n                Object.entries(tokensMap).map((item, index) => {\n                  return (\n                    <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\"}>\n                      {item[0]} = {tokensMap[item[0]][1]}%\n                    </li>\n                  )\n                })\n              }\n            </ul>\n            <div className=\"m-4 pb-4\">\n              <div className=\"text-base\">Total: {probabilitiesMap.logProbSum} logprob on 1 tokens</div>\n              <div className=\"text-xs\">({probabilitiesMap.simpleProbSum}% probability covered in top {Object.keys(probabilitiesMap.tokens).length} logits)</div>\n            </div>\n          </div>\n        )\n        content = (\n          <Popover \n            isOpen={popoverOpen} \n            onClickOutside={() => setPopoverOpen(false)}\n            positions={[\"bottom\", \"top\", \"left\", \"right\"]}\n            content={popoverContent}\n            containerStyle={{zIndex: \"1000\"}}\n          >\n            <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}}>\n              {children}\n            </span>\n          </Popover>\n        )\n      }\n\n      return content\n    } else {\n      return <span data-offset-key={children[0].key}>{children}</span>\n    }\n  }\n  \n  const getEditorState = useCallback((): EditorState => editorStateRef.current, [])\n\n  const createDecorator = () => new CompositeDecorator([{\n    strategy: findEntityRangesByType(\"HIGHLIGHTED_WORD\"),\n    component: Decorated,\n    props: {\n      getEditorState,\n    }\n  }])\n\n  const [editorState, setEditorState] = React.useState(EditorState.createEmpty(createDecorator()))\n  const editorStateRef = useRef<EditorState>(editorState)\n\n  function findEntityRangesByType(entityType: any) {\n    return (contentBlock: any, callback: any, contentState: any) => {\n      contentBlock.findEntityRanges((character: any) => {\n        const entityKey = character.getEntity()\n        if (entityKey === null) {\n          return false\n        }\n        return contentState.getEntity(entityKey).getType() === entityType\n      }, callback)\n    }\n  }\n\n  useEffect(() => {\n    let current_editor_state = editorState;\n    let aggregate_new_chars = 0;\n\n    try {\n      for(const output_entry of output) {\n\n        aggregate_new_chars += output_entry.message.split(\"\").length\n        const currentContent = current_editor_state.getCurrentContent()\n        const blockMap = currentContent.getBlockMap()\n        const key = blockMap.last().getKey()\n        const length = blockMap.last().getLength()\n        const selection = new SelectionState({\n          anchorKey: key,\n          anchorOffset: length,\n          focusKey: key,\n          focusOffset: length,\n        })\n\n        currentContent.createEntity(\"HIGHLIGHTED_WORD\", \"MUTABLE\", output_entry)\n\n        const entityKey = currentContent.getLastCreatedEntityKey()\n\n        const textWithInsert = Modifier.insertText(\n          currentContent,\n          selection,\n          output_entry.message,\n          null,\n          entityKey\n        )\n        const editorWithInsert = EditorState.push(\n          editorState,\n          textWithInsert,\n          \"insert-characters\"\n        )\n        current_editor_state = editorWithInsert\n      }\n    } catch (e) {\n    }\n    setTotalCharacters(totalCharacters + aggregate_new_chars)\n    setEditorState(current_editor_state)\n  }, [output])\n\n  useEffect(() => {\n    if (status.message === \"[INITIALIZING]\") {\n      setServerModelState(\"INITIALIZED\")\n      setTotalCharacters(0)\n      setErrorMessage(null)\n      return\n    }\n    if (status.message && status.message.indexOf(\"[ERROR] \") === 0 && (serverModelState !== \"COMPLETED\" && serverModelState !== \"IDLE\")) {\n      setServerModelState(\"ERROR\")\n      setErrorMessage(status.message.replace(\"[ERROR] \", \"\"))\n      return\n    }\n    if (status.message === \"[COMPLETED]\") {\n      setServerModelState(\"COMPLETED\")\n      return\n    }\n  }, [status])\n\n  const handleNotification = (output: any) => {\n    setOutput(output)\n  }\n\n  const handleNotificationStatus = (status: any) => {\n    setStatus(status)\n  }\n\n  const handleUndo = (output: any) => {\n    setEditorState(\n      EditorState.createWithContent(\n        ContentState.createFromText(\"\"),\n        createDecorator()\n      )\n    )\n  }\n\n  useImperativeHandle(ref, () => ({\n    handleNotification,\n    handleUndo,\n    handleNotificationStatus\n  }))\n\n  let border_class = \"\"\n  switch (serverModelState) {\n    case \"INITIALIZED\":\n      border_class = \"border_inference_pending border_inference_animate\"\n      break\n    case \"RUNNING\":\n      border_class = \"border_inference_animate\"\n      break\n    case \"COMPLETED\":\n      border_class = \"border_inference_complete\"\n      break\n    case \"ERROR\":\n      border_class = \"border_inference_error\"\n      break\n    default:\n      break\n  }\n\n  return (\n    <div className={`flex flex-col items-center text-gray-600 text-lg font-bold h-96`}\n      style = {model.selected? {\n        transition: \"all 0.3s ease\",\n        backgroundColor: \"#f5f5f5\",\n        borderRadius: 4,\n        padding: 6\n      } : {\n        transition: \"all 0.3s ease\",\n        backgroundColor: \"#ffffff\",\n        borderRadius: 0,\n        padding: 0\n      } }>\n      <div className=\"flex justify  max-w-[100%]\">\n        <h2\n          onClick={(event) => {\n            handleSelectModel(\n              model,\n              modelsStateContext,\n              setModelsStateContext,\n              parametersContext,\n              setParametersContext,\n              event.ctrlKey || event.metaKey\n            )\n          }}\n          className={\n            `select-none cursor-pointer text-ellipsis overflow-hidden max-w-full whitespace-nowrap overflow-hidden ${model.selected ? \"font-medium\" : \"font-normal\"}`\n          }>\n            {model.name}\n        </h2>\n      </div>\n      <div className=\"relative editor-container h-full w-full text-base flex mt-2\" style = {{clipPath: \"inset(-1px)\"}}>\n        <div\n          className={`font-medium relative p-3 overflow-hidden flex-1 flex flex-col loading_border ${border_class}`}\n        >\n          <ModelEditor {...props} editorState ={editorState} setEditorState ={setEditorState} />\n          <ModelCardStats\n            errorMessage={errorMessage}\n            totalCharacters={totalCharacters}\n            is_running = {serverModelState !== \"ERROR\" && serverModelState !== \"COMPLETED\" && serverModelState !== \"IDLE\"}\n          />\n       </div>\n      </div>\n    </div>\n  )\n})\n\nfunction PromptEditor({editorState, setEditorState, ...props}: any) {\n  const scrollRef = useRef(null)\n  const editorRef = React.useRef(null)\n\n  const {modelsStateContext} = useContext(ModelsStateContext)\n  const {parametersContext} = useContext(ParametersContext)\n\n  const number_of_models_enabled = modelsStateContext.filter((modelState) => modelState.enabled).length \n\n  function focusEditor() {\n    editorRef.current.focus()\n  }\n\n  const handleKeyCommand = (command: any, editorState: any) => {\n    if (command === \"bold\") {\n      setEditorState(RichUtils.toggleInlineStyle(editorState, \"BOLD\"))\n      return \"handled\"\n    }\n    if (command === \"ignore_enter\") {\n      return \"handled\"\n    }\n    return \"not-handled\"\n  }\n\n  const keyBindingFn = (event: any) => {\n    if (event.code === \"Enter\" && event.metaKey) {\n      return \"ignore_enter\"\n    }\n\n    if (event.metaKey && event.keyCode === 66) {\n      return \"bold\"\n    } else if (event.ctrlKey && event.keyCode === 66) {\n      return \"bold\"\n    }\n    return getDefaultKeyBinding(event)\n  }\n  \n  /*useEffect(() => {\n    setEditorContext({\n      internalState: convertToRaw(editorState.getCurrentContent())\n    })\n  }, [\n    editorState\n  ])*/\n\n  useEffect(() => {\n    setEditorState(\n      EditorState.forceSelection(editorState, editorState.getSelection())\n    )\n  }, [parametersContext.showProbabilities])\n  \n  return (\n    <div\n      ref={scrollRef}\n      onClick={focusEditor}\n      className=\"overflow-y-auto editor-container h-[100%] w-full text-base\"\n    >\n      <Editor\n        placeholder={\n        number_of_models_enabled >= 1\n          ? \"Type your prompt here...\"\n          : \"No models have been enabled...\"\n        }\n        ref={editorRef}\n        keyBindingFn={keyBindingFn}\n        handleKeyCommand={handleKeyCommand}\n        customStyleMap={styleMap}\n        editorState={editorState}\n        onChange={(editorState: any) => {\n          setEditorState(editorState)\n        }}\n      />\n    </div>\n  )\n}\n\nfunction PromptArea({showDialog}) {\n  const is_mac_os = navigator.platform.toUpperCase().indexOf(\"MAC\") >= 0\n  const cancel_callback = React.useRef<any>(null)\n  const [generating, setGenerating] = React.useState<boolean>(false)\n  const apiContext = useContext(APIContext)\n  const {modelsStateContext} = useContext(ModelsStateContext)\n  const {editorContext, setEditorContext} = useContext(EditorContext)\n  \n  React.useEffect(() => {\n    return () => {\n      setEditorContext({\n        ...editorContext,\n        internalState: convertToRaw(editorStateRef.current.getCurrentContent()),\n        prompt: editorStateRef.current.getCurrentContent().getPlainText()\n      }, true)\n    }\n  }, []);\n\n  const [editorState, setEditorState] = React.useState(\n    EditorState.moveFocusToEnd(EditorState.createWithContent(\n      editorContext.internalState !== null ? convertFromRaw(editorContext.internalState): ContentState.createFromText(editorContext.prompt)\n    ))\n  )\n\n  const editorStateRef = useRef(editorState)\n\n  useEffect(() => {\n    editorStateRef.current = editorState\n  }, [editorState])\n\n  const generatingRef =  useRef(generating);\n\n  const number_of_models_enabled = modelsStateContext.filter((modelState) => modelState.enabled).length\n\n  const abortCompletion = () => {\n    if (cancel_callback.current) {\n      cancel_callback.current()\n    }\n  }\n\n  useKeyPress([\"Escape\"], (event: any) => {\n    abortCompletion()\n  })\n\n  useEffect(() => {\n    const completion_callback = ({event}) => {\n      switch (event) {\n        case \"close\":\n          setEditorContext({\n            prompt: editorStateRef.current.getCurrentContent().getPlainText()\n          })\n          setGenerating(false)\n        break;\n\n        default:\n        break;\n      }\n    }\n\n    apiContext.Inference.subscribeTextCompletion(completion_callback)\n\n    return () => {\n      apiContext.Inference.unsubscribeTextCompletion(completion_callback);\n    };\n  }, []);\n\n  useMetaKeyPress([\"Enter\"], (event: any) => {\n    handleSubmit()\n  })\n\n  const handleStreamingSubmit = async (\n    regenerate = false,\n    passedInPrompt = \"\",\n  ) => {\n    const prompt  = regenerate ? passedInPrompt : editorState.getCurrentContent().getPlainText();\n    \n    setGenerating(true)\n    setEditorContext({\n      prePrompt: prompt\n    })\n\n    const _cancel_callback = apiContext.Inference.textCompletionRequest({\n      prompt: regenerate ? passedInPrompt : prompt,\n      models: modelsStateContext.map((modelState) => {\n        if(modelState.enabled) {\n          return modelState\n        }\n      }).filter(Boolean)\n    })\n    cancel_callback.current = _cancel_callback\n  }\n\n  useEffect(() => {\n    generatingRef.current = generating\n  })\n\n  const handleSubmit = async (regenerate = false, passedInPrompt = \"\") => {\n    return handleStreamingSubmit(regenerate, passedInPrompt)\n  }\n\n  return (\n    <form\n      className=\"flex flex-col grow basis-auto min-h-[25%] max-h-[75%] overflow-auto\"\n      onSubmit={(e) => {\n        e.preventDefault()\n        handleSubmit()\n      }}\n    >\n    <div className=\"flex-1 flex border border-slate-400\">\n      <div className={`relative overflow-hidden flex-1 flex flex-col p-2`}>\n        <PromptEditor editorState = {editorState} setEditorState = {setEditorState}/>\n        <div className=\"absolute bottom-[.5em] right-[1em] z-[2]\">\n          {generating && (\n            <Tooltip delayDuration={100}>\n            <TooltipTrigger asChild>\n              <Button\n                variant=\"default\"\n                className=\"inline-flex items-center ml-1 text-sm font-medium text-center\"\n                onClick={(e) => {\n                  e.stopPropagation()\n                  abortCompletion()\n                }}\n                >\n                {\" \"}\n                <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                Cancel\n            </Button>\n            </TooltipTrigger>\n            <TooltipContent\n              side=\"top\"\n              align=\"center\"\n              className=\"bg-slate-600 text-white hidden md:block\"\n            >\n              Cancel &nbsp;\n              <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\">\n                Escape\n              </kbd>\n              &nbsp;\n            </TooltipContent>\n          </Tooltip>)\n          }\n          {!generating && (\n            <Tooltip delayDuration={100}>\n            <TooltipTrigger asChild>\n              <Button\n                disabled={number_of_models_enabled === 0}\n                variant=\"default\"\n                className=\"bg-emerald-500 hover:bg-emerald-700 inline-flex items-center ml-1 text-sm font-medium text-center\"\n                type=\"submit\"\n                value=\"submit\"\n              >\n                Submit\n              </Button>\n            </TooltipTrigger>\n            <TooltipContent\n              side=\"top\"\n              align=\"center\"\n              className=\"bg-slate-600 text-white hidden md:block\"\n            >\n              Submit &nbsp;\n              <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\">\n              {is_mac_os ? \"⌘\" : \"Control\"}\n              </kbd>\n              &nbsp;\n              <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\">\n                Enter\n              </kbd>\n            </TooltipContent>\n          </Tooltip>\n          )}\n        </div>\n      </div>\n    </div>\n  </form>\n  )\n}\n\nconst ModelsCompletion = ({showDialog}) => {\n  const modelEditorRefs = useRef({})\n  const [modelsCompletionState, setModelsCompletionState] = React.useState({});\n  const [_, signalRender] = React.useState(0);\n\n  const apiContext = React.useContext(APIContext);\n  const {modelsStateContext} = useContext(ModelsStateContext)\n  const {parametersContext} = React.useContext(ParametersContext);\n  const number_of_models_enabled = modelsStateContext.filter((modelState) => modelState.enabled).length\n\n  const notifyEditorStatus = (model_tag, message) => {\n    if (model_tag === \"*\") {\n      for(const model_tag in modelEditorRefs.current) {\n        if (modelEditorRefs.current[model_tag])\n          modelEditorRefs.current[model_tag].handleNotificationStatus(message)\n      }\n\n      return \n    }\n  \n    if (modelEditorRefs.current[model_tag])\n      modelEditorRefs.current[model_tag].handleNotificationStatus(message)\n  }\n\n  const handleUndoLast = () => {\n    for (const editor_model_tag of Object.keys(modelEditorRefs.current)) {\n      if (modelEditorRefs.current[editor_model_tag])\n        modelEditorRefs.current[editor_model_tag].handleUndo()\n    }\n  }\n\n  useMetaKeyPress([\"O\"], (event: any) => {\n    if (editorContext.prePrompt === \"\") {\n      return\n    }\n    handleUndoLast()\n  })\n\n  useEffect(() => {\n    const completion_callback = ({event, data}) => {\n      switch (event) {\n        case \"open\":\n          handleUndoLast()\n        break;\n\n        case \"status\":\n          notifyEditorStatus(data.modelTag, data)\n        break;\n\n        case \"cancel\":\n          notifyEditorStatus(\"*\", {\n            message: \"[ERROR] Cancelled by user\"\n          })\n        break;\n\n        case \"completion\":\n          for (let model_tag in data) {\n            modelsCompletionState[model_tag] = [\n              ...(modelsCompletionState[model_tag] || []),\n              ...data[model_tag]\n            ]\n          }\n\n          setModelsCompletionState(modelsCompletionState)\n          signalRender((x) => x + 1)\n        break;\n\n        case \"error\":\n          switch(data) {\n            case \"Too many pending requests\":\n              showDialog({\n                title: \"Too many pending requests\",\n                message: \"Please wait a few seconds before trying again.\",\n              })\n            break;\n\n            case \"Too many daily completions\":\n              showDialog({\n                title: \"Daily limit reached\",\n                message: \"It seems you've reached your daily limit. Please try again tomorrow.\",\n              })\n            break;\n\n            case \"Unauthorized\":\n              showDialog({\n                title: \"Unauthorized\",\n                message: \"Please log in to use this feature.\",\n              })\n            break;\n\n            default:\n              showDialog({\n                title: \"Error\",\n                message: data,\n              })\n            break;\n          }\n        break;\n\n\n        default:\n          console.log(\"Unknown event\", event, data);\n        break;\n      }\n    }\n\n    apiContext.Inference.subscribeTextCompletion(completion_callback)\n\n    return () => {\n      apiContext.Inference.unsubscribeTextCompletion(completion_callback);\n    };\n  }, []);\n\n  return (\n    <TransitionGroup\n      className={`grid h-full mt-3 pr-1 overflow-auto \n        ${\n          number_of_models_enabled === 1 \n          ? \"grid-cols-1 gap-1 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2\"\n          : number_of_models_enabled === 2 \n          ? \"grid-cols-1 gap-3 sm:grid-cols-1 md:grid-cols-2\"\n          : number_of_models_enabled === 3\n          ? \"grid-cols-1 gap-3 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\n          : number_of_models_enabled === 4 \n          ? \"grid-cols-1 gap-3 gap-3 sm:grid-cols-1 md:grid-cols-2\"\n          : \"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\"\n        }\n      `}\n    > {\n      modelsStateContext\n        .filter((modelState) => modelState.enabled && modelState.tag)\n        .map((modelState) => (\n          <CSSTransition\n            key ={modelState.tag}\n            timeout={500}\n            classNames=\"fade\"\n            unmountOnExit>\n            <ModelCard\n              key={modelState.tag}\n              ref={(ref) => (modelEditorRefs.current[modelState.tag] = ref) }\n              completion = {modelsCompletionState[modelState.tag] || []} \n              model={modelState}\n              showProbabilities={parametersContext.showProbabilities}\n              showHighlights={parametersContext.highlightModels}\n              />\n            </CSSTransition>\n          )\n        )\n      }\n    </TransitionGroup>\n  )\n}\n\nconst ParametersTable = () => {\n  const {parametersContext, setParametersContext} = React.useContext(ParametersContext);\n  const {modelsContext} = React.useContext(ModelsContext);\n  const {modelsStateContext, setModelsStateContext} = useContext(ModelsStateContext)\n  const number_of_models_enabled = modelsStateContext.filter((modelState) => modelState.enabled).length\n\n  const generate_headers = () => (\n    [\"Model\", \"ML\", \"T\", \"TP\", \"TK\", \"FP\", \"PP\", \"RP\"].map((header, index) => (\n      <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\"}`}>\n        {header}\n      </div>\n    )\n  ))\n  \n  const generate_table_line_graph = (parameterValue, parameterDefaults) => {\n    if (!parameterValue) return (\n      <div className=\"flex-1\">\n        <span className=\"cursor-default text-xs\">N/A</span>\n      </div>\n    )\n\n    return (\n      <div className=\"flex-1\">\n        <div className = \"mx-4 relative flx items-center\">\n          <div className=\"rounded-md bg-slate-200 w-[100%] h-[3px]\"/>\n          <div\n            className=\"rounded-md bg-slate-600 absolute h-[3px] top-[0px] ease-in duration-300\"\n            style={{ width: `${100 * (parameterValue / parameterDefaults.range[1])}%`}}\n          />\n        </div>\n        <span className= \"cursor-default absolute top-[-1px] right-[16px] text-[12px]\">\n          {normalize_parameter(parameterValue)}\n        </span>\n      </div>\n    )\n  }\n  \n  const generate_row = (modelState, model) => (\n    [\n      \"name\", \"maximumLength\", \"temperature\", \"topP\", \"topK\", \"frequencyPenalty\", \"presencePenalty\", \"repetitionPenalty\"\n    ].map((parameter, index) => (\n      (parameter === \"name\") ?\n        <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\" : \"\"}`}>\n          <span>{modelState.name.split(\":\")[1]}</span>\n        </div>\n        :\n      <div className=\"bg-inherit text-center font-light relative min-w-[150px] flex flex-1 items-center\">\n        {generate_table_line_graph(modelState.parameters[parameter], model.defaultParameters[parameter])}\n      </div>\n    ))\n  )\n\n  const generate_rows = () => {\n    return modelsStateContext\n      .filter((modelState) => modelState.enabled)\n      .map((modelState) => {\n        return (<div\n          key={modelState.tag}\n          className={`cursor-pointer flex flex-row hover:bg-slate-100 ${modelState.selected ? \"bg-slate-100\" : \"bg-white\"}`}\n          onClick={(event) => {\n            handleSelectModel(\n              modelState,\n              modelsStateContext,\n              setModelsStateContext,\n              parametersContext,\n              setParametersContext,\n              event.ctrlKey || event.metaKey\n            )\n          }}>\n          {generate_row(modelState, modelsContext[modelState.name])}\n        </div>)\n      })\n  }\n  \n  return (\n    <div>\n      {!parametersContext.showParametersTable || number_of_models_enabled === 0 ? null :\n        <div className=\"flex mt-4 justify-center\">\n          <div className=\"rounded-sm border border-slate-200 max-h-[350px] max-w-[1400px] overflow-auto\">\n            <div className=\"flex flex-col m-2 min-w-[900px]\">\n              <div className=\"flex flex-row sticky top-[0] z-[3] bg-white\">\n                {generate_headers()}\n              </div>\n                {generate_rows()}\n            </div>\n          </div>\n        </div>\n      }\n    </div>\n  )\n}\n\nconst CustomAlertDialogue = ({dialog}) => {\n  const [openDialog, setOpenDialog] = React.useState<boolean>(false)\n  const [_dialogue, _setDialogue] = React.useState<any>({\n    title: \"\",\n    message: \"\"\n  })\n\n  useEffect(() => {\n    if (!openDialog && dialog.title !== \"\" && dialog.message !== \"\") {\n      _setDialogue({\n        title: dialog.title,\n        message: dialog.message\n      })\n      setOpenDialog(true)\n    }\n  }, [dialog])\n\n  return (\n    <AlertDialog open={openDialog} onOpenChange={setOpenDialog}>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>{_dialogue.title}</AlertDialogTitle>\n          <AlertDialogDescription className=\"text-base text-slate-700 dark:text-slate-400\">\n            {_dialogue.message}\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogAction>Ok</AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n    )\n}\n\nexport default function Compare() {\n  const [openParameterSheet, setSaveOpenParameterSheet] = React.useState<boolean>(false)\n  const parametersSidePanel = (<ParameterSidePanel showModelList = {true} />)\n\n  const [dialog, showDialog] = React.useState({\n    title: \"\",\n    message: \"\"\n  })\n\n  return (\n    <div className=\"flex flex-col h-full\">\n      <NavBar tab=\"compare\">\n        <div className=\"align-middle mt-1\">\n          <div className=\"flex basis-full mb-2 lg:mb-0 space-x-2\">\n            <Sheet open={openParameterSheet} onOpenChange={setSaveOpenParameterSheet}>\n              <SheetTrigger asChild>\n                <Button variant=\"subtle\" className=\"lg:hidden\">\n                  <Settings2 className=\"h-6 w-6\" />\n                </Button>\n              </SheetTrigger>\n              <SheetContent className=\"w-[80vw] p-4 pt-10 overflow-auto\">\n                {parametersSidePanel}\n              </SheetContent>\n            </Sheet>\n          </div>\n        </div>\n      </NavBar>\n      <CustomAlertDialogue dialog = {dialog} />\n      <div className=\"flex flex-grow flex-col font-display min-h-0 min-w-0\">\n        <div className=\"flex flex-row space-x-4 flex-grow mx-2 md:ml-5 lg:ml-5 min-h-0 min-w-0\">\n          <div className=\"flex-1 flex flex-col lg:max-w-[calc(100%-266px)] max-w-[100%]\">\n            <PromptArea showDialog = {showDialog}/>\n            <ParametersTable showDialog = {showDialog}/>\n            <ModelsCompletion showDialog = {showDialog}/>\n          </div>\n        <div className=\"hidden p-1 grow-0 shrink-0 basis-auto lg:w-[250px] overflow-auto lg:block\">\n          {parametersSidePanel}\n        </div>\n      </div>\n    </div>\n  </div>\n  )\n}"
  },
  {
    "path": "app/src/pages/index.tsx",
    "content": "import Playground from \"./playground\"\nimport Compare from \"./compare\"\nimport Settings from \"./settings\"\n\nexport { Playground, Compare, Settings };"
  },
  {
    "path": "app/src/pages/playground.tsx",
    "content": "import React, {\n  useCallback, useContext, useEffect, useRef\n} from \"react\"\nimport {\n  Editor,\n  EditorState,\n  CompositeDecorator,\n  SelectionState,\n  Modifier,\n  ContentState,\n  RichUtils,\n  getDefaultKeyBinding,\n  convertToRaw,\n  convertFromRaw,\n} from \"draft-js\"\nimport { Button } from \"../components/ui/button\"\nimport NavBar from \"../components/navbar\"\nimport * as DropdownMenu from \"@radix-ui/react-dropdown-menu\"\nimport {\n  X,\n  HistoryIcon,\n  Loader2,\n  Settings2,\n} from \"lucide-react\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"../components/ui/tooltip\"\nimport { Popover } from \"react-tiny-popover\"\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n} from \"../components/ui/alert-dialog\"\nimport { useMetaKeyPress } from \"../lib/meta-keypress\"\nimport { useKeyPress } from \"../lib/keypress\"\nimport \"draft-js/dist/Draft.css\"\nimport { Sheet, SheetContent, SheetTrigger } from \"../components/ui/right-sheet\"\nimport chroma from \"chroma-js\"\nimport { useToast } from \"../hooks/ui/use-toast\"\nimport {styleMap, getDecoratedStyle} from \"../lib/editor-styles\"\nimport { APIContext, EditorContext, ModelsStateContext, ParametersContext, HistoryContext} from \"../app\"\nimport ParameterSidePanel from \"../components/parameters-side-panel\"\nimport { TooltipProvider } from \"@radix-ui/react-tooltip\"\n\nconst HistorySidePanel = () => {\n  const {\n    historyContext, toggleShowHistory, clearHistory, selectHistoryItem\n  } = useContext(HistoryContext)\n\n  const handleDeleteAllHistory = () => {\n    clearHistory()\n  }\n\n  if (!historyContext.show) return null;\n\n  const downloadHistory = () => {\n    const element = document.createElement(\"a\")\n    const history_json = historyContext.entries.map((entry: any) => {\n      const model = entry.modelsState.find(({selected}) => selected)\n      const text = EditorState.createWithContent(convertFromRaw(entry.editor.internalState)).getCurrentContent().getPlainText()\n      return {\n        model: model.name,\n        date: entry.date,\n        timestamp: entry.timestamp,\n        text: text,\n        parameters: entry.parameters\n      }\n    })\n\n    const file = new Blob([JSON.stringify(history_json)], {\n      type: \"application/json\",\n    })\n    element.href = URL.createObjectURL(file)\n    element.download = \"history.json\"\n    document.body.appendChild(element) // Required for this to work in FireFox\n    element.click()\n  }\n  return (\n      <div className=\"flex flex-col h-full relative overflow-auto\">\n        <div\n          className=\"text-lg tracking-tight font-semibold text-slate-900 flex sticky top-[0] right-[0]\"\n          style={{ justifyContent: \"flex-end\" }}\n        >\n          <div>\n          <DropdownMenu.Root>\n            <DropdownMenu.Trigger asChild>\n              <Button\n                type=\"button\"\n                variant=\"subtle\"\n                className=\"inline-flex text-sm font-medium outline-0\"\n                onClick={(e) => {\n                  setShowHistory((e) => !e)\n                }}\n                disabled={history.length == 0}\n              >\n                ...\n              </Button>\n            </DropdownMenu.Trigger>\n            <DropdownMenu.Portal>\n              <DropdownMenu.Content\n                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\"\n                sideOffset={5}\n              >\n                <DropdownMenu.Item\n                  className=\"cursor-pointer outline-0 hover:bg-slate-200 text-sm p-2 text-center\"\n                  onClick={() => {\n                    downloadHistory()\n                  }}\n                  >\n                  Download as JSON\n                </DropdownMenu.Item>\n                <DropdownMenu.Separator className=\"h-[1px] bg-slate-200\" />\n                <DropdownMenu.Item\n                  className=\"cursor-pointer outline-0 hover:bg-slate-200 text-sm p-2 text-center\"\n                  onClick={() => {\n                    handleDeleteAllHistory()\n                  }}\n                >\n                  Clear History\n                </DropdownMenu.Item>\n                <DropdownMenu.Arrow className=\"fill-white\" />\n              </DropdownMenu.Content>\n            </DropdownMenu.Portal>\n            </DropdownMenu.Root>\n          </div>\n\n          <div className=\"cursor-pointer inline m-2 align-middle lg:inline align-middle mb-1\" style = {{height: 20, width: 20}}>\n            <X\n              size={20}\n              onClick={(e) => {\n                toggleShowHistory()\n              }}\n            />\n          </div>\n        </div>\n      \n        <div className=\"overflow-y-auto max-h-[100%] mt-2\">\n          {historyContext.entries\n              .reduce((accumulator: any, value: any) => {\n                let val = value.date \n                if (!accumulator.includes(val)) {\n                  accumulator.push(val)\n                }\n                return accumulator.sort((a, b) => (new Date(b) - new Date(a)))\n              }, [])\n              .map((unique_date: any, main_index) => {\n                \n                return (\n                  <div key = {unique_date}>\n                    <div className=\"text-xs tracking-tight mb-4 mt-2 font-semibold uppercase text-slate-900\">\n                      {new Date(unique_date).toLocaleDateString(\n                        [\"en-GB\", \"en-us\"],\n                        {\n                          weekday: \"long\",\n                          year: \"numeric\",\n                          month: \"short\",\n                          day: \"numeric\",\n                        }\n                      )}\n                    </div>\n                    {historyContext.entries\n                      .filter((value: any) => (value.date === unique_date))\n                      .sort((a: any, b: any) => (new Date(b.timestamp) - new Date(a.timestamp)))\n                      .map((historyItem: any, index: number) => {\n                        const isSelectedHistoryItem = historyContext.current.timestamp === historyItem.timestamp && historyContext.current.editor.prompt === historyItem.editor.prompt;\n\n                        return (\n                          <div key={historyItem.timestamp}>\n                            <div\n                              onClick={() => {\n                                selectHistoryItem(historyItem)\n                              }}\n                              className={`[&>div:nth-child(2)]:hover:w-[7px]\n                              [&>div:nth-child(2)]:hover:h-[7px]\n                              [&>div:nth-child(2)]:hover:left-[77px]\n                              [&>div:nth-child(2)]:hover:border-slate-800\n                              [&>div:nth-child(2)]:hover:border-2\n                              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  ${\n                                isSelectedHistoryItem\n                                  ? \"bg-slate-200\"\n                                  : \"hover:bg-slate-100\"\n                              }`}\n                            >\n                              <div\n                                className={`bg-slate-300 w-[1px] absolute left-[80px] ${\n                                  main_index === 0 && index === 0\n                                    ? \"h-[75%] top-[25%]\"\n                                    : \"h-[100%] top-[0]\"\n                                }`}\n                              />\n                              <div\n                                className={`ease-in duration-100 border rounded-full bg-white absolute top-[22px] ${\n                                  isSelectedHistoryItem\n                                    ? \"border-slate-800 w-[7px] h-[7px] border-2 left-[77px]\"\n                                    : \"border-slate-500 w-[5px] h-[5px] left-[78px] \"\n                                }\n                              `}\n                              />\n                              <div className=\"text-xs pl-4 pr-10\">\n                                {main_index === 0 && index === 0 ? (\n                                  <span style = {{marginRight: 6}}>Now</span>\n                                ) : (\n                                  new Date(historyItem.timestamp)\n                                    .toTimeString()\n                                    .split(\":\")\n                                    .slice(0, 2)\n                                    .join(\":\")\n                                )}\n                              </div>\n                              <div className=\"text-xs overflow-hidden \">\n                                <p className=\"truncate tracking-wide\">\n                                  {main_index === 0 && index === 0\n                                    ? \"Current\"\n                                    : historyItem.editor.prompt}\n                                </p>\n                                <div\n                                  className=\"mt font-medium\"\n                                  style={{\n                                    whiteSpace: \"nowrap\",\n                                    overflow: \"hidden\",\n                                  }}\n                                >\n                                  {historyItem.modelsState.find(({selected})=> selected).name}\n                                </div>\n                              </div>\n                            </div>\n                          </div>\n                        )\n                      })}\n                  </div>\n                )\n              })}\n        </div>\n      </div>\n \n  )\n}\n\nclass EditorWrapper extends React.Component {\n  componentDidCatch() {\n    const {resetEditorState} = this.props\n    resetEditorState()\n  }\n\n  keyBindingFn(event: any) {\n    if (event.code === \"Enter\" && event.metaKey) {\n      return \"ignore_enter\"\n    }\n\n    if (event.metaKey && event.keyCode === 66) {\n      return \"bold\"\n    } else if (event.ctrlKey && event.keyCode === 66) {\n      return \"bold\"\n    }\n    return getDefaultKeyBinding(event)\n  }\n\n  handleKeyCommand(command: any, editorState: any) {\n    const {setEditorState} = this.props\n\n    if (command === \"bold\") {\n      setEditorState(RichUtils.toggleInlineStyle(editorState, \"BOLD\"))\n      return \"handled\"\n    }\n    if (command === \"ignore_enter\") {\n      return \"handled\"\n    }\n    return \"not-handled\"\n  }\n\n  render() {\n    const {editorState, setEditorState} = this.props\n    return (\n      <Editor\n        keyBindingFn={this.keyBindingFn.bind(this)}\n        handleKeyCommand={this.handleKeyCommand.bind(this)}\n        customStyleMap={styleMap}\n        editorState={editorState}\n        onChange={(editorState: any) => {\n          setEditorState(editorState)\n        }}\n        stripPastedStyles\n      />\n    )\n  }\n}\n\nconst PromptCompletionEditor = ({showDialog}) => {\n  const {editorContext, setEditorContext} = useContext(EditorContext)\n  const {parametersContext} = useContext(ParametersContext)\n  const {modelsStateContext} = useContext(ModelsStateContext)\n  const {\n    historyContext, addHistoryEntry, toggleShowHistory\n  } = useContext(HistoryContext)\n  const number_of_models_selected = modelsStateContext.filter(({selected}) => selected).length\n\n  const [status, setStatus] = React.useState<string[]>([])\n  const [output, setOutput] = React.useState<string[]>([])\n  const apiContext = useContext(APIContext)\n  const scrollRef = useRef(null)\n  const is_mac_os = navigator.platform.toUpperCase().indexOf(\"MAC\") >= 0\n  const [_, signalRender] = React.useState(0);\n\n  const [generating, setGenerating] = React.useState<boolean>(false);\n  const cancel_callback = React.useRef<any>(null)\n  const { toast } = useToast()\n\n  const showProbabilitiesRef = useRef(parametersContext.showProbabilities)\n  const highlightModelsRef = useRef(parametersContext.highlightModels)\n\n  useEffect(() => {\n    showProbabilitiesRef.current = parametersContext.showProbabilities\n    highlightModelsRef.current = parametersContext.highlightModels\n  })\n\n  React.useEffect(() => {\n    return () => {\n      setEditorContext({\n        ...editorContext,\n        internalState: convertToRaw(editorStateRef.current.getCurrentContent()),\n        prompt: editorStateRef.current.getCurrentContent().getPlainText()\n      }, true)\n    }\n  }, []);\n\n  useEffect(() => {\n    if (editorContext.internalState) {\n      setEditorState(\n        EditorState.createWithContent(convertFromRaw(editorContext.internalState),\n        createDecorator())\n      )\n    }\n  }, [editorContext.internalState])\n\n  const handleStreamingSubmit = async (\n    regenerate = false,\n    passedInPrompt = \"\"\n  ) => {\n    const prompt  = regenerate ? passedInPrompt : editorState.getCurrentContent().getPlainText();\n\n    setGenerating(true)\n    setEditorContext({\n      prePrompt: prompt,\n      previousInternalState: convertToRaw(editorState.getCurrentContent())\n    })\n\n    const _cancel_callback = apiContext.Inference.textCompletionRequest({\n      prompt: regenerate ? passedInPrompt : prompt,\n      models: modelsStateContext.map((modelState) => {\n        if(modelState.selected) {\n          return modelState\n        }\n      }).filter(Boolean)\n    })\n\n    cancel_callback.current = _cancel_callback\n  }\n\n  useEffect(() => {\n    const completionCallback = ({event, data, meta}) => {\n      switch (event) {\n        case \"cancel\":\n          setGenerating(false)\n        break;\n\n        case \"close\":\n          if (!meta.error)\n            addHistoryEntry(convertToRaw(editorStateRef.current.getCurrentContent()))\n          \n            \n          setEditorContext({\n            prompt: editorStateRef.current.getCurrentContent().getPlainText(),\n            internalState: convertToRaw(editorStateRef.current.getCurrentContent()),\n          })\n          setGenerating(false)\n        break;\n\n        case \"completion\":\n          setOutput(data[Object.keys(data)[0]])\n          signalRender((x) => x + 1)\n        break;\n\n        case \"status\":\n          const {message} = data\n          if (message.indexOf(\"[ERROR] \") === 0) {\n            showDialog({\n              title: \"Model Error\",\n              message: message.replace(\"[ERROR] \", \"\"),\n            })\n          }\n        break;\n\n        case \"error\":\n          switch(data) {\n            case \"Too many pending requests\":\n              showDialog({\n                title: \"Too many pending requests\",\n                message: \"Please wait a few seconds before trying again.\",\n              })\n            break;\n\n            case \"Too many daily completions\":\n              showDialog({\n                title: \"Daily limit reached\",\n                message: \"It seems you've reached your daily limit of completions. Please try again tomorrow.\",\n              })\n            break;\n\n            case \"Unauthorized\":\n              showDialog({\n                title: \"Unauthorized\",\n                message: \"Please log in to use this feature.\",\n              })\n            break;\n\n            default:\n              console.log(\"default error handling?\")\n              showDialog({\n                title: \"Error\",\n                message: data,\n              })\n            break;\n          }\n        break;\n\n        default:\n          console.log(\"Unknown event\", event, data);\n        break;\n      }\n    }\n    \n    apiContext.Inference.subscribeTextCompletion(completionCallback)\n\n    return () => {\n      apiContext.Inference.unsubscribeTextCompletion(completionCallback);\n    };\n  }, []);\n\n  const handleSubmit = async (regenerate = false, passedInPrompt = \"\") => {\n    return handleStreamingSubmit(regenerate, passedInPrompt)\n  }\n\n  useMetaKeyPress([\"Enter\"], (event: any) => {\n    handleSubmit()\n  })\n\n  const abortCompletion = () => {\n    if (cancel_callback.current) {\n      cancel_callback.current()\n    }\n  }\n\n  useKeyPress([\"Escape\"], (event: any) => {\n    abortCompletion()\n  })\n\n  useMetaKeyPress([\"u\"], (event: any) => {\n    if (editorContext.prePrompt === \"\") {\n      return\n    } else {\n      handleUndoLast()\n    }\n  })\n\n  const regenerateKeyPress = (event: any) => {\n    event.preventDefault()\n    if (editorContext.prePrompt === \"\") {\n      return\n    } else {\n      handleUndoLast()\n      handleSubmit(true, editorContext.prePrompt)\n    }\n  }\n\n  useMetaKeyPress([\"alt\", \"r\"], regenerateKeyPress)\n  useMetaKeyPress([\"alt\", \"®\"], regenerateKeyPress)\n\n  const Decorated = (props: any) => {\n    const children = props.children\n    const entity = props.contentState.getEntity(props.entityKey)\n    const entityData = entity.getData()\n    const style = getDecoratedStyle(entityData.modelProvider, highlightModelsRef.current)\n    const probabilitiesMap = entityData.topNDistribution\n    const tokensMap = probabilitiesMap ? probabilitiesMap[\"tokens\"] : []\n\n    const [popoverOpen, setPopoverOpen] = React.useState<boolean>(false)\n    if (entityData.message === props.decoratedText) {\n      let content = (\n        <span style={style} key={children[0].key} data-offset-key={children[0].key}>\n          {children}\n        </span>\n      )\n \n      if (probabilitiesMap && (tokensMap[props.decoratedText] != undefined && tokensMap[props.decoratedText].length > 0)) {\n        let percentage = Math.min(tokensMap[props.decoratedText][1] / probabilitiesMap.simpleProbSum, 1.0)\n        let f = chroma.scale([\"#ff8886\", \"ffff00\", \"#96f29b\"])\n        let highlight_color = f(percentage)\n\n        let custom_style = showProbabilitiesRef.current ? {\n          backgroundColor: highlight_color,\n          padding: \"2px 0\",\n        } : style\n\n        let popoverContent = \n        (\n          <div className=\"shadow-xl shadow-inner rounded-sm bg-white mb-2\" data-container=\"body\">\n            <ul key={children[0].key} className=\"grid pt-4\">\n              {\n                Object.entries(tokensMap).map((item, index) => {\n                  return (\n                    <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\"}>\n                      {item[0]} = {tokensMap[item[0]][1]}%\n                    </li>\n                  )\n                })\n              }\n            </ul>\n            <div className=\"m-4 pb-4\">\n              <div className=\"text-base\">Total: {probabilitiesMap.logProbSum} logprob on 1 tokens</div>\n              <div className=\"text-xs\">({probabilitiesMap.simpleProbSum}% probability covered in top {Object.keys(probabilitiesMap.tokens).length} logits)</div>\n            </div>\n          </div>\n        )\n        content = (\n          <Popover \n            isOpen={popoverOpen} \n            onClickOutside={() => setPopoverOpen(false)}\n            positions={[\"bottom\", \"top\", \"left\", \"right\"]}\n            content={popoverContent}\n            containerStyle={{zIndex: \"1000\"}}\n          >\n            <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}}>\n              {children}\n            </span>\n          </Popover>\n        )\n      }\n\n      return content\n    } else {\n      return <span data-offset-key={children[0].key}>{children}</span>\n    }\n  }\n\n  function findEntityRangesByType(entityType: any) {\n    return (contentBlock: any, callback: any, contentState: any) => {\n      contentBlock.findEntityRanges((character: any) => {\n        const entityKey = character.getEntity()\n        if (entityKey === null) {\n          return false\n        }\n        return contentState.getEntity(entityKey).getType() === entityType\n      }, callback)\n    }\n  }\n\n  const getEditorState = useCallback((): EditorState => {\n    return editorStateRef.current\n  }, [])\n\n  const createDecorator = () => {\n    return new CompositeDecorator([\n      {\n        strategy: findEntityRangesByType(\"HIGHLIGHTED_WORD\"),\n        component: Decorated,\n        props: {\n          getEditorState,\n        },\n      },\n    ])\n  }\n\n  const [editorState, setEditorState] = React.useState(\n    EditorState.moveFocusToEnd(EditorState.createWithContent(\n      editorContext.internalState !== null ? convertFromRaw(editorContext.internalState): ContentState.createFromText(editorContext.prompt),\n      createDecorator()\n    ))\n  )\n\n  const editorStateRef = useRef<EditorState>(editorState)\n  \n  useEffect(() => {\n    editorStateRef.current = editorState;\n  }, [editorState]);\n\n  useEffect(() => {\n    setEditorState(\n      EditorState.forceSelection(editorState, editorState.getSelection())\n    )\n  }, [parametersContext.showProbabilities, parametersContext.highlightModels])\n\n  const resetEditorState = () => {\n    setEditorState(\n      EditorState.moveFocusToEnd(EditorState.createWithContent(\n        ContentState.createFromText(\"\"),\n        createDecorator()\n      ))\n    )\n  }\n\n  useEffect(() => {\n    let current_editor_state = editorState;\n    try {\n      for(const output_entry of output) {\n        const currentContent = current_editor_state.getCurrentContent()\n        const blockMap = currentContent.getBlockMap()\n        const key = blockMap.last().getKey()\n        const length = blockMap.last().getLength()\n        const selection = new SelectionState({\n          anchorKey: key,\n          anchorOffset: length,\n          focusKey: key,\n          focusOffset: length,\n        })\n        currentContent.createEntity(\"HIGHLIGHTED_WORD\", \"MUTABLE\", output_entry)\n\n        const entityKey = currentContent.getLastCreatedEntityKey()\n        const textWithInsert = Modifier.insertText(\n          currentContent,\n          selection,\n          output_entry.message,\n          null,\n          entityKey\n        )\n        const editorWithInsert = EditorState.push(\n          current_editor_state,\n          textWithInsert,\n          \"insert-characters\"\n        )\n        const newEditorState = EditorState.moveSelectionToEnd(editorWithInsert)\n        const finalEditorState = EditorState.forceSelection(\n          newEditorState,\n          newEditorState.getSelection()\n        )\n        current_editor_state = finalEditorState\n\n        if (scrollRef.current) {\n          const scrollEl = scrollRef.current\n          scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.clientHeight\n        }\n      }\n    } catch (e) {\n      console.log(\"Error in editor update\", e)\n    }\n\n    setEditorState(current_editor_state)\n    editorStateRef.current = current_editor_state\n  }, [output])\n\n  useEffect(() => {\n    if (status.message && status.message.indexOf(\"[QUEUE] \") === 0) {\n      toast({\n        title: \"Inference request queued\",\n        description: \"We're currently experiencing high load, your compeletion request is in a queue and will be compeleted shortly\"\n      })\n      return\n    } \n    if (status.message && status.message.indexOf(\"[ERROR] \") === 0) {\n      showDialog({\n        title: \"An error occured!\",\n        description: status.message.replace(\"[ERROR] \", \"\")\n      })\n      return\n    }\n  }, [status])\n\n  const handleUndoLast = () => {\n    setEditorState(\n      EditorState.moveFocusToEnd(\n        EditorState.createWithContent(\n          convertFromRaw(editorContext.previousInternalState),\n          createDecorator()\n        )\n      )\n    )\n    setEditorContext({\n      prompt: editorContext.prePrompt,\n      prePrompt: \"\",\n      previousInternalState: null,\n    })\n  }\n  \n  return (\n    <form\n      onSubmit={(e) => {\n        e.preventDefault()\n        handleSubmit()\n      }}\n      className=\"flex flex-col grow basis-auto lg:max-w-[calc(100%-266px)]\"\n    >\n      <div\n        id=\"editor\"\n        ref={scrollRef}\n        className=\"overflow-y-auto editor-container h-full w-full py-3 px-3 text-base rounded-md border border-slate-300\"\n      >\n        <EditorWrapper\n          editorState = {editorState}\n          setEditorState= {setEditorState}\n          resetEditorState = {resetEditorState}\n        />\n      </div>\n    \n      <div className=\"flex space-x-2 mb-8\">\n        {generating && (\n          <TooltipProvider>\n            <Tooltip delayDuration={100}>\n              <TooltipTrigger asChild>\n              <div>\n              <Button\n                  type=\"button\"\n                  variant=\"subtle\"\n                  className=\"hidden lg:inline-flex md:inline-flex items-center mt-4 text-sm font-medium text-center\"\n                  onClick={(e) => {\n                    e.stopPropagation()\n                    abortCompletion()\n                  }}\n                >\n                  {\" \"}\n                  <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                  Cancel Generation\n                </Button>\n\n                <Button\n                  type=\"button\"\n                  variant=\"subtle\"\n                  className=\"inline-flex lg:hidden md:hidden items-center mt-4 text-sm font-medium text-center\"\n                  onClick={(e) => {\n                    e.stopPropagation()\n                    abortCompletion()\n                  }}\n                >\n                  {\" \"}\n                  <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n                  Cancel\n                </Button>\n              </div>\n              </TooltipTrigger>\n              <TooltipContent\n                side=\"top\"\n                align=\"center\"\n                className=\"bg-slate-600 text-white hidden hidden md:block\"\n              >\n                Cancel Generation &nbsp;\n                <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\">\n                  Esc\n                </kbd>\n              </TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n        )}\n        <TooltipProvider>\n          {!generating && (\n            <Tooltip delayDuration={100}>\n              <TooltipTrigger asChild>\n                <Button\n                  variant=\"default\"\n                  className=\"bg-emerald-500 hover:bg-emerald-700 inline-flex items-center mt-4 text-sm font-medium text-center\"\n                  type=\"submit\"\n                  value=\"submit\"\n                  disabled={number_of_models_selected === 0}\n                >\n                  Submit\n                </Button>\n              </TooltipTrigger>\n              <TooltipContent\n                side=\"top\"\n                align=\"center\"\n                className=\"bg-slate-600 text-white hidden md:block\"\n              >\n                Submit &nbsp;\n                <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\">\n                {is_mac_os ? \"⌘\" : \"Control\"}\n                </kbd>\n                &nbsp;\n                <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\">\n                  Enter\n                </kbd>\n              </TooltipContent>\n            </Tooltip>\n          )}\n          <Tooltip delayDuration={100}>\n            <TooltipTrigger asChild>\n              <div>\n                <Button\n                  type=\"button\"\n                  variant=\"subtle\"\n                  className=\"inline-flex items-center mt-4 text-sm font-medium text-center\"\n                  onClick={handleUndoLast}\n                  disabled={editorContext.prePrompt === \"\"}\n                >\n                  Undo\n                </Button>\n              </div>\n\n            </TooltipTrigger>\n            <TooltipContent\n              side=\"top\"\n              align=\"center\"\n              className=\"bg-slate-600 text-white hidden md:block\"\n            >\n              Undo Last &nbsp;\n              <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\">\n                {is_mac_os ? \"⌘\" : \"Control\"}\n              </kbd>\n              &nbsp;\n              <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\">\n                U\n              </kbd>\n            </TooltipContent>\n          </Tooltip>\n          <Tooltip delayDuration={100}>\n            <TooltipTrigger asChild>\n\n            <div>\n              <Button\n                type=\"button\"\n                variant=\"subtle\"\n                className=\"inline-flex items-center mt-4 text-sm font-medium text-center\"  \n                onClick={(e) => {\n                  e.stopPropagation()\n                  handleUndoLast()\n                  handleSubmit(true, editorContext.prePrompt)\n                }}\n                disabled={editorContext.prePrompt === \"\"}\n              >\n                Regenerate\n              </Button>\n              </div> \n            </TooltipTrigger>\n            <TooltipContent\n              side=\"top\"\n              align=\"center\"\n              className=\"bg-slate-600 text-white hidden md:block\"\n            >\n              Regenerate &nbsp;\n              <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\">\n                {is_mac_os ? \"⌘\" : \"Control\"}\n              </kbd>\n              &nbsp;\n              <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\">\n              {is_mac_os ? \"Option\" : \"Alt\"}\n              </kbd>\n              &nbsp;\n              <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\">\n                R\n              </kbd>\n            </TooltipContent>\n          </Tooltip>\n\n          <Tooltip delayDuration={100}>\n            <TooltipTrigger asChild>\n              <Button\n                type=\"button\"\n                variant=\"subtle\"\n                className=\"inline-flex items-center py-2.5 mt-4 text-sm font-medium text-center hidden lg:inline-flex\"\n                onClick={(e) => {\n                  e.stopPropagation()\n                  toggleShowHistory()\n                }}\n                disabled={historyContext.entries.length == 0}\n              >\n                <HistoryIcon className=\"h-4 w-4\" />\n              </Button>\n            </TooltipTrigger>\n            <TooltipContent\n              side=\"top\"\n              align=\"center\"\n              className=\"bg-slate-600 text-white\"\n            >\n              Show History &nbsp;\n              <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\">\n                {is_mac_os ? \"⌘\" : \"Control\"}\n              </kbd>\n              &nbsp;\n              <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\">\n                H\n              </kbd>\n            </TooltipContent>\n          </Tooltip>\n        </TooltipProvider>\n      </div>\n    </form>\n  )\n}\n\nconst CustomAlertDialogue = ({dialog}) => {\n  const [openDialog, setOpenDialog] = React.useState<boolean>(false)\n  const [_dialogue, _setDialogue] = React.useState<any>({\n    title: \"\",\n    message: \"\"\n  })\n\n  useEffect(() => {\n    if (!openDialog && dialog.title !== \"\" && dialog.message !== \"\") {\n      _setDialogue({\n        title: dialog.title,\n        message: dialog.message\n      })\n      setOpenDialog(true)\n    }\n  }, [dialog])\n\n  return (\n    <AlertDialog open={openDialog} onOpenChange={setOpenDialog}>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>{_dialogue.title}</AlertDialogTitle>\n          <AlertDialogDescription className=\"text-base text-slate-700 dark:text-slate-400\">\n            {_dialogue.message}\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogAction>Ok</AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n    )\n}\nexport default function Playground() {\n  const apiContext = useContext(APIContext)\n  const {historyContext, toggleShowHistory} = useContext(HistoryContext)\n  const [openHistorySheet, setOpenHistorySheet] = React.useState<boolean>(false)\n  const [openParameterSheet, setSaveOpenParameterSheet] = React.useState<boolean>(false)\n  const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n  const [deleteHistoryDialog, setDeleteHistoryDialog] = React.useState<boolean>(false)\n  const [dialog, showDialog] = React.useState({\n    title: \"\",\n    message: \"\"\n  })\n\n  const historySidebar = (<HistorySidePanel />)\n  const parameterSidebar = (<ParameterSidePanel showModelDropdown={true} showModelList ={false} />)\n\n\n  useMetaKeyPress([\"h\"], (event: any) => {\n    event.preventDefault()\n\n    if (historyContext.entries.length > 0 && !isMobile) toggleShowHistory()\n  })\n\n  const mobileOpenParametersButton = (\n    <Sheet open={openParameterSheet} onOpenChange={setSaveOpenParameterSheet}>\n      <SheetTrigger asChild>\n        <Button variant=\"subtle\" className=\"lg:hidden\">\n          <Settings2 className=\"h-6 w-6\" />\n        </Button>\n      </SheetTrigger>\n      <SheetContent className=\"w-[80vw] p-4 pt-8\">\n        {parameterSidebar}\n        </SheetContent>\n    </Sheet>\n  )\n\n  const mobileOpenHistoryButton = (\n    <Sheet open={openHistorySheet} onOpenChange={() => {\n        if (historyContext.entries.length == 0) {\n          alert(\"No history to show!\")\n        } else {\n          toggleShowHistory(!openHistorySheet)\n        }\n      setOpenHistorySheet(!openHistorySheet)\n    }}>\n      <SheetTrigger asChild>\n        <Button variant=\"subtle\" className=\"lg:hidden\">\n          <HistoryIcon className=\"h-4 w-4\" />\n        </Button>\n      </SheetTrigger>\n      <SheetContent className=\"w-[80vw]\">{historySidebar}</SheetContent>\n    </Sheet>\n  )\n \n  return (\n    <div className=\"flex flex-col h-full\">\n      <NavBar tab=\"playground\">\n        <div className=\"align-middle mt-1\">\n          <div className=\"flex basis-full my-2 lg:mb-0 space-x-2\">\n            {mobileOpenParametersButton}\n            {/*(!isMobile) ? mobileOpenHistoryButton : null */}\n          </div>\n        </div>\n      </NavBar>\n\n      <AlertDialog\n        open={deleteHistoryDialog}\n        onOpenChange={setDeleteHistoryDialog}\n      >\n        <AlertDialogContent>\n          <AlertDialogHeader>\n            <AlertDialogTitle>\n              Are you sure you want to delete all of your history?\n            </AlertDialogTitle>\n            <AlertDialogDescription>\n              This action cannot be <b>reversed.</b> Please make sure you have\n              saved any important generations before proceeding.\n            </AlertDialogDescription>\n          </AlertDialogHeader>\n          <AlertDialogFooter>\n            <AlertDialogCancel>Cancel</AlertDialogCancel>\n            <AlertDialogAction\n              className=\"bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600\"\n              asChild\n            >\n              <Button variant=\"destructive\">\n                Delete History\n              </Button>\n            </AlertDialogAction>\n          </AlertDialogFooter>\n        </AlertDialogContent>\n      </AlertDialog>\n      <CustomAlertDialogue dialog = {dialog} />\n      <div className=\"flex flex-grow flex-col font-display min-h-0 min-w-0 ml-5\">\n        <div className=\"flex flex-row space-x-4 flex-grow mr-5 min-h-0 min-w-0\">\n          {\n            historyContext.show ? (\n            <div className=\"hidden p-1 grow-0 shrink-0 basis-auto lg:w-[250px] overflow-auto lg:block\">\n              {historySidebar}\n            </div>) : null\n          }\n          <PromptCompletionEditor showDialog = {showDialog}/>\n            <div className=\"hidden p-1 grow-0 shrink-0 basis-auto lg:w-[250px] overflow-auto lg:block\">\n              {parameterSidebar}\n            </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/src/pages/settings.tsx",
    "content": "import React, { useContext, useEffect, useState } from \"react\"\nimport NavBar from \"../components/navbar\"\nimport { Button } from \"../components/ui/button\"\nimport { Input } from \"../components/ui/input\"\nimport { EyeIcon, EyeOffIcon, Loader2, X } from \"lucide-react\"\nimport { ScrollArea } from \"../components/ui/scroll-area\"\nimport { Checkbox } from \"../components/ui/checkbox\"\nimport { APIContext } from \"../app\"\nimport { useBreakpoint } from \"../hooks/use-breakpoint\"\nimport { useToast } from \"../hooks/ui/use-toast\"\n\ninterface Provider {\n  name: string;\n  remoteInference: boolean;\n  requiresApiKey: boolean;\n  apiKey: string;\n  models: Model[];\n  searchUrl: string | null;\n}\n\ninterface Model {\n  name: string;\n  enabled: boolean;\n  provider: string;\n}\n\ninterface ProviderProps {\n  provider: string;\n  providerModels: Model[];\n  providerSearchURL: string;\n  providerSearchResults: any[];\n  providerRequiresAPIKey: boolean;\n  providerRemoteInference: boolean;\n  apiKey: string;\n  searchProviderModels: (provider: string, query: string) => void;\n  setAPIKey: (key: string) => void;\n  toggleModel: (provider: string, model: string) => void;\n}\n\nconst ProviderSearchModels = ({\n  provider,\n  providerModels,\n  providerRequiresAPIKey,\n  apiKey,\n  providerSearchURL,\n  providerSearchResults,\n  searchProviderModels,\n  toggleModel\n}: ProviderProps) => {\n  if (!providerSearchURL) return null\n\n  const handleModelSelect = (model: string, checked: boolean) => {\n    toggleModel(provider, model)\n  }\n\n  const searchModel = (e: React.FormEvent<HTMLFormElement>) => {\n    e.preventDefault()\n    const searchQuery = e.target.elements[\"model-query\"].value\n      \n    searchProviderModels(provider, searchQuery)\n  }\n\n  const searchResults = () => providerSearchResults.map((modelName: any) => (\n    <div\n      key={modelName}\n      className=\"rounded-md border border-slate-200 px-4 py-3 my-2 font-mono text-sm dark:border-slate-700\"\n    >\n      {modelName}\n      <Checkbox\n        key={modelName}\n        className=\"float-right\"\n        onCheckedChange={(event) =>\n          handleModelSelect(modelName, event)\n        }\n        checked={providerModels.find(({name}) => name === modelName)?.enabled || false}\n      />\n    </div>\n  ))\n\n  return (\n    <>\n      <h3 className=\"scroll-m-20 text-xl font-extrabold tracking-tight mt-2\">\n        Model Search\n      </h3>\n      <p>\n        Search for a model or part of a model to get matches from {provider} Hub\n      </p>\n\n      <form onSubmit={searchModel}>\n        <div className=\"flex w-full max-w-sm items-center space-x-2 mt-2\">\n          <Input\n            disabled = {providerRequiresAPIKey && (apiKey === \"\" || apiKey === null)}\n            type=\"text\"\n            id=\"model-query\"\n             placeholder=\"Search Query\"\n            className=\"flex text-left placeholder:text-left h-10 w-full rounded-md border border-slate-300 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\"\n          />\n          <Button\n            type=\"submit\"\n            disabled = {providerRequiresAPIKey && (apiKey === \"\" || apiKey === null)}\n          >\n            Search\n          </Button>\n        </div>\n      </form>\n\n      <div>\n        <div className=\"min-h-[320px] w-full border rounded-md mt-2\">\n          <div className=\"p-2\">  \n            {searchResults()}\n          </div>\n        </div>\n      </div>\n    </>\n  )\n}\n\nconst ProviderCredentials = ({provider, providerRequiresAPIKey, apiKey, setAPIKey}: ProviderProps) => {\n  if (!providerRequiresAPIKey) return null;\n\n  const [revealAPIKey, setRevealAPIKey] = useState<boolean>(false)\n  const [apiKeyCopy, setAPIKeyCopy] = React.useState<string>(apiKey)\n\n  useEffect(() => {\n    setAPIKeyCopy(apiKey)\n  }, [apiKey])\n\n  \n  const apiKeyDescription = () => {\n    if (apiKey !== null && apiKey !== \"\") return null;\n\n    return (\n      <>\n        <p className=\"text-red-500\">\n          <b>No API key is saved for {provider}</b>\n        </p>\n            \n        <p>\n          Your API key allows us make generation requests for you in the playground\n        </p>\n      </>\n    )\n  }\n\n  const handleAPIKeySubmit = async (e: React.FormEvent<HTMLFormElement>) => {\n    e.preventDefault()\n    setAPIKey(apiKeyCopy)\n  }\n\n  return (\n    <div>\n      <h3 className=\"scroll-m-20 text-xl font-extrabold tracking-tight mt-2\">\n        API Key\n      </h3>\n      {apiKeyDescription()}\n      <form onSubmit={handleAPIKeySubmit}>\n        <div className=\"flex w-full max-w-lg items-center space-x-2 mt-2\">\n          <Input\n            type={revealAPIKey ? \"text\" : \"password\"}\n            placeholder={`Enter your ${provider} API Key`}\n            value={apiKeyCopy || \"\"}\n            onChange={(e) => setAPIKeyCopy(e.target.value)}\n            className=\"flex text-left placeholder:text-left h-8 w-full rounded-md border border-slate-300 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\"\n          />\n           \n          <div\n            key={provider}\n            onClick={() => setRevealAPIKey(e => !e)}\n            className=\"cursor-pointer\">\n            {\n              revealAPIKey ? <EyeIcon className=\"h-5 w-5 align-middle\" /> : <EyeOffIcon className=\"h-5 w-5 align-middle\" />\n            }\n          </div>\n          <Button type=\"submit\">Save</Button>\n        </div>\n      </form>\n    </div>\n  )\n}\n\nconst ProviderModelSelection = ({\n  provider,\n  apiKey,\n  providerRequiresAPIKey,\n  providerModels, toggleModel\n}: ProviderProps) => {\n  const handleModelSubmit = (e: React.FormEvent<HTMLFormElement>) => {\n    e.preventDefault()\n  }\n  \n  const handleModelSelect = (model: string, checked: boolean) => {\n    toggleModel(provider, model)\n  }\n\n  return (\n    <>\n      <h3 className=\"scroll-m-20 text-xl font-extrabold tracking-tight mt-2\">\n        Model Selection\n      </h3>\n      <p>\n        Please select from the following models to show in the\n        dropdown in playground\n      </p>\n      <form onSubmit={handleModelSubmit}>\n        <div className=\"min-h-[320px] w-full border rounded-md mt-2\">\n          <div className=\"p-2\">\n            {providerModels.map((model: any) => (\n            <div\n              key={model.name}\n              className=\"rounded-md border border-slate-200 px-4 py-3 my-2 font-mono text-sm dark:border-slate-700\"\n            >\n              {model.name}\n              <Checkbox\n                key={model}\n                className=\"float-right\"\n                disabled={providerRequiresAPIKey && (apiKey === \"\" || apiKey === null)}\n                onCheckedChange={(event) =>\n                  handleModelSelect(model.name, event)\n                }\n                checked={model.enabled}\n              />\n            </div>\n            ))}\n          </div>\n        </div>\n      </form>\n    </>\n  )\n}\n\nconst ProviderView = (props: ProviderProps) => {\n  const {provider} = props\n\n  return (\n    <div className=\"overflow-hidden flex col-span-6 lg:col-span-3 flex flex-row mx-2 lg:mx-0\">\n      <div>\n        <h1 className=\"scroll-m-20 text-3xl font-extrabold tracking-tight\">\n          {provider} Setup\n        </h1>\n        <div className=\"max-h-[100%] flex-1 overflow-auto mt-2\">\n          <ProviderCredentials {...props}/>\n          <ProviderModelSelection {...props}/>\n          <ProviderSearchModels {...props}/>\n        </div>\n      </div>\n    </div>\n  )\n}\n\ninterface AllSelectedModelsProps {\n  enabledModels: Model[];\n  toggleModel: ProviderProps[\"toggleModel\"];\n}\n\nconst AllSelectedModels = ({enabledModels, toggleModel}: AllSelectedModelsProps) => {  \n  const selectedModelsCard = () => {\n    return enabledModels.map((model: any) => (\n      <div\n        key={`selected_${model.name}_${model.provider}`}\n        className=\"rounded-md border border-slate-200 px-4 py-3 my-2 font-mono text-sm dark:border-slate-700\"\n      > \n        {model.name}\n        <br/>\n        <span style={{\"fontSize\": \"12px\"}}>\n          Provider:\n          <i>{model.provider}</i>\n        </span>\n        <button\n          key={`selected_${model}`}\n          className=\"float-right center\"\n          onClick={() => toggleModel(model.provider, model.name)}\n        >\n          <X className=\"h-4 w-4\" />\n        </button>\n        <br/>\n        <span\n          style={{\"fontSize\": \"12px\"}}>Status:\n          <i>{model.status}</i>\n        </span>\n      </div>\n    ))\n  }\n  return (\n    <div className=\"col-span-6 lg:col-span-2 flex flex-row mx-2 lg:mx-0 \">\n      <div>\n        <h1 className=\"scroll-m-20 text-3xl font-extrabold tracking-tight\">\n          Your Selected Models\n        </h1>\n\n        <div className=\"mt-1\">\n          <p>These models will be available in the menu dropdown</p>\n        </div>\n        <div className=\"max-h-[100%] overflow-hidden\">\n          <div>\n            {selectedModelsCard()}\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default function Settings() {\n  const apiContext = useContext(APIContext)\n\n  const [providers, setProviders] = React.useState<{[key: string]: Provider}>({});\n\n  const [provider, setProvider] = React.useState<Provider | {}>({})\n  const [providerName, setProviderName] = React.useState<string>(\"openai\")\n  const [providerSearchURL, setProviderSearchURL] = React.useState<any>(null)\n  const [providerAPIKey, setProviderAPIKey] = React.useState<string>(\"\")\n  const [providerRequiresAPIKey, setProviderRequiresAPIKey] = React.useState<boolean>(true)\n  const [providerRemoteInference, setProviderRemoteInference] = React.useState<boolean>(true)\n  const [providerModels, setProviderModels] = React.useState<any[]>([])\n  const [providerSearchResults, setProviderSearchResults] = React.useState<any[]>([])\n\n  const [enabledModels, setEnabledModels] = React.useState<Model[]>([])\n\n  const {toast} = useToast()\n  const {isLg} = useBreakpoint(\"lg\")\n\n  useEffect(() => {\n    const preloadData = async () => {\n      const providersWithModels: { [key: string]: Provider } = await apiContext.Provider.getAllWithModels()\n\n      const _enabledModels = Object.entries(providersWithModels)\n        .map(([_, provider]: [string, Provider]) => provider.models.filter(({enabled}: {enabled: boolean}) => enabled))\n        .flat()\n\n      setProviders(providersWithModels)\n      setEnabledModels(_enabledModels)\n    };    \n    \n    const notificationCallback = ({event, data, meta}) => {\n      switch (event) {\n        case \"modelAdded\":\n          toast({\n            title: \"New Model is available!\",\n            description: `${data.provider}'s model ${data.model} has been added to the playground!`,\n          })\n\n          preloadData().catch(console.error)\n        break;\n\n        case \"modelRemoved\":\n          toast({\n            title: \"Model removed!\",\n            description: `${data.provider}'s model ${data.model} has been removed from the playground!`,\n          })\n\n          preloadData().catch(console.error)\n        break;\n\n        default:\n          console.log(\"Unknown event????\", event, data);\n        break;\n      }\n    }\n    \n    apiContext.Notifications.subscribe(notificationCallback)\n    preloadData().catch(console.error)\n    return () => {\n      apiContext.Notifications.unsubscribe(notificationCallback);\n    };\n  }, []);\n\n  useEffect(() => {\n    if (Object.keys(providers).length === 0) return;\n\n    const currentProvider: Provider = providers[providerName]; \n\n    setProvider(currentProvider)\n    setProviderAPIKey(currentProvider.apiKey)\n    setProviderModels(currentProvider.models)\n    setProviderRequiresAPIKey(currentProvider.requiresApiKey)\n    setProviderRemoteInference(currentProvider.remoteInference)\n    setProviderSearchURL(currentProvider.searchUrl)\n    setProviderSearchResults((provider.name === currentProvider.name) ? providerSearchResults : [])\n  }, [providerName, providers])\n\n  const setAPIKey = async (apiKey: string ) => { \n    try {\n      await apiContext.Provider.setAPIKey(providerName, apiKey);\n\n      toast({\n        title: \"API Key Saved\",\n        description: `${providerName} API key is saved and ready for generations!`,\n      })\n      \n      setProviders({\n        ...providers,\n        [providerName]: {\n          ...providers[providerName],\n          apiKey\n        }\n      })   \n    } catch (error) {\n      toast({\n        title: \"API Key Error\",\n        description: `There was an error saving your ${provider} API key. ${error}}`,\n      })\n    }\n  }\n\n  const toggleModel = async (providerName: string, modelName: string) => {\n    try {\n      const {enabled, model} = await apiContext.Model.toggle(providerName, modelName);\n     \n      const providerModel = providers[providerName].models.find((m) => m.name === modelName)\n      if (providerModel) {\n        providerModel.enabled = enabled\n      } else {\n        providerModels.push(model)\n      }\n\n      setProviders({\n        ...providers,\n        [providerName]: {\n          ...providers[providerName],\n          models: providerModels\n        }\n      })\n      \n      let _newEnabledModels = enabledModels\n\n      if (enabled) {\n        if (!_newEnabledModels.find((m) => m.name === modelName)) {\n          _newEnabledModels.push(model)\n        }\n      } else {\n        _newEnabledModels = _newEnabledModels.filter((m) => m.name !== modelName)\n      }\n\n      setEnabledModels(_newEnabledModels)\n    } catch (error) {\n      toast({\n        title: \"Model Error\",\n        description: `There was an error enabling your ${modelName} model. ${error}}`,\n      })\n    }\n  }\n\n  const searchProviderModels = async (providerName: string, searchTerm: string) => {\n    try {\n      const models = await apiContext.Model.search(providerName, searchTerm);\n\n      setProviderSearchResults(models.map((model: any) => model.name))\n    } catch (error) {\n      toast({\n        title: \"Model Error\",\n        description: `There was an error searching for models. ${error}}`,\n      })\n    }\n  }\n\n  const providersButtons = () => \n    Object.entries(providers).map(([name, _]) => \n      <React.Fragment key={name}>\n        <button\n          className={`block w-full text-left px-2 py-1 border-l-2 ${\n            providerName === name ? \"border-blue-500\" : \"border-transparent\"\n          }`}\n          onClick={(e) => setProviderName(name)}\n        >\n          <span className={providerName === name ? \"font-bold\": \"font-normal\"}>{name}</span>\n        </button>\n      </React.Fragment>\n    )\n\n  return (\n    <div className=\"flex flex-col h-full\">\n      <NavBar tab=\"settings\" />\n      <div className=\"flex flex-1 flex-col font-display flex-grow overflow-hidden\">\n        <div className=\"max-h-[100%] lg:flex-grow grid gap-6 grid-cols-6 mx-1 lg:mx-5 flex flex-row\">\n          <div className=\"flex col-span-6 lg:flex-col lg:col-span-1\">\n            <h1 className=\"scroll-m-20 text-3xl mb-5 font-extrabold tracking-tight hidden lg:inline-block\">\n              Providers\n            </h1>\n            {providersButtons()}\n          </div>\n\n          <ProviderView\n            apiKey={providerAPIKey}\n            provider={providerName}\n            providerRequiresAPIKey={providerRequiresAPIKey}\n            providerRemoteInference={providerRemoteInference}\n            providerModels={providerModels}\n            providerSearchURL={providerSearchURL}\n            providerSearchResults={providerSearchResults}\n\n            searchProviderModels={searchProviderModels}\n            setAPIKey = {setAPIKey}\n            toggleModel = {toggleModel}\n          />\n\n          <AllSelectedModels\n            enabledModels={enabledModels}\n            toggleModel={toggleModel}\n          />\n        </div>\n      </div>\n    </div>\n  )\n}"
  },
  {
    "path": "app/tailwind.config.js",
    "content": "const { fontFamily } = require(\"tailwindcss/defaultTheme\")\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  darkMode: [\"class\", '[data-theme=\"dark\"]'],\n  content: [\"./src/**/*.{html,js,ts,jsx,tsx}\"],\n  theme: {\n    extend: {\n      colors: {\n        'highlight-tokens': '#ff395ad1',\n      },\n      screens: {\n        '3xl': '1792px',\n        '4xl': '2048px',\n        '5xl': '2748px',\n        '6xl': '3448px',\n        '8xl': '4000px'\n      },\n      fontFamily: {\n        sans: [\"var(--font-sans)\", ...fontFamily.sans],\n        display: [\"Inter\", \"Manrope\", \"sans-serif\"],\n      },\n      keyframes: {\n        \"accordion-down\": {\n          from: { height: 0 },\n          to: { height: \"var(--radix-accordion-content-height)\" },\n        },\n        \"accordion-up\": {\n          from: { height: \"var(--radix-accordion-content-height)\" },\n          to: { height: 0 },\n        },\n      },\n      animation: {\n        \"accordion-down\": \"accordion-down 0.2s ease-out\",\n        \"accordion-up\": \"accordion-up 0.2s ease-out\",\n      },\n    },\n  },\n  plugins: [require(\"tailwindcss-animate\")],\n}\n"
  },
  {
    "path": "app/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "app/tsconfig.tsbuildinfo",
    "content": "{\"program\":{\"fileNames\":[\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es5.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2016.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2017.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2018.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2019.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2020.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2021.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2022.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.esnext.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.dom.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.dom.iterable.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.core.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.collection.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.generator.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.iterable.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.promise.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.proxy.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.reflect.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.symbol.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2016.array.include.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2017.object.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2017.string.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2017.intl.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2018.intl.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2018.promise.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2018.regexp.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2019.array.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2019.object.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2019.string.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2019.symbol.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2019.intl.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2020.bigint.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2020.date.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2020.promise.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2020.string.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2020.intl.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2020.number.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2021.promise.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2021.string.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2021.weakref.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2021.intl.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2022.array.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2022.error.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2022.intl.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2022.object.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.es2022.string.d.ts\",\"../../../.nvm/versions/node/v19.0.0/lib/node_modules/typescript/lib/lib.esnext.intl.d.ts\",\"./node_modules/react-responsive/types/types.d.ts\",\"./node_modules/react-responsive/types/useMediaQuery.d.ts\",\"./node_modules/@types/react/global.d.ts\",\"./node_modules/csstype/index.d.ts\",\"./node_modules/@types/prop-types/index.d.ts\",\"./node_modules/@types/scheduler/tracing.d.ts\",\"./node_modules/@types/react/index.d.ts\",\"./node_modules/react-responsive/types/Component.d.ts\",\"./node_modules/react-responsive/types/toQuery.d.ts\",\"./node_modules/react-responsive/types/Context.d.ts\",\"./node_modules/react-responsive/types/index.d.ts\",\"./node_modules/tailwindcss/types/generated/corePluginList.d.ts\",\"./node_modules/tailwindcss/types/generated/colors.d.ts\",\"./node_modules/tailwindcss/types/config.d.ts\",\"./node_modules/source-map-js/source-map.d.ts\",\"./node_modules/postcss/lib/comment.d.ts\",\"./node_modules/postcss/lib/at-rule.d.ts\",\"./node_modules/postcss/lib/rule.d.ts\",\"./node_modules/postcss/lib/container.d.ts\",\"./node_modules/postcss/lib/declaration.d.ts\",\"./node_modules/postcss/lib/previous-map.d.ts\",\"./node_modules/postcss/lib/input.d.ts\",\"./node_modules/postcss/lib/css-syntax-error.d.ts\",\"./node_modules/postcss/lib/warning.d.ts\",\"./node_modules/postcss/lib/document.d.ts\",\"./node_modules/postcss/lib/root.d.ts\",\"./node_modules/postcss/lib/lazy-result.d.ts\",\"./node_modules/postcss/lib/no-work-result.d.ts\",\"./node_modules/postcss/lib/processor.d.ts\",\"./node_modules/postcss/lib/result.d.ts\",\"./node_modules/postcss/lib/node.d.ts\",\"./node_modules/postcss/lib/list.d.ts\",\"./node_modules/postcss/lib/postcss.d.ts\",\"./node_modules/tailwindcss/types/index.d.ts\",\"./node_modules/tailwindcss/types/generated/default-theme.d.ts\",\"./node_modules/tailwindcss/defaultTheme.d.ts\",\"./tailwind.config.js\",\"./node_modules/tailwindcss/resolveConfig.d.ts\",\"./src/hooks/useBreakpoint.ts\",\"./node_modules/clsx/clsx.d.ts\",\"./node_modules/tailwind-merge/dist/lib/tw-join.d.ts\",\"./node_modules/tailwind-merge/dist/lib/tw-merge.d.ts\",\"./node_modules/tailwind-merge/dist/lib/validators.d.ts\",\"./node_modules/tailwind-merge/dist/lib/types.d.ts\",\"./node_modules/tailwind-merge/dist/lib/default-config.d.ts\",\"./node_modules/tailwind-merge/dist/lib/extend-tailwind-merge.d.ts\",\"./node_modules/tailwind-merge/dist/lib/create-tailwind-merge.d.ts\",\"./node_modules/tailwind-merge/dist/lib/merge-configs.d.ts\",\"./node_modules/tailwind-merge/dist/lib/from-theme.d.ts\",\"./node_modules/tailwind-merge/dist/index.d.ts\",\"./src/lib/utils.ts\",\"./node_modules/@types/draft-js/node_modules/immutable/dist/immutable.d.ts\",\"./node_modules/@types/draft-js/index.d.ts\",\"./src/components/ui/textarea.tsx\",\"./node_modules/@radix-ui/react-primitive/dist/index.d.ts\",\"./node_modules/@radix-ui/react-context/dist/index.d.ts\",\"./node_modules/@radix-ui/react-slider/dist/index.d.ts\",\"./src/components/ui/slider.tsx\",\"./node_modules/class-variance-authority/dist/types.d.ts\",\"./node_modules/class-variance-authority/dist/index.d.ts\",\"./src/components/ui/button.tsx\",\"./node_modules/@remix-run/router/dist/history.d.ts\",\"./node_modules/@remix-run/router/dist/utils.d.ts\",\"./node_modules/@remix-run/router/dist/router.d.ts\",\"./node_modules/@remix-run/router/dist/index.d.ts\",\"./node_modules/react-router/dist/lib/context.d.ts\",\"./node_modules/react-router/dist/lib/components.d.ts\",\"./node_modules/react-router/dist/lib/hooks.d.ts\",\"./node_modules/react-router/dist/index.d.ts\",\"./node_modules/react-router-dom/dist/dom.d.ts\",\"./node_modules/react-router-dom/dist/index.d.ts\",\"./src/components/navbar.tsx\",\"./node_modules/@radix-ui/react-dismissable-layer/dist/index.d.ts\",\"./node_modules/@radix-ui/react-focus-scope/dist/index.d.ts\",\"./node_modules/@radix-ui/react-arrow/dist/index.d.ts\",\"./node_modules/@radix-ui/rect/dist/index.d.ts\",\"./node_modules/@radix-ui/react-popper/dist/index.d.ts\",\"./node_modules/@radix-ui/react-portal/dist/index.d.ts\",\"./node_modules/@radix-ui/react-select/dist/index.d.ts\",\"./node_modules/lucide-react/dist/lucide-react.d.ts\",\"./src/components/ui/select.tsx\",\"./src/components/ui/input.tsx\",\"./node_modules/@radix-ui/react-tooltip/dist/index.d.ts\",\"./src/components/ui/tooltip.tsx\",\"./node_modules/@radix-ui/react-dialog/dist/index.d.ts\",\"./src/components/ui/dialog.tsx\",\"./node_modules/@radix-ui/react-label/dist/index.d.ts\",\"./src/components/ui/label.tsx\",\"./node_modules/@radix-ui/react-alert-dialog/dist/index.d.ts\",\"./src/components/ui/alert-dialog.tsx\",\"./src/lib/metakeypress.tsx\",\"./src/lib/keypress.tsx\",\"./src/components/ParameterSlider.tsx\",\"./src/components/ui/right-sheet.tsx\",\"./node_modules/react-select/dist/declarations/src/filters.d.ts\",\"./node_modules/@emotion/utils/types/index.d.ts\",\"./node_modules/@emotion/cache/types/index.d.ts\",\"./node_modules/@emotion/serialize/types/index.d.ts\",\"./node_modules/@emotion/react/types/jsx-namespace.d.ts\",\"./node_modules/@emotion/react/types/helper.d.ts\",\"./node_modules/@emotion/react/types/theming.d.ts\",\"./node_modules/@emotion/react/types/index.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/containers.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/Control.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/Group.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/indicators.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/Input.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/Placeholder.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/Option.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/Menu.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/SingleValue.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/MultiValue.d.ts\",\"./node_modules/react-select/dist/declarations/src/styles.d.ts\",\"./node_modules/react-select/dist/declarations/src/types.d.ts\",\"./node_modules/react-select/dist/declarations/src/accessibility/index.d.ts\",\"./node_modules/react-select/dist/declarations/src/components/index.d.ts\",\"./node_modules/react-select/dist/declarations/src/theme.d.ts\",\"./node_modules/react-select/dist/declarations/src/useStateManager.d.ts\",\"./node_modules/react-select/dist/declarations/src/stateManager.d.ts\",\"./node_modules/react-select/dist/declarations/src/NonceProvider.d.ts\",\"./node_modules/react-select/dist/declarations/src/index.d.ts\",\"./node_modules/react-select/dist/declarations/src/Select.d.ts\",\"./node_modules/react-select/dist/declarations/src/useCreatable.d.ts\",\"./node_modules/react-select/dist/declarations/src/Creatable.d.ts\",\"./node_modules/react-select/dist/declarations/src/creatable/index.d.ts\",\"./node_modules/react-select/creatable/dist/react-select-creatable.cjs.d.ts\",\"./src/components/MultiSelect.tsx\",\"./node_modules/@radix-ui/react-checkbox/dist/index.d.ts\",\"./src/components/ui/checkbox.tsx\",\"./src/pages/playground.tsx\",\"./src/pages/compare.tsx\",\"./node_modules/@radix-ui/react-scroll-area/dist/index.d.ts\",\"./src/components/ui/scroll-area.tsx\",\"./node_modules/@radix-ui/react-separator/dist/index.d.ts\",\"./src/components/ui/separator.tsx\",\"./src/pages/settings.tsx\",\"./src/error-page.tsx\",\"./src/app.tsx\",\"./node_modules/@types/react-dom/client.d.ts\",\"./src/index.tsx\",\"./src/components/inputarea.tsx\",\"./src/components/ui/dialog-sheet-base.tsx\",\"./node_modules/@radix-ui/react-visually-hidden/dist/index.d.ts\",\"./node_modules/@radix-ui/react-navigation-menu/dist/index.d.ts\",\"./src/components/ui/navigation-menu.tsx\",\"./src/components/ui/sheet.tsx\",\"./node_modules/@types/parse-json/index.d.ts\",\"./node_modules/@types/react-dom/index.d.ts\",\"./node_modules/@types/react-transition-group/Transition.d.ts\",\"./node_modules/@types/react-transition-group/CSSTransition.d.ts\",\"./node_modules/@types/react-transition-group/TransitionGroup.d.ts\",\"./node_modules/@types/react-transition-group/SwitchTransition.d.ts\",\"./node_modules/@types/react-transition-group/config.d.ts\",\"./node_modules/@types/react-transition-group/index.d.ts\",\"./node_modules/@types/scheduler/index.d.ts\"],\"fileInfos\":[{\"version\":\"8730f4bf322026ff5229336391a18bcaa1f94d4f82416c8b2f3954e2ccaae2ba\",\"affectsGlobalScope\":true},\"dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6\",\"7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467\",\"8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9\",\"5f4e733ced4e129482ae2186aae29fde948ab7182844c3a5a51dd346182c7b06\",\"4b421cbfb3a38a27c279dec1e9112c3d1da296f77a1a85ddadf7e7a425d45d18\",\"1fc5ab7a764205c68fa10d381b08417795fc73111d6dd16b5b1ed36badb743d9\",\"746d62152361558ea6d6115cf0da4dd10ede041d14882ede3568bce5dc4b4f1f\",\"d11a03592451da2d1065e09e61f4e2a9bf68f780f4f6623c18b57816a9679d17\",\"aea179452def8a6152f98f63b191b84e7cbd69b0e248c91e61fb2e52328abe8c\",{\"version\":\"3aafcb693fe5b5c3bd277bd4c3a617b53db474fe498fc5df067c5603b1eebde7\",\"affectsGlobalScope\":true},{\"version\":\"f3d4da15233e593eacb3965cde7960f3fddf5878528d882bcedd5cbaba0193c7\",\"affectsGlobalScope\":true},{\"version\":\"adb996790133eb33b33aadb9c09f15c2c575e71fb57a62de8bf74dbf59ec7dfb\",\"affectsGlobalScope\":true},{\"version\":\"8cc8c5a3bac513368b0157f3d8b31cfdcfe78b56d3724f30f80ed9715e404af8\",\"affectsGlobalScope\":true},{\"version\":\"cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a\",\"affectsGlobalScope\":true},{\"version\":\"c5c05907c02476e4bde6b7e76a79ffcd948aedd14b6a8f56e4674221b0417398\",\"affectsGlobalScope\":true},{\"version\":\"5f406584aef28a331c36523df688ca3650288d14f39c5d2e555c95f0d2ff8f6f\",\"affectsGlobalScope\":true},{\"version\":\"22f230e544b35349cfb3bd9110b6ef37b41c6d6c43c3314a31bd0d9652fcec72\",\"affectsGlobalScope\":true},{\"version\":\"7ea0b55f6b315cf9ac2ad622b0a7813315bb6e97bf4bb3fbf8f8affbca7dc695\",\"affectsGlobalScope\":true},{\"version\":\"3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93\",\"affectsGlobalScope\":true},{\"version\":\"eb26de841c52236d8222f87e9e6a235332e0788af8c87a71e9e210314300410a\",\"affectsGlobalScope\":true},{\"version\":\"3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006\",\"affectsGlobalScope\":true},{\"version\":\"17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a\",\"affectsGlobalScope\":true},{\"version\":\"7ce9f0bde3307ca1f944119f6365f2d776d281a393b576a18a2f2893a2d75c98\",\"affectsGlobalScope\":true},{\"version\":\"6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577\",\"affectsGlobalScope\":true},{\"version\":\"81cac4cbc92c0c839c70f8ffb94eb61e2d32dc1c3cf6d95844ca099463cf37ea\",\"affectsGlobalScope\":true},{\"version\":\"b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e\",\"affectsGlobalScope\":true},{\"version\":\"0eb85d6c590b0d577919a79e0084fa1744c1beba6fd0d4e951432fa1ede5510a\",\"affectsGlobalScope\":true},{\"version\":\"da233fc1c8a377ba9e0bed690a73c290d843c2c3d23a7bd7ec5cd3d7d73ba1e0\",\"affectsGlobalScope\":true},{\"version\":\"d154ea5bb7f7f9001ed9153e876b2d5b8f5c2bb9ec02b3ae0d239ec769f1f2ae\",\"affectsGlobalScope\":true},{\"version\":\"bb2d3fb05a1d2ffbca947cc7cbc95d23e1d053d6595391bd325deb265a18d36c\",\"affectsGlobalScope\":true},{\"version\":\"c80df75850fea5caa2afe43b9949338ce4e2de086f91713e9af1a06f973872b8\",\"affectsGlobalScope\":true},{\"version\":\"9d57b2b5d15838ed094aa9ff1299eecef40b190722eb619bac4616657a05f951\",\"affectsGlobalScope\":true},{\"version\":\"6c51b5dd26a2c31dbf37f00cfc32b2aa6a92e19c995aefb5b97a3a64f1ac99de\",\"affectsGlobalScope\":true},{\"version\":\"6e7997ef61de3132e4d4b2250e75343f487903ddf5370e7ce33cf1b9db9a63ed\",\"affectsGlobalScope\":true},{\"version\":\"2ad234885a4240522efccd77de6c7d99eecf9b4de0914adb9a35c0c22433f993\",\"affectsGlobalScope\":true},{\"version\":\"5e5e095c4470c8bab227dbbc61374878ecead104c74ab9960d3adcccfee23205\",\"affectsGlobalScope\":true},{\"version\":\"09aa50414b80c023553090e2f53827f007a301bc34b0495bfb2c3c08ab9ad1eb\",\"affectsGlobalScope\":true},{\"version\":\"d7f680a43f8cd12a6b6122c07c54ba40952b0c8aa140dcfcf32eb9e6cb028596\",\"affectsGlobalScope\":true},{\"version\":\"3787b83e297de7c315d55d4a7c546ae28e5f6c0a361b7a1dcec1f1f50a54ef11\",\"affectsGlobalScope\":true},{\"version\":\"e7e8e1d368290e9295ef18ca23f405cf40d5456fa9f20db6373a61ca45f75f40\",\"affectsGlobalScope\":true},{\"version\":\"faf0221ae0465363c842ce6aa8a0cbda5d9296940a8e26c86e04cc4081eea21e\",\"affectsGlobalScope\":true},{\"version\":\"06393d13ea207a1bfe08ec8d7be562549c5e2da8983f2ee074e00002629d1871\",\"affectsGlobalScope\":true},{\"version\":\"2768ef564cfc0689a1b76106c421a2909bdff0acbe87da010785adab80efdd5c\",\"affectsGlobalScope\":true},{\"version\":\"b248e32ca52e8f5571390a4142558ae4f203ae2f94d5bac38a3084d529ef4e58\",\"affectsGlobalScope\":true},{\"version\":\"6c55633c733c8378db65ac3da7a767c3cf2cf3057f0565a9124a16a3a2019e87\",\"affectsGlobalScope\":true},{\"version\":\"fb4416144c1bf0323ccbc9afb0ab289c07312214e8820ad17d709498c865a3fe\",\"affectsGlobalScope\":true},{\"version\":\"5b0ca94ec819d68d33da516306c15297acec88efeb0ae9e2b39f71dbd9685ef7\",\"affectsGlobalScope\":true},{\"version\":\"34c839eaaa6d78c8674ae2c37af2236dee6831b13db7b4ef4df3ec889a04d4f2\",\"affectsGlobalScope\":true},{\"version\":\"34478567f8a80171f88f2f30808beb7da15eac0538ae91282dd33dce928d98ed\",\"affectsGlobalScope\":true},{\"version\":\"ab7d58e6161a550ff92e5aff755dc37fe896245348332cd5f1e1203479fe0ed1\",\"affectsGlobalScope\":true},{\"version\":\"6bda95ea27a59a276e46043b7065b55bd4b316c25e70e29b572958fa77565d43\",\"affectsGlobalScope\":true},{\"version\":\"aedb8de1abb2ff1095c153854a6df7deae4a5709c37297f9d6e9948b6806fa66\",\"affectsGlobalScope\":true},{\"version\":\"a4da0551fd39b90ca7ce5f68fb55d4dc0c1396d589b612e1902f68ee090aaada\",\"affectsGlobalScope\":true},{\"version\":\"11ffe3c281f375fff9ffdde8bbec7669b4dd671905509079f866f2354a788064\",\"affectsGlobalScope\":true},{\"version\":\"52d1bb7ab7a3306fd0375c8bff560feed26ed676a5b0457fa8027b563aecb9a4\",\"affectsGlobalScope\":true},\"b07d355914793f1ba45a46df48c57b7a67358525e40f2f67ba6351045d73310d\",\"bf56d63408c0c739f36dbb241366805b385ae84030e3ece3eb365cb4b08b89d3\",{\"version\":\"bbdf156fea2fabed31a569445835aeedcc33643d404fcbaa54541f06c109df3f\",\"affectsGlobalScope\":true},\"1c29793071152b207c01ea1954e343be9a44d85234447b2b236acae9e709a383\",\"6a386ff939f180ae8ef064699d8b7b6e62bc2731a62d7fbf5e02589383838dea\",\"f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5\",{\"version\":\"7fb6faf1006c3503d44e4922c8c65772ddc98e92bad7c2ae2d5db123f3e297b3\",\"affectsGlobalScope\":true},\"23f35d9dbe58d84683a30de562648acb26dbfcac77cc39e72100b1b6bb4225ba\",\"21a4573a1a4e7c7855ce6e6ef206a1d17f551007c841887ed13a4ef3a6aa75d4\",\"12c5ef4cc23e325b219b59a98cdcf3d2d53888e311b55461c64e886a7623964b\",\"6c17249585667bb9d1c830d3cf6621f6afcc2494e389f11829bd433e550bb892\",\"10a9bad57ceace0e3401cc6fb608352089678e07e7e61776fab36d05245cedc8\",\"434fd8f637e771a395956d586648b8470f2f506a50a2ab1aa11a32e51aa9cb5b\",\"cb6d309d01b7fa6579e26771f9391b8d1e154e33fc77e13ba8f1801460d2acdb\",\"858d0d831826c6eb563df02f7db71c90e26deadd0938652096bea3cc14899700\",\"d1c89db652113258e4ba4bbdf5cc7a2a3a600403d4d864a2087b95186253cd5b\",\"11a90d2cb2eaf7fdf931a63b58279e8161f1477a1bd1e914ae026c1bbf9afed3\",\"af18e30f3ba06e9870b61dfa4a109215caabdaa337590c51b4a044a9f338ce96\",\"ace603f7b60599f2dcdbd71c07137b60a747dd33be540f4a294b890f9e0b89dc\",\"7658fbdd425c656fb1849b44932ae7431e8c3198d22c65ce1490deb582743b52\",\"7786c75c1b46e93b33c63dccf689143a5f47ff451a6b3bd9b10e4801cdeadcc2\",\"dbef2851e33a4c2fd2f3164fec70e45df647eb0e87f250de784500a3037e2c37\",\"31491a01ed7466e0b3b0ef8407f2524683055eceb955b1d5ccf7096129468b39\",\"f4b12f7dde4fc0e386648318481bdcfe861b566be246bebf0e8a11ebd909adf9\",\"e8966f7c424780bb0b9d411ebe13eda8555ca15aa675603316c2952bc027b0e3\",\"df0e5f3c4a518111d160cf3bebc9a3ac7d39c6e3bfb7a21d43c304896c3015e2\",\"df4e2f161f74870708c2cc5e1036a6405b878496408fda1ee50d5b10e50d6601\",\"bf791da347fb1c0ffc1e2fcd35867e64bb8355270ae26278198c521bdcf94569\",\"e0e0e3c068e145fbb322120979299ff130ffdd39f0dcd0d5aeaa9f3f8a0d01d9\",\"fde91356172e35b9ea68bbdf33721f7c80307a4ce65b82105eac800e9e744995\",\"9bd5e5a4a1e66b35efe3c48ddac1116537ef86e041717f3a9b9f1e060c74efa6\",\"d7e4a5f4ccfb749c3033fafc233073b4d1dcca0249785186c589602a81f9d86f\",\"68161b6f3004fc10f8bb47a4986cef13c3b0728fb1ca3e1dc7316227d09b2c8d\",\"e2e69d4946fe8da5ee1001a3ef5011ff2a3d0be02a1bff580b7f1c7d2cf4a02f\",\"dcb2838c6b357167cf70a1b2b65fe15ebaf1eb201be6eefb8cc71855fbef8c1d\",\"f616824b06a300d995220d1e80d4a8b97024655b775251f10611755b1f4a7553\",\"098ea8cce345d2f3f40dbd13c53ea2fb7e5df5bd8d04abfbcf8fe7147a4831be\",\"1cc202516488f54bc3ad8f1bf63391135737299f2ec539e997e6609696e19e6b\",\"4fe6f4f7889eaff9059698b9368493c70fa7c1378b26d0263259b5ec9ae76c75\",\"7a129438cedf12b5f1b5f773a3e96242b7569c95243432dcf12618f80fca5cdc\",\"0165115e3c8788801fb59e9c3cca0c7ba573515a8b7ce010c7d75deba92b455f\",\"0e000fd63bd08abc96dbd4746c371b8a0368f351629590ee7ba12c9787cdc8e6\",\"13b896ef2b85b5ad28ebd9af31374d28e8e2245b545f21d0e6cf250fc3b480e9\",\"559b37eeaef148c3434230c5ff4deef949e945f821b14e317966244c44602fac\",\"01a43a56faaa5b096afbcb9c282b2cd4a3be49affa1a47053ac0d9ee4da224a5\",\"bc1899efe6dcdce9c9c8bd9ce97d58b197facb7f4a516e5d13a804fa5f77cf4f\",\"4d080c54c10ec25e6e84337dbaff7953066150baf94fd48db6c70a37cdd43a8a\",\"3ad4660add51c3c3adf49524d61cc1e3ed57e16274c62c6cfb047b64aab973aa\",\"22f25ec27fa1c1ca03010aa71b8077c742ab5bf6a08a44663a287a296be34481\",\"74ac2c0b3fa70bb0cf47f80fabc73f6b868ba177ced233e294068fc099cf5a06\",\"12c453fe9c994ee8dda079a9af00b41fa48475dc1f8778b34c3680351bdf2ae1\",{\"version\":\"0e21a67f000a830bf1c28da1685b94f3fdc7ef1d181bb0e0fceac3fa3b84a57a\",\"affectsGlobalScope\":true},\"69d7bbbfaadc9fd1bbdaa65467d512016f294bc21c7143a80a44310b1cc3d156\",\"ff5b61ac1a1d4347e9a7acc5fee4716ca118ac3c7ad4afc86edb31d693d63dba\",\"725d7c13ea07d985981d8d815fcc2de328fb01e7ae7edc1b28f8331764f22ac5\",\"bd409b19dd2d7fc2d965e2fe7758d8af696150f3300d7d7f83c1a82eecda9979\",\"369e600fdd358d6e0e0dd611818bbafb24d341f197185f5775b8b91783076b0c\",\"e18a92c0dd2bcbf13e9acbe5ccaf58f004f0e66e47c7369e7488168c8d49f5bc\",\"951a918a753179bf5c018065e3302c8e2d899600ac1e7e5dfc1c2d9867a643c5\",\"842454d0222fa151b62f4f18c893335993829311367f4502270d449bd32c1b72\",\"fb42b5f44d923e74dd0e8ed20aad3dfb51aef63b5f2362a1be4e6d8c0f59a683\",\"3789bee1ae5f6d01ef84c916f8c99bb318e2795d696a0c73002b2c1a50cc7d06\",\"d0f74ca1e2ac07504fcf4c463903a420d4a13b3940827660f9aeb8e6e8d3fd46\",\"295dd674a19818a7e1a4357836fbdf26d3261d1e3ef064dc784cd908e0035785\",\"bfd248ab3a5717b027fc07f8b556b814c3540e6207b8621285ba926941921980\",\"b620e82a2a4b595315172015f8a7ef55710c05e4dd3ca50835597a4d4196f3ae\",\"3eb661239a774c68406c13beb7903e82bb66c632d62a74b143775527a650c6ba\",\"c6717e7154ad074418b7eb6481722c9555a9e0c3ae55b9536d11ee9fc1b06163\",\"83fd8a6c123446feea250b451b7599dbe606602333d5715b2525e26646885fd5\",\"16de4701f79e3af991e6a79701aad1b58109d269664cd24fbf4ddedf4201eac5\",{\"version\":\"d0fb679ac5334d2383e4a621f4aca2ff0ff4c8f416a6822061269166f1fa3530\",\"affectsGlobalScope\":true},\"3cb65b32850d8763fb8c945d9e117ced75628b0b5eb877b4e9647c367ff6f62e\",\"f3095dfa3cf7be7b80dee24765c5213d51ed3f954a925839963da606ee6df74d\",\"1616b4d2596ae32500e2087bd0d80b3a3d9098c808854ec4e4932ae0dcb59469\",\"76b9a2fc8918f9b852911d4c311941581bca1448c6a29b831cfa1f54378dbeb0\",\"f6333512273b449f40a9c1a7809fc9a6abc518c03ab8f2738e8ff8f9ddc41acd\",\"010d7414f0f9d740d26633d45654516e3564e2592b95c4bfa16c0f95f9fe3158\",\"f234afe14bc84b3711e7b4e7efd0e4b73151a0538b62b69b176b01a08141c7d9\",\"72a22437a50c66c93e984469dd2a0320acd986b3400d4b32e995221e4f751474\",\"c9ab3d1ec83628e40791f1988b20b123aa37683f863b6bf76f6d5ec9f09111c2\",\"dd56340191bb4d459b87b7d90293604538f9d505d1643f3782664db4bc88fec9\",\"71681dcc4489aa2b3ddc2ef763539c829058d95adb7f8c3f6d1a88fa0687a3f3\",\"513b53524e6080141f59e27f3f72130412a62446d7aa63efdbf788c0981fc46f\",\"7aa689a214bdb5429540f211319398038cc9db384e54cb92fda9b228c3bace97\",\"da44c89678d93683d56a71e4ea63f968ee1402d81df44d5947fa1be2f99f0341\",\"2de765891736edfc6f0c50840ae33d8dd0864aea67cdededfa852813a6f3737d\",\"82e7e7faeedffe629d0c088e305814b19011aa84ad1b1b5f6ffc368354dc0a9f\",\"b483e3a785e869105a15e127cae9726860c8700836c1e8bd0e41b0dde79bdab8\",\"11c2fcf981188bed6f189f39f4d88e3eced6f6e41df7865e7ad4627f0aa5a281\",\"5829c321ef2dda8e542ba591bae3115a3ee7857718ed252a8be5ad16d88cf12b\",\"2851d99fa80851e3c5d58e4780b8fdd631422f3be63aa0441e867975ef045c25\",\"ffb6c4083aeab9a89329e9c1bb7ce35c35fcfac7f74cfa31a2913ff06f4791f3\",\"b110c16aa3c43753f8e1d8218fe975d89ed60122333958cd61e99d099d9f7360\",\"bade4f06aae23099f4508e36bf161f29e4d53bdf7dc0c4310ddee62059e07054\",\"9ef3463398bac78b932ecb19ab4a9820199d24d5dca832d8dead30d17d5afffd\",\"531cd80e4dba2620d86844a50e7d21b89436e56a14e66d6774e99b3759ac69ad\",\"c83a76ad0bf69b0176042adfc65f8c6be7dd1141b66cfa20954520315dcf2c1a\",\"af99fcbddd1d45ac4b1ab3d21ef6665c3a4c78003f3dcf2855f406e0d41f5545\",\"ecfa9ce3a5a37d15b813065e8a7cdf677a0f493018e47ce59815443dfbb9c910\",\"83e56d3337e1a6dbafdbe5a2502a84c330b1a328ed2860d689b2ded82b1f5c95\",\"f186de91b1c50640e4d2bef41307ee06446d7ec76f787d4384ef808981025546\",\"909bac92983e542dd29efcf9eedf4ab5a330767c70c505a52326f7f5ee4b288d\",\"54b02365008778dd11d0a231fa83fddcf16c8e0f59641fd18f61a167bb31d692\",\"d7d7ca7546a0f11f81501f2c6091d4f23c8a5cd4206038d738c0845a749b549f\",\"855f5f20e17d4ab6b0738d9869af473f90ae33a02b56b4bbf16f0d315c5caa18\",\"0f33b3fd0f984c9e9611a2c57d87b364bec1c42663d2d3b39345c90ed57d10ad\",\"5717d899bd25adfcf4639b36991a76917eb8a7922cdbf5a549c810f605780144\",\"e074f251c5f1f93504d9dc3a1579892a0e749dd9c635ee17a798ecf6a71ab245\",\"a08b0c5f1b56aaf0dcdcf067700b7b39e064f29105f2b4ce0188a6560018c0e5\",\"46ba1d0e280bedd7ff83e4df70f5e43deddc290f0c7420813a671b81c8ceda98\",\"2d78fd0ad30dde468060318380beb50f5e9e0e42751b661c9c2bb90be106d283\",\"eee7bd2ec082b75a4b2408dd98613ec4296fe04216bed6604c1c5e0a661dfd85\",\"04ffd65cd3e602f6b03472c0e12eff2cd969e5f4141f142f44d05dbac3b6686b\",\"d747268dd5f760f55765c74b8cb9bd505808c9494f00aa89f37a7153cef32afb\",\"59a49b5fd96799ef66cdcd0fca3b8b13986c147f243a46d81c980834da0ddf83\",\"ccc72ab0b42bf4d6b1b7b41f5b95d7ebb8752000f0fd5baf96941ba1e659b401\",\"e01f2da71e54a1cd22982d63d3473f42c6eb5140c8e94fe309b1f739b7d24bd8\",\"1e6f83f746b7cd4987335905f4c339ffc9d71dddf19f309cb40c5052e1667608\",\"dfd5a5761262563b1b102019fc3f72510e68efe1e4731d89c8e55bde0c03e321\",\"93283b859c4429b9776c16fedb459722b156c99057e037f63ba74cfdc91b0e42\",\"7698c020193a21574ef24f01fcfe087e538f5a290eee859a9fa325b2112773e8\",\"0bb12baf4731034d73cf0fb10a73ee5d3236c287a5c7acaa9cf23b23cb8ff7a0\",\"e4d6a3b4118ff9ecddf8f95cc0af5cede1dd45fcc1e83c4cf1c4570a9af1c6b8\",\"6b8231fd19df683235e70a5d1440d5a5e0b840adae48d5ad3fe0693cbe217c33\",\"1a07878e33e5f8dd8e9d4b8d4e70dff30671c1c18ffcf56ab7e17d0f1475641a\",\"57b72c59436e773668ecaee372649fdcd30ebb3cfe02cd716a6688442dd21141\",\"a57778daf397b071c06f50387085bb656e0dc1b3e43f41f738ca543d9ac13632\",\"7de7e156bed4a97d5cf802347a97e358f97399e49f187aa9bdfbfa10b1699695\",\"2d03a8d0df4495fdf8b4e7c0c22f23edc544570112552d0913b3df1242420807\",\"714d87bb439fe423606ec13b8e570eccbbbdbf81866b4fefc2909dc2e311186a\",\"eed4bb44fa9677f32a74f2ab18f0e3b445c557015a0cc4741cb818d4c846c013\",\"a87f37030562b075f460027fa5af16194451ef1ffe29e0f13c328c84d05bf9e8\",\"e2174023bf7470e411c2fb2e19c9f7b9a87a7ae4f042fa398f3c71b9b7ef2baa\",\"9ffaf4f9bc289291981a261f341e01fc1e3f66e2687a30ae1b4a7a7769047935\",\"b0d9545c318f7b4a7834b9fa51df7b9afaaa39f2feae1edb58bc93f6d7a9e15a\",\"ce684c899e4209345f2e947473ff7b524921bf28a79341dfd92c07e09daa15b1\",\"557435183d60d251654ff0b5ee0a96b149b4a3bd6ae6e4928db5672196f4a696\",\"62d140657f10323ee4e07c0eb9920e1600edc4b3f2c13238693f85c20c901214\",\"83e27bbd7304ea67f9afa1535f1d4fdb15866089f0d893c784cbb5b1c6fb3386\",\"5adfb6b9f30ff88d1903f9d44f2978a29329a7e2a9d9d41c790e7c594a011816\",\"7c7b7879b41d269f79fa353a40be34caf7a494908390ec0ae02bb6fc6b9ac798\",\"73e3734a51d332fa10cadda549ce5a0d49441c9945eb9d867257c477a9eb5d08\",\"642ff7d2fd4ae85285aec72664b12e0987482104e1218ee16162568fb9a67101\",\"f61a507e024a6f8cbbf133a67d0627aa2c4e118117d439dbead0f84ffceedc45\",\"aa92cc56bcd582014a25349cdf29a9b9227fd8ad865690911e73b6d259fca37d\",\"014ea4f9cb760bcaec899d64ac30e5e46869dbc7a7ab7b08e62e6bbdaf80066c\",\"2b8264b2fefd7367e0f20e2c04eed5d3038831fe00f5efbc110ff0131aab899b\",\"e4dd91dd4789a109aab51d8a0569a282369fcda9ba6f2b2297bc61bacfb1a042\",\"30688eab034d1aa3bbe4d8f2c7f462ddaec9f30f1a38a306a4728a9a06a58b11\",\"e03334588c63840b7054accd0b90f29c5890db6a6555ac0869a78a23297f1396\",\"c3052485f32a96bfde75a2976c1238995522584ba464f04ff16a8a40af5e50d1\",\"c220410b8e956fa157ce4e5e6ac871f0f433aa120c334d906ff1f5e2c7369e95\",\"960a68ced7820108787135bdae5265d2cc4b511b7dcfd5b8f213432a8483daf1\",\"5e8db4872785292074b394d821ae2fc10e4f8edc597776368aebbe8aefb24422\",\"74b0245c42990ed8a849df955db3f4362c81b13f799ebc981b7bec2d5b414a57\"],\"options\":{\"esModuleInterop\":true,\"jsx\":1,\"module\":99,\"skipLibCheck\":true,\"strict\":true,\"target\":1},\"fileIdsList\":[[152],[63],[63,153,154,155,156,157],[63,154,158],[63,156,158],[60,152],[63,111,112,141],[63,111],[63,111,112],[63,111,112,129,130,134],[63,111,112,129,199],[63,111,112,131,132],[63,111,112,129,130,133,134],[63,111,112,129,133,134],[118,119,120],[118,119],[118],[63,108],[63,205],[205,206,207,208,209],[59,60,61,62],[115],[75],[75,87],[72,73,74,76,87],[78],[75,82,86,89],[77,89],[80,82,85,86,89],[80,82,83,85,86,89],[72,73,74,75,76,78,79,80,81,82,86,89],[71,72,73,74,75,76,78,79,80,81,82,83,85,86,87,88],[71,89],[82,83,84,86,89],[85,89],[75,81,86,89],[79,87],[57,63],[57,58,64,65,66],[57],[121,125],[63,121,125,126],[121,122,123,124],[63,121,122],[63,121],[181],[63,170,174,178,179],[63,151,158,162,166,168,169,170,171,172,173,177],[63,170],[63,158,170],[63,158,170,178],[63,158,159,160,161,162,163,164,165,166,167,168,170],[180],[151,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,178],[63,170,174,178],[159,160,161,162,163,164,165,166,167,168,170],[170],[158,169,178],[63,170,178],[170,178],[71],[97,98,99,100,101,102,103,104,105],[97,100],[99,100],[100],[97],[70,91],[70],[68,69],[90],[70,89],[63,127,186,187,192,193],[63,95,140,182],[63,95,114,138,140],[63,127],[63,107,145],[63,107,116],[63,107,136,184],[63,107,136,141],[63,107],[63,107,143],[63,107,116,136,200],[63,107,188],[63,107,135,136],[63,107,190],[63,107,116,136,141],[63,107,113],[63,107,139],[127],[67,93,94],[63,194,195],[96,106],[63,95,109,110,114,117,128,136,137,138,139,140,142,144,146,147,148,149,150,183,185,194],[63,95,109,117,128,136,138,144,185,189,191,194],[90,92]],\"referencedMap\":[[153,1],[156,2],[158,3],[155,4],[157,5],[154,6],[145,7],[131,8],[184,9],[112,2],[141,10],[129,8],[130,8],[143,8],[200,11],[133,12],[134,8],[111,2],[188,9],[135,13],[190,8],[113,9],[139,14],[199,8],[121,15],[120,16],[119,17],[109,18],[195,2],[204,2],[206,19],[208,2],[205,2],[207,19],[210,20],[63,21],[116,22],[136,2],[73,23],[72,24],[75,25],[79,26],[76,24],[81,27],[78,28],[83,29],[84,30],[87,31],[89,32],[77,33],[85,34],[86,35],[82,36],[74,23],[80,37],[64,38],[66,38],[67,39],[65,40],[58,40],[126,41],[127,42],[125,43],[123,44],[122,45],[124,44],[182,46],[180,47],[176,2],[178,48],[171,49],[160,50],[161,51],[163,50],[166,50],[168,51],[165,50],[164,50],[167,50],[159,50],[172,52],[162,50],[181,53],[177,54],[175,55],[169,56],[173,57],[170,58],[179,59],[174,60],[71,61],[106,62],[103,63],[101,64],[102,63],[105,65],[104,65],[98,66],[92,67],[94,68],[70,69],[91,70],[90,71],[194,72],[183,73],[149,74],[128,75],[146,76],[117,77],[185,78],[198,79],[142,79],[138,80],[144,81],[201,82],[150,79],[189,83],[137,84],[191,85],[202,86],[114,87],[110,80],[140,88],[193,89],[95,90],[196,91],[148,2],[147,2],[107,92],[187,93],[186,93],[192,94],[93,95]],\"exportedModulesMap\":[[153,1],[156,2],[158,3],[155,4],[157,5],[154,6],[145,7],[131,8],[184,9],[112,2],[141,10],[129,8],[130,8],[143,8],[200,11],[133,12],[134,8],[111,2],[188,9],[135,13],[190,8],[113,9],[139,14],[199,8],[121,15],[120,16],[119,17],[109,18],[195,2],[204,2],[206,19],[208,2],[205,2],[207,19],[210,20],[63,21],[116,22],[136,2],[73,23],[72,24],[75,25],[79,26],[76,24],[81,27],[78,28],[83,29],[84,30],[87,31],[89,32],[77,33],[85,34],[86,35],[82,36],[74,23],[80,37],[64,38],[66,38],[67,39],[65,40],[58,40],[126,41],[127,42],[125,43],[123,44],[122,45],[124,44],[182,46],[180,47],[176,2],[178,48],[171,49],[160,50],[161,51],[163,50],[166,50],[168,51],[165,50],[164,50],[167,50],[159,50],[172,52],[162,50],[181,53],[177,54],[175,55],[169,56],[173,57],[170,58],[179,59],[174,60],[71,61],[106,62],[103,63],[101,64],[102,63],[105,65],[104,65],[98,66],[92,67],[94,68],[70,69],[91,70],[90,71],[194,72],[183,73],[149,74],[128,75],[146,76],[117,77],[185,78],[198,79],[142,79],[138,80],[144,81],[201,82],[150,79],[189,83],[137,84],[191,85],[202,86],[114,87],[110,80],[140,88],[193,89],[95,90],[196,91],[148,2],[147,2],[107,92],[187,93],[186,93],[192,94],[93,95]],\"semanticDiagnosticsPerFile\":[11,12,14,13,2,15,16,17,18,19,20,21,22,3,4,26,23,24,25,27,28,29,5,30,31,32,33,6,37,34,35,36,38,7,39,44,45,40,41,42,43,8,49,46,47,48,50,9,51,52,53,54,55,1,10,56,153,156,158,155,157,154,152,145,131,184,112,141,129,130,143,200,133,134,111,188,135,190,113,139,199,132,118,121,120,119,109,108,203,61,195,204,206,208,205,207,209,210,59,63,211,62,116,115,96,60,136,73,72,75,79,76,81,78,83,88,84,87,89,77,85,86,82,74,80,64,66,67,65,57,58,126,127,125,123,122,124,182,180,176,178,171,160,161,163,166,168,165,164,167,159,172,162,181,151,177,175,169,173,170,179,174,71,106,103,101,102,105,104,97,98,100,99,92,94,70,69,68,91,90,194,183,149,[197,[{\"file\":\"./src/components/inputarea.tsx\",\"start\":23,\"length\":24,\"messageText\":\"Cannot find module '@/components/ui/button' or its corresponding type declarations.\",\"category\":1,\"code\":2307},{\"file\":\"./src/components/inputarea.tsx\",\"start\":73,\"length\":26,\"messageText\":\"Cannot find module '@/components/ui/textarea' or its corresponding type declarations.\",\"category\":1,\"code\":2307}]],128,146,117,185,198,142,138,144,201,150,189,137,191,202,114,110,140,[193,[{\"file\":\"./src/error-page.tsx\",\"start\":278,\"length\":5,\"messageText\":\"'error' is of type 'unknown'.\",\"category\":1,\"code\":18046},{\"file\":\"./src/error-page.tsx\",\"start\":298,\"length\":5,\"messageText\":\"'error' is of type 'unknown'.\",\"category\":1,\"code\":18046}]],[95,[{\"file\":\"./src/hooks/useBreakpoint.ts\",\"start\":564,\"length\":11,\"messageText\":\"'breakpoints' is possibly 'undefined'.\",\"category\":1,\"code\":18048},{\"file\":\"./src/hooks/useBreakpoint.ts\",\"start\":564,\"length\":26,\"code\":7053,\"category\":1,\"messageText\":\"Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'ScreensConfig'.\"}]],196,148,147,107,[187,[{\"file\":\"./src/pages/compare.tsx\",\"start\":3716,\"length\":15,\"messageText\":\"Property 'availableModels' does not exist on type '{}'.\",\"category\":1,\"code\":2339},{\"file\":\"./src/pages/compare.tsx\",\"start\":3733,\"length\":18,\"messageText\":\"Property 'setAvailableModels' does not exist on type '{}'.\",\"category\":1,\"code\":2339},{\"file\":\"./src/pages/compare.tsx\",\"start\":9455,\"length\":21,\"code\":7053,\"category\":1,\"messageText\":{\"messageText\":\"Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.\",\"category\":1,\"code\":7053,\"next\":[{\"messageText\":\"No index signature with a parameter of type 'string' was found on type '{}'.\",\"category\":1,\"code\":7054}]}},{\"file\":\"./src/pages/compare.tsx\",\"start\":11978,\"length\":4,\"code\":2345,\"category\":1,\"messageText\":\"Argument of type 'null' is not assignable to parameter of type 'DraftInlineStyle | undefined'.\"},{\"file\":\"./src/pages/compare.tsx\",\"start\":12573,\"length\":9,\"code\":2339,\"category\":1,\"messageText\":\"Property 'scrollTop' does not exist on type 'never'.\"},{\"file\":\"./src/pages/compare.tsx\",\"start\":12594,\"length\":12,\"code\":2339,\"category\":1,\"messageText\":\"Property 'scrollHeight' does not exist on type 'never'.\"},{\"file\":\"./src/pages/compare.tsx\",\"start\":12618,\"length\":12,\"code\":2339,\"category\":1,\"messageText\":\"Property 'clientHeight' does not exist on type 'never'.\"},{\"file\":\"./src/pages/compare.tsx\",\"start\":14212,\"length\":23,\"code\":2322,\"category\":1,\"messageText\":\"Type 'AbortController' is not assignable to type 'null'.\"},{\"file\":\"./src/pages/compare.tsx\",\"start\":14344,\"length\":23,\"messageText\":\"'abortController.current' is possibly 'null'.\",\"category\":1,\"code\":18047},{\"file\":\"./src/pages/compare.tsx\",\"start\":16054,\"length\":23,\"code\":2322,\"category\":1,\"messageText\":\"Type 'AbortController' is not assignable to type 'null'.\"},{\"file\":\"./src/pages/compare.tsx\",\"start\":16186,\"length\":23,\"messageText\":\"'abortController.current' is possibly 'null'.\",\"category\":1,\"code\":18047},{\"file\":\"./src/pages/compare.tsx\",\"start\":20099,\"length\":17,\"messageText\":\"'editorRef.current' is possibly 'null'.\",\"category\":1,\"code\":18047},{\"file\":\"./src/pages/compare.tsx\",\"start\":20286,\"length\":5,\"code\":2339,\"category\":1,\"messageText\":\"Property 'abort' does not exist on type 'never'.\"},{\"file\":\"./src/pages/compare.tsx\",\"start\":27444,\"length\":12,\"code\":2322,\"category\":1,\"messageText\":\"Type 'unknown' is not assignable to type 'ReactNode'.\",\"relatedInformation\":[{\"file\":\"./node_modules/@types/react/index.d.ts\",\"start\":60651,\"length\":8,\"messageText\":\"The expected type comes from property 'children' which is declared here on type 'IntrinsicAttributes & Omit<SelectItemProps & RefAttributes<HTMLDivElement>, \\\"ref\\\"> & RefAttributes<...>'\",\"category\":3,\"code\":6500}]}]],[186,[{\"file\":\"./src/pages/playground.tsx\",\"start\":3457,\"length\":15,\"messageText\":\"Property 'availableModels' does not exist on type '{}'.\",\"category\":1,\"code\":2339},{\"file\":\"./src/pages/playground.tsx\",\"start\":3474,\"length\":18,\"messageText\":\"Property 'setAvailableModels' does not exist on type '{}'.\",\"category\":1,\"code\":2339},{\"file\":\"./src/pages/playground.tsx\",\"start\":8902,\"length\":21,\"code\":7053,\"category\":1,\"messageText\":{\"messageText\":\"Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.\",\"category\":1,\"code\":7053,\"next\":[{\"messageText\":\"No index signature with a parameter of type 'string' was found on type '{}'.\",\"category\":1,\"code\":7054}]}},{\"file\":\"./src/pages/playground.tsx\",\"start\":11285,\"length\":4,\"code\":2345,\"category\":1,\"messageText\":\"Argument of type 'null' is not assignable to parameter of type 'DraftInlineStyle | undefined'.\"},{\"file\":\"./src/pages/playground.tsx\",\"start\":11844,\"length\":9,\"code\":2339,\"category\":1,\"messageText\":\"Property 'scrollTop' does not exist on type 'never'.\"},{\"file\":\"./src/pages/playground.tsx\",\"start\":11865,\"length\":12,\"code\":2339,\"category\":1,\"messageText\":\"Property 'scrollHeight' does not exist on type 'never'.\"},{\"file\":\"./src/pages/playground.tsx\",\"start\":11889,\"length\":12,\"code\":2339,\"category\":1,\"messageText\":\"Property 'clientHeight' does not exist on type 'never'.\"},{\"file\":\"./src/pages/playground.tsx\",\"start\":13379,\"length\":23,\"code\":2322,\"category\":1,\"messageText\":\"Type 'AbortController' is not assignable to type 'null'.\"},{\"file\":\"./src/pages/playground.tsx\",\"start\":13507,\"length\":23,\"messageText\":\"'abortController.current' is possibly 'null'.\",\"category\":1,\"code\":18047},{\"file\":\"./src/pages/playground.tsx\",\"start\":15111,\"length\":23,\"code\":2322,\"category\":1,\"messageText\":\"Type 'AbortController' is not assignable to type 'null'.\"},{\"file\":\"./src/pages/playground.tsx\",\"start\":15239,\"length\":23,\"messageText\":\"'abortController.current' is possibly 'null'.\",\"category\":1,\"code\":18047},{\"file\":\"./src/pages/playground.tsx\",\"start\":18888,\"length\":17,\"messageText\":\"'editorRef.current' is possibly 'null'.\",\"category\":1,\"code\":18047},{\"file\":\"./src/pages/playground.tsx\",\"start\":19063,\"length\":5,\"code\":2339,\"category\":1,\"messageText\":\"Property 'abort' does not exist on type 'never'.\"},{\"file\":\"./src/pages/playground.tsx\",\"start\":25791,\"length\":12,\"code\":2322,\"category\":1,\"messageText\":\"Type 'unknown' is not assignable to type 'ReactNode'.\",\"relatedInformation\":[{\"file\":\"./node_modules/@types/react/index.d.ts\",\"start\":60651,\"length\":8,\"messageText\":\"The expected type comes from property 'children' which is declared here on type 'IntrinsicAttributes & Omit<SelectItemProps & RefAttributes<HTMLDivElement>, \\\"ref\\\"> & RefAttributes<...>'\",\"category\":3,\"code\":6500}]}]],[192,[{\"file\":\"./src/pages/settings.tsx\",\"start\":2408,\"length\":20,\"code\":7053,\"category\":1,\"messageText\":{\"messageText\":\"Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.\",\"category\":1,\"code\":7053,\"next\":[{\"messageText\":\"No index signature with a parameter of type 'string' was found on type '{}'.\",\"category\":1,\"code\":7054}]}},{\"file\":\"./src/pages/settings.tsx\",\"start\":2922,\"length\":15,\"messageText\":\"Property 'availableModels' does not exist on type '{}'.\",\"category\":1,\"code\":2339},{\"file\":\"./src/pages/settings.tsx\",\"start\":2939,\"length\":18,\"messageText\":\"Property 'setAvailableModels' does not exist on type '{}'.\",\"category\":1,\"code\":2339},{\"file\":\"./src/pages/settings.tsx\",\"start\":5721,\"length\":32,\"code\":7053,\"category\":1,\"messageText\":{\"messageText\":\"Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ OpenAI: string[]; \\\"co:here\\\": string[]; \\\"HuggingFace Hosted\\\": string[]; \\\"HuggingFace Local\\\": string[]; }'.\",\"category\":1,\"code\":7053,\"next\":[{\"messageText\":\"No index signature with a parameter of type 'string' was found on type '{ OpenAI: string[]; \\\"co:here\\\": string[]; \\\"HuggingFace Hosted\\\": string[]; \\\"HuggingFace Local\\\": string[]; }'.\",\"category\":1,\"code\":7054}]}},{\"file\":\"./src/pages/settings.tsx\",\"start\":6324,\"length\":15,\"messageText\":\"Property 'availableModels' does not exist on type '{}'.\",\"category\":1,\"code\":2339},{\"file\":\"./src/pages/settings.tsx\",\"start\":6341,\"length\":18,\"messageText\":\"Property 'setAvailableModels' does not exist on type '{}'.\",\"category\":1,\"code\":2339},{\"file\":\"./src/pages/settings.tsx\",\"start\":7201,\"length\":15,\"code\":7053,\"category\":1,\"messageText\":{\"messageText\":\"Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.\",\"category\":1,\"code\":7053,\"next\":[{\"messageText\":\"No index signature with a parameter of type 'string' was found on type '{}'.\",\"category\":1,\"code\":7054}]}},{\"file\":\"./src/pages/settings.tsx\",\"start\":7266,\"length\":10,\"code\":2345,\"category\":1,\"messageText\":{\"messageText\":\"Argument of type '{}' is not assignable to parameter of type 'SetStateAction<null>'.\",\"category\":1,\"code\":2345,\"next\":[{\"messageText\":\"Type '{}' provides no match for the signature '(prevState: null): null'.\",\"category\":1,\"code\":2658}]}},{\"file\":\"./src/pages/settings.tsx\",\"start\":7470,\"length\":11,\"code\":7053,\"category\":1,\"messageText\":{\"messageText\":\"Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ openai: { models: string[]; tags: string[]; }; cohere: { models: string[]; tags: string[]; }; \\\"HuggingFace Hosted\\\": { models: string[]; tags: string[]; }; \\\"HuggingFace Local\\\": { models: string[]; tags: string[]; }; }'.\",\"category\":1,\"code\":7053,\"next\":[{\"messageText\":\"No index signature with a parameter of type 'string' was found on type '{ openai: { models: string[]; tags: string[]; }; cohere: { models: string[]; tags: string[]; }; \\\"HuggingFace Hosted\\\": { models: string[]; tags: string[]; }; \\\"HuggingFace Local\\\": { models: string[]; tags: string[]; }; }'.\",\"category\":1,\"code\":7054}]}},{\"file\":\"./src/pages/settings.tsx\",\"start\":9539,\"length\":18,\"messageText\":\"Spread types may only be created from object types.\",\"category\":1,\"code\":2698},{\"file\":\"./src/pages/settings.tsx\",\"start\":16435,\"length\":5,\"code\":2345,\"category\":1,\"messageText\":{\"messageText\":\"Argument of type 'string | boolean' is not assignable to parameter of type 'boolean'.\",\"category\":1,\"code\":2345,\"next\":[{\"messageText\":\"Type 'string' is not assignable to type 'boolean'.\",\"category\":1,\"code\":2322}]}}]],93],\"affectedFilesPendingEmit\":[[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1],[153,1],[156,1],[158,1],[155,1],[157,1],[154,1],[152,1],[145,1],[131,1],[184,1],[112,1],[141,1],[129,1],[130,1],[143,1],[200,1],[133,1],[134,1],[111,1],[188,1],[135,1],[190,1],[113,1],[139,1],[199,1],[132,1],[118,1],[121,1],[120,1],[119,1],[109,1],[108,1],[203,1],[61,1],[195,1],[204,1],[206,1],[208,1],[205,1],[207,1],[209,1],[210,1],[59,1],[63,1],[211,1],[62,1],[116,1],[115,1],[96,1],[60,1],[136,1],[73,1],[72,1],[75,1],[79,1],[76,1],[81,1],[78,1],[83,1],[88,1],[84,1],[87,1],[89,1],[77,1],[85,1],[86,1],[82,1],[74,1],[80,1],[64,1],[66,1],[67,1],[65,1],[57,1],[58,1],[126,1],[127,1],[125,1],[123,1],[122,1],[124,1],[182,1],[180,1],[176,1],[178,1],[171,1],[160,1],[161,1],[163,1],[166,1],[168,1],[165,1],[164,1],[167,1],[159,1],[172,1],[162,1],[181,1],[151,1],[177,1],[175,1],[169,1],[173,1],[170,1],[179,1],[174,1],[71,1],[106,1],[103,1],[101,1],[102,1],[105,1],[104,1],[97,1],[98,1],[100,1],[99,1],[92,1],[94,1],[70,1],[69,1],[68,1],[91,1],[90,1],[194,1],[183,1],[149,1],[197,1],[128,1],[146,1],[117,1],[185,1],[198,1],[142,1],[138,1],[144,1],[201,1],[150,1],[189,1],[137,1],[191,1],[202,1],[114,1],[110,1],[140,1],[193,1],[95,1],[196,1],[148,1],[147,1],[107,1],[187,1],[186,1],[192,1],[93,1]]},\"version\":\"4.9.5\"}"
  },
  {
    "path": "build.py",
    "content": "import os\nimport shutil\nimport subprocess\n\nos.environ['NODE_ENV'] = 'production'\n\nos.chdir('./app')\n\nsubprocess.run(['parcel', 'build', 'src/index.html', '--no-cache', '--no-source-maps'])\n\nos.chdir('..')\n\nif os.path.exists('./server/static'):\n    shutil.rmtree('./server/static')\n\nshutil.copytree('./app/dist', './server/static')"
  },
  {
    "path": "dockerfile",
    "content": "# ==== FRONTEND ====\nFROM node:19-alpine AS builder\n\nWORKDIR /frontend\n\n# Copy the package.json and install dependencies\nCOPY app/package*.json ./\nRUN npm install\n\n# Copy rest of the files\nCOPY app/src ./src/\nCOPY app/* ./\n\n# Build the project\nRUN npx parcel build src/index.html --no-cache --no-source-maps\n\n# ==== BACKEND ====\nFROM pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime\n\nWORKDIR /web/\n\n# set environment variables\nENV PYTHONDONTWRITEBYTECODE 1\nENV PYTHONUNBUFFERED 1\nENV XDG_CONFIG_HOME=/web/config\n\nARG POETRY_VERSION=1.4.1\n\nRUN pip install --no-cache-dir --upgrade pip\n\n# install poetry\nRUN pip install poetry==${POETRY_VERSION}\nRUN poetry config virtualenvs.create false\n\nCOPY server/ ./server/\nCOPY README.md .\nCOPY --from=builder /frontend/dist ./server/static/\n\n# install python dependencies\nCOPY pyproject.toml .\nCOPY poetry.lock .\nRUN poetry install --without=dev --no-interaction --no-ansi\n\nENTRYPOINT [\"openplayground\", \"run\", \"--host\", \"0.0.0.0\", \"--env\", \"/web/config/.env\"]"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"openplayground\"\nversion = \"0.1.5\"\ndescription = \"An LLM playground you can run on your laptop.\"\nauthors = [\"Nat Friedman <nat@nat.org>\"]\nreadme = \"README.md\"\nhomepage = \"https://nat.dev/\"\nrepository = \"https://github.com/nat/openplayground\"\nkeywords = [\"llm\", \"playground\", \"openplayground\"]\npackages = [\n  { include = \"server\", from = \"\" },\n]\ninclude = [\"server/static/*\"]\n\n[tool.poetry.dependencies]\npython = \"^3.9\"\naleph_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_hub=\"^0.13.2\"\nopenai=\"^0.27.2\"\npsutil=\"^5.9.4\"\npython-dotenv=\"^1.0.0\"\nrequests=\"^2.28.2\"\nsix=\"^1.16.0\"\nsseclient=\"^0.0.27\"\ntorch=\"^2.0.0\"\ntransformers=\"^4.27.1\"\n\n[tool.poetry.scripts]\nopenplayground = \"server.app:cli\"\n\n[[tool.poetry.source]]\nname = 'pypi-public'\nurl = \"https://pypi.org/simple/\"\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\nexclude = [\n  \".env\",\n]"
  },
  {
    "path": "server/__init__.py",
    "content": ""
  },
  {
    "path": "server/app.py",
    "content": "import click\nimport logging\nimport os\nimport warnings\nimport io\nimport queue\nimport sys\nfrom pathlib import Path\nimport json\nimport threading\nimport time\nimport re\n\nfrom contextlib import contextmanager\n\nfrom server.lib.entities import Model, Provider\nfrom server.lib.inference import ProviderDetails, InferenceManager, InferenceRequest\nfrom server.lib.event_emitter import EventEmitter, EVENTS\nfrom server.lib.storage import Storage\nfrom server.lib.sseserver import SSEQueueWithTopic\nfrom server.lib.api import api_bp\n\nfrom flask import Flask, g, send_from_directory\nfrom flask_cors import CORS\n\nfrom transformers import AutoTokenizer, AutoModel\nfrom huggingface_hub import hf_hub_download, try_to_load_from_cache, scan_cache_dir, _CACHED_NO_EXIST\n\n# Monkey patching for warnings, for convenience\ndef warning_on_one_line(message, category, filename, lineno, file=None, line=None):\n    return '%s:%s: %s: %s\\n' % (filename, lineno, category.__name__, message)\n\nwarnings.formatwarning = warning_on_one_line\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\napp = Flask(__name__)\n\n@app.route('/', defaults={'path': ''})\n@app.route('/<path:path>')\ndef serve(path):\n    if path == \"\" or not os.path.exists(f'{app.static_folder}/{path}'):\n        path = 'index.html'\n\n    return send_from_directory(app.static_folder, path)\n\n@app.errorhandler(404)\ndef page_not_found(i):\n    path = 'index.html'\n    return send_from_directory(app.static_folder, path)\n\n@app.before_request\ndef before_request():\n    g.global_state = app.config['GLOBAL_STATE']\n    g.storage = g.global_state.get_storage()\n\napp.register_blueprint(api_bp)\n\nCORS(app)\n\nclass RedirectStderr:\n    def __init__(self, new_stderr):\n        self.new_stderr = new_stderr\n        self.old_stderr = None\n\n    def __enter__(self):\n        self.old_stderr = sys.stderr\n        sys.stderr = self.new_stderr\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        sys.stderr = self.old_stderr\n\n@contextmanager\ndef redirect_stderr(new_stderr):\n    with RedirectStderr(new_stderr):\n        yield\n\nclass MonitorThread(threading.Thread):\n    def __init__(self, model, output_buffer):\n        super().__init__()\n        self.model = model\n        self.output_buffer = output_buffer\n        self._stop_event = threading.Event()\n        self.event_emitter = EventEmitter()\n\n    def run(self):\n        with redirect_stderr(self.output_buffer):\n            # Code that may generate errors goes here\n            output_buffer = self.output_buffer\n            current_shard = 0\n            total_shards = 0\n            last_line = 0\n   \n            while not self._stop_event.is_set():\n                try:\n                    lines = output_buffer.getvalue().splitlines()[last_line:]\n                    last_line += len(lines)\n\n                    for line in lines:\n                        if line == \"\":\n                            continue\n\n                        if line.startswith(\"Downloading shards:\"):\n                            if progress := re.search(r\"\\| (\\d+)/(\\d+) \\[\", line):\n                                current_shard, total_shards = int(progress[1]), int(progress[2])\n                        elif line.startswith(\"Downloading\"):\n                            logger.info(line)\n                            percentage = re.search(r\":\\s+(\\d+)%\", line)\n                            percentage = percentage[0][2:] if percentage else \"\"\n\n                            progress = re.search(r\"\\[(.*?)\\]\", line)\n                            if progress and \"?\" not in progress[0]:\n                                current_duration, rest = progress[0][1:-1].split(\"<\")\n                                total_duration, speed = rest.split(\",\")\n\n                                if download_size := re.search(r\"\\| (.*?)\\[\", line):\n                                    current_size, total_size = download_size[0][2:-1].strip().split(\"/\")\n\n                                self.event_emitter.emit(EVENTS.MODEL_DOWNLOAD_UPDATE, self.model, {\n                                    'current_shard': current_shard,\n                                    'total_shards': total_shards,\n                                    'percentage': percentage.strip(),\n                                    'current_duration': current_duration,\n                                    'total_duration': total_duration,\n                                    'speed': speed.strip(),\n                                    'current_size': current_size,\n                                    'total_size': total_size,\n                                })\n                except Exception as e:\n                    logger.info(f\"\"\"[ERROR] {str(e)}\"\"\")\n                time.sleep(0.5)\n\n    def stop(self):\n        self._stop_event.set()\n\nclass NotificationManager:\n    def __init__(self, sse_queue: SSEQueueWithTopic):\n        self.event_emitter = EventEmitter()\n        self.event_emitter.on(EVENTS.MODEL_UPDATED, self.__model_updated_callback__)\n        self.event_emitter.on(EVENTS.MODEL_ADDED, self.__model_added_callback__)\n        #TODO Fix the bug where SSE gets blocked\n        #self.event_emitter.on(EVENTS.MODEL_DOWNLOAD_UPDATE, self.__model_download_update_callback__)\n        self.sse_queue = sse_queue\n\n    def __model_added_callback__(self, model_name, model):\n        if model.status == 'ready':\n            self.sse_queue.publish(json.dumps({\n                'type': 'notification',\n                'data': {\n                    'message': {\n                        'event': 'modelAdded',\n                        'data': {\n                            'model': model.name,\n                            'provider': model.provider\n                        }\n                    }\n                }\n            }))\n\n    def __model_updated_callback__(self, model_name, model):\n        if model.status == 'ready':\n            self.sse_queue.publish(json.dumps({\n                'type': 'notification',\n                'data': {\n                    'message': {\n                        'event': 'modelAdded' if model.enabled == True else 'modelRemoved',\n                        'data': {\n                            'model': model.name,\n                            'provider': model.provider\n                        }\n                    }\n                }\n            }))\n\n    def __model_download_update_callback__(self, _, model, progress):\n        self.sse_queue.publish(json.dumps({\n            'type': 'notification',\n            'data': {\n                'message': {\n                    'event': 'modelDownloadProgress',\n                    'data': {\n                        'model': model.name,\n                        'provider': model.provider,\n                        'progress': progress\n                    }\n                }\n            }\n        }))\n\n### Perhaps this should be a singleton or each provider should have its own instance\n### For now this will only deal with HuggingFace\nclass DownloadManager:\n    def __init__(self, storage: Storage):\n        logger.info(\"Initializing download manager...\")\n\n        self.event_emitter = EventEmitter()\n        self.event_emitter.on(EVENTS.MODEL_ADDED, self.__model_added_callback__)\n        self.storage = storage\n        self.model_queue = queue.Queue()\n        self.__initialization_check__()\n\n    def __initialization_check__(self):\n        models = self.storage.get_models()\n\n        for model in models:\n            if model.status == 'pending':\n                self.model_queue.put(model)\n\n        # TODO: In the future it might make sense to have local provider specific instances\n        cache_info = scan_cache_dir()\n        hugging_face_local = self.storage.get_provider(\"huggingface-local\")\n \n        for repo_info in cache_info.repos:\n            repo_id = repo_info.repo_id\n            repo_type = repo_info.repo_type\n            if repo_type == \"model\":\n                if hugging_face_local.has_model(repo_id):\n                    continue\n                else:\n                    model = Model(\n                        name=repo_id,\n                        capabilities=hugging_face_local.default_capabilities,\n                        provider=\"huggingface-local\",\n                        status=\"ready\",\n                        enabled=False,\n                        parameters=hugging_face_local.default_parameters\n                    )\n                    hugging_face_local.add_model(model)\n\n        t = threading.Thread(target=self.__download_loop__)\n        t.start()\n\n        logger.info(\"Download loop started...\")\n\n    def __model_added_callback__(self, model_name, model):\n        if model.status == 'pending':\n            self.model_queue.put(model)\n     \n    def __download_loop__(self):\n        while True:\n            try:\n                output_buffer = io.StringIO()\n                with redirect_stderr(output_buffer):\n                    model = self.model_queue.get(block=False)\n\n                    monitor_thread =  MonitorThread(model, output_buffer)\n                    monitor_thread.start()\n\n                    logger.info(\"Inside loop, about to download model\", model.name)\n\n                    _ = AutoTokenizer.from_pretrained(model.name)\n                    _ = AutoModel.from_pretrained(model.name)\n\n                    model.status = 'ready'\n\n                    self.storage.update_model(model.name, model)\n\n                    monitor_thread.stop()\n                    monitor_thread.join()\n                    \n                    logger.info(\"Finished downloading model\", model.name)\n            except queue.Empty:\n                time.sleep(1)\n            except Exception as e:\n                logger.error(\"error\", e)\n                logger.error(f\"Failed to download {model.name} from {model.provider}\")\n            finally:\n                time.sleep(1)\n\nclass GlobalStateManager:\n    def __init__(self, storage):\n        self.sse_manager = SSEQueueWithTopic()\n        self.sse_manager.add_topic(\"inferences\")\n        self.sse_manager.add_topic(\"notifications\")\n\n        self.notification_manager = NotificationManager(self.sse_manager.get_topic(\"notifications\"))\n\n        self.inference_manager = InferenceManager(\n            self.sse_manager.get_topic(\"inferences\")\n        )\n        self.storage = storage\n        self.download_manager = DownloadManager(storage)\n\n    def get_storage(self):\n        return self.storage\n    \n    def get_sse_manager(self):\n        return self.sse_manager\n\n    def text_generation(self, inference_request: InferenceRequest):\n        provider = self.storage.get_provider(inference_request.model_provider)\n\n        provider_details = ProviderDetails(\n            api_key=provider.api_key ,\n            version_key=None\n        )\n        logger.info(f\"Received inference request {inference_request.model_provider}\")\n\n        if inference_request.model_provider == \"openai\":\n            return self.inference_manager.openai_text_generation(provider_details, inference_request)\n        elif inference_request.model_provider == \"cohere\":\n            return self.inference_manager.cohere_text_generation(provider_details, inference_request)\n        elif inference_request.model_provider == \"huggingface\":\n            return self.inference_manager.huggingface_text_generation(provider_details, inference_request)\n        elif inference_request.model_provider == \"forefront\":\n            return self.inference_manager.forefront_text_generation(provider_details, inference_request)\n        elif inference_request.model_provider == \"huggingface-local\":\n            return self.inference_manager.local_text_generation(provider_details, inference_request)\n        elif inference_request.model_provider == \"anthropic\":\n            return self.inference_manager.anthropic_text_generation(provider_details, inference_request)\n        elif inference_request.model_provider == \"aleph-alpha\":\n            return self.inference_manager.aleph_alpha_text_generation(provider_details, inference_request)\n        else:\n            raise Exception(\n                f\"Unknown model provider, {inference_request.model_provider}. Please add a generation function in InferenceManager or route in ModelManager.text_generation\"\n            )\n    \n    def get_announcer(self):\n        return self.inference_manager.get_announcer()\n\n@click.group()\ndef cli():\n    pass\n\n@click.command()\n@click.help_option('-h', '--help')\n@click.option('--host',  '-H', default='localhost', help='The host to bind to. Default: localhost.')\n@click.option('--port', '-p', default=5432, help='The port to bind to. Default: 5432.')\n@click.option('--debug/--no-debug', default=False, help='Enable or disable Flask debug mode. Default: False.')\n@click.option('--env', '-e', default=\".env\", help='Path to the environment file for storing and reading API keys. Default: .env.')\n@click.option('--models', '-m', default=None, help='Path to the configuration file for loading models. Default: None.')\n@click.option('--log-level', '-l', default='INFO', help='Set the logging level. Default: INFO.', type=click.Choice(['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']))\ndef run(host, port, debug, env, models, log_level):\n    \"\"\"\n    Run the OpenPlayground server.\n\n    This command starts the OpenPlayground server with the specified options.\n\n    Arguments:\n    --host, -H: The host to bind to. Default: localhost.\n    --port, -p: The port to bind to. Default: 5432.\n    --debug/--no-debug: Enable or disable Flask debug mode. Default: False.\n    --env, -e: Path to the environment file for storing and reading API keys. Default: .env.\n    --models, -m: Path to the configuration file for loading models. Default: None.\n    --log-level, -l: Set the logging level. Default: INFO. Choices: DEBUG, INFO, WARNING, ERROR, CRITICAL.\n\n    Example usage:\n\n    $ openplayground run --host=0.0.0.0 --port=8080 --debug --env=keys.env --models=models.json --log-level=DEBUG\n    \"\"\"\n    logging.basicConfig(level=getattr(logging, log_level.upper()))\n    storage = Storage(models, env)\n    app.config['GLOBAL_STATE'] = GlobalStateManager(storage)\n\n    app.run(host=host, port=port, debug=debug)\n\n@click.command()\n@click.help_option('-h', '--help')\n@click.option('--input', '-i', default=None, help='Path to the configuration file for importing models')\ndef import_config(input):\n    \"\"\"\n    Import configuration settings.\n\n    This command imports configuration settings for one or more models from a file.\n\n    Arguments:\n    --input, -i: Path to the configuration file for importing models. Default: None.\n\n    Example usage:\n\n    $ openplayground import-config --input=/path/to/config.json\n    \"\"\"\n    Storage.import_config(input)\n\n@click.command()\n@click.help_option('-h', '--help')\n@click.option('--output', '-o', default=None, help='Output file path for the exported configuration settings')\n@click.pass_context\ndef export_config(ctx, output):\n    \"\"\"\n    Export configuration settings.\n\n    This command exports the current configuration settings to a file.\n\n    Arguments:\n    --output, -o: Output file path for the exported configuration settings. Default: None.\n\n    Example usage:\n\n    $ openplayground export-config --output=/path/to/config.json\n    \"\"\"\n    Storage.export_config(output)\n\ncli.add_command(export_config)\ncli.add_command(import_config)\ncli.add_command(run)\n\nif __name__ == '__main__':\n    app.static_folder='../app/dist'\n    run()\nelse:\n    app.static_folder='./static'"
  },
  {
    "path": "server/lib/__init__.py",
    "content": ""
  },
  {
    "path": "server/lib/api/__init__.py",
    "content": "import logging\nimport json\n\nfrom ..entities import ProviderEncoder, ModelEncoder\nfrom ..sse import Message\nfrom .inference import inference_bp\nfrom .provider import provider_bp\nfrom flask import g, Blueprint, current_app, stream_with_context, Response\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\napi_bp = Blueprint('api', __name__, url_prefix='/api')\napi_bp.register_blueprint(provider_bp)\napi_bp.register_blueprint(inference_bp)\n\n@api_bp.after_request\ndef add_cors_header(response):\n    response.headers['Access-Control-Allow-Origin'] = '*'\n    return response\n\n@api_bp.before_app_request\ndef set_app_context():\n    g.app = current_app\n    \n@api_bp.route('/models', methods=['GET'])\ndef all_models():\n    '''\n    Returns a list of all models\n    '''\n    logger.info(\"Getting all models\")\n\n    return current_app.response_class(\n        response=json.dumps(\n            g.get('storage').get_models(), cls=ModelEncoder, indent=4, serialize_as_list=False\n        ),\n        status=200,\n        mimetype='application/json'\n    )\n\n@api_bp.route('/models-enabled-names', methods=['GET'])\ndef enabled_models_names():\n    '''\n    Returns a list of enabled models\n    '''\n    logger.info(\"Getting enabled models\")\n\n    return current_app.response_class(\n        response=json.dumps(g.get('storage').get_enabled_models_names()),\n        status=200,\n        mimetype='application/json'\n    )\n\n@api_bp.route('/models-enabled', methods=['GET'])\ndef enabled_models():\n    '''\n    Returns a list of enabled models\n    '''\n    logger.info(\"Getting enabled models\")\n    storage = g.get('storage')\n    models_list = storage.get_enabled_models()\n    models_dict = {f\"{model.provider}:{model.name}\": model for model in models_list}\n\n    return current_app.response_class(\n        response=json.dumps(models_dict, cls=ModelEncoder, indent=4, serialize_as_list=True),\n        status=200,\n        mimetype='application/json'\n    )\n\n@api_bp.route('/providers-list', methods=['GET'])\ndef providers():\n    '''\n    Returns a list of providers\n    '''\n    logger.info(\"Getting providers\")\n    storage = g.get('storage')\n  \n    return current_app.response_class(\n        response=json.dumps(storage.get_providers_names()),\n        status=200,\n        mimetype='application/json'\n    )\n\n@api_bp.route('/providers-with-key-and-models', methods=['GET'])\ndef providers_with_models():\n    '''\n    Returns a list of providers with their models and API keys\n    '''\n    logger.info(\"Getting providers with models\")\n    storage = g.get('storage')\n\n    providers_list = storage.get_providers()\n    providers_dict = {provider.name: provider for provider in providers_list}\n\n    return current_app.response_class(\n        response=json.dumps(\n            providers_dict, cls=ProviderEncoder, indent=4, serialize_models_as_list=True\n        ),\n        status=200,\n        mimetype='application/json'\n    )\n    \n@api_bp.route('/providers-check-keys', methods=['GET'])\ndef providers_check_api_keys():\n    '''\n    Checks all the API keys stored in the .env file - matches against the providers in models.json\n    Model must have \"api_key\" field set to true in models.json, otherwise \"None\" is returned\n    Keys are stored in .env in {PROVIDER}_API_KEY format\n    '''\n    logger.info(\"Checking API key store\")\n    storage = g.get('storage')\n\n    return current_app.response_class(\n        response=json.dumps(storage.get_providers_keys()),\n        status=200,\n        mimetype='application/json'\n    )\n\n@api_bp.route(\"/notifications\", methods=['GET'])\ndef notifications():\n    '''\n    Notifies the client when a model has been successfully downloaded\n    '''\n    logger.info(\"Received notification request\")\n\n    global_state = g.get('global_state')\n    request_uuid = \"1\"\n\n    @stream_with_context\n    def generator():\n        SSE_MANAGER = global_state.get_sse_manager()\n\n        messages = SSE_MANAGER.listen(\"notifications\")\n        try:\n            while True:\n                message = messages.get()\n                message = json.loads(message)\n                if message[\"type\"] == \"done\":\n                    logger.info(\"Done streaming SSE\")\n                    break\n                print(\"Sending message: \", message)\n                logger.info(f\"Yielding message: {message}\")\n                yield str(Message(**message))\n        except GeneratorExit:\n            logger.info(\"GeneratorExit\")\n\n    return Response(stream_with_context(generator()), mimetype='text/event-stream')"
  },
  {
    "path": "server/lib/api/inference.py",
    "content": "import logging\nimport json\nimport time\nimport threading\n\nfrom .response_utils import create_response_message\nfrom ..inference import InferenceRequest, InferenceResult, InferenceRequest\nfrom ..sse import Message\n\nfrom concurrent.futures import ThreadPoolExecutor\nfrom flask import g, request, Response, stream_with_context, Blueprint, current_app\nfrom typing import List, Tuple\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\ninference_bp = Blueprint('inference', __name__, url_prefix='/inference')\n\n@inference_bp.before_app_request\ndef set_app_context():\n    g.app = current_app\n\n@inference_bp.route(\"/text/stream\", methods=[\"POST\"])\ndef stream_inference():\n    data = request.get_json(force=True)\n    logger.info(f\"Path: {request.path}, Request: {data}\")\n\n    storage = g.get('storage')\n    global_state = g.get('global_state')\n\n    if not is_valid_request_data(data):\n        return create_response_message(\"Invalid request\", 400)\n\n    request_uuid = \"1\"\n    prompt = data['prompt']\n    models = data['models']\n    \n    all_tasks = [task for task in (create_inference_request(model, storage, prompt, request_uuid) for model in models) if task is not None]\n\n    if not all_tasks:\n        return create_response_message(\"Invalid Request\", 400)\n\n    thread = threading.Thread(target=bulk_completions, args=(global_state, all_tasks,))\n    thread.start()\n\n    return stream_response(global_state, request_uuid)\n\ndef is_valid_request_data(data):\n    return isinstance(data['prompt'], str) and isinstance(data['models'], list)\n\ndef create_inference_request(model, storage, prompt, request_uuid):\n    model_name, provider_name, model_tag, parameters = extract_model_data(model)\n    model_name = model_name.removeprefix(f\"{provider_name}:\")\n    provider = next((provider for provider in storage.get_providers() if provider.name == provider_name), None)\n    if provider is None or not provider.has_model(model_name):\n        return None\n    \n    if validate_parameters(provider.get_model(model_name), parameters):\n        return InferenceRequest(uuid=request_uuid, model_name=model_name, model_tag=model_tag,\n            model_provider=provider_name, model_parameters=parameters, prompt=prompt\n        )\n\n    return None\n\ndef extract_model_data(model):\n    return model['name'],  model['provider'], model['tag'], model['parameters']\n\ndef validate_parameters(model, parameters):\n    default_parameters = model.parameters\n    for parameter in parameters:\n        if parameter not in default_parameters:\n            return False\n\n        value = parameters[parameter]\n        parameter_range = default_parameters[parameter][\"range\"]\n\n        if len(parameter_range) == 2:\n            if value < parameter_range[0] or value > parameter_range[1]:\n                return False\n    return True\n\ndef stream_response(global_state, uuid):\n    @stream_with_context\n    def generator():\n        SSE_MANAGER = global_state.get_sse_manager()\n        messages = SSE_MANAGER.listen(\"inferences\")\n        try:\n            while True:\n                message = json.loads(message := messages.get())\n                if message[\"type\"] == \"done\":\n                    logger.info(\"Done streaming SSE\")\n                    break\n                logger.debug(f\"Yielding message: {json.dumps(message)}\")\n                yield str(Message(**message))\n        except GeneratorExit:\n            logger.info(\"GeneratorExit\")\n            SSE_MANAGER.publish(\"inferences\", message=json.dumps({\"uuid\": uuid}))\n\n    return Response(stream_with_context(generator()), mimetype='text/event-stream')\n\ndef bulk_completions(global_state, tasks: List[InferenceRequest]):\n    time.sleep(1)\n    local_tasks, remote_tasks = split_tasks_by_provider(tasks)\n\n    if remote_tasks:\n        with ThreadPoolExecutor(max_workers=len(remote_tasks)) as executor:\n            futures = [executor.submit(global_state.text_generation, task) for task in remote_tasks]\n            [future.result() for future in futures]\n\n    for task in local_tasks:\n        global_state.text_generation(task)\n\n    global_state.get_announcer().announce(InferenceResult(\n        uuid=tasks[0].uuid,\n        model_name=None,\n        model_tag=None,\n        model_provider=None,\n        token=None,\n        probability=None,\n        top_n_distribution=None\n    ), event=\"done\")\n\ndef split_tasks_by_provider(tasks: List[InferenceRequest]) -> Tuple[List[InferenceRequest], List[InferenceRequest]]:\n    local_tasks, remote_tasks = [], []\n\n    for task in tasks:\n        (local_tasks if task.model_provider == \"huggingface-local\" else remote_tasks).append(task)\n\n    return local_tasks, remote_tasks"
  },
  {
    "path": "server/lib/api/provider.py",
    "content": "import logging\nimport json\nimport requests\n\nfrom .response_utils import create_response_message\nfrom ..entities import Model, ModelEncoder\n\nfrom flask import g, request, jsonify, Blueprint, current_app\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\nprovider_bp = Blueprint('provider', __name__, url_prefix='/provider')\n\n@provider_bp.before_app_request\ndef set_app_context():\n    g.app = current_app\n    \n@provider_bp.before_request\ndef verify_provider():\n    storage = g.get('storage')\n    provider_name = request.view_args.get('provider_name')\n    model_name = request.view_args.get('model_name')\n\n    provider = g.provider = storage.get_provider(provider_name)\n\n    if provider is None:\n        return create_response_message(f\"Invalid provider: {provider_name}\", 400)\n    \n    if not provider.search_url and (model_name and not provider.has_model(model_name)):\n        return create_response_message(f\"Invalid model: {model_name}\", 400)\n    \n    g.model = provider.get_model(model_name)\n       \n@provider_bp.route('/<string:provider_name>/models')\ndef provider_models(provider_name):\n    '''\n    Route to get models for a given provider\n    '''\n    logger.info(f\"Getting models for provider {provider_name}\")\n\n    return current_app.response_class(\n        response=json.dumps(g.provider.models, cls=ModelEncoder),\n        status=200,\n        mimetype='application/json',\n        headers={'Access-Control-Allow-Origin': '*'}\n    )\n       \n@provider_bp.route('/<string:provider_name>/model/<string:model_name>')\ndef provider_model(provider_name, model_name):\n    '''\n    Route to get model information for a given provider\n    '''\n    logger.info(f\"Getting model {model_name} for provider {provider_name}\")\n\n    return current_app.response_class(\n        response=json.dumps(g.provider.get_model(model_name), cls=ModelEncoder),\n        status=200,\n        mimetype='application/json',\n        headers={'Access-Control-Allow-Origin': '*'}\n    )\n\n@provider_bp.route('/<string:provider_name>/model/<path:model_name>/toggle-status')\ndef provider_toggle_model(provider_name, model_name):\n    logger.info(f\"Enabling Provider Model {provider_name}  {model_name}\")\n    provider = g.provider\n    model = g.model\n\n    #if it made it this far then it has to a dynamic model (HF/OpenPlayground Hub)\n    if model is None:\n        model = Model(\n            name=model_name,\n            capabilities=provider.default_capabilities,\n            provider=provider_name,\n            status=\"ready\" if provider.remote_inference else \"pending\",\n            enabled=True,\n            parameters=provider.default_parameters\n        )\n        provider.add_model(model)\n    else:\n        model.enabled = not model.enabled\n        provider.update_model(model.name, model)\n    \n    return current_app.response_class(\n        response=json.dumps({\n            'status': 'success',\n            'enabled': model.enabled,\n            'model': model\n        }, cls=ModelEncoder),\n        status=200,\n        mimetype='application/json'\n    )\n\n@provider_bp.route('/<string:provider_name>/models/search')\ndef provider_models_search(provider_name):\n    logger.info(f\"Searching Provider Models {provider_name}\")\n    provider = g.provider\n\n    search_name = request.args.get('query', None)\n    if search_name is None:\n        return \"Missing query parameter\", 400\n    \n    #TODO make this provider agnostic\n    search_url = provider.search_url\n\n    if search_url is None:\n        return \"Search not supported for this provider\", 400\n    \n    search_url = search_url.replace('{searchQuery}', search_name)\n\n    response = requests.get(search_url)\n    content_json = response.json()\n    models = content_json.get('models', [])\n    models = list(map(lambda model: {'name': model['id']}, models))\n\n    return current_app.response_class(\n        response=json.dumps(models),\n        status=200,\n        mimetype='application/json'\n    )\n\n@provider_bp.route('/<string:provider_name>/api-key', methods=['PUT'])\ndef provider_update_api_key(provider_name):\n    '''\n    Routes to update the API key for a given provider\n    '''\n    logger.info(f\"Storing API key for {provider_name}\")\n\n    data = request.get_json(force=True)\n    storage = g.get('storage')\n\n    api_key = data['apiKey']\n    if api_key is None:\n        return create_response_message(\"Invalid API key\", 400)\n\n    storage.update_provider_api_key(provider_name, api_key)\n\n    response = jsonify({'status': 'success'})\n    response.headers.add('Access-Control-Allow-Origin', '*')\n    return response"
  },
  {
    "path": "server/lib/api/response_utils.py",
    "content": "from flask import Response, jsonify\n\ndef create_response_message(message: str, status_code: int) -> Response:\n    response = jsonify({'status': message})\n    response.status_code = status_code\n    response.headers.add('Access-Control-Allow-Origin', '*')\n    return response"
  },
  {
    "path": "server/lib/entities.py",
    "content": "import json\nfrom typing import List\nfrom .event_emitter import EventEmitter, EVENTS\n\nclass Model:\n    def __init__(\n        self, name: str, enabled: bool, capabilities: List[str],  provider: str, status: str, parameters: dict = None\n    ):\n        self.name = name\n        self.capabilities = capabilities\n        self.enabled = enabled\n        self.provider = provider\n        self.status = status\n        self.parameters = parameters\n\n    def copy(self):\n        return Model(\n            name=self.name,\n            capabilities=self.capabilities,\n            enabled=self.enabled,\n            provider=self.provider,\n            status=self.status,\n            parameters=self.parameters.copy()\n        )\n\n    def __repr__(self):\n        return f'Model({self.name}, {self.capabilities}, {self.enabled}, {self.provider}, {self.status}, {self.parameters})'\n\nclass ModelEncoder(json.JSONEncoder):\n    def __init__(self, *args, serialize_as_list=True, **kwargs):\n        self.serialize_as_list = serialize_as_list\n        super().__init__(*args, **kwargs)\n\n    def default(self, obj):\n        if isinstance(obj, Model):\n            properties = {\n                \"capabilities\": obj.capabilities,\n                \"enabled\": obj.enabled, \"status\": obj.status, \"parameters\": obj.parameters\n            }\n            if self.serialize_as_list:\n                return {**{\"name\": obj.name, \"provider\": obj.provider}, **properties}\n            else:\n                return {f\"{obj.provider}:{obj.name}\": properties}\n        return super().default(obj)\n\nclass Provider:\n    def __init__(\n        self, name: str, models: List[Model], remote_inference: bool = False,\n        default_capabilities: List[str] = None, default_parameters: dict = None,\n        api_key: str = None, requires_api_key: bool = False,\n        search_url: str = None\n    ):\n        self.event_emitter = EventEmitter()\n        self.name = name\n        self.models = models\n        self.remote_inference = remote_inference\n        self.default_capabilities = default_capabilities\n        self.default_parameters = default_parameters\n        self.api_key = api_key\n        self.requires_api_key = requires_api_key\n        self.search_url = search_url\n    \n    def has_model(self, model_name: str) -> bool:\n        return any(model.name == model_name for model in self.models)\n    \n    def get_model(self, model_name: str) -> Model:\n        for model in self.models:\n            if model.name == model_name:\n                return model\n            \n    def update_model(self, model_name: str, model: Model) -> None:\n        for i, m in enumerate(self.models):\n            if m.name == model_name:\n                self.models[i] = model\n                self.event_emitter.emit(EVENTS.MODEL_UPDATED, model)\n                return\n    \n    def add_model(self, model: Model) -> None:\n        self.models.append(model)\n        print(\"Added model!\")\n        self.event_emitter.emit(EVENTS.MODEL_ADDED, model)\n\n    def remove_model(self, model_name: str) -> None:\n        for model in self.models:\n            if model.name == model_name:\n                self.models.remove(model)\n                self.event_emitter.emit(EVENTS.MODEL_REMOVED, model)\n                return\n            \n    def copy(self):\n        return Provider(\n            name=self.name,\n            models=[model.copy() for model in self.models],\n            remote_inference=self.remote_inference,\n            default_capabilities=self.default_capabilities.copy() if self.default_capabilities else None,\n            default_parameters=self.default_parameters.copy() if self.default_parameters else None,\n            api_key=self.api_key,\n            requires_api_key=self.requires_api_key,\n            search_url=self.search_url\n        )\n    \n    def __repr__(self):\n        return f'Provider({self.name}, {self.models}, {self.remote_inference}, {self.default_parameters}, {self.api_key}, {self.requires_api_key}, {self.search_url})'\n\nclass ProviderEncoder(json.JSONEncoder):\n    def __init__(self, *args, serialize_models_as_list=True, **kwargs):\n        self.serialize_models_as_list = serialize_models_as_list\n        super().__init__(*args, **kwargs)\n\n    def default(self, obj):\n        if isinstance(obj, Provider):\n            models = [{\n                \"name\": model.name, \"capabilities\": model.capabilities,\n                \"enabled\": model.enabled, \"provider\": model.provider,\n                \"status\": model.status, \"parameters\": model.parameters\n            } for model in obj.models]\n            \n            if not self.serialize_models_as_list:\n                models = dict(zip([model[\"name\"] for model in models], models))\n        \n            return {self.to_camel_case(k): v for k, v in obj.__dict__.items() if k not in {'models', 'event_emitter'}} | {'models': models}\n        return super().default(obj)\n    \n    @staticmethod\n    def to_camel_case(snake_str):\n        components = snake_str.split('_')\n        return components[0] + ''.join(x.capitalize() for x in components[1:])"
  },
  {
    "path": "server/lib/event_emitter.py",
    "content": "import threading\nfrom enum import Enum\n\nclass EVENTS(Enum):\n    MODEL_ADDED = 'update_model_added'\n    MODEL_REMOVED = 'update_model_removed'\n    MODEL_STATUS_UPDATE = 'update_model_status'\n    MODEL_UPDATED = 'update_model'\n    MODEL_DOWNLOAD_UPDATE = 'update_model_download'\n    PROVIDER_API_KEY_UPDATE = 'update_provider_api_key'\n    SAVED_TO_DISK = 'saved_to_disk'\n\nclass Singleton(type):\n    _instances = {}\n    _lock = threading.Lock()\n\n    def __call__(cls, *args, **kwargs):\n        with cls._lock:\n            if cls not in cls._instances:\n                cls._instances[cls] = super().__call__(*args, **kwargs)\n        return cls._instances[cls]\n\nclass EventEmitter(metaclass=Singleton):\n    def __init__(self):\n        self.listeners = {}\n        self._lock = threading.Lock()\n\n    def on(self, event: EVENTS, listener):\n        event = event.value\n        with self._lock:\n            if event not in self.listeners:\n                self.listeners[event] = []\n            self.listeners[event].append(listener)\n\n    def off(self, event, listener):\n        with self._lock:\n            if event in self.listeners and listener in self.listeners[event]:\n                self.listeners[event].remove(listener)\n\n    def emit(self, event: EVENTS, *args, **kwargs):\n        if event not in EVENTS.__members__.values():\n            raise ValueError(f\"Invalid event type: {event}\")\n        if event.value not in self.listeners:\n            return\n\n        with self._lock:\n            listeners_to_notify = self.listeners[event.value].copy()\n\n        for listener in listeners_to_notify:\n            listener(event, *args, **kwargs)\n"
  },
  {
    "path": "server/lib/inference/__init__.py",
    "content": "import anthropic\nimport cachetools\nimport math\nimport openai\nimport os\nimport json\nimport requests\nimport sseclient\nimport urllib\nimport traceback\nimport logging\n\nfrom aleph_alpha_client import Client as aleph_client, CompletionRequest, Prompt\nfrom datetime import datetime\nfrom dataclasses import dataclass\nfrom typing import Callable, Union\nfrom .huggingface.hf import HFInference\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\n@dataclass\nclass ProviderDetails:\n    '''\n    Args:\n        api_key (str): API key for provider\n        version_key (str): version key for provider\n    '''\n    api_key: str\n    version_key: str\n\n@dataclass\nclass InferenceRequest:\n    '''\n    Args:\n        uuid (str): unique identifier for inference request\n        model_name (str): name of model to use\n        model_tag (str): tag of model to use\n        model_provider (str): provider of model to use\n        model_parameters (dict): parameters for model\n        prompt (str): prompt to use for inference\n    '''\n    uuid: str\n    model_name: str\n    model_tag: str\n    model_provider: str\n    model_parameters: dict\n    prompt: str\n\n@dataclass\nclass ProablityDistribution:\n    '''\n    Args:\n        log_prob_sum (float): sum of log probabilities\n        simple_prob_sum (float): sum of simple probabilities\n        tokens (dict): dictionary of tokens and their probabilities\n    '''\n    log_prob_sum: float\n    simple_prob_sum: float\n    tokens: dict\n\n@dataclass\nclass InferenceResult:\n    '''\n    Args:\n        uuid (str): unique identifier for inference request\n        model_name (str): name of model to use\n        model_tag (str): tag of model to use\n        model_provider (str): provider of model to use\n        token (str): token returned by inference\n        probability (float): probability of token\n        top_n_distribution (ProablityDistribution): top n distribution of tokens\n    '''\n    uuid: str\n    model_name: str\n    model_tag: str\n    model_provider: str\n    token: str\n    probability: Union[float, None]\n    top_n_distribution: Union[ProablityDistribution, None]\n\nInferenceFunction = Callable[[str, InferenceRequest], None]\n\nclass InferenceAnnouncer:\n    def __init__(self, sse_topic):\n        self.sse_topic = sse_topic\n        self.cancel_cache = cachetools.TTLCache(maxsize=1000, ttl=60)\n\n    def __format_message__(self, event: str, infer_result: InferenceResult) -> str:\n        logger.debug(\"formatting message\")\n        encoded = {\n            \"message\": infer_result.token,\n            \"modelName\": infer_result.model_name,\n            \"modelTag\": infer_result.model_tag,\n            \"modelProvider\": infer_result.model_provider,\n        }\n\n        if infer_result.probability is not None:\n            encoded[\"prob\"] = round(math.exp(infer_result.probability) * 100, 2) \n\n        if infer_result.top_n_distribution is not None:\n            encoded[\"topNDistribution\"] = {\n                \"logProbSum\": infer_result.top_n_distribution.log_prob_sum,\n                \"simpleProbSum\": infer_result.top_n_distribution.simple_prob_sum,\n                \"tokens\": infer_result.top_n_distribution.tokens\n            }\n\n        return json.dumps({\"data\": encoded, \"type\": event})\n    \n    def announce(self, infer_result: InferenceResult, event: str):\n        if infer_result.uuid in self.cancel_cache:\n            return False\n\n        message = None\n        if event == \"done\":\n            message = json.dumps({\"data\": {}, \"type\": \"done\"})\n        else:\n            message = self.__format_message__(event=event, infer_result=infer_result)\n\n        logger.debug(f\"Announcing {event} for uuid: {infer_result.uuid}, message: {message}\")\n        self.sse_topic.publish(message)\n\n        return True\n\n    def cancel_callback(self, message):\n        if message['type'] == 'pmessage':\n            data = json.loads(message['data'])\n            uuid = data['uuid']\n            logger.info(f\"Received cancel message for uuid: {uuid}\")\n            self.cancel_cache[uuid] = True      \n   \nclass InferenceManager:\n    def __init__(self, sse_topic):\n        self.announcer = InferenceAnnouncer(sse_topic)\n\n    def __error_handler__(self, inference_fn: InferenceFunction, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        logger.info(f\"Requesting inference from {inference_request.model_name} on {inference_request.model_provider}\")\n        infer_result = InferenceResult(\n            uuid=inference_request.uuid,\n            model_name=inference_request.model_name,\n            model_tag=inference_request.model_tag,\n            model_provider=inference_request.model_provider,\n            token=None,\n            probability=None,\n            top_n_distribution=None\n        )\n\n        if not self.announcer.announce(InferenceResult(\n            uuid=inference_request.uuid,\n            model_name=inference_request.model_name,\n            model_tag=inference_request.model_tag,\n            model_provider=inference_request.model_provider,\n            token=\"[INITIALIZING]\",\n            probability=None,\n            top_n_distribution=None\n        ), event=\"status\"):\n            return\n\n        try:\n            inference_fn(provider_details, inference_request)\n        except openai.error.Timeout as e:\n            infer_result.token = f\"[ERROR] OpenAI API request timed out: {e}\"\n            logger.error(f\"OpenAI API request timed out: {e}\")\n        except openai.error.APIError as e:\n            infer_result.token = f\"[ERROR] OpenAI API returned an API Error: {e}\"\n            logger.error(f\"OpenAI API returned an API Error: {e}\")\n        except openai.error.APIConnectionError as e:\n            infer_result.token = f\"[ERROR] OpenAI API request failed to connect: {e}\"\n            logger.error(f\"OpenAI API request failed to connect: {e}\")\n        except openai.error.InvalidRequestError as e:\n            infer_result.token = f\"[ERROR] OpenAI API request was invalid: {e}\"\n            logger.error(f\"OpenAI API request was invalid: {e}\")\n        except openai.error.AuthenticationError as e:\n            infer_result.token = f\"[ERROR] OpenAI API request was not authorized: {e}\"\n            logger.error(f\"OpenAI API request was not authorized: {e}\")\n        except openai.error.PermissionError as e:\n            infer_result.token = f\"[ERROR] OpenAI API request was not permitted: {e}\"\n            logger.error(f\"OpenAI API request was not permitted: {e}\")\n        except openai.error.RateLimitError as e:\n            infer_result.token = f\"[ERROR] OpenAI API request exceeded rate limit: {e}\"\n            logger.error(f\"OpenAI API request exceeded rate limit: {e}\")\n        except requests.exceptions.RequestException as e:\n            logging.error(f\"RequestException: {e}\")\n            infer_result.token = f\"[ERROR] No response from {infer_result.model_provider } after sixty seconds\"\n        except ValueError as e:\n            if infer_result.model_provider == \"huggingface-local\":\n                infer_result.token = f\"[ERROR] Error parsing response from local inference: {traceback.format_exc()}\"\n                logger.error(f\"Error parsing response from local inference: {traceback.format_exc()}\")\n            else:\n                infer_result.token = f\"[ERROR] Error parsing response from API: {e}\"\n                logger.error(f\"Error parsing response from API: {e}\")\n        except Exception as e:\n            infer_result.token = f\"[ERROR] {e}\"\n            logger.error(f\"Error: {e}\")\n        finally:\n            if infer_result.token is None:\n                infer_result.token = \"[COMPLETED]\"\n            self.announcer.announce(infer_result, event=\"status\")\n            logger.info(f\"Completed inference for {inference_request.model_name} on {inference_request.model_provider}\")\n    \n    def __openai_chat_generation__(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        openai.api_key = provider_details.api_key\n\n        current_date = datetime.now().strftime(\"%Y-%m-%d\")\n\n        if inference_request.model_name == \"gpt-4\":\n            system_content = \"You are GPT-4, a large language model trained by OpenAI. Answer as concisely as possible\"\n        else:\n            system_content = f\"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible. Knowledge cutoff: 2021-09-01 Current date: {current_date}\"\n\n        response = openai.ChatCompletion.create(\n             model=inference_request.model_name,\n             messages = [\n                {\"role\": \"system\", \"content\": system_content},\n                {\"role\": \"user\", \"content\": inference_request.prompt},\n            ],\n            temperature=inference_request.model_parameters['temperature'],\n            max_tokens=inference_request.model_parameters['maximumLength'],\n            top_p=inference_request.model_parameters['topP'],\n            frequency_penalty=inference_request.model_parameters['frequencyPenalty'],\n            presence_penalty=inference_request.model_parameters['presencePenalty'],\n            stream=True,\n            timeout=60\n        )\n\n        tokens = \"\"\n        cancelled = False\n\n        for event in response:\n            response = event['choices'][0]\n            if response['finish_reason'] == \"stop\":\n                break\n\n            delta = response['delta']\n\n            if \"content\" not in delta:\n                continue\n\n            generated_token = delta[\"content\"]\n            tokens += generated_token\n\n            infer_response = InferenceResult(\n                uuid=inference_request.uuid,\n                model_name=inference_request.model_name,\n                model_tag=inference_request.model_tag,\n                model_provider=inference_request.model_provider,\n                token=generated_token,\n                probability=None,\n                top_n_distribution=None\n             )\n\n            if cancelled: continue\n\n            if not self.announcer.announce(infer_response, event=\"infer\"):\n                cancelled = True\n                logger.info(f\"Cancelled inference for {inference_request.uuid} - {inference_request.model_name}\")\n\n    def __openai_text_generation__(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        openai.api_key = provider_details.api_key\n\n        response = openai.Completion.create(\n            model=inference_request.model_name,\n            prompt=inference_request.prompt,\n            temperature=inference_request.model_parameters['temperature'],\n            max_tokens=inference_request.model_parameters['maximumLength'],\n            top_p=inference_request.model_parameters['topP'],\n            stop=None if len(inference_request.model_parameters['stopSequences']) == 0 else inference_request.model_parameters['stopSequences'],\n            frequency_penalty=inference_request.model_parameters['frequencyPenalty'],\n            presence_penalty=inference_request.model_parameters['presencePenalty'],\n            logprobs=5,\n            stream=True\n        )\n        cancelled = False\n\n        for event in response:\n            generated_token = event['choices'][0]['text']\n            infer_response = None\n            try:\n                chosen_log_prob = 0\n                likelihood = event['choices'][0][\"logprobs\"]['top_logprobs'][0]\n\n                prob_dist = ProablityDistribution(\n                    log_prob_sum=0, simple_prob_sum=0, tokens={},\n                )\n\n                for token, log_prob in likelihood.items():\n                    simple_prob = round(math.exp(log_prob) * 100, 2)\n                    prob_dist.tokens[token] = [log_prob, simple_prob]\n\n                    if token == generated_token:\n                        chosen_log_prob = round(log_prob, 2)\n  \n                    prob_dist.simple_prob_sum += simple_prob\n                \n                prob_dist.tokens = dict(\n                    sorted(prob_dist.tokens.items(), key=lambda item: item[1][0], reverse=True)\n                )\n                prob_dist.log_prob_sum = chosen_log_prob\n                prob_dist.simple_prob_sum = round(prob_dist.simple_prob_sum, 2)\n             \n                infer_response = InferenceResult(\n                    uuid=inference_request.uuid,\n                    model_name=inference_request.model_name,\n                    model_tag=inference_request.model_tag,\n                    model_provider=inference_request.model_provider,\n                    token=generated_token,\n                    probability=event['choices'][0]['logprobs']['token_logprobs'][0],\n                    top_n_distribution=prob_dist\n                )\n            except IndexError:\n                infer_response = InferenceResult(\n                    uuid=inference_request.uuid,\n                    model_name=inference_request.model_name,\n                    model_tag=inference_request.model_tag,\n                    model_provider=inference_request.model_provider,\n                    token=generated_token,\n                    probability=-1,\n                    top_n_distribution=None\n                )\n\n            if cancelled: continue\n\n            if not self.announcer.announce(infer_response, event=\"infer\"):\n                cancelled = True\n                logger.info(f\"Cancelled inference for {inference_request.uuid} - {inference_request.model_name}\")\n\n    def openai_text_generation(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        # TODO: Add a meta field to the inference so we know when a model is chat vs text\n        if inference_request.model_name in [\"gpt-3.5-turbo\", \"gpt-4\"]:\n            self.__error_handler__(self.__openai_chat_generation__, provider_details, inference_request)\n        else:\n            self.__error_handler__(self.__openai_text_generation__, provider_details, inference_request)\n\n    def __cohere_text_generation__(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        with requests.post(\"https://api.cohere.ai/generate\",\n            headers={\n                \"Authorization\": f\"Bearer {provider_details.api_key}\",\n                \"Content-Type\": \"application/json\",\n                \"Cohere-Version\": \"2021-11-08\",\n            },\n            data=json.dumps({\n                \"prompt\": inference_request.prompt,\n                \"model\": inference_request.model_name,\n                \"temperature\": float(inference_request.model_parameters['temperature']),\n                \"p\": float(inference_request.model_parameters['topP']),\n                \"k\": int(inference_request.model_parameters['topK']),\n                \"stopSequences\": inference_request.model_parameters['stopSequences'],\n                \"frequencyPenalty\": float(inference_request.model_parameters['frequencyPenalty']),\n                \"presencePenalty\": float(inference_request.model_parameters['presencePenalty']),\n                \"return_likelihoods\": \"GENERATION\",\n                \"max_tokens\": int(inference_request.model_parameters['maximumLength']),\n                \"stream\": True,\n            }),\n            stream=True\n        ) as response:\n            if response.status_code != 200:\n                raise Exception(f\"Request failed: {response.status_code} {response.reason}\")\n\n            cancelled = False\n\n            for token in response.iter_lines():\n                token = token.decode('utf-8')\n                token_json = json.loads(token)\n                if cancelled: continue\n\n                if not self.announcer.announce(InferenceResult(\n                    uuid=inference_request.uuid,\n                    model_name=inference_request.model_name,\n                    model_tag=inference_request.model_tag,\n                    model_provider=inference_request.model_provider,\n                    token=token_json['text'],\n                    probability=None, #token_json['likelihood']\n                    top_n_distribution=None\n                ), event=\"infer\"):\n                    cancelled = True\n                    logger.info(f\"Cancelled inference for {inference_request.uuid} - {inference_request.model_name}\")\n\n    def cohere_text_generation(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        self.__error_handler__(self.__cohere_text_generation__, provider_details, inference_request)\n    \n    def __huggingface_text_generation__(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        response = requests.request(\"POST\",\n            f\"https://api-inference.huggingface.co/models/{inference_request.model_name}\",\n            headers={\"Authorization\": f\"Bearer {provider_details.api_key}\"},\n            json={\n                \"inputs\": inference_request.prompt,\n                \"stream\": True,\n                \"parameters\": {\n                    \"max_length\": min(inference_request.model_parameters['maximumLength'], 250), # max out at 250 tokens per request, we should handle for this in client side but just in case\n                    \"temperature\": inference_request.model_parameters['temperature'],\n                    \"top_k\": inference_request.model_parameters['topK'],\n                    \"top_p\": inference_request.model_parameters['topP'],\n                    \"repetition_penalty\": inference_request.model_parameters['repetitionPenalty'],\n                    \"stop_sequences\": inference_request.model_parameters['stopSequences'],\n                },\n                \"options\": {\n                    \"use_cache\": False\n                }\n            },\n            timeout=60\n        )\n\n        content_type = response.headers[\"content-type\"]\n\n        cancelled = False\n\n        if response.status_code != 200:\n            raise Exception(f\"Request failed: {response.status_code} {response.reason}\")\n\n        if content_type == \"application/json\":\n            return_data = json.loads(response.content.decode(\"utf-8\"))\n            outputs = return_data[0][\"generated_text\"]\n            outputs = outputs.removeprefix(inference_request.prompt)\n\n            self.announcer.announce(InferenceResult(\n                uuid=inference_request.uuid,\n                model_name=inference_request.model_name,\n                model_tag=inference_request.model_tag,\n                model_provider=inference_request.model_provider,\n                token=outputs,\n                probability=None,\n                top_n_distribution=None\n            ), event=\"infer\")\n        else:\n            total_tokens = 0\n            for response in response.iter_lines():\n                response = response.decode('utf-8')\n                if response == \"\":\n                    continue\n\n                response_json = json.loads(response[5:])\n                if \"error\" in response:\n                    error = response_json[\"error\"]\n                    raise Exception(f\"{error}\")\n\n                token = response_json['token']\n\n                total_tokens += 1\n\n                if token[\"special\"]:\n                    continue\n\n                if cancelled: continue\n\n                if not self.announcer.announce(\n                    InferenceResult(\n                        uuid=inference_request.uuid,\n                        model_name=inference_request.model_name,\n                        model_tag=inference_request.model_tag,\n                        model_provider=inference_request.model_provider,\n                        token=\" \" if token['id'] == 3 else token['text'],\n                        probability=token['logprob'],\n                        top_n_distribution=None,\n                    ),\n                    event=\"infer\",\n                ):\n                    cancelled = True\n                    logger.info(f\"Cancelled inference for {inference_request.uuid} - {inference_request.model_name}\")\n           \n    def huggingface_text_generation(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        self.__error_handler__(self.__huggingface_text_generation__, provider_details, inference_request)\n\n    def __forefront_text_generation__(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        with requests.post(\n                f\"https://shared-api.forefront.link/organization/gPn2ZLSO3mTh/{inference_request.model_name}/completions/{provider_details.version_key}\",\n                headers={\n                    \"Authorization\": f\"Bearer {provider_details.api_key}\",\n                    \"Content-Type\": \"application/json\",\n                },\n                data=json.dumps({\n                    \"text\": inference_request.prompt,\n                    \"top_p\": float(inference_request.model_parameters['topP']),\n                    \"top_k\": int(inference_request.model_parameters['topK']),\n                    \"temperature\":  float(inference_request.model_parameters['temperature']),\n                    \"repetition_penalty\":  float(inference_request.model_parameters['repetitionPenalty']),\n                    \"length\": int(inference_request.model_parameters['maximumLength']),\n                    \"stop\": inference_request.model_parameters['stopSequences'],\n                    \"logprobs\": 5,\n                    \"stream\": True,\n                }),\n                stream=True\n            ) as response:\n            if response.status_code != 200:\n                raise Exception(f\"Request failed: {response.status_code} {response.reason}\")\n            cancelled = False\n            total_tokens = 0\n            aggregate_string_length = 0\n\n            for packet in sseclient.SSEClient(response).events():\n                generated_token = None\n                probability = None\n                prob_dist = None\n\n                if packet.event == \"update\":\n                    packet.data = urllib.parse.unquote(packet.data)\n                    generated_token = packet.data[aggregate_string_length:]\n                    aggregate_string_length = len(packet.data)\n\n                    if not self.announcer.announce(InferenceResult(\n                        uuid=inference_request.uuid,\n                        model_name=inference_request.model_name,\n                        model_tag=inference_request.model_tag,\n                        model_provider=inference_request.model_provider,\n                        token=generated_token,\n                        probability=probability,\n                        top_n_distribution=prob_dist\n                    ), event=\"infer\"):\n                        cancelled = True\n                        logger.info(f\"Cancelled inference for {inference_request.uuid} - {inference_request.model_name}\")\n                elif packet.event == \"message\":\n                    data = json.loads(packet.data)\n\n                    logprobs = data[\"logprobs\"][0]\n                    tokens = logprobs[\"tokens\"]\n                    token_logprobs = logprobs[\"token_logprobs\"]\n\n                    new_tokens = tokens[total_tokens:]\n\n                    for index, new_token in enumerate(new_tokens):\n                        generated_token = new_token\n\n                        probability = token_logprobs[total_tokens + index]\n                        top_logprobs = logprobs[\"top_logprobs\"][total_tokens + index]\n\n                        chosen_log_prob = 0\n                        prob_dist = ProablityDistribution(\n                            log_prob_sum=0, simple_prob_sum=0, tokens={},\n                        )\n\n                        for token, log_prob in top_logprobs.items():\n                            if log_prob == -3000.0: continue\n                            simple_prob = round(math.exp(log_prob) * 100, 2)\n                            prob_dist.tokens[token] = [log_prob, simple_prob]\n\n                            if token == generated_token:\n                                chosen_log_prob = round(log_prob, 2)\n\n                            prob_dist.simple_prob_sum += simple_prob\n\n                        prob_dist.tokens = dict(\n                            sorted(prob_dist.tokens.items(), key=lambda item: item[1][0], reverse=True)\n                        )\n                        prob_dist.log_prob_sum = chosen_log_prob\n                        prob_dist.simple_prob_sum = round(prob_dist.simple_prob_sum, 2)\n\n                        if not self.announcer.announce(InferenceResult(\n                            uuid=inference_request.uuid,\n                            model_name=inference_request.model_name,\n                            model_tag=inference_request.model_tag,\n                            model_provider=inference_request.model_provider,\n                            token=generated_token,\n                            probability=probability,\n                            top_n_distribution=prob_dist\n                        ), event=\"infer\"):\n                            cancelled = True\n                            logger.info(f\"Cancelled inference for {inference_request.uuid} - {inference_request.model_name}\")\n\n                    total_tokens = len(tokens)\n                elif packet.event == \"end\":\n                    break\n                else:\n                    continue\n\n                if cancelled: continue\n\n    def forefront_text_generation(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        self.__error_handler__(self.__forefront_text_generation__, provider_details, inference_request)\n\n    def __local_text_generation__(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        cancelled = False\n        logger.info(f\"Starting inference for {inference_request.uuid} - {inference_request.model_name}\")\n\n        hf = HFInference(inference_request.model_name)\n        output = hf.generate(\n            prompt=inference_request.prompt,\n            max_length=int(inference_request.model_parameters['maximumLength']),\n            top_p=float(inference_request.model_parameters['topP']),\n            top_k=int(inference_request.model_parameters['topK']),\n            temperature=float(inference_request.model_parameters['temperature']),\n            repetition_penalty=float(inference_request.model_parameters['repetitionPenalty']),\n            stop_sequences=None,\n        )\n\n        infer_response = None\n        for generated_token in output:\n            if cancelled: break\n            infer_response = InferenceResult(\n                uuid=inference_request.uuid,\n                model_name=inference_request.model_name,\n                model_tag=inference_request.model_tag,\n                model_provider=inference_request.model_provider,\n                token=generated_token,\n                probability=None,\n                top_n_distribution=None\n            )\n        \n            if not self.announcer.announce(infer_response, event=\"infer\"):\n                cancelled = True\n                logger.info(f\"Cancelled inference for {inference_request.uuid} - {inference_request.model_name}\")\n\n    def local_text_generation(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n       self.__error_handler__(self.__local_text_generation__, provider_details, inference_request)\n    \n    def __anthropic_text_generation__(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        c = anthropic.Client(provider_details.api_key)\n\n        response = c.completion_stream(\n            prompt=f\"{anthropic.HUMAN_PROMPT} {inference_request.prompt}{anthropic.AI_PROMPT}\",\n            stop_sequences=[anthropic.HUMAN_PROMPT] + inference_request.model_parameters['stopSequences'],\n            temperature=float(inference_request.model_parameters['temperature']),\n            top_p=float(inference_request.model_parameters['topP']),\n            max_tokens_to_sample=inference_request.model_parameters['maximumLength'],\n            model=inference_request.model_name,\n            stream=True,\n        )\n\n        completion = \"\"\n        cancelled = False\n\n        for data in response:\n            new_completion = data[\"completion\"]\n            generated_token = new_completion[len(completion):]\n            if cancelled: continue\n\n            if not self.announcer.announce(InferenceResult(\n                uuid=inference_request.uuid,\n                model_name=inference_request.model_name,\n                model_tag=inference_request.model_tag,\n                model_provider=inference_request.model_provider,\n                token=generated_token,\n                probability=None,\n                top_n_distribution=None\n             ), event=\"infer\"):\n                cancelled = True\n                logger.info(f\"Cancelled inference for {inference_request.uuid} - {inference_request.model_name}\")\n\n            completion = new_completion\n\n    def anthropic_text_generation(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        self.__error_handler__(self.__anthropic_text_generation__, provider_details, inference_request)\n    \n    def __aleph_alpha_text_generation__(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        client = aleph_client(provider_details.api_key)\n        \n        request = CompletionRequest(\n            prompt = Prompt.from_text(inference_request.prompt),\n            temperature= inference_request.model_parameters['temperature'],\n            maximum_tokens=inference_request.model_parameters['maximumLength'],\n            top_p=float(inference_request.model_parameters['topP']),\n            top_k=int(inference_request.model_parameters['topK']),\n            presence_penalty=float(inference_request.model_parameters['repetitionPenalty']),\n            stop_sequences=inference_request.model_parameters['stopSequences']\n        )\n        \n        response = client.complete(request, model=inference_request.model_name)\n        \n        self.announcer.announce(InferenceResult(\n            uuid=inference_request.uuid,\n            model_name=inference_request.model_name,\n            model_tag=inference_request.model_tag,\n            model_provider=inference_request.model_provider,\n            token=response.completions[0].completion,\n            probability=None,\n            top_n_distribution=None\n        ), event=\"infer\")\n\n    def aleph_alpha_text_generation(self, provider_details: ProviderDetails, inference_request: InferenceRequest):\n        self.__error_handler__(self.__aleph_alpha_text_generation__, provider_details, inference_request)\n    \n    def get_announcer(self):\n        return self.announcer "
  },
  {
    "path": "server/lib/inference/huggingface/__init__.py",
    "content": ""
  },
  {
    "path": "server/lib/inference/huggingface/generator.py",
    "content": "from typing import Optional, Union, List, Dict, Tuple\nimport warnings\nimport torch\nimport torch.distributed as dist\nimport transformers\nfrom transformers.generation.stopping_criteria import (\n    StoppingCriteriaList,\n    validate_stopping_criteria,\n)\nfrom transformers.generation.logits_process import LogitsProcessorList\nfrom transformers.generation.utils import GreedySearchDecoderOnlyOutput, GreedySearchEncoderDecoderOutput\n\nGreedySearchOutput = Union[GreedySearchEncoderDecoderOutput, GreedySearchDecoderOnlyOutput]\n\ndef greedy_search_generator(\n    self,\n    input_ids: torch.LongTensor,\n    logits_processor: Optional[LogitsProcessorList] = None,\n    stopping_criteria: Optional[StoppingCriteriaList] = None,\n    max_length: Optional[int] = None,\n    pad_token_id: Optional[int] = None,\n    eos_token_id: Optional[Union[int, List[int]]] = None,\n    output_attentions: Optional[bool] = None,\n    output_hidden_states: Optional[bool] = None,\n    output_scores: Optional[bool] = None,\n    return_dict_in_generate: Optional[bool] = None,\n    synced_gpus: Optional[bool] = False,\n    **model_kwargs,\n) -> Union[GreedySearchOutput, torch.LongTensor]:\n    \"\"\"\n    Generates sequences for models with a language modeling head using greedy decoding.\n    Monkey patched function to create a generator for next_token - allows for token streaming\n    \"\"\"\n    # init values\n    logits_processor = logits_processor if logits_processor is not None else LogitsProcessorList()\n    stopping_criteria = stopping_criteria if stopping_criteria is not None else StoppingCriteriaList()\n    if max_length is not None:\n        warnings.warn(\n            \"`max_length` is deprecated in this function, use\"\n            \" `stopping_criteria=StoppingCriteriaList([MaxLengthCriteria(max_length=max_length)])` instead.\",\n            UserWarning,\n        )\n        stopping_criteria = validate_stopping_criteria(stopping_criteria, max_length)\n    pad_token_id = pad_token_id if pad_token_id is not None else self.generation_config.pad_token_id\n    eos_token_id = eos_token_id if eos_token_id is not None else self.generation_config.eos_token_id\n    if isinstance(eos_token_id, int):\n        eos_token_id = [eos_token_id]\n    output_scores = output_scores if output_scores is not None else self.generation_config.output_scores\n    output_attentions = (\n        output_attentions if output_attentions is not None else self.generation_config.output_attentions\n    )\n    output_hidden_states = (\n        output_hidden_states if output_hidden_states is not None else self.generation_config.output_hidden_states\n    )\n    return_dict_in_generate = (\n        return_dict_in_generate\n        if return_dict_in_generate is not None\n        else self.generation_config.return_dict_in_generate\n    )\n\n    # init attention / hidden states / scores tuples\n    scores = () if (return_dict_in_generate and output_scores) else None\n    decoder_attentions = () if (return_dict_in_generate and output_attentions) else None\n    cross_attentions = () if (return_dict_in_generate and output_attentions) else None\n    decoder_hidden_states = () if (return_dict_in_generate and output_hidden_states) else None\n\n    # if model is an encoder-decoder, retrieve encoder attention weights and hidden states\n    if return_dict_in_generate and self.config.is_encoder_decoder:\n        encoder_attentions = model_kwargs[\"encoder_outputs\"].get(\"attentions\") if output_attentions else None\n        encoder_hidden_states = (\n            model_kwargs[\"encoder_outputs\"].get(\"hidden_states\") if output_hidden_states else None\n        )\n\n    # keep track of which sequences are already finished\n    unfinished_sequences = input_ids.new(input_ids.shape[0]).fill_(1)\n\n    this_peer_finished = False  # used by synced_gpus only\n    while True:\n        if synced_gpus:\n            # Under synced_gpus the `forward` call must continue until all gpus complete their sequence.\n            # The following logic allows an early break if all peers finished generating their sequence\n            this_peer_finished_flag = torch.tensor(0.0 if this_peer_finished else 1.0).to(input_ids.device)\n            # send 0.0 if we finished, 1.0 otherwise\n            dist.all_reduce(this_peer_finished_flag, op=dist.ReduceOp.SUM)\n            # did all peers finish? the reduced sum will be 0.0 then\n            if this_peer_finished_flag.item() == 0.0:\n                break\n\n        # prepare model inputs\n        model_inputs = self.prepare_inputs_for_generation(input_ids, **model_kwargs)\n\n        # forward pass to get next token\n        outputs = self(\n            **model_inputs,\n            return_dict=True,\n            output_attentions=output_attentions,\n            output_hidden_states=output_hidden_states,\n        )\n\n        if synced_gpus and this_peer_finished:\n            continue  # don't waste resources running the code we don't need\n\n        next_token_logits = outputs.logits[:, -1, :]\n\n        # pre-process distribution\n        next_tokens_scores = logits_processor(input_ids, next_token_logits)\n\n        # Store scores, attentions and hidden_states when required\n        if return_dict_in_generate:\n            if output_scores:\n                scores += (next_tokens_scores,)\n            if output_attentions:\n                decoder_attentions += (\n                    (outputs.decoder_attentions,) if self.config.is_encoder_decoder else (outputs.attentions,)\n                )\n                if self.config.is_encoder_decoder:\n                    cross_attentions += (outputs.cross_attentions,)\n\n            if output_hidden_states:\n                decoder_hidden_states += (\n                    (outputs.decoder_hidden_states,)\n                    if self.config.is_encoder_decoder\n                    else (outputs.hidden_states,)\n                )\n\n        # argmax\n        next_tokens = torch.argmax(next_tokens_scores, dim=-1)\n\n        # finished sentences should have their next token be a padding token\n        if eos_token_id is not None:\n            if pad_token_id is None:\n                raise ValueError(\"If `eos_token_id` is defined, make sure that `pad_token_id` is defined.\")\n            next_tokens = next_tokens * unfinished_sequences + pad_token_id * (1 - unfinished_sequences)\n\n        # update generated ids, model inputs, and length for next step\n        input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1) # THIS IS WHERE THE NEXT OTKEN IS STORED\n        yield next_tokens # INTERMEIDATE TOKENS, intput_ids, scores \n\n        model_kwargs = self._update_model_kwargs_for_generation(\n            outputs, model_kwargs, is_encoder_decoder=self.config.is_encoder_decoder\n        )\n\n        # if eos_token was found in one sentence, set sentence to finished\n        if eos_token_id is not None:\n            unfinished_sequences = unfinished_sequences.mul((sum(next_tokens != i for i in eos_token_id)).long())\n\n        # stop when each sentence is finished, or if we exceed the maximum length\n        if unfinished_sequences.max() == 0 or stopping_criteria(input_ids, scores):\n            if not synced_gpus:\n                break\n            else:\n                this_peer_finished = True\n\n    if return_dict_in_generate:\n        if self.config.is_encoder_decoder:\n            return GreedySearchEncoderDecoderOutput(\n                sequences=input_ids,\n                scores=scores,\n                encoder_attentions=encoder_attentions,\n                encoder_hidden_states=encoder_hidden_states,\n                decoder_attentions=decoder_attentions,\n                cross_attentions=cross_attentions,\n                decoder_hidden_states=decoder_hidden_states,\n            )\n        else:\n            return GreedySearchDecoderOnlyOutput(\n                sequences=input_ids,\n                scores=scores,\n                attentions=decoder_attentions,\n                hidden_states=decoder_hidden_states,\n            )\n    else:\n        yield input_ids\n"
  },
  {
    "path": "server/lib/inference/huggingface/helpers.py",
    "content": "from transformers import StoppingCriteria\nimport torch\n\nclass StoppingCriteriaSub(StoppingCriteria):\n    def __init__(self, stops = []):\n      StoppingCriteria.__init__(self), \n\n    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, stops = []):\n      self.stops = stops\n      for i in range(len(stops)):\n        self.stops = self.stops[i]"
  },
  {
    "path": "server/lib/inference/huggingface/hf.py",
    "content": "import os\nimport transformers\nimport psutil\nimport torch\nimport importlib\nimport logging\nimport warnings\n\nfrom transformers import AutoTokenizer, AutoConfig, PreTrainedModel, PreTrainedTokenizer, AutoModelForCausalLM\nfrom .generator import greedy_search_generator\nfrom .helpers import StoppingCriteriaSub\n\n# monkey patch for transformers\ntransformers.generation.utils.GenerationMixin.greedy_search = greedy_search_generator\nos.environ['TOKENIZERS_PARALLELISM'] = 'true'\n\n# Set constants\nMODULE = importlib.import_module(\"transformers\") # dynamic import of module class, AutoModel not good enough for text generation\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\" # support gpu inference if possible\n\nlogger = logging.getLogger(__name__)\n\nclass HFInference:\n    '''\n    Class for huggingface local inference\n    '''\n    def __init__(self, model_name: str):\n        self.model_name = model_name\n        self.model, self.tokenizer = self.load_model(model_name)\n\n    # Helper function to load model from transformers library\n    def load_model(self, model_name: str) -> (PreTrainedModel, PreTrainedTokenizer):\n        '''\n        Load model from transformers library\n        dynamically instantiates the right model class for text generation from model config architecture\n        '''\n        tokenizer = AutoTokenizer.from_pretrained(model_name)\n        config = AutoConfig.from_pretrained(model_name) # load config for model\n        if config.architectures:\n            model_classname = config.architectures[0]\n            model_class = getattr(MODULE, model_classname) # get model class from config\n            model = model_class.from_pretrained(model_name, config=config) # dynamically load right model class for text generation\n        else:\n            model = AutoModelForCausalLM.from_pretrained(model_name, device_map='auto' if DEVICE == 'cuda' else None)\n\n        param_size = sum(\n            param.nelement() * param.element_size() for param in model.parameters()\n        )\n        buffer_size = sum(\n            buffer.nelement() * buffer.element_size() for buffer in model.buffers()\n        )\n        size_all_mb = (param_size + buffer_size) / 1024**2\n        logger.info('model size: {:.3f}MB'.format(size_all_mb))\n\n        if DEVICE == 'cuda':\n            device_memory = torch.cuda.get_device_properties(0).total_memory / 1024**2\n        else:\n            device_memory = psutil.virtual_memory().total / 1024**2\n\n        logger.info('device memory: {:.3f}MB'.format(device_memory))\n\n        if size_all_mb > device_memory * 0.95: #some padding\n            raise Exception('Model size is too large for host to run inference on')\n\n        model.to(DEVICE) # gpu inference if possible\n        return model, tokenizer\n\n    def generate(self, \n            prompt: str, \n            max_length: int, \n            temperature: float, \n            top_k: int, \n            top_p: float, \n            repetition_penalty: float, \n            stop_sequences: list = None,\n            **kwargs\n        ):\n        '''\n        Generate text from prompt, using monkey patched transformers.generation.utils.GenerationMixin.greedy_search\n        '''\n        inputs_str = prompt.strip()\n        inputs = self.tokenizer(inputs_str, return_tensors=\"pt\")\n        input_ids = inputs['input_ids'].to(DEVICE)\n        attention_mask = inputs['attention_mask'].to(DEVICE)\n\n        try:\n            outputs = self.model.generate(inputs=input_ids, \n                attention_mask=attention_mask, \n                max_new_tokens=max_length,\n                temperature=temperature,\n                top_k=top_k,\n                top_p=top_p,\n                repetition_penalty=repetition_penalty,\n                early_stopping=False,\n                # stopping_criteria=stopping_criteria if stopping_criteria else None,\n            )\n        except Exception as e:\n            raise Exception(f\"Error generating text: {e}\")\n\n        curr_token = \"\"\n        sentence = \"<|endoftext|>\"\n        first_token = True\n        for output in outputs:\n            next_token = output\n            if len(next_token.size()) > 1: continue # skip the last generated full array\n            if curr := self.tokenizer.convert_ids_to_tokens(\n                next_token, skip_special_tokens=True\n            ):\n                curr = curr[0] # string with special character potentially\n                if (curr[0] == \"Ġ\"): # BPE tokenizer\n                    curr_token = curr_token.replace(\"Ċ\", \"\\n\")\n                    curr_token = curr.replace(\"Ġ\", \" \")\n                    sentence += curr_token # we can yield here/print here\n                    yield curr_token\n                    # BPE\n                elif (curr[0] == \"▁\"): # sentence piece tokenizer\n                    if first_token:\n                        curr_token = curr.replace(\"▁\", \"\")\n                        first_token = False\n                    else:\n                        curr_token = curr.replace(\"▁\", \" \")\n                    sentence += curr_token # we can yield here/print here\n                    yield curr_token\n                else:\n                    curr_token = curr\n                    curr_token = curr_token.replace(\"Ċ\", \"\\n\")\n                    yield curr_token\n                    sentence += curr_token\n\n                curr_token = \"\"\n\n        # dispatch last token, if we can\n        if curr_token != \"\": \n            yield curr_token # send only if non empty\n\n        logger.info(f'[COMPLETION]: {sentence}')           \n"
  },
  {
    "path": "server/lib/sse.py",
    "content": "# coding=utf-8\n# credit to https://github.com/singingwolfboy/flask-sse\nfrom __future__ import unicode_literals\nimport six\nimport json\nimport logging\n\nfrom collections import OrderedDict\nfrom flask import Blueprint, request, current_app, json, stream_with_context\n\nlogger = logging.getLogger(__name__)\n\n__version__ = '1.0.0'\n\n@six.python_2_unicode_compatible\nclass Message(object):\n    \"\"\"\n    Data that is published as a server-sent event.\n    \"\"\"\n    def __init__(self, data, type=None, id=None, retry=None):\n        \"\"\"\n        Create a server-sent event.\n\n        :param data: The event data. If it is not a string, it will be\n            serialized to JSON using the Flask application's\n            :class:`~flask.json.JSONEncoder`.\n        :param type: An optional event type.\n        :param id: An optional event ID.\n        :param retry: An optional integer, to specify the reconnect time for\n            disconnected clients of this stream.\n        \"\"\"\n        self.data = data\n        self.type = type\n        self.id = id\n        self.retry = retry\n\n    def to_dict(self):\n        \"\"\"\n        Serialize this object to a minimal dictionary, for storing in Redis.\n        \"\"\"\n        # data is required, all others are optional\n        d = {\"data\": self.data}\n        if self.type:\n            d[\"type\"] = self.type\n        if self.id:\n            d[\"id\"] = self.id\n        if self.retry:\n            d[\"retry\"] = self.retry\n        return d\n\n    def __str__(self):\n        \"\"\"\n        Serialize this object to a string, according to the `server-sent events\n        specification <https://www.w3.org/TR/eventsource/>`_.\n        \"\"\"\n        if isinstance(self.data, six.string_types):\n            data = self.data\n        else:\n            data = json.dumps(self.data)\n        lines = [\"data:{value}\".format(value=line) for line in data.splitlines()]\n        if self.type:\n            lines.insert(0, \"event:{value}\".format(value=self.type))\n        if self.id:\n            lines.append(\"id:{value}\".format(value=self.id))\n        if self.retry:\n            lines.append(\"retry:{value}\".format(value=self.retry))\n        return \"\\n\".join(lines) + \"\\n\\n\"\n\n    def __repr__(self):\n        kwargs = OrderedDict()\n        if self.type:\n            kwargs[\"type\"] = self.type\n        if self.id:\n            kwargs[\"id\"] = self.id\n        if self.retry:\n            kwargs[\"retry\"] = self.retry\n        kwargs_repr = \"\".join(\n            \", {key}={value!r}\".format(key=key, value=value)\n            for key, value in kwargs.items()\n        )\n        return \"{classname}({data!r}{kwargs})\".format(\n            classname=self.__class__.__name__,\n            data=self.data,\n            kwargs=kwargs_repr,\n        )\n\n    def __eq__(self, other):\n        return (\n            isinstance(other, self.__class__) and\n            self.data == other.data and\n            self.type == other.type and\n            self.id == other.id and\n            self.retry == other.retry\n        )\n\n\nclass ServerSentEventsBlueprint(Blueprint):\n    \"\"\"\n    A :class:`flask.Blueprint` subclass that knows how to publish, subscribe to,\n    and stream server-sent events.\n    \"\"\"\n    @property\n    def sse_server(self):\n        \"\"\"\n        Return the :class:`SSEServer` instance for this blueprint.\n        \"\"\"\n        sse_manager = sse_server.SSEManager(address=(\"127.0.0.1\", 9001), authkey=b'sse')\n        sse_manager.connect()\n\n        return sse_manager\n\n    def publish(self, data, type=None, id=None, retry=None, channel='sse'):\n        \"\"\"\n        Publish data as a server-sent event.\n\n        :param data: The event data. If it is not a string, it will be\n            serialized to JSON using the Flask application's\n            :class:`~flask.json.JSONEncoder`.\n        :param type: An optional event type.\n        :param id: An optional event ID.\n        :param retry: An optional integer, to specify the reconnect time for\n            disconnected clients of this stream.\n        :param channel: If you want to direct different events to different\n            clients, you may specify a channel for this event to go to.\n            Only clients listening to the same channel will receive this event.\n            Defaults to \"sse\".\n        \"\"\"\n        message = Message(data, type=type, id=id, retry=retry)\n        msg_json = json.dumps(message.to_dict())\n        logger.debug(f\"PUBLISHING: {msg_json} to channel: {channel}\")\n        return self.sse_server.sse_publish(channel=channel, message=msg_json)\n\n    def messages(self, channel='sse'):\n        \"\"\"\n        A generator of :class:`~flask_sse.Message` objects from the given channel.\n        \"\"\"\n        self.sse_server.sse_subscribe(channel)\n        try:\n            for pubsub_message in self.sse_server.sse_listen(channel)._getvalue():\n                logger.debug(f\"pubsub_message: {pubsub_message}\")            \n                if pubsub_message['type'] == 'message':\n                    msg_dict = json.loads(pubsub_message['data'])\n                    if msg_dict[\"type\"] == \"done\":\n                        logger.info(\"Done streaming SSE\")\n                        break\n\n                    yield Message(**msg_dict)\n        except GeneratorExit:\n            logger.error(\"GeneratorExit\")\n        finally:\n            logger.info(f\"Unsubscribing from channel: {channel}\")\n            try:\n                self.sse_server.sse_unsubscribe(channel)\n            except ConnectionError:\n                pass\n        return None\n\n    def stream(self):\n        \"\"\"\n        A view function that streams server-sent events. Ignores any\n        :mailheader:`Last-Event-ID` headers in the HTTP request.\n        Use a \"channel\" query parameter to stream events from a different\n        channel than the default channel (which is \"sse\").\n        \"\"\"\n\n        uuid = json.loads(request.data)[\"uuid\"] # this is sent upon connection from the frontend\n        logger.info(f\"Streaming SSE: {uuid}\")\n\n        @stream_with_context\n        def generator():\n            try:\n                for message in self.messages(channel=uuid):\n                    yield str(message)\n            except GeneratorExit:\n                self.sse_server.sse_publish(\"cancel_inference\", message=json.dumps({\"uuid\": uuid}))\n            finally:\n                pass\n\n        return current_app.response_class(\n            generator(),\n            mimetype='text/event-stream',\n        )\n\nsse = ServerSentEventsBlueprint('sse', __name__)\n\"\"\"\nAn instance of :class:`~flask_sse.ServerSentEventsBlueprint`\nthat hooks up the :meth:`~flask_sse.ServerSentEventsBlueprint.stream`\nmethod as a view function at the root of the blueprint. If you don't\nwant to customize this blueprint at all, you can simply import and\nuse this instance in your application.\n\"\"\"\nsse.add_url_rule(rule=\"\", endpoint=\"stream\", view_func=sse.stream, methods=[\"POST\", \"OPTIONS\"])"
  },
  {
    "path": "server/lib/sseserver.py",
    "content": "# Thread Safe and Singular Global Instance of SSE Server\nimport queue\nimport logging\n\nlogger = logging.getLogger(__name__)\nclass SSEQueue:\n    def __init__(self):\n        self.listeners = []\n\n    def listen(self):\n        logger.info(\"LISTENING\")\n        q = queue.Queue(maxsize=50) # what about multiprocessing.Queue?\n        self.listeners.append(q)\n        return q\n\n    def publish(self, message: str):\n        logger.debug(f\"PUBLISHING {message}\")\n        for i in reversed(range(len(self.listeners))):\n            try:\n                self.listeners[i].put_nowait(message)\n            except queue.Full:\n                del self.listeners[i]\n\nclass SSEQueueWithTopic:\n    def __init__(self):\n        self.pubsub : dict[str, SSEQueue] = {}\n\n    def listen(self, topic: str):\n        logger.info(f\"LISTENING TO: {topic}\")\n        if topic not in self.pubsub:\n            raise ValueError(f\"Channel {topic} not found\")\n        return self.pubsub[topic].listen()\n\n    def publish(self, topic: str, message: str):\n        logger.debug(f\"PUBLISHING TO: {topic} MESSAGE: {message}\")\n        if topic not in self.pubsub:\n            raise ValueError(f\"Topic {topic} not found\")\n        self.pubsub[topic].announce(message=message)\n    \n    def add_topic(self, topic: str):\n        logger.info(f\"SUBSCRIBING TO: {topic}\")\n        if topic not in self.pubsub:\n            self.pubsub[topic] = SSEQueue()\n        return self.pubsub[topic]\n\n    def get_topic(self, topic: str):\n        logger.info(f\"GETTING TOPIC: {topic}\")\n        if topic not in self.pubsub:\n            raise ValueError(f\"Topic {topic} not found\")\n        return self.pubsub[topic]\n    \n    def remove_topic(self, topic: str):\n        logger.info(f\"REMOVING TOPIC: {topic}\")\n        if topic not in self.pubsub:\n            raise ValueError(f\"Topic {topic} not found\")\n        del self.pubsub[topic]"
  },
  {
    "path": "server/lib/storage.py",
    "content": "import os\nimport importlib.resources as pkg_resources\nimport json\nimport logging\n\nfrom .event_emitter import EventEmitter, EVENTS\nfrom .entities import Model, Provider\nfrom dotenv import set_key, load_dotenv\nfrom typing import List, Dict, Any\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO)\n\nconfig_dir = os.environ.get('XDG_CONFIG_HOME')\nif not config_dir:\n    if os.name == 'nt':  # Windows\n        config_dir = os.environ.get('APPDATA')\n    else:  # Linux/Unix\n        config_dir = os.path.expanduser('~/.config')\n\nAPP_DIR = os.path.join(config_dir, 'openplayground')\nos.makedirs(APP_DIR, exist_ok=True)\n\nclass Storage:\n    def __init__(self, models_json_path: str = None, env_file_path: str = None):\n        self.event_emitter = EventEmitter()\n        self.providers = []\n        self.models = []\n        self.models_json, self.models_json_path = self.__initialize_config__(models_json_path)\n        self.env_file_path = env_file_path\n\n        if env_file_path is None:\n            self.env_file_path = os.path.join(os.getcwd(), 'env')\n        elif os.path.exists(env_file_path):\n            load_dotenv(env_file_path)\n\n        for provider_name, provider in self.models_json.items():\n            models = [\n                Model(\n                    name=model_name,\n                    provider=provider_name,\n                    capabilities=model.get(\"capabilities\", []),\n                    enabled=model.get(\"enabled\", False),\n                    status=model.get(\"status\", \"ready\"),\n                    parameters=model.get(\"parameters\", {})\n                )\n                for model_name, model in provider['models'].items()\n            ]\n            self.providers.append(\n                Provider(\n                    name=provider_name,\n                    models=models,\n                    remote_inference=provider.get('remoteInference', None),\n                    default_capabilities=provider.get('defaultCapabilities', []),\n                    default_parameters=provider.get('defaultParameters', None),\n                    api_key=os.environ.get(f'{provider_name.upper()}_API_KEY'),\n                    requires_api_key=provider.get(\"requiresAPIKey\", False),\n                    search_url=provider.get('searchURL', None),\n                )\n            )\n\n            self.models.extend(models)\n\n        for event in [\n            EVENTS.MODEL_ADDED,\n            EVENTS.MODEL_REMOVED,\n            EVENTS.MODEL_STATUS_UPDATE,\n            EVENTS.MODEL_UPDATED,\n            EVENTS.PROVIDER_API_KEY_UPDATE,\n        ]:\n            EventEmitter().on(event, self.__update___)\n\n    def __initialize_config__(self, models_json_path: str = None):\n        if models_json_path is None:\n            models_json_path = os.path.join(APP_DIR, 'models.json')\n\n        original_models_json = None\n        if not pkg_resources.is_resource('server', 'models.json'):\n            original_models_json = open('./models.json').read()\n        else:\n            original_models_json = pkg_resources.read_text('server', 'models.json')\n\n        if not os.path.exists(os.path.join(APP_DIR, 'models.json')):\n            with open(os.path.join(APP_DIR, 'models.json'), 'w') as f:\n                f.write(original_models_json)\n        else:\n            original_models_json = json.loads(original_models_json)\n\n            with open(os.path.join(APP_DIR, 'models.json'), 'r') as f:\n                cached_models_json = json.load(f)\n \n                cached_providers = cached_models_json.keys()\n                original_providers = original_models_json.keys()\n\n                provider_in_original_not_cache = [provider for provider in original_providers if provider not in cached_providers]\n\n                for provider in provider_in_original_not_cache:\n                    cached_models_json[provider] = original_models_json[provider]\n                \n                cached_providers = cached_models_json.keys()\n\n                for cached_provider in cached_providers:\n                    cached_provider_keys = cached_models_json[cached_provider].keys()\n                    original_provider_keys = original_models_json[cached_provider].keys()\n\n                    #keys in cache but not in original\n                    cache_keys_missing = [key for key in cached_provider_keys if key not in original_provider_keys]\n                    #keys in original but not in cache\n                    missing_original_keys = [key for key in original_provider_keys if key not in cached_provider_keys]\n\n                    for missing_cached_key in cache_keys_missing:\n                        del cached_models_json[cached_provider][missing_cached_key]\n\n                    for missing_original_key in missing_original_keys:\n                        cached_models_json[cached_provider][missing_original_key] = original_models_json[cached_provider][missing_original_key]\n\n                    cached_provider_models = cached_models_json[cached_provider]['models'].keys()\n                    original_provider_models = original_models_json[cached_provider]['models'].keys()\n\n                    for cached_model in cached_provider_models:\n                        if cached_model not in original_provider_models:\n                            continue\n\n                        cached_model_keys = cached_models_json[cached_provider]['models'][cached_model].keys()\n                        original_model_keys = original_models_json[cached_provider]['models'][cached_model].keys()\n\n                        for original_model_key in original_model_keys:\n                            if original_model_key not in cached_model_keys:\n                                cached_models_json[cached_provider]['models'][cached_model][original_model_key] = original_models_json[cached_provider]['models'][cached_model][original_model_key]\n\n        with open(models_json_path, 'r') as f:\n            return json.load(f), models_json_path\n\n    def get_models(self) -> List[Model]:\n        return self.models\n    \n    def get_enabled_models(self) -> List[Model]:\n        return [model for model in self.models if model.enabled]\n    \n    def get_enabled_models_names(self) -> List[str]:\n        return [model.name for model in self.models if model.enabled]\n\n    def get_enabled_models_by_provider(self) -> Dict[str, List[Model]]:\n        models_by_provider = {}\n        for model in self.models:\n            if model.enabled:\n                if model.provider not in models_by_provider:\n                    models_by_provider[model.provider] = []\n                models_by_provider[model.provider].append(model)\n        return models_by_provider\n    \n    def get_model(self, model_name: str) -> Model:\n        return next((model for model in self.models if model.name == model_name), None)\n    \n    def get_providers(self) -> List[Provider]:\n        return self.providers\n    \n    def get_provider_names(self) -> List[str]:\n        return [provider.name for provider in self.providers]\n    \n    def get_provider(self, provider_name: str) -> Provider:\n        return next(\n            (\n                provider\n                for provider in self.providers\n                if provider.name == provider_name\n            ),\n            None,\n        )\n    \n    def update_provider_api_key(self, provider_name: str, api_key: str):\n        provider = self.get_provider(provider_name)\n        if provider is None:\n            raise ValueError(f'Provider {provider_name} not found')\n        provider.api_key = api_key\n\n        if not os.path.exists(self.env_file_path):\n            open(self.env_file_path, 'a').close()\n\n        set_key(self.env_file_path, f'{provider_name.upper()}_API_KEY', api_key)\n        load_dotenv(self.env_file_path)\n\n        self.event_emitter.emit(EVENTS.PROVIDER_API_KEY_UPDATE, provider_name)\n    \n    def __update___(self, event: str, *args, **kwargs):\n        if event == EVENTS.MODEL_ADDED:\n            model = args[0]\n            self.models.append(model)\n        elif event == EVENTS.MODEL_REMOVED:\n            model = args[0]\n            self.models.remove(model)\n       \n        self.__save__()\n\n    def update_model(self, model_name: str, model: Model):\n        for i, m in enumerate(self.models):\n            if m.name == model_name:\n                self.models[i] = model\n                break\n\n        for provider in self.providers:\n            if provider.name == model.provider:\n                for i, m in enumerate(provider.models):\n                    if m.name == model_name:\n                        provider.models[i] = model\n                        break              \n                break\n        self.event_emitter.emit(EVENTS.MODEL_UPDATED, model)\n\n    def __save__(self):\n        '''\n        Saves the models.json file\n        '''\n        logger.info('Saving models.json')\n        new_json = {\n            provider.name: {\n                'models': {\n                    model.name: {\n                        'capabilities': model.capabilities,\n                        'enabled': model.enabled,\n                        'status': model.status,\n                        'parameters': model.parameters,\n                    }\n                    for model in provider.models\n                },\n                'requiresAPIKey': provider.requires_api_key,\n                'remoteInference': provider.remote_inference,\n                'defaultParameters': provider.default_parameters,\n                'searchURL': provider.search_url,\n            }\n            for provider in self.providers\n        }\n        with open(self.models_json_path, 'w') as f:\n            json.dump(new_json, f, indent=4)\n\n        self.event_emitter.emit(EVENTS.SAVED_TO_DISK)\n\n    def import_config(config_path: str):\n        '''\n        Imports the models.json file from the specified path\n        '''\n        if not os.path.exists(config_path):\n            raise FileNotFoundError(f'{config_path} not found')\n        \n        with open(config_path, 'r') as f:\n            with open(os.path.join(APP_DIR, 'models.json'), 'w') as f2:\n                f2.write(f.read())\n\n    def export_config(output_path: str):\n        '''\n        Exports the models.json file to the specified path\n        '''\n        if not os.path.exists(os.path.join(APP_DIR, 'models.json')):\n            raise FileNotFoundError('models.json not found')\n        \n        with open(os.path.join(APP_DIR, 'models.json'), 'r') as f:\n            with open(output_path, 'w') as f2:\n                f2.write(f.read())"
  },
  {
    "path": "server/models.json",
    "content": "{\n  \"openai\": {\n    \"requiresAPIKey\": true,\n    \"remoteInference\": true,\n    \"models\": {\n      \"text-ada-001\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 0.5,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              2048\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"presencePenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"frequencyPenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"text-babbage-001\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 0.5,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              2048\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"presencePenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"frequencyPenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"text-curie-001\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 0.5,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              2048\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"presencePenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"frequencyPenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"text-davinci-003\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 0.5,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              4000\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"presencePenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"frequencyPenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"gpt-3.5-turbo\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 0.5,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              4097\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"presencePenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"frequencyPenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"gpt-4\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 0.5,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              8192\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"presencePenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"frequencyPenalty\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      }\n    }\n  },\n  \"cohere\": {\n    \"requiresAPIKey\": true,\n    \"remoteInference\": true,\n    \"models\": {\n      \"command-light\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              2\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              4096\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"topK\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              500\n            ]\n          },\n          \"presencePenalty\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"frequencyPenalty\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"command\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              2\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              4096\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"topK\": {\n            \"value\": 0,\n            \"range\": [\n              0,\n              500\n            ]\n          },\n          \"presencePenalty\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"frequencyPenalty\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      }\n    }\n  },\n  \"huggingface\": {\n    \"requiresAPIKey\": true,\n    \"remoteInference\": true,\n    \"searchURL\": \"https://huggingface.co/api/quicksearch?q={searchQuery}&type=model\",\n    \"defaultCapabilities\": [],\n    \"defaultParameters\": {\n      \"temperature\": {\n        \"value\": 1,\n        \"range\": [\n          0.1,\n          1\n        ]\n      },\n      \"maximumLength\": {\n        \"value\": 200,\n        \"range\": [\n          50,\n          1024\n        ]\n      },\n      \"topP\": {\n        \"value\": 0.99,\n        \"range\": [\n          0.01,\n          0.99\n        ]\n      },\n      \"topK\": {\n        \"value\": 1,\n        \"range\": [\n          1,\n          500\n        ]\n      },\n      \"repetitionPenalty\": {\n        \"value\": 1,\n        \"range\": [\n          0.1,\n          2\n        ]\n      },\n      \"stopSequences\": {\n        \"value\": [],\n        \"range\": []\n      }\n    },\n    \"models\": {}\n  },\n  \"huggingface-local\": {\n    \"requiresAPIKey\": false,\n    \"remoteInference\": false,\n    \"searchURL\": \"https://huggingface.co/api/quicksearch?q={searchQuery}&type=model\",\n    \"defaultCapabilities\": [],\n    \"defaultParameters\": {\n      \"temperature\": {\n        \"value\": 1,\n        \"range\": [\n          0.1,\n          1\n        ]\n      },\n      \"maximumLength\": {\n        \"value\": 200,\n        \"range\": [\n          50,\n          1024\n        ]\n      },\n      \"topP\": {\n        \"value\": 0.99,\n        \"range\": [\n          0.1,\n          0.99\n        ]\n      },\n      \"topK\": {\n        \"value\": 1,\n        \"range\": [\n          1,\n          500\n        ]\n      },\n      \"repetitionPenalty\": {\n        \"value\": 1,\n        \"range\": [\n          0.1,\n          2\n        ]\n      }\n    },\n    \"models\": {}\n  },\n  \"anthropic\": {\n    \"requiresAPIKey\": true,\n    \"remoteInference\": true,\n    \"models\": {\n      \"claude-instant-v1\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 205,\n            \"range\": [\n              50,\n              9216\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"claude-v1\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              9216\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      }\n    }\n  },\n  \"aleph-alpha\": {\n    \"requiresAPIKey\": true,\n    \"remoteInference\": true,\n    \"models\": {\n      \"luminous-supreme-control\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              1024\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"topK\": {\n            \"value\": 1,\n            \"range\": [\n              1,\n              500\n            ]\n          },\n          \"repetitionPenalty\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"luminous-base\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              1024\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"topK\": {\n            \"value\": 1,\n            \"range\": [\n              1,\n              500\n            ]\n          },\n          \"repetitionPenalty\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"luminous-supreme\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              1024\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"topK\": {\n            \"value\": 1,\n            \"range\": [\n              1,\n              500\n            ]\n          },\n          \"repetitionPenalty\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      },\n      \"luminous-extended\": {\n        \"enabled\": false,\n        \"status\": \"ready\",\n        \"capabilities\": [\n          \"logprobs\"\n        ],\n        \"parameters\": {\n          \"temperature\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"maximumLength\": {\n            \"value\": 200,\n            \"range\": [\n              50,\n              1024\n            ]\n          },\n          \"topP\": {\n            \"value\": 1,\n            \"range\": [\n              0,\n              1\n            ]\n          },\n          \"topK\": {\n            \"value\": 1,\n            \"range\": [\n              1,\n              500\n            ]\n          },\n          \"repetitionPenalty\": {\n            \"value\": 1,\n            \"range\": [\n              0.1,\n              1\n            ]\n          },\n          \"stopSequences\": {\n            \"value\": [],\n            \"range\": []\n          }\n        }\n      }\n    }\n  },\n  \"openplayground\": {\n    \"requiresAPIKey\": false,\n    \"remoteInference\": false,\n    \"searchURL\": \"https://openplayground.filler/api/search?q={searchQuery}\",\n    \"defaultCapabilities\": [],\n    \"defaultParameters\": {\n      \"temperature\": {\n        \"value\": 1,\n        \"range\": [\n          0.1,\n          1\n        ]\n      },\n      \"maximumLength\": {\n        \"value\": 200,\n        \"range\": [\n          50,\n          1024\n        ]\n      },\n      \"topP\": {\n        \"value\": 1,\n        \"range\": [\n          0.1,\n          1\n        ]\n      },\n      \"topK\": {\n        \"value\": 1,\n        \"range\": [\n          1,\n          500\n        ]\n      },\n      \"repetitionPenalty\": {\n        \"value\": 1,\n        \"range\": [\n          0.1,\n          2\n        ]\n      }\n    },\n    \"models\": {}\n  }\n}\n"
  },
  {
    "path": "server/requirements.txt",
    "content": "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_hub==0.13.2\nopenai==0.27.2\npsutil==5.9.4\npython-dotenv==1.0.0\nrequests==2.28.2\nsentencepiece==0.1.97\nsix==1.16.0\nsseclient==0.0.27\ntorch==2.0.0\ntransformers==4.27.1\n"
  }
]