[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: { browser: true, es2020: true },\n  extends: [\n    \"eslint:recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:react-hooks/recommended\",\n    \"plugin:prettier/recommended\",\n  ],\n  ignorePatterns: [\".eslintrc.js\", \"package-scripts.js\"],\n  parser: \"@typescript-eslint/parser\",\n  plugins: [\"react-refresh\", \"prettier\"],\n  rules: {\n    \"prettier/prettier\": \"error\",\n  },\n};\n"
  },
  {
    "path": ".gitignore",
    "content": "/node_modules\n/venv\n.DS_Store\n.env\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n__pycache__/\ntraining-data.jsonl\nsymphony/*.js"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"endOfLine\": \"lf\",\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"es5\",\n  \"bracketSpacing\": true\n}\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2023 Jeremy Philemon\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": "![symphony preview](https://github.com/symphony-hq/symphony/assets/17938322/5aa7b5ee-79de-4fd7-a793-368c1a453b12)\n\n## Getting Started\nVisit https://symphony.run/docs to get started with Symphony.\n\n## Contributing\nOpen to improvements and bug fixes!\n"
  },
  {
    "path": "functions/getCoordinates.py",
    "content": "from typing import Optional, TypedDict\nimport geocoder\nimport json\nimport sys\nfrom pydantic import Field, BaseModel\n\n\nclass SymphonyRequest(BaseModel):\n    ipAddress: str = Field(\n        description=\"The IP address; Use 'me' to get own IP address\")\n\n\nclass SymphonyResponse(BaseModel):\n    lat: float = Field(description=\"The latitude of IP address\")\n    lng: float = Field(description=\"The longitude of IP address\")\n\n\ndef handler(request: SymphonyRequest) -> SymphonyResponse:\n    \"\"\"\n    Get latitude and longitude from IP address\n    \"\"\"\n    ipAddress = request['ipAddress']\n\n    g = geocoder.ip(ipAddress)\n    lat, lng = g.latlng\n\n    return SymphonyResponse(lat=lat, lng=lng)\n"
  },
  {
    "path": "functions/getWeather.ts",
    "content": "import axios from \"axios\";\n\n/**\n * lat: Latitude of the city\n * lon: Longitude of the city\n */\ninterface SymphonyRequest {\n  lat: number;\n  lon: number;\n}\n\n/**\n * temperature: The temperature of the city\n * unit: The unit of the temperature\n */\ninterface SymphonyResponse {\n  temperature: number;\n  unit: string;\n}\n\n/**\n * Gets temperature of a city\n */\nexport const handler = async (\n  request: SymphonyRequest\n): Promise<SymphonyResponse> => {\n  const { lat, lon } = request;\n\n  const result = await axios\n    .get(\n      `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=bab3aa11dfaea12cda0525211c1cf3a5`\n    )\n    .then((response) => {\n      return response.data;\n    });\n\n  return {\n    temperature: result.main.temp,\n    unit: \"Kelvin\",\n  };\n};\n"
  },
  {
    "path": "functions/kelvinToCelsius.ts",
    "content": "/**\n * value: Value in Kelvin\n */\ninterface SymphonyRequest {\n  value: number;\n}\n\n/**\n * value: Value in Celsius\n */\ninterface SymphonyResponse {\n  value: number;\n}\n\n/**\n * Converts Kelvin to Celsius\n */\nexport const handler = async (\n  request: SymphonyRequest\n): Promise<SymphonyResponse> => {\n  const { value } = request;\n\n  return {\n    value: Math.round(value - 273.15),\n  };\n};\n"
  },
  {
    "path": "interfaces/getCoordinates-py.tsx",
    "content": "import * as React from \"react\";\n\ninterface Request {\n  ipAddress: string;\n}\nexport function Request({ props }: { props: Request }) {\n  return <div className=\"json\">{JSON.stringify(props, null, 2)}</div>;\n}\n\ninterface Response {\n  lat: number;\n  lng: number;\n}\nexport function Response({ props }: { props: Response }) {\n  const { lat, lng } = props;\n\n  return (\n    <img\n      style={{ borderRadius: \"3px\", maxWidth: \"400px\", width: \"100%\" }}\n      src={`https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/${lng},${lat},9,0,0/400x200@2x?access_token=pk.eyJ1IjoianJteSIsImEiOiJjazA5MXQwdngwNDZhM2lxOHFheTlieHM3In0.1Jh_NjL_Nu3YYeMUOZvmrA&logo=false&attribution=false`}\n    />\n  );\n}\n"
  },
  {
    "path": "interfaces/getWeather-ts.tsx",
    "content": "import * as React from \"react\";\n\ninterface Request {\n  lat: number;\n  lon: number;\n}\nexport function Request({ props }: { props: Request }) {\n  return <div className=\"json\">{JSON.stringify(props, null, 2)}</div>;\n}\n\ninterface Response {\n  temperature: number;\n  unit: string;\n}\nexport function Response({ props }: { props: Response }) {\n  return <div className=\"json\">{JSON.stringify(props, null, 2)}</div>;\n}\n"
  },
  {
    "path": "interfaces/kelvinToCelsius-ts.tsx",
    "content": "import * as React from \"react\";\n\ninterface Request {\n  value: number;\n}\nexport function Request({ props }: { props: Request }) {\n  return <div className=\"json\">{JSON.stringify(props, null, 2)}</div>;\n}\n\ninterface Response {\n  value: number;\n}\nexport function Response({ props }: { props: Response }) {\n  return <div className=\"json\">{JSON.stringify(props, null, 2)}</div>;\n}\n"
  },
  {
    "path": "package-scripts.js",
    "content": "const npsUtils = require(\"nps-utils\");\nconst dotenv = require(\"dotenv\");\nconst config = dotenv.config();\n\nif (config.error) {\n  throw config.error;\n}\n\nlet requiredEnvVariables = [\"OPENAI_API_KEY\"];\nrequiredEnvVariables.forEach((variable) => {\n  if (!process.env[variable]) {\n    console.error(`Missing environment variable: ${variable}`);\n    process.exit(1);\n  }\n});\n\nmodule.exports = {\n  scripts: {\n    initialize: {\n      py: \"virtualenv -p python3 venv && source venv/bin/activate && pip install -r requirements.txt\",\n      database: \"symphony/database/setup.sh\",\n      default: npsUtils.concurrent.nps(\"initialize.py\", \"initialize.database\"),\n    },\n    describe: {\n      ts: \"node -r @swc-node/register symphony/server/typescript/describe.ts\",\n      py: \"source venv/bin/activate && python symphony/server/python/describe.py\",\n    },\n    serve: {\n      ts: \"node -r @swc-node/register symphony/server/typescript/serve.ts\",\n      py: \"source venv/bin/activate && python symphony/server/python/serve.py\",\n      all: npsUtils.concurrent.nps(\"serve.ts\", \"serve.py\"),\n    },\n    jig: \"node -r @swc-node/register symphony/server/jig.ts\",\n    watch: \"node -r @swc-node/register symphony/server/watch.ts\",\n    client: \"yarn vite --port 3000\",\n    service: \"node -r @swc-node/register symphony/server/service.ts\",\n    database: \"postgrest symphony/database/postgrest.conf\",\n    start: npsUtils.concurrent.nps(\n      \"client\",\n      \"service\",\n      \"database\",\n      \"serve.all\",\n      \"jig\",\n      \"watch\"\n    ),\n    lint: \"eslint .\",\n    clean: {\n      ts: \"rm -rf node_modules\",\n      py: \"rm -rf venv\",\n      database: \"symphony/database/destroy.sh\",\n      all: npsUtils.concurrent.nps(\"clean.ts\", \"clean.py\"),\n      default: \"yarn nps clean.all\",\n    },\n  },\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"symphony\",\n  \"version\": \"1.0.0\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"start\": \"nps\"\n  },\n  \"dependencies\": {\n    \"@primer/octicons-react\": \"^19.8.0\",\n    \"@xstate/immer\": \"^0.3.3\",\n    \"axios\": \"^1.5.0\",\n    \"chokidar\": \"^3.5.3\",\n    \"classnames\": \"^2.3.2\",\n    \"date-fns\": \"^2.30.0\",\n    \"dotenv\": \"^16.3.1\",\n    \"fastify\": \"^4.24.3\",\n    \"fp-ts\": \"^2.16.1\",\n    \"immer\": \"^10.0.3\",\n    \"nps\": \"^5.10.0\",\n    \"nps-utils\": \"^1.7.0\",\n    \"openai\": \"^4.6.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"sass\": \"^1.67.0\",\n    \"uuid\": \"^9.0.1\",\n    \"vite\": \"^4.4.9\",\n    \"ws\": \"^8.14.1\",\n    \"xstate\": \"^4.38.2\"\n  },\n  \"devDependencies\": {\n    \"@swc-node/register\": \"^1.6.8\",\n    \"@types/node\": \"^20.6.0\",\n    \"@types/react-dom\": \"^18.2.7\",\n    \"@typescript-eslint/eslint-plugin\": \"^6.7.3\",\n    \"@typescript-eslint/parser\": \"^6.7.3\",\n    \"@vitejs/plugin-react-swc\": \"^3.4.0\",\n    \"eslint\": \"^8.50.0\",\n    \"eslint-config-prettier\": \"^9.0.0\",\n    \"eslint-plugin-prettier\": \"^5.0.0\",\n    \"eslint-plugin-react-hooks\": \"^4.6.0\",\n    \"eslint-plugin-react-refresh\": \"^0.4.3\",\n    \"modularscale-sass\": \"^3.0.10\",\n    \"prettier\": \"^3.0.3\",\n    \"ts-morph\": \"^20.0.0\",\n    \"typescript\": \"^5.2.2\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"react-app\"\n    ]\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "requirements.txt",
    "content": "annotated-types==0.5.0\nblinker==1.6.3\ncertifi==2023.7.22\ncharset-normalizer==3.2.0\nclick==8.1.7\ndecorator==5.1.1\nFlask==3.0.0\nfuture==0.18.3\ngeocoder==1.38.1\nidna==3.4\nitsdangerous==2.1.2\nJinja2==3.1.2\nMarkupSafe==2.1.3\npydantic==2.3.0\npydantic_core==2.6.3\nratelim==0.1.6\nsniffio==1.3.0\ntyping_extensions==4.8.0\nwatchdog==3.0.0\nWerkzeug==3.0.1\n"
  },
  {
    "path": "symphony/client/colors.scss",
    "content": "$slate-50: #f8fafc;\n$slate-100: #f1f5f9;\n$slate-200: #e2e8f0;\n$slate-300: #cbd5e1;\n$slate-400: #94a3b8;\n$slate-500: #64748b;\n$slate-600: #475569;\n$slate-700: #334155;\n$slate-800: #1e293b;\n$slate-900: #0f172a;\n$gray-50: #f9fafb;\n$gray-100: #f3f4f6;\n$gray-200: #e5e7eb;\n$gray-300: #d1d5db;\n$gray-400: #9ca3af;\n$gray-500: #6b7280;\n$gray-600: #4b5563;\n$gray-700: #374151;\n$gray-800: #1f2937;\n$gray-900: #111827;\n$zinc-50: #fafafa;\n$zinc-100: #f4f4f5;\n$zinc-200: #e4e4e7;\n$zinc-300: #d4d4d8;\n$zinc-400: #a1a1aa;\n$zinc-500: #71717a;\n$zinc-600: #52525b;\n$zinc-700: #3f3f46;\n$zinc-800: #27272a;\n$zinc-900: #18181b;\n$neutral-50: #fafafa;\n$neutral-100: #f5f5f5;\n$neutral-200: #e5e5e5;\n$neutral-300: #d4d4d4;\n$neutral-400: #a3a3a3;\n$neutral-500: #737373;\n$neutral-600: #525252;\n$neutral-700: #404040;\n$neutral-800: #262626;\n$neutral-900: #171717;\n$stone-50: #fafaf9;\n$stone-100: #f5f5f4;\n$stone-200: #e7e5e4;\n$stone-300: #d6d3d1;\n$stone-400: #a8a29e;\n$stone-500: #78716c;\n$stone-600: #57534e;\n$stone-700: #44403c;\n$stone-800: #292524;\n$stone-900: #1c1917;\n$red-50: #fef2f2;\n$red-100: #fee2e2;\n$red-200: #fecaca;\n$red-300: #fca5a5;\n$red-400: #f87171;\n$red-500: #ef4444;\n$red-600: #dc2626;\n$red-700: #b91c1c;\n$red-800: #991b1b;\n$red-900: #7f1d1d;\n$orange-50: #fff7ed;\n$orange-100: #ffedd5;\n$orange-200: #fed7aa;\n$orange-300: #fdba74;\n$orange-400: #fb923c;\n$orange-500: #f97316;\n$orange-600: #ea580c;\n$orange-700: #c2410c;\n$orange-800: #9a3412;\n$orange-900: #7c2d12;\n$amber-50: #fffbeb;\n$amber-100: #fef3c7;\n$amber-200: #fde68a;\n$amber-300: #fcd34d;\n$amber-400: #fbbf24;\n$amber-500: #f59e0b;\n$amber-600: #d97706;\n$amber-700: #b45309;\n$amber-800: #92400e;\n$amber-900: #78350f;\n$yellow-50: #fefce8;\n$yellow-100: #fef9c3;\n$yellow-200: #fef08a;\n$yellow-300: #fde047;\n$yellow-400: #facc15;\n$yellow-500: #eab308;\n$yellow-600: #ca8a04;\n$yellow-700: #a16207;\n$yellow-800: #854d0e;\n$yellow-900: #713f12;\n$lime-50: #f7fee7;\n$lime-100: #ecfccb;\n$lime-200: #d9f99d;\n$lime-300: #bef264;\n$lime-400: #a3e635;\n$lime-500: #84cc16;\n$lime-600: #65a30d;\n$lime-700: #4d7c0f;\n$lime-800: #3f6212;\n$lime-900: #365314;\n$green-50: #f0fdf4;\n$green-100: #dcfce7;\n$green-200: #bbf7d0;\n$green-300: #86efac;\n$green-400: #4ade80;\n$green-500: #22c55e;\n$green-600: #16a34a;\n$green-700: #15803d;\n$green-800: #166534;\n$green-900: #14532d;\n$emerald-50: #ecfdf5;\n$emerald-100: #d1fae5;\n$emerald-200: #a7f3d0;\n$emerald-300: #6ee7b7;\n$emerald-400: #34d399;\n$emerald-500: #10b981;\n$emerald-600: #059669;\n$emerald-700: #047857;\n$emerald-800: #065f46;\n$emerald-900: #064e3b;\n$teal-50: #f0fdfa;\n$teal-100: #ccfbf1;\n$teal-200: #99f6e4;\n$teal-300: #5eead4;\n$teal-400: #2dd4bf;\n$teal-500: #14b8a6;\n$teal-600: #0d9488;\n$teal-700: #0f766e;\n$teal-800: #115e59;\n$teal-900: #134e4a;\n$cyan-50: #ecfeff;\n$cyan-100: #cffafe;\n$cyan-200: #a5f3fc;\n$cyan-300: #67e8f9;\n$cyan-400: #22d3ee;\n$cyan-500: #06b6d4;\n$cyan-600: #0891b2;\n$cyan-700: #0e7490;\n$cyan-800: #155e75;\n$cyan-900: #164e63;\n$sky-50: #f0f9ff;\n$sky-100: #e0f2fe;\n$sky-200: #bae6fd;\n$sky-300: #7dd3fc;\n$sky-400: #38bdf8;\n$sky-500: #0ea5e9;\n$sky-600: #0284c7;\n$sky-700: #0369a1;\n$sky-800: #075985;\n$sky-900: #0c4a6e;\n$blue-50: #eff6ff;\n$blue-100: #dbeafe;\n$blue-200: #bfdbfe;\n$blue-300: #93c5fd;\n$blue-400: #60a5fa;\n$blue-500: #3b82f6;\n$blue-600: #2563eb;\n$blue-700: #1d4ed8;\n$blue-800: #1e40af;\n$blue-900: #1e3a8a;\n$indigo-50: #eef2ff;\n$indigo-100: #e0e7ff;\n$indigo-200: #c7d2fe;\n$indigo-300: #a5b4fc;\n$indigo-400: #818cf8;\n$indigo-500: #6366f1;\n$indigo-600: #4f46e5;\n$indigo-700: #4338ca;\n$indigo-800: #3730a3;\n$indigo-900: #312e81;\n$violet-50: #f5f3ff;\n$violet-100: #ede9fe;\n$violet-200: #ddd6fe;\n$violet-300: #c4b5fd;\n$violet-400: #a78bfa;\n$violet-500: #8b5cf6;\n$violet-600: #7c3aed;\n$violet-700: #6d28d9;\n$violet-800: #5b21b6;\n$violet-900: #4c1d95;\n$purple-50: #faf5ff;\n$purple-100: #f3e8ff;\n$purple-200: #e9d5ff;\n$purple-300: #d8b4fe;\n$purple-400: #c084fc;\n$purple-500: #a855f7;\n$purple-600: #9333ea;\n$purple-700: #7e22ce;\n$purple-800: #6b21a8;\n$purple-900: #581c87;\n$fuchsia-50: #fdf4ff;\n$fuchsia-100: #fae8ff;\n$fuchsia-200: #f5d0fe;\n$fuchsia-300: #f0abfc;\n$fuchsia-400: #e879f9;\n$fuchsia-500: #d946ef;\n$fuchsia-600: #c026d3;\n$fuchsia-700: #a21caf;\n$fuchsia-800: #86198f;\n$fuchsia-900: #701a75;\n$pink-50: #fdf2f8;\n$pink-100: #fce7f3;\n$pink-200: #fbcfe8;\n$pink-300: #f9a8d4;\n$pink-400: #f472b6;\n$pink-500: #ec4899;\n$pink-600: #db2777;\n$pink-700: #be185d;\n$pink-800: #9d174d;\n$pink-900: #831843;\n"
  },
  {
    "path": "symphony/client/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/public/favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Symphony</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "symphony/client/index.scss",
    "content": "@import \"../node_modules/modularscale-sass/stylesheets/modularscale\";\n@import \"colors.scss\";\n\n@font-face {\n  font-family: \"apercu\";\n  src: url(\"public/fonts/apercu-regular.woff2\") format(\"woff2\");\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: \"apercu mono\";\n  src: url(\"public/fonts/apercu-mono.woff2\") format(\"woff2\");\n  font-style: normal;\n  font-display: swap;\n}\n\n$modularscale: (\n  base: 16px,\n  ratio: 1.25\n);\n\nbody {\n  margin: 0;\n}\n\n* {\n  font-family: \"apercu\", sans-serif;\n}\n\n.window {\n  display: flex;\n  flex-direction: row;\n  width: 100vw;\n  height: 100dvh;\n\n  .page {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    flex-grow: 1;\n\n    .navigation {\n      display: flex;\n      flex-direction: row;\n      justify-content: space-between;\n      align-items: center;\n      padding: ms(0);\n      background: $neutral-50;\n\n      .name {\n        font-size: ms(-1);\n        color: $neutral-500;\n      }\n\n      .right {\n        display: flex;\n        flex-direction: row;\n        gap: ms(0);\n\n        .connections {\n          display: flex;\n          flex-direction: row-reverse;\n          gap: ms(-6);\n\n          .connection {\n            position: relative;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            cursor: pointer;\n\n            &.selected {\n              z-index: 999;\n              pointer-events: none;\n            }\n\n            .avatar {\n              width: ms(0);\n              height: ms(0);\n              border-radius: 50%;\n            }\n\n            .name {\n              display: none;\n              position: absolute;\n              font-size: ms(-1);\n              background: $neutral-800;\n              padding: ms(-6) ms(-4);\n              color: $neutral-50;\n              border-radius: ms(-6);\n              top: ms(2);\n              white-space: nowrap;\n              text-transform: capitalize;\n            }\n\n            &:hover {\n              filter: brightness(0.9);\n\n              .name {\n                display: block;\n              }\n            }\n          }\n        }\n\n        .menu {\n          display: flex;\n          color: $neutral-400;\n          cursor: pointer;\n\n          &:hover {\n            color: $neutral-500;\n          }\n        }\n      }\n    }\n\n    .conversation {\n      display: flex;\n      flex-direction: column;\n      justify-content: flex-end;\n      height: calc(100vh - 48.5px - 68.7px - #{ms(0)});\n      padding-top: ms(0);\n      padding-left: ms(-2);\n      padding-right: ms(-2);\n\n      .generations {\n        display: flex;\n        flex-direction: column;\n        overflow-y: scroll;\n\n        .generation {\n          display: flex;\n          flex-direction: row;\n          padding: ms(-4);\n          gap: ms(-4);\n          border-radius: ms(-6);\n          cursor: pointer;\n\n          &:hover,\n          &.editing {\n            background: $neutral-100;\n          }\n\n          .avatar {\n            flex-shrink: 0;\n            height: 21px;\n            width: 21px;\n            border-radius: ms(-8);\n          }\n\n          .content {\n            display: flex;\n            flex-direction: column;\n            gap: ms(-4);\n            white-space: pre-wrap;\n            word-break: break-word;\n\n            .tools {\n              display: flex;\n              flex-direction: row;\n              gap: ms(0);\n              overflow-x: scroll;\n\n              .tool {\n                display: flex;\n                flex-direction: column;\n                gap: ms(-4);\n                flex-shrink: 0;\n\n                .label {\n                  font-size: ms(-1);\n                  width: fit-content;\n                  color: $neutral-600;\n                  display: flex;\n                  flex-direction: row;\n                  color: $neutral-700;\n\n                  .status {\n                    padding: 2.75px ms(-6);\n                    background: $neutral-300;\n                    border-radius: ms(-8) 0 0 ms(-8);\n                  }\n\n                  .name {\n                    padding: 2.75px ms(-6);\n                    border-radius: 0 ms(-8) ms(-8) 0;\n                    background: $neutral-200;\n                  }\n                }\n              }\n            }\n\n            .json {\n              font-family: \"apercu mono\", monospace;\n              font-size: ms(-1);\n              color: $neutral-500;\n              margin: 0;\n            }\n          }\n\n          .editing {\n            width: 100%;\n            display: flex;\n            flex-direction: column;\n            gap: ms(-4);\n\n            .textareas {\n              display: flex;\n              flex-direction: row;\n              gap: ms(0);\n\n              .textarea {\n                display: flex;\n                flex-direction: column;\n                flex-grow: 1;\n                gap: ms(-4);\n\n                .label {\n                  font-size: ms(-1);\n                  background: $neutral-200;\n                  width: fit-content;\n                  padding: 2.75px ms(-6);\n                  border-radius: ms(-8);\n                  color: $neutral-600;\n                }\n              }\n            }\n\n            .input {\n              font-size: ms(0);\n              resize: none;\n              outline: none;\n              border: none;\n              width: 100%;\n              padding: 0;\n              background: transparent;\n              font-family: \"apercu mono\", monospace;\n              font-size: ms(-1);\n              color: $neutral-500;\n              overflow-y: hidden;\n            }\n\n            .actions {\n              display: flex;\n              flex-direction: row;\n              gap: ms(-4);\n              justify-content: flex-end;\n\n              .line {\n                width: 1px;\n                height: calc(100%);\n                background: $neutral-200;\n              }\n\n              .save,\n              .discard,\n              .delete {\n                font-size: ms(-1);\n                padding: ms(-4) ms(-3);\n                border-radius: ms(-8);\n                cursor: pointer;\n              }\n\n              .save {\n                color: $green-700;\n                background: $green-200;\n\n                &:hover {\n                  color: $green-900;\n                  background: $green-300;\n                }\n              }\n\n              .delete {\n                color: $red-700;\n                background: $red-200;\n\n                &:hover {\n                  color: $red-900;\n                  background: $red-300;\n                }\n              }\n\n              .discard {\n                color: $neutral-700;\n                background: $neutral-200;\n\n                &:hover {\n                  color: $neutral-900;\n                  background: $neutral-300;\n                }\n              }\n            }\n          }\n\n          .error {\n            color: $red-500;\n            font-size: ms(-1);\n          }\n        }\n      }\n    }\n\n    .controls {\n      display: flex;\n      flex-direction: row;\n      gap: ms(0);\n      padding: ms(0);\n      padding-top: ms(-2);\n\n      .input {\n        flex-grow: 1;\n        padding: ms(-2);\n        font-size: ms(0);\n        border: 1px solid $neutral-300;\n        border-radius: ms(-4);\n        outline: none;\n      }\n\n      .send {\n        font-size: ms(0);\n      }\n    }\n  }\n\n  .history {\n    display: none;\n    background: $neutral-900;\n    height: calc(100dvh);\n    flex-direction: column;\n    overflow-y: scroll;\n    flex-shrink: 0;\n    width: ms(13);\n\n    &.visible {\n      display: flex;\n    }\n\n    .bar {\n      display: flex;\n      flex-direction: row;\n      justify-content: space-between;\n      align-items: center;\n      padding: ms(0);\n      background: $neutral-800;\n      color: $neutral-300;\n      font-size: ms(-1);\n      position: sticky;\n      top: 0;\n\n      .finetune {\n        display: flex;\n        flex-direction: row;\n        align-items: center;\n\n        .icon {\n          display: flex;\n          cursor: pointer;\n        }\n\n        .tooltip {\n          display: none;\n          position: absolute;\n          font-size: ms(-1);\n          background: $neutral-600;\n          right: ms(0) * 3;\n          padding: ms(-6) ms(-4);\n          color: $neutral-50;\n          border-radius: ms(-6);\n          white-space: nowrap;\n        }\n\n        &:hover {\n          .tooltip {\n            display: block;\n          }\n        }\n      }\n    }\n\n    .conversations {\n      padding: ms(-2);\n\n      .line {\n        margin: ms(-4);\n        height: 1px;\n        width: calc(100% - #{ms(-4) * 2});\n        background: $neutral-700;\n      }\n\n      .conversation {\n        cursor: pointer;\n        padding: ms(-4);\n        border-radius: ms(-6);\n\n        .top {\n          display: flex;\n          flex-direction: row;\n          gap: ms(-4);\n\n          .timestamp {\n            font-size: ms(-1);\n            color: $neutral-400;\n          }\n\n          .delete {\n            display: flex;\n            color: $neutral-500;\n\n            &:hover {\n              color: $red-500;\n            }\n          }\n        }\n\n        .content {\n          color: $neutral-200;\n          word-break: break-word;\n        }\n\n        &:hover,\n        &.selected {\n          background: $neutral-800;\n        }\n      }\n    }\n  }\n\n  .personalize {\n    position: absolute;\n    left: 0;\n    top: 0;\n    display: flex;\n    flex-direction: row;\n    justify-content: center;\n    align-items: flex-start;\n    width: 100vw;\n    height: calc(100dvh - 48px - ms(-2));\n    background: #{$neutral-900}dd;\n    padding-top: calc(48px + ms(-2));\n\n    .connection {\n      display: flex;\n      flex-direction: column;\n      width: ms(13);\n      background-color: $neutral-50;\n      padding: ms(-2);\n      gap: ms(-2);\n      border-radius: ms(-6);\n\n      .top {\n        display: flex;\n        justify-content: space-between;\n\n        .left {\n          display: flex;\n          flex-direction: row;\n          align-items: center;\n          gap: ms(-4);\n\n          .avatar {\n            width: ms(1);\n            height: ms(1);\n            flex-shrink: 0;\n            border-radius: ms(-8);\n          }\n\n          .name {\n            text-transform: capitalize;\n          }\n        }\n\n        .model {\n          display: flex;\n          color: $neutral-800;\n          border: 1px solid $neutral-200;\n          border-radius: ms(-6);\n          padding: ms(-6);\n          cursor: pointer;\n          position: relative;\n          width: ms(9);\n\n          .choice {\n            font-size: ms(0);\n            overflow: hidden;\n            text-overflow: ellipsis;\n            white-space: nowrap;\n          }\n\n          &:hover {\n            color: $neutral-800;\n          }\n\n          .options {\n            position: absolute;\n            display: flex;\n            flex-direction: column;\n            top: calc(-#{ms(-2)} - 1px);\n            left: ms(10);\n            background: $neutral-800;\n            padding: ms(-4);\n            border-radius: ms(-6);\n            width: ms(10);\n            font-family: \"apercu mono\", monospace;\n            height: ms(13);\n            overflow-y: scroll;\n\n            .description {\n              font-size: ms(-1);\n              padding-bottom: ms(-4);\n              color: $neutral-500;\n            }\n\n            .option {\n              padding: ms(-6);\n              border-radius: ms(-8);\n              color: $neutral-200;\n              display: flex;\n              flex-direction: row;\n              align-items: center;\n              gap: ms(-6);\n              word-break: break-all;\n\n              .check {\n                display: flex;\n                opacity: 0;\n\n                &.selected {\n                  opacity: 1;\n                  color: $green-500 !important;\n                }\n              }\n\n              &:hover {\n                background: $neutral-700;\n\n                .check {\n                  color: $neutral-500;\n                  opacity: 1;\n                }\n              }\n            }\n          }\n        }\n      }\n\n      .input {\n        border: 1px solid $neutral-200;\n        border-radius: ms(-6);\n        outline: none;\n        background: transparent;\n        font-size: ms(0);\n        padding: ms(-6);\n      }\n\n      .actions {\n        display: flex;\n        flex-direction: row;\n        gap: ms(-2);\n        justify-content: flex-end;\n\n        .save,\n        .discard {\n          font-size: ms(-1);\n          padding: ms(-4) ms(-3);\n          border-radius: ms(-8);\n          cursor: pointer;\n        }\n\n        .save {\n          color: $green-700;\n          background: $green-200;\n\n          &:hover {\n            color: $green-900;\n            background: $green-300;\n          }\n        }\n\n        .discard {\n          color: $neutral-700;\n          background: $neutral-200;\n\n          &:hover {\n            color: $neutral-900;\n            background: $neutral-300;\n          }\n        }\n      }\n    }\n  }\n\n  .alert {\n    position: absolute;\n    left: 0;\n    top: 0;\n    display: flex;\n    flex-direction: row;\n    justify-content: center;\n    align-items: center;\n    width: calc(100vw - #{ms(13)});\n    height: calc(100dvh);\n    background: #{$neutral-900}dd;\n\n    .dialog {\n      display: flex;\n      flex-direction: column;\n      gap: ms(-2);\n      background: $neutral-50;\n      border-radius: ms(-6);\n      width: ms(13);\n      padding: ms(-2);\n\n      .actions {\n        display: flex;\n        flex-direction: row;\n        justify-content: flex-end;\n        gap: ms(-2);\n\n        .save,\n        .discard {\n          font-size: ms(-1);\n          padding: ms(-4) ms(-3);\n          border-radius: ms(-8);\n          cursor: pointer;\n        }\n\n        .save {\n          color: $green-700;\n          background: $green-200;\n\n          &:hover {\n            color: $green-900;\n            background: $green-300;\n          }\n        }\n\n        .discard {\n          color: $neutral-700;\n          background: $neutral-200;\n\n          &:hover {\n            color: $neutral-900;\n            background: $neutral-300;\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "symphony/client/index.tsx",
    "content": "import * as React from \"react\";\nimport { Suspense, useEffect, useRef, useState } from \"react\";\nimport * as ReactDOM from \"react-dom/client\";\nimport * as cx from \"classnames\";\nimport {\n  encodeFunctionName,\n  decodeFunctionName,\n  getAssistantFromConnections,\n  getModelIdFromAssistant,\n} from \"../utils/functions\";\nimport { Connection, Generation, Tool } from \"../utils/types\";\nimport \"./index.scss\";\nimport { parseISO, format } from \"date-fns\";\nimport {\n  XIcon,\n  ThreeBarsIcon,\n  TrashIcon,\n  GoalIcon,\n  CheckIcon,\n} from \"@primer/octicons-react\";\nimport { pipe } from \"fp-ts/lib/function\";\nimport * as AR from \"fp-ts/Array\";\nimport * as O from \"fp-ts/Option\";\nimport { produce } from \"immer\";\nimport { Model } from \"openai/resources\";\n\nconst interfaceCache = {};\n\nconst getInterface = (name: string, type: string) => {\n  if (name) {\n    const hash = `${name}-${type}`;\n\n    if (!interfaceCache[hash]) {\n      interfaceCache[hash] = React.lazy(async () => {\n        const module = await import(\n          `../../interfaces/${encodeFunctionName(name)}.tsx`\n        );\n        return { default: module[type] };\n      });\n    }\n    return interfaceCache[hash];\n  } else {\n    return <div />;\n  }\n};\n\nconst EditGeneration = ({ generation, setIsEditing, socketRef }) => {\n  const [content, setContent] = useState(generation.message.content);\n  const [tools, setTools] = useState(generation.message.tool_calls);\n\n  const contentRef = useRef(null);\n  const argsRef = useRef(null);\n\n  useEffect(() => {\n    contentRef.current.style.height = \"inherit\";\n    const contentScrollHeight = contentRef.current.scrollHeight;\n    contentRef.current.style.height = contentScrollHeight + \"px\";\n\n    if (argsRef.current) {\n      argsRef.current.style.height = \"inherit\";\n      const argsScrollHeight = argsRef.current.scrollHeight;\n      argsRef.current.style.height = argsScrollHeight + \"px\";\n    }\n\n    const length = contentRef.current.value.length;\n    contentRef.current.setSelectionRange(length, length);\n  }, []);\n\n  return (\n    <div className=\"editing\">\n      <div className=\"textareas\">\n        <div className=\"textarea\">\n          <div className=\"label\">{tools ? \"Reasoning\" : \"Output\"}</div>\n          <textarea\n            className={cx(\"input\")}\n            value={content}\n            onChange={(event) => {\n              setContent(event.target.value);\n            }}\n            ref={contentRef}\n            autoFocus={true}\n          />\n        </div>\n\n        {tools &&\n          tools.map((toolCall) => (\n            <div className=\"textarea\">\n              <div className=\"label\">\n                {decodeFunctionName(toolCall.function.name)}\n              </div>\n              <textarea\n                className={cx(\"input\")}\n                value={toolCall.function.arguments}\n                onChange={(event) => {\n                  setTools((tools: Tool[]) => {\n                    const newTools = produce(tools, (draft) => {\n                      draft.find(\n                        (tool) => tool.id === toolCall.id\n                      ).function.arguments = event.target.value;\n                    });\n\n                    return newTools;\n                  });\n                }}\n                ref={argsRef}\n              />\n            </div>\n          ))}\n      </div>\n\n      <div className=\"actions\">\n        <div\n          className=\"delete\"\n          onClick={() => {\n            socketRef.current.send(\n              JSON.stringify({\n                role: \"deleteGeneration\",\n                content: generation.id,\n              })\n            );\n          }}\n        >\n          Delete\n        </div>\n\n        <div className=\"line\" />\n\n        <div\n          className=\"discard\"\n          onClick={() => {\n            setIsEditing(false);\n          }}\n        >\n          Discard Changes\n        </div>\n\n        <div\n          className=\"save\"\n          onClick={() => {\n            setIsEditing(false);\n\n            const updatedMessage = produce(generation.message, (draft) => {\n              draft.content = content;\n              draft.tool_calls = tools;\n            });\n\n            socketRef.current.send(\n              JSON.stringify({\n                role: \"edit\",\n                content: {\n                  id: generation.id,\n                  message: updatedMessage,\n                },\n              })\n            );\n          }}\n        >\n          Save Changes\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst Generation = ({\n  generation,\n  socketRef,\n  connections,\n}: {\n  generation: Generation;\n  socketRef: React.RefObject<WebSocket>;\n  connections: Connection[];\n}) => {\n  const [isEditing, setIsEditing] = useState(false);\n  const { message } = generation;\n\n  const isToolCall = message.tool_calls;\n  const isToolResponse = message.role === \"tool\";\n\n  return (\n    <div\n      className={cx(\"generation\", { editing: isEditing })}\n      onClick={() => {\n        if (!isEditing) setIsEditing(true);\n      }}\n    >\n      <div\n        className=\"avatar\"\n        style={{\n          backgroundColor: pipe(\n            connections,\n            AR.findFirst((connection) => connection.name === message.role),\n            O.map((connection) => connection.color),\n            O.getOrElse(() => \"#d4d4d4\")\n          ),\n        }}\n      />\n\n      {isEditing ? (\n        <EditGeneration {...{ generation, setIsEditing, socketRef }} />\n      ) : (\n        <div className=\"content\">\n          {!isToolCall && !isToolResponse && message.content}\n\n          <div className=\"tools\">\n            {isToolCall\n              ? message.tool_calls.map((toolCall) => {\n                  const Interface = getInterface(\n                    toolCall.function.name,\n                    \"Request\"\n                  );\n\n                  return (\n                    <div key={toolCall.id} className=\"tool\">\n                      <div className=\"label\">\n                        <div className=\"status\">Execute</div>\n                        <div className=\"name\">\n                          {decodeFunctionName(toolCall.function.name)}\n                        </div>\n                      </div>\n\n                      <Suspense>\n                        <ErrorBoundary>\n                          <Interface\n                            props={JSON.parse(toolCall.function.arguments)}\n                          />\n                        </ErrorBoundary>\n                      </Suspense>\n                    </div>\n                  );\n                })\n              : isToolResponse\n              ? [\"\"].map(() => {\n                  const Interface = getInterface(message.name, \"Response\");\n\n                  return (\n                    <div key={message.name} className=\"tool\">\n                      <div className=\"label\">\n                        <div className=\"status\">Execute</div>\n                        <div className=\"name\">\n                          {decodeFunctionName(message.name)}\n                        </div>\n                      </div>\n\n                      <Suspense>\n                        <ErrorBoundary>\n                          <Interface props={JSON.parse(message.content)} />\n                        </ErrorBoundary>\n                      </Suspense>\n                    </div>\n                  );\n                })\n              : null}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n\nconst App = () => {\n  const socketRef = useRef(null);\n\n  const [generations, setGenerations] = useState([]);\n  const [conversations, setConversations] = useState([]);\n  const [isHistoryVisible, setIsHistoryVisible] = useState(false);\n\n  const [connections, setConnections] = useState<Connection[]>([]);\n  const [models, setModels] = useState<Model[]>([]);\n  const [selectedConnection, setSelectedConnection] = useState<\n    O.Option<Connection>\n  >(O.none);\n  const [finetune, setFinetune] = useState(false);\n\n  useEffect(() => {\n    const socket = new WebSocket(\"ws://localhost:3001\");\n\n    socket.addEventListener(\"open\", () => {\n      console.log(\"Connected to Symphony Service\");\n      socket.send(JSON.stringify({ role: \"restore\", content: \"\" }));\n    });\n\n    socket.addEventListener(\"message\", (event) => {\n      const message = JSON.parse(event.data);\n\n      if (message.role === \"history\") {\n        setConversations(message.content);\n      } else if (message.role === \"switch\") {\n        const { content: generations } = message;\n        setGenerations(generations);\n      } else if (message.role === \"edit\") {\n        const { content: updatedGeneration } = message;\n        setGenerations((generations: Generation[]) =>\n          generations.map((generation) => {\n            if (generation.id === updatedGeneration.id) {\n              return updatedGeneration;\n            } else {\n              return generation;\n            }\n          })\n        );\n      } else if (message.role === \"deleteConversation\") {\n        const { content: deletedGeneration } = message;\n        setConversations((conversations) =>\n          conversations.filter(\n            (conversation) =>\n              conversation.id !== deletedGeneration.conversationId\n          )\n        );\n      } else if (message.role === \"deleteGeneration\") {\n        const { content: deletedGeneration } = message;\n        setGenerations((generations: Generation[]) =>\n          generations.filter(\n            (generation) => generation.id !== deletedGeneration.id\n          )\n        );\n      } else if (message.role === \"restore\") {\n        const { content: context } = message;\n        const { connections, generations } = context;\n        setSelectedConnection(O.none);\n        setGenerations(generations);\n        setConnections(connections);\n      } else if (message.role === \"finetune\") {\n        const { content: job } = message;\n        window.open(`https://platform.openai.com/finetune/${job.id}`, \"_blank\");\n      } else if (message.role === \"models\") {\n        const { content: models } = message;\n        setModels(models);\n      } else {\n        setGenerations((generations: Generation[]) => [\n          ...generations,\n          message,\n        ]);\n      }\n    });\n\n    socket.addEventListener(\"close\", (event) => {\n      console.log(\"Disconnected from Symphony Service\", event.code);\n      setConnections((connections) =>\n        connections.filter((connection) => connection.name !== \"assistant\")\n      );\n    });\n\n    socket.addEventListener(\"error\", (event) => {\n      console.error(\"WebSocket error: \", event);\n    });\n\n    socketRef.current = socket;\n\n    return () => socket.close();\n  }, []);\n\n  const generationsRef = useRef<HTMLDivElement | null>(null);\n\n  useEffect(() => {\n    if (generationsRef.current) {\n      const observer = new MutationObserver(() => {\n        setTimeout(() => {\n          generationsRef.current.scrollTop =\n            generationsRef.current.scrollHeight;\n        }, 25);\n      });\n\n      const config = { attributes: false, childList: true, subtree: false };\n      observer.observe(generationsRef.current, config);\n\n      return () => observer.disconnect();\n    }\n  }, []);\n\n  useEffect(() => {\n    if (O.isSome(selectedConnection)) {\n      socketRef.current.send(\n        JSON.stringify({\n          role: \"models\",\n          content: \"\",\n        })\n      );\n    }\n  }, [selectedConnection]);\n\n  return (\n    <div className=\"window\">\n      <div className=\"page\">\n        <div className=\"navigation\">\n          <div className=\"name\">Symphony</div>\n\n          <div className=\"right\">\n            <div className=\"connections\">\n              {connections.map((connection) => (\n                <div\n                  key={connection.name}\n                  className={cx(\"connection\", {\n                    selected: pipe(\n                      selectedConnection,\n                      O.map(\n                        (selectedConnection) =>\n                          selectedConnection.name === connection.name\n                      ),\n                      O.getOrElse(() => false)\n                    ),\n                  })}\n                  onClick={() => {\n                    setSelectedConnection(O.some(connection));\n                  }}\n                >\n                  <div\n                    className=\"avatar\"\n                    style={{ backgroundColor: connection.color }}\n                  />\n                  <div className=\"name\">{connection.name}</div>\n                </div>\n              ))}\n            </div>\n\n            <div\n              className=\"menu\"\n              onClick={() => {\n                setIsHistoryVisible(!isHistoryVisible);\n                socketRef.current.send(\n                  JSON.stringify({\n                    role: \"history\",\n                    content: \"\",\n                  })\n                );\n              }}\n            >\n              {isHistoryVisible ? <XIcon /> : <ThreeBarsIcon />}\n            </div>\n          </div>\n        </div>\n\n        <div className=\"conversation\">\n          <div className=\"generations\" ref={generationsRef}>\n            {generations.map((generation: Generation) => (\n              <Generation\n                key={generation.id}\n                {...{ generation, socketRef, connections }}\n              />\n            ))}\n          </div>\n        </div>\n\n        <div className=\"controls\">\n          <input\n            className=\"input\"\n            placeholder=\"Send a message\"\n            onKeyDown={(event) => {\n              if (event.key === \"Enter\") {\n                const message = {\n                  role: \"user\",\n                  content: (event.target as HTMLInputElement).value,\n                };\n\n                socketRef.current.send(JSON.stringify(message));\n\n                setTimeout(() => {\n                  (event.target as HTMLInputElement).value = \"\";\n                }, 10);\n              }\n            }}\n          />\n        </div>\n      </div>\n\n      <div className={cx(\"history\", { visible: isHistoryVisible })}>\n        <div className=\"bar\">\n          <div className=\"name\">History</div>\n\n          <div\n            className={cx(\"finetune\")}\n            onClick={() => {\n              setFinetune(true);\n            }}\n          >\n            <div className=\"icon\">\n              <GoalIcon />\n            </div>\n            <div className=\"tooltip\">Fine-tune</div>\n          </div>\n        </div>\n\n        <div className=\"conversations\">\n          <div\n            className=\"conversation\"\n            onClick={() => {\n              setGenerations([]);\n              socketRef.current.send(\n                JSON.stringify({\n                  role: \"new\",\n                  content: \"\",\n                })\n              );\n            }}\n          >\n            <div className=\"top\">\n              <div className=\"timestamp\">Now</div>\n            </div>\n            <div className=\"content\">Start a new conversation!</div>\n          </div>\n\n          <div className=\"line\" />\n\n          {conversations.map((conversation) => (\n            <div\n              key={conversation.id}\n              className={cx(\"conversation\", {\n                selected: generations\n                  .map((generation) => generation.conversationId)\n                  .includes(conversation.id),\n              })}\n              onClick={() => {\n                socketRef.current.send(\n                  JSON.stringify({\n                    role: \"switch\",\n                    content: conversation.id,\n                  })\n                );\n              }}\n            >\n              <div className=\"top\">\n                <div className=\"timestamp\">\n                  {format(\n                    parseISO(conversation.timestamp),\n                    \"dd MMM yyyy, hh:mmaa\"\n                  )}\n                </div>\n\n                {generations\n                  .map((generation) => generation.conversationId)\n                  .includes(conversation.id) && (\n                  <div\n                    className=\"delete\"\n                    onClick={() => {\n                      socketRef.current.send(\n                        JSON.stringify({\n                          role: \"deleteConversation\",\n                          content: conversation.id,\n                        })\n                      );\n                    }}\n                  >\n                    <TrashIcon size={14} />\n                  </div>\n                )}\n              </div>\n              <div className=\"content\">{conversation.message.content}</div>\n            </div>\n          ))}\n        </div>\n      </div>\n\n      {O.isSome(selectedConnection) && (\n        <div className=\"personalize\">\n          <div className=\"connection\">\n            <div className=\"top\">\n              <div className=\"left\">\n                <div\n                  className=\"avatar\"\n                  style={{\n                    backgroundColor: selectedConnection.value.color,\n                  }}\n                />\n                <div className=\"name\">{selectedConnection.value.name}</div>\n              </div>\n\n              <div className=\"model\">\n                <div className=\"choice\">{selectedConnection.value.modelId}</div>\n\n                {selectedConnection.value.name !== \"user\" && (\n                  <div className=\"options\">\n                    <div className=\"description\">\n                      Select a model to use for the assistant.\n                    </div>\n\n                    {models\n                      .filter((model: Model) => model.id.includes(\"gpt\"))\n                      .map((model: Model) => (\n                        <div\n                          key={model.id}\n                          className=\"option\"\n                          onClick={() => {\n                            setSelectedConnection(\n                              O.some(\n                                produce(selectedConnection.value, (draft) => {\n                                  draft.modelId = model.id;\n                                })\n                              )\n                            );\n                          }}\n                        >\n                          <div\n                            className={cx(\"check\", {\n                              selected:\n                                model.id === selectedConnection.value.modelId,\n                            })}\n                          >\n                            <CheckIcon />\n                          </div>\n                          <div className=\"label\">{model.id}</div>\n                        </div>\n                      ))}\n                  </div>\n                )}\n              </div>\n            </div>\n\n            <textarea\n              className=\"input\"\n              value={selectedConnection.value.description}\n              onChange={(event) => {\n                setSelectedConnection(\n                  O.some(\n                    produce(selectedConnection.value, (draft) => {\n                      draft.description = event.target.value;\n                    })\n                  )\n                );\n              }}\n              placeholder=\"What would you like the assistant to know about you?\"\n            />\n\n            <div className=\"actions\">\n              <div\n                className=\"discard\"\n                onClick={() => {\n                  setSelectedConnection(O.none);\n                }}\n              >\n                Discard changes\n              </div>\n\n              <div\n                className=\"save\"\n                onClick={() => {\n                  socketRef.current.send(\n                    JSON.stringify({\n                      role: \"personalize\",\n                      content: selectedConnection.value,\n                    })\n                  );\n                }}\n              >\n                Save changes\n              </div>\n            </div>\n          </div>\n        </div>\n      )}\n\n      {finetune && (\n        <div className=\"alert\">\n          <div className=\"dialog\">\n            <div className=\"content\">\n              {`Would you like to fine-tune ${pipe(\n                connections,\n                getAssistantFromConnections,\n                getModelIdFromAssistant\n              )} using ${conversations.length} conversations?`}\n            </div>\n\n            <div className=\"actions\">\n              <div\n                className=\"discard\"\n                onClick={() => {\n                  setFinetune(false);\n                }}\n              >\n                Go Back\n              </div>\n\n              <div\n                className=\"save\"\n                onClick={() => {\n                  socketRef.current.send(\n                    JSON.stringify({\n                      role: \"finetune\",\n                      content: \"\",\n                    })\n                  );\n                  setFinetune(false);\n                }}\n              >\n                Confirm\n              </div>\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n\nclass ErrorBoundary extends React.Component<{ children: React.ReactNode }> {\n  state = { hasError: false, error: null };\n\n  static getDerivedStateFromError(error) {\n    return { hasError: true, error };\n  }\n\n  render() {\n    if (this.state.hasError) {\n      return <div className=\"error\">{this.state.error.toString()}</div>;\n    } else {\n      return this.props.children;\n    }\n  }\n}\n\nconst root = ReactDOM.createRoot(document.getElementById(\"root\"));\nroot.render(<App />);\n"
  },
  {
    "path": "symphony/database/destroy.sh",
    "content": "#!/bin/bash\n\n# Database connection details\nDB_NAME=\"symphony\"\nDB_HOST=\"localhost\"\nDB_PORT=\"5432\"\nDB_ROLE=\"anon\"\n\n# Drop database\npsql -h $DB_HOST -p $DB_PORT -c \"DROP DATABASE IF EXISTS $DB_NAME;\"\n\n# Drop role\npsql -h $DB_HOST -p $DB_PORT -c \"DROP ROLE IF EXISTS $DB_ROLE;\""
  },
  {
    "path": "symphony/database/postgrest.conf",
    "content": "db-uri = \"postgres://localhost:5432/symphony\"\ndb-schemas = \"public\"\ndb-anon-role = \"anon\"\nserver-port = \"3002\""
  },
  {
    "path": "symphony/database/setup.sh",
    "content": "#!/bin/bash\n\n# Check if PostgreSQL and PostgREST are installed\ncommand -v psql >/dev/null 2>&1 || { echo >&2 \"PostgreSQL is not installed. Please install it and try again. Aborting.\"; exit 1; }\ncommand -v postgrest >/dev/null 2>&1 || { echo >&2 \"PostgREST is not installed. Please install it and try again. Aborting.\"; exit 1; }\n\n# Database connection details\nDB_NAME=\"symphony\"\nDB_HOST=\"localhost\"\nDB_PORT=\"5432\"\nDB_ROLE=\"anon\"\n\n# Check if database already exists\nif psql -h $DB_HOST -p $DB_PORT -lqt | cut -d \\| -f 1 | grep -qw $DB_NAME; then\n   echo \"Database $DB_NAME already exists, skipping setup.\"\n   exit\nfi\n\n# Create database\npsql -h $DB_HOST -p $DB_PORT -c \"CREATE DATABASE $DB_NAME;\"\n\n# Add pgcrypto extension to the database\npsql -h $DB_HOST -p $DB_PORT -d $DB_NAME -c \"CREATE EXTENSION IF NOT EXISTS pgcrypto;\"\n\n# Create role\npsql -h $DB_HOST -p $DB_PORT -c \"CREATE ROLE $DB_ROLE nologin;\"\n\n# SQL commands\nSQL_COMMANDS=\"\ncreate table public.generations (\n  id uuid not null default gen_random_uuid(), \n  \\\"conversationId\\\" uuid not null, \n  timestamp timestamp with time zone null default (now() at time zone 'utc'::text), \n  message json not null, \n  constraint messages_pkey primary key (id)\n);\ngrant all on public.generations to anon;\n\"\n\n# Execute SQL commands\npsql -h $DB_HOST -p $DB_PORT -d $DB_NAME -c \"$SQL_COMMANDS\""
  },
  {
    "path": "symphony/server/jig.ts",
    "content": "import { pipe } from \"fp-ts/lib/function\";\nimport * as RAR from \"fp-ts/ReadonlyArray\";\nimport * as fs from \"fs\";\nimport { Project } from \"ts-morph\";\nimport { Descriptions, Property } from \"../utils/types\";\n\nconst interfaceToProperty = {\n  Request: \"parameters\",\n  Response: \"returns\",\n};\n\nconst getTypeFromProperty = (property: Property) => {\n  const { type } = property;\n\n  if (type === \"array\") {\n    return `${property.items.type}[]`;\n  } else {\n    return type;\n  }\n};\n\nfunction createInterfaces({ descriptions }: { descriptions: Descriptions[] }) {\n  pipe(\n    descriptions,\n    RAR.map((fx) => {\n      const { name } = fx;\n      const filePath = `./interfaces/${name}.tsx`;\n\n      const source = `import * as React from \"react\";\n\n      interface Request {}\n      export function Request({ props }: { props: Request }) {\n        return <div className=\"json\">{JSON.stringify(props, null, 2)}</div>;\n      }      \n      \n      interface Response {}\n      export function Response({ props }: { props: Response }) {\n        return <div className=\"json\">{JSON.stringify(props, null, 2)}</div>;\n      }               \n      `;\n\n      if (!fs.existsSync(filePath)) {\n        fs.writeFileSync(filePath, source, { flag: \"w\" });\n      }\n\n      return fx;\n    }),\n    RAR.map(async (fx) => {\n      const { name } = fx;\n\n      const filePath = `./interfaces/${name}.tsx`;\n\n      const project = new Project();\n      const sourceFile = project.addSourceFileAtPath(filePath);\n\n      const interfaceNames = [\"Request\", \"Response\"];\n\n      interfaceNames.forEach((interfaceName) => {\n        const interfaceNode = sourceFile.getInterface(interfaceName)!;\n        const interfaceProperties = interfaceNode.getProperties();\n\n        interfaceProperties.map((property) => {\n          property.remove();\n        });\n\n        const propertiesFromDescriptions = pipe(\n          Object.keys(fx[interfaceToProperty[interfaceName]].properties),\n          RAR.map((name) => {\n            const property =\n              fx[interfaceToProperty[interfaceName]].properties[name];\n            const type = getTypeFromProperty(property);\n\n            return {\n              name,\n              type,\n            };\n          })\n        );\n\n        propertiesFromDescriptions.map(({ name, type }) => {\n          interfaceNode.addProperty({\n            name: name,\n            type: type,\n            hasQuestionToken:\n              !fx[interfaceToProperty[interfaceName]].required.includes(name),\n          });\n        });\n      });\n\n      sourceFile.formatText({\n        indentSize: 2,\n      });\n\n      await sourceFile.save();\n\n      return fx;\n    })\n  );\n}\n\nconst removeInterfaces = ({\n  descriptions,\n}: {\n  descriptions: Descriptions[];\n}) => {\n  const interfacesDir = \"./interfaces\";\n\n  const namesFromDescriptions = descriptions.map(({ name }) => name);\n\n  fs.readdir(interfacesDir, (err, interfaceFiles) => {\n    if (err) throw err;\n\n    interfaceFiles.forEach((interfaceFile) => {\n      const name = interfaceFile.split(\".\")[0];\n      if (!namesFromDescriptions.includes(name)) {\n        fs.unlinkSync(`${interfacesDir}/${interfaceFile}`);\n      }\n    });\n  });\n};\n\nconst refreshInterfaces = () => {\n  let descriptions = [];\n\n  try {\n    const pythonFunctions = JSON.parse(\n      fs.readFileSync(\"./symphony/server/python/descriptions.json\", \"utf8\")\n    );\n\n    const typescriptFunctions = JSON.parse(\n      fs.readFileSync(\"./symphony/server/typescript/descriptions.json\", \"utf8\")\n    );\n\n    descriptions = [...pythonFunctions, ...typescriptFunctions];\n  } catch (error) {\n    // TODO: Handle error\n  }\n\n  removeInterfaces({ descriptions });\n  createInterfaces({ descriptions });\n};\n\nrefreshInterfaces();\n"
  },
  {
    "path": "symphony/server/python/describe.py",
    "content": "import os\nimport sys\nimport json\nfrom pydantic import BaseModel\nfrom typing import Any, Callable, Type\n\nsys.path.insert(0, os.path.abspath('functions'))\n\ntemplate = \"\"\"import sys\nimport json\nfrom pydantic import BaseModel, Field\n\n\nclass SymphonyRequest(BaseModel):\n    name: str = Field(description=\"Name of person\")\n\n\nclass SymphonyResponse(BaseModel):\n    greeting: str = Field(description=\"Greeting with name of person\")\n\n\ndef handler(request: SymphonyRequest) -> SymphonyResponse:\n    \\\"\"\"\n     Greet person by name\n    \\\"\"\"\n\n    return SymphonyResponse(\n        greeting='Hello {name}'.format(name=request['name']))\n\"\"\"\n\n\ndef remove_title(schema):\n    if 'title' in schema:\n        schema.pop('title')\n\n    for value in schema.values():\n        if isinstance(value, dict):\n            remove_title(value)\n\n\ndef generate_function_description(name, function: Callable[..., Any], request_model: Type[BaseModel], response_model: Type[BaseModel]) -> dict:\n    request_schema = request_model.model_json_schema()\n    response_schema = response_model.model_json_schema()\n\n    remove_title(request_schema)\n    remove_title(response_schema)\n\n    request_schema = {'type': request_schema['type'], **request_schema}\n    response_schema = {'type': response_schema['type'], **response_schema}\n\n    function_description = {\n        \"name\": name,\n        \"description\": function.__doc__.strip(),\n        \"parameters\": request_schema,\n        \"returns\": response_schema,\n    }\n\n    return function_description\n\n\ndef describe():\n    descriptions = []\n\n    for filename in os.listdir('functions'):\n        if filename.endswith('.py'):\n            with open(os.path.join('functions', filename), 'r+') as file:\n                if file.read().strip() == '':\n                    file.write(template)\n\n            module_name = filename[:-3]\n            module = __import__(f'{module_name}')\n            function = getattr(module, 'handler')\n            symphony_request = getattr(module, 'SymphonyRequest')\n            symphony_response = getattr(module, 'SymphonyResponse')\n            fn_name = module_name + '-py'\n            description = generate_function_description(\n                fn_name, function, symphony_request, symphony_response)\n            descriptions.append(description)\n\n    with open('./symphony/server/python/descriptions.json', 'w') as file:\n        json.dump(descriptions, file, indent=4)\n\n\nif __name__ == \"__main__\":\n    describe()\n"
  },
  {
    "path": "symphony/server/python/descriptions.json",
    "content": "[\n    {\n        \"name\": \"getCoordinates-py\",\n        \"description\": \"Get latitude and longitude from IP address\",\n        \"parameters\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ipAddress\": {\n                    \"description\": \"The IP address; Use 'me' to get own IP address\",\n                    \"type\": \"string\"\n                }\n            },\n            \"required\": [\n                \"ipAddress\"\n            ]\n        },\n        \"returns\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"lat\": {\n                    \"description\": \"The latitude of IP address\",\n                    \"type\": \"number\"\n                },\n                \"lng\": {\n                    \"description\": \"The longitude of IP address\",\n                    \"type\": \"number\"\n                }\n            },\n            \"required\": [\n                \"lat\",\n                \"lng\"\n            ]\n        }\n    }\n]"
  },
  {
    "path": "symphony/server/python/serve.py",
    "content": "import os\nimport sys\nfrom flask import Flask, request\nimport importlib\nimport importlib.util\nfrom watchdog.observers import Observer\nfrom watchdog.events import FileSystemEventHandler\nimport threading\nimport time\nimport logging\n\nlog = logging.getLogger('werkzeug')\nlogging.disable(logging.CRITICAL)\n\napp = Flask(__name__)\n\n\ndef load_handlers():\n    handlers = {}\n    dir_path = os.path.dirname(os.path.realpath(__file__))\n    functions_dir = os.path.join(dir_path, \"../../../functions\")\n\n    for file in os.listdir(functions_dir):\n        if file.endswith(\".py\"):\n            module_name = file[:-3]\n            spec = importlib.util.spec_from_file_location(\n                module_name, os.path.join(functions_dir, file))\n            module = importlib.util.module_from_spec(spec)\n            spec.loader.exec_module(module)\n            handlers[module_name] = module.handler\n\n    return handlers\n\n\n@app.route('/<handler_name>', methods=['POST'])\ndef handle_request(handler_name):\n    handlers = load_handlers()\n\n    if handler_name in handlers:\n        handler = handlers[handler_name]\n        response = handler(request.json)\n        return response.model_dump_json()\n    else:\n        return {\"error\": \"Handler not found\"}, 404\n\n\nclass MyHandler(FileSystemEventHandler):\n    def on_modified(self, event):\n        if not event.src_path.endswith('.py'):\n            return\n\n        print(\"Python file changed: \", event.src_path)\n        os.execv(sys.executable, ['python'] + sys.argv)\n\n\nif __name__ == \"__main__\":\n    event_handler = MyHandler()\n    observer = Observer()\n    observer.schedule(event_handler, path='../../../functions', recursive=True)\n    observer_thread = threading.Thread(target=observer.start)\n    observer_thread.start()\n\n    try:\n        app.run(port=3004)\n    except KeyboardInterrupt:\n        observer.stop()\n\n    observer.join()\n"
  },
  {
    "path": "symphony/server/service.ts",
    "content": "import { Server, WebSocket } from \"ws\";\nimport { createServer } from \"http\";\nimport { createMachine, interpret, EventObject } from \"xstate\";\nimport { assign } from \"@xstate/immer\";\nimport OpenAI from \"openai\";\nimport { pipe } from \"fp-ts/lib/function\";\nimport * as O from \"fp-ts/Option\";\nimport * as dotenv from \"dotenv\";\nimport {\n  decodeFunctionName,\n  encodeFunctionName,\n  getColor,\n  getModelIdFromAssistant,\n  getAssistantFromConnections,\n  getSystemDescription,\n  getUserFromConnections,\n  getDescriptionFromConnection,\n  getNameFromFunction,\n} from \"../utils/functions\";\nimport { Generation, Message, Context } from \"../utils/types\";\nimport { v4 as id } from \"uuid\";\nimport * as S from \"fp-ts/string\";\nimport axios from \"axios\";\nimport * as AR from \"fp-ts/Array\";\nimport { UUID } from \"crypto\";\nimport * as fs from \"fs\";\nimport { FineTuningJob } from \"openai/resources/fine-tuning\";\nimport { FileObject } from \"openai/resources\";\n\ndotenv.config();\n\nconst DATABASE_ENDPOINT = \"http://127.0.0.1:3002\";\n\nconst openai = new OpenAI({\n  apiKey: process.env.OPENAI_API_KEY,\n});\n\ninterface SymphonyEvent extends EventObject {\n  type: \"CLIENT_MESSAGE\";\n  data: Message;\n}\n\nconst server = createServer();\nconst wss = new Server({ server });\n\nconst createGeneration = (\n  message: Message,\n  conversationId: UUID\n): Generation => {\n  return {\n    id: id(),\n    message,\n    conversationId,\n    timestamp: new Date().toISOString(),\n  };\n};\n\nconst machine = createMachine(\n  {\n    id: \"machine\",\n    initial: \"idle\",\n    context: {\n      id: id(),\n      generations: [],\n      connections: [\n        {\n          name: \"assistant\",\n          color: \"#d4d4d4\",\n          description:\n            \"You are a friendly assistant. Keep your responses short.\",\n          modelId: \"gpt-4-1106-preview\",\n        },\n        {\n          name: \"user\",\n          color: getColor(),\n          description: \"I'm a user. I'm here to talk to you.\",\n          modelId: \"human\",\n        },\n      ],\n    },\n    schema: {\n      context: {} as Context,\n    },\n    predictableActionArguments: true,\n    on: {\n      CLIENT_MESSAGE: [\n        {\n          target: \"gpt\",\n          cond: (_, event) => event.data.role === \"user\",\n          actions: [\n            assign((context, event) => {\n              const { generations, id } = context;\n              const { data } = event as SymphonyEvent;\n\n              context.generations = [\n                ...generations,\n                createGeneration(data, id),\n              ];\n            }),\n            \"receiveUserMessageFromClient\",\n            \"sendHistoryToClients\",\n          ],\n        },\n        {\n          target: \"restore\",\n          cond: (_, event) => event.data.role === \"restore\",\n        },\n        {\n          target: \"idle\",\n          cond: (_, event) => event.data.role === \"history\",\n          actions: [\"sendHistoryToClients\"],\n        },\n        {\n          target: \"new\",\n          cond: (_, event) => event.data.role === \"new\",\n        },\n        {\n          target: \"deleteConversation\",\n          cond: (_, event) => event.data.role === \"deleteConversation\",\n        },\n        {\n          target: \"edit\",\n          cond: (_, event) => event.data.role === \"edit\",\n        },\n        {\n          target: \"deleteGeneration\",\n          cond: (_, event) => event.data.role === \"deleteGeneration\",\n        },\n        {\n          target: \"finetune\",\n          cond: (_, event) => event.data.role === \"finetune\",\n        },\n        {\n          target: \"idle\",\n          cond: (_, event) => event.data.role === \"models\",\n          actions: [\"sendModelsToClients\"],\n        },\n        {\n          target: \"idle\",\n          cond: (_, event) => event.data.role === \"personalize\",\n          actions: [\n            assign((context, event) => {\n              const { connections } = context;\n              const { data } = event;\n              const { content: updatedConnection } = data;\n\n              context.connections = pipe(\n                connections,\n                AR.filter(\n                  (connection) => connection.name !== updatedConnection.name\n                ),\n                AR.append(updatedConnection)\n              );\n            }),\n            \"sendContextToClients\",\n          ],\n        },\n        {\n          target: \"switch\",\n          cond: (_, event) => event.data.role === \"switch\",\n          actions: [\n            assign((context, event) => {\n              const { data } = event;\n              const { content: conversationId } = data;\n              context.id = conversationId;\n            }),\n          ],\n        },\n      ],\n    },\n    states: {\n      function: {\n        invoke: {\n          src: (context) =>\n            new Promise((resolve) => {\n              const { generations } = context;\n\n              const toolCalls = pipe(\n                generations,\n                AR.last,\n                O.map(\n                  (generation: Generation) => generation.message.tool_calls\n                ),\n                O.chain(O.fromNullable)\n              );\n\n              if (O.isSome(toolCalls)) {\n                Promise.all(\n                  toolCalls.value.map(async (toolCall) => {\n                    const name = decodeFunctionName(toolCall.function.name);\n                    const args = JSON.parse(toolCall.function.arguments);\n\n                    return axios\n                      .post(\n                        `${\n                          name.includes(\".ts\")\n                            ? \"http://localhost:3003\"\n                            : name.includes(\".py\")\n                            ? `http://0.0.0.0:3004`\n                            : \"\"\n                        }/${getNameFromFunction(name)}`,\n                        args\n                      )\n                      .then((response) => {\n                        const { data } = response;\n\n                        const message = {\n                          tool_call_id: toolCall.id,\n                          role: \"tool\",\n                          name: encodeFunctionName(name),\n                          content: JSON.stringify(data),\n                        };\n\n                        return message;\n                      })\n                      .catch((error) => {\n                        const message = {\n                          tool_call_id: toolCall.id,\n                          role: \"tool\",\n                          name: encodeFunctionName(name),\n                          content: JSON.stringify({\n                            errorMessage: error.message,\n                          }),\n                        };\n\n                        return message;\n                      });\n                  })\n                ).then((messages) => {\n                  resolve(messages);\n                });\n              } else {\n                resolve(null);\n              }\n            }).then((response) => response),\n          onDone: [\n            {\n              target: \"gpt\",\n              cond: (_, event) => event.data,\n              actions: [\n                assign((context, event) => {\n                  const { id, generations } = context;\n                  const { data: messages } = event;\n\n                  const newGenerations = messages.map((message) =>\n                    createGeneration(message, id)\n                  );\n\n                  context.generations = [...generations, ...newGenerations];\n                }),\n                \"sendToolMessagesToClients\",\n              ],\n            },\n            {\n              target: \"idle\",\n            },\n          ],\n        },\n      },\n      gpt: {\n        invoke: {\n          src: (context) => {\n            const pythonFunctions = JSON.parse(\n              fs.readFileSync(\n                \"./symphony/server/python/descriptions.json\",\n                \"utf8\"\n              )\n            );\n\n            const typescriptFunctions = JSON.parse(\n              fs.readFileSync(\n                \"./symphony/server/typescript/descriptions.json\",\n                \"utf8\"\n              )\n            );\n\n            return openai.chat.completions.create({\n              messages: [\n                {\n                  role: \"system\",\n                  content: getSystemDescription(\n                    pipe(\n                      context.connections,\n                      getAssistantFromConnections,\n                      getDescriptionFromConnection\n                    ),\n                    pipe(\n                      context.connections,\n                      getUserFromConnections,\n                      getDescriptionFromConnection\n                    )\n                  ),\n                },\n                ...context.generations.map((generation) => generation.message),\n              ],\n              model: pipe(\n                context.connections,\n                getAssistantFromConnections,\n                getModelIdFromAssistant\n              ),\n              tools: [...typescriptFunctions, ...pythonFunctions].map((fn) => ({\n                type: \"function\",\n                function: fn,\n              })),\n            });\n          },\n          onDone: {\n            target: \"function\",\n            actions: [\n              assign((context, event) => {\n                const { id, generations } = context;\n                const { data } = event;\n                const { choices } = data;\n                const { message } = choices[0];\n\n                context.generations = [\n                  ...generations,\n                  createGeneration(message, id),\n                ];\n              }),\n              \"sendAssistantMessageToClients\",\n            ],\n          },\n          onError: {\n            target: \"idle\",\n            actions: [\n              (_, event) => {\n                console.log(event);\n              },\n            ],\n          },\n        },\n      },\n      new: {\n        invoke: {\n          src: () => Promise.resolve({}),\n          onDone: {\n            target: \"idle\",\n            actions: [\n              assign((context) => {\n                context.id = id();\n                context.generations = [];\n              }),\n            ],\n          },\n        },\n      },\n      restore: {\n        invoke: {\n          src: async () => {},\n          onDone: {\n            target: \"idle\",\n            actions: [\"sendContextToClients\"],\n          },\n        },\n      },\n      switch: {\n        invoke: {\n          src: async (context) => {\n            const { id } = context;\n\n            const { data: generations } = await axios.get(\n              `${DATABASE_ENDPOINT}/generations?conversationId=eq.${id}&order=timestamp`\n            );\n\n            return pipe(\n              generations,\n              AR.filter(\n                (generation: Generation) => generation.conversationId === id\n              )\n            );\n          },\n          onDone: {\n            target: \"idle\",\n            actions: [\n              assign((context, event) => {\n                const { data: generations } = event;\n                context.generations = generations;\n              }),\n              \"sendConversationToClients\",\n            ],\n          },\n        },\n      },\n      deleteConversation: {\n        invoke: {\n          src: async (context) => {\n            const { id } = context;\n\n            await axios\n              .delete(\n                `${DATABASE_ENDPOINT}/generations?conversationId=eq.${id}`,\n                {\n                  headers: {\n                    Prefer: \"return=representation\",\n                  },\n                }\n              )\n              .then((response) => {\n                const deletedGeneration = pipe(response.data, AR.head);\n\n                if (O.isSome(deletedGeneration)) {\n                  wss.clients.forEach((client: WebSocket) => {\n                    client.send(\n                      JSON.stringify({\n                        role: \"deleteConversation\",\n                        content: deletedGeneration.value,\n                      })\n                    );\n                  });\n                }\n              });\n          },\n          onDone: {\n            target: \"new\",\n          },\n        },\n      },\n      edit: {\n        invoke: {\n          src: async (_, event) => {\n            const { data: message } = event;\n            const { content } = message;\n\n            await axios\n              .patch(\n                `${DATABASE_ENDPOINT}/generations?id=eq.${content.id}`,\n                {\n                  message: content.message,\n                },\n                {\n                  headers: {\n                    Prefer: \"return=representation\",\n                  },\n                }\n              )\n              .then((response) => {\n                const updatedGeneration = pipe(response.data, AR.head);\n\n                if (O.isSome(updatedGeneration)) {\n                  wss.clients.forEach((client: WebSocket) => {\n                    client.send(\n                      JSON.stringify({\n                        role: \"edit\",\n                        content: updatedGeneration.value,\n                      })\n                    );\n                  });\n                }\n              });\n\n            return content;\n          },\n          onDone: {\n            target: \"idle\",\n            actions: [\n              assign((context, event) => {\n                const { generations } = context;\n                const { data } = event;\n                const { id, message } = data;\n\n                context.generations = pipe(\n                  generations,\n                  AR.map((generation: Generation) => {\n                    if (generation.id === id) {\n                      return { ...generation, message };\n                    } else {\n                      return generation;\n                    }\n                  })\n                );\n              }),\n            ],\n          },\n        },\n      },\n      deleteGeneration: {\n        invoke: {\n          src: async (_, event) => {\n            const { data: message } = event;\n            const { content: generationId } = message;\n\n            await axios\n              .delete(\n                `${DATABASE_ENDPOINT}/generations?id=eq.${generationId}`,\n                {\n                  headers: {\n                    Prefer: \"return=representation\",\n                  },\n                }\n              )\n              .then((response) => {\n                const deletedGeneration = pipe(response.data, AR.head);\n\n                if (O.isSome(deletedGeneration)) {\n                  wss.clients.forEach((client: WebSocket) => {\n                    client.send(\n                      JSON.stringify({\n                        role: \"deleteGeneration\",\n                        content: deletedGeneration.value,\n                      })\n                    );\n                  });\n                }\n              });\n\n            return generationId;\n          },\n          onDone: {\n            target: \"idle\",\n            actions: [\n              assign((context, event) => {\n                const { generations } = context;\n                const { data: generationId } = event;\n\n                context.generations = pipe(\n                  generations,\n                  AR.filter(\n                    (generation: Generation) => generation.id !== generationId\n                  )\n                );\n              }),\n            ],\n          },\n        },\n      },\n      finetune: {\n        invoke: {\n          src: async (context) => {\n            const { data: generations } = await axios.get(\n              `${DATABASE_ENDPOINT}/generations?order=timestamp`\n            );\n\n            const conversations = generations.reduce((acc, generation) => {\n              const key = generation.conversationId;\n\n              if (!acc[key]) {\n                acc[key] = [\n                  {\n                    role: \"system\",\n                    content: getSystemDescription(\n                      pipe(\n                        context.connections,\n                        getAssistantFromConnections,\n                        getDescriptionFromConnection\n                      ),\n                      pipe(\n                        context.connections,\n                        getUserFromConnections,\n                        getDescriptionFromConnection\n                      )\n                    ),\n                  },\n                ];\n              }\n\n              acc[key].push(generation.message);\n              return acc;\n            }, {});\n\n            const conversationsJsonl = Object.values(conversations)\n              .map((conversation) => JSON.stringify({ messages: conversation }))\n              .join(\"\\n\");\n\n            fs.writeFile(\n              \"./symphony/server/training-data.jsonl\",\n              conversationsJsonl,\n              () => {}\n            );\n\n            return openai.files\n              .create({\n                file: fs.createReadStream(\n                  \"./symphony/server/training-data.jsonl\"\n                ),\n                purpose: \"fine-tune\",\n              })\n              .then((file: FileObject) => {\n                return openai.fineTuning.jobs\n                  .create({\n                    training_file: file.id,\n                    model: pipe(\n                      context.connections,\n                      getAssistantFromConnections,\n                      getModelIdFromAssistant\n                    ),\n                  })\n                  .then((job: FineTuningJob) => {\n                    return job;\n                  });\n              });\n          },\n          onDone: {\n            target: \"idle\",\n            actions: [\"sendFinetuneMessageToClients\"],\n          },\n        },\n      },\n      idle: {},\n    },\n  },\n  {\n    actions: {\n      receiveUserMessageFromClient: async (context) => {\n        const { generations } = context;\n\n        const recentUserGeneration = pipe(\n          generations,\n          AR.findLast(\n            (generation: Generation) => generation.message.role === \"user\"\n          )\n        );\n\n        if (O.isSome(recentUserGeneration)) {\n          wss.clients.forEach((client: WebSocket) => {\n            client.send(JSON.stringify(recentUserGeneration.value));\n          });\n\n          await axios.post(\n            `${DATABASE_ENDPOINT}/generations`,\n            recentUserGeneration.value\n          );\n        }\n      },\n      sendAssistantMessageToClients: async (context) => {\n        const { generations } = context;\n\n        const recentAssistantGeneration = pipe(\n          generations,\n          AR.findLast(\n            (generation: Generation) => generation.message.role === \"assistant\"\n          )\n        );\n\n        if (O.isSome(recentAssistantGeneration)) {\n          wss.clients.forEach((client: WebSocket) => {\n            client.send(JSON.stringify(recentAssistantGeneration.value));\n          });\n\n          await axios.post(\n            `${DATABASE_ENDPOINT}/generations`,\n            recentAssistantGeneration.value\n          );\n        }\n      },\n      sendToolMessagesToClients: async (context, event) => {\n        const { generations } = context;\n        const { data: messages } = event;\n\n        messages.forEach(async (message: Message) => {\n          const toolGeneration = pipe(\n            generations,\n            AR.findFirst(\n              (generation: Generation) =>\n                generation.message.role === \"tool\" &&\n                generation.message.tool_call_id === message.tool_call_id\n            )\n          );\n\n          if (O.isSome(toolGeneration)) {\n            wss.clients.forEach((client: WebSocket) => {\n              client.send(JSON.stringify(toolGeneration.value));\n            });\n\n            await axios.post(\n              `${DATABASE_ENDPOINT}/generations`,\n              toolGeneration.value\n            );\n          }\n        });\n      },\n      sendConversationToClients: (context) => {\n        const { generations } = context;\n\n        wss.clients.forEach((client: WebSocket) => {\n          client.send(\n            JSON.stringify({\n              role: \"switch\",\n              content: generations.filter(\n                (generation) => generation.message.role !== \"system\"\n              ),\n            })\n          );\n        });\n      },\n      sendContextToClients: (context) => {\n        wss.clients.forEach((client: WebSocket) => {\n          client.send(\n            JSON.stringify({\n              role: \"restore\",\n              content: context,\n            })\n          );\n        });\n      },\n      sendFinetuneMessageToClients: (_, event) => {\n        const { data: job } = event;\n\n        wss.clients.forEach((client: WebSocket) => {\n          client.send(\n            JSON.stringify({\n              role: \"finetune\",\n              content: job,\n            })\n          );\n        });\n      },\n      sendHistoryToClients: async () => {\n        const { data: generations } = await axios.get(\n          `${DATABASE_ENDPOINT}/generations?order=timestamp`\n        );\n\n        const history = pipe(\n          generations,\n          AR.map((generation: Generation) => generation.conversationId),\n          AR.uniq(S.Eq),\n          AR.map((conversationId) =>\n            pipe(\n              generations,\n              AR.filter(\n                (generation: Generation) =>\n                  generation.conversationId === conversationId\n              ),\n              AR.head,\n              O.map(({ conversationId, message, timestamp }) => ({\n                id: conversationId,\n                timestamp,\n                message,\n              })),\n              O.toUndefined\n            )\n          ),\n          AR.reverse\n        );\n\n        wss.clients.forEach((client: WebSocket) => {\n          client.send(\n            JSON.stringify({\n              role: \"history\",\n              content: history,\n            })\n          );\n        });\n      },\n      sendModelsToClients: async () => {\n        const { data: models } = await openai.models.list();\n\n        wss.clients.forEach((client: WebSocket) => {\n          client.send(\n            JSON.stringify({\n              role: \"models\",\n              content: models,\n            })\n          );\n        });\n      },\n    },\n  }\n);\n\nconst service = interpret(machine).start();\n\ntype Data = string | Buffer | ArrayBuffer | Buffer[] | ArrayBufferView;\n\nwss.on(\"connection\", (connection: WebSocket) => {\n  connection.on(\"message\", (message: Data) => {\n    const decodedMessage = message.toString();\n    const parsedMessage = JSON.parse(decodedMessage);\n\n    const symphonyEvent: SymphonyEvent = {\n      type: \"CLIENT_MESSAGE\",\n      data: parsedMessage,\n    };\n\n    service.send(symphonyEvent);\n  });\n});\n\nserver.listen(3001);\n"
  },
  {
    "path": "symphony/server/typescript/describe.ts",
    "content": "import * as ts from \"typescript\";\nimport * as fs from \"fs\";\nimport { Properties } from \"../../utils/types\";\n\nconst template = `/**\n * name: Name of person\n */\ninterface SymphonyRequest {\n  name: string;\n}\n\n/**\n * greeting: Greeting with name of person\n */\ninterface SymphonyResponse {\n  greeting: string;\n}\n\n/**\n * Greet person by name\n */\nexport const handler = (request: SymphonyRequest): SymphonyResponse => {\n  const { name } = request;\n\n  return {\n    greeting: \\`Hello \\${name}\\`,\n  };\n};\n`;\n\nconst FUNCTIONS_DIRECTORY = \"./functions\";\n\ninterface Parameters {\n  type: string;\n  properties: Properties;\n  required: string[];\n}\n\ntype Returns = Parameters;\n\ninterface Schema {\n  name: string;\n  description: string;\n  parameters: Parameters;\n  returns: Returns;\n}\n\nfunction getSchema(propertyType) {\n  if (propertyType === \"string\") {\n    return { type: \"string\" };\n  } else if (propertyType === \"number\") {\n    return { type: \"number\" };\n  } else if (propertyType === \"boolean\") {\n    return { type: \"boolean\" };\n  } else if (propertyType.includes(\"[]\")) {\n    return { type: \"array\", items: { type: propertyType.replace(\"[]\", \"\") } };\n  }\n}\n\nfunction hasConstArrowFunction(node: ts.Node) {\n  if (ts.isArrowFunction(node) && ts.isVariableDeclaration(node.parent)) {\n    return true;\n  }\n  return node.getChildren().some((child) => hasConstArrowFunction(child));\n}\n\nfunction extractParameters(node: ts.InterfaceDeclaration) {\n  const parameters: Parameters = {\n    type: \"object\",\n    properties: {},\n    required: [],\n  };\n\n  const jsDocComments = ts.getJSDocCommentsAndTags(node);\n\n  for (const member of node.members) {\n    if (ts.isPropertySignature(member)) {\n      const name = member.name.getText();\n      const type = member.type.getText();\n\n      parameters.properties[name] = getSchema(type);\n\n      if (!member.questionToken) {\n        parameters.required.push(name);\n      }\n\n      for (const comment of jsDocComments) {\n        const commentText = comment.getFullText();\n        const propertyCommentMatch = new RegExp(`${name}: (.*)`).exec(\n          commentText\n        );\n\n        if (propertyCommentMatch && propertyCommentMatch[1]) {\n          parameters.properties[name][\"description\"] =\n            propertyCommentMatch[1].trim();\n        }\n      }\n    }\n  }\n\n  return parameters;\n}\n\nfunction generateSchema(sourceFile: ts.SourceFile, fileName: string) {\n  const schema: Schema = {\n    name: \"\",\n    description: \"\",\n    parameters: {\n      type: \"object\",\n      properties: {},\n      required: [],\n    },\n    returns: {\n      type: \"object\",\n      properties: {},\n      required: [],\n    },\n  };\n\n  ts.forEachChild(sourceFile, (node) => {\n    if (ts.isInterfaceDeclaration(node)) {\n      if (node.name.text === \"SymphonyRequest\") {\n        schema.parameters = extractParameters(node);\n      } else if (node.name.text === \"SymphonyResponse\") {\n        schema.returns = extractParameters(node);\n      }\n    }\n\n    if (\n      ts.isFunctionDeclaration(node) ||\n      (ts.isVariableStatement(node) && hasConstArrowFunction(node))\n    ) {\n      const jsDocComments = ts.getJSDocCommentsAndTags(node);\n\n      for (const comment of jsDocComments) {\n        const commentText = comment.getFullText();\n        const propertyCommentMatch = new RegExp(/\\* (.*)/).exec(commentText);\n\n        if (propertyCommentMatch && propertyCommentMatch[1]) {\n          schema.description = propertyCommentMatch[1].trim();\n        }\n      }\n    }\n  });\n\n  schema.name = fileName.replace(\".\", \"-\");\n  return schema;\n}\n\nconst describe = () => {\n  const readFiles = new Promise((resolve, reject) => {\n    const schemas = [] as Schema[];\n\n    fs.readdir(FUNCTIONS_DIRECTORY, (error, files) => {\n      if (error) {\n        reject(error);\n      }\n\n      files\n        .filter((fileName) => fileName.endsWith(\".ts\"))\n        .forEach((fileName) => {\n          const content = fs.readFileSync(\n            `${FUNCTIONS_DIRECTORY}/${fileName}`,\n            \"utf8\"\n          );\n\n          const sourceFile = ts.createSourceFile(\n            \"temp.ts\",\n            content,\n            ts.ScriptTarget.Latest,\n            true\n          );\n\n          if (sourceFile.statements.length === 0) {\n            fs.writeFileSync(\n              `${FUNCTIONS_DIRECTORY}/${fileName}`,\n              template,\n              \"utf8\"\n            );\n          } else {\n            const schema = generateSchema(sourceFile, fileName);\n            schemas.push(schema);\n          }\n        });\n\n      resolve(schemas);\n    });\n  });\n\n  readFiles\n    .then((metadatas) => {\n      fs.writeFileSync(\n        \"./symphony/server/typescript/descriptions.json\",\n        JSON.stringify(metadatas, null, 2)\n      );\n    })\n    .catch((error) => console.log(error));\n};\n\ndescribe();\n"
  },
  {
    "path": "symphony/server/typescript/descriptions.json",
    "content": "[\n  {\n    \"name\": \"getWeather-ts\",\n    \"description\": \"Gets temperature of a city\",\n    \"parameters\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"lat\": {\n          \"type\": \"number\",\n          \"description\": \"Latitude of the city\"\n        },\n        \"lon\": {\n          \"type\": \"number\",\n          \"description\": \"Longitude of the city\"\n        }\n      },\n      \"required\": [\n        \"lat\",\n        \"lon\"\n      ]\n    },\n    \"returns\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"temperature\": {\n          \"type\": \"number\",\n          \"description\": \"The temperature of the city\"\n        },\n        \"unit\": {\n          \"type\": \"string\",\n          \"description\": \"The unit of the temperature\"\n        }\n      },\n      \"required\": [\n        \"temperature\",\n        \"unit\"\n      ]\n    }\n  },\n  {\n    \"name\": \"kelvinToCelsius-ts\",\n    \"description\": \"Converts Kelvin to Celsius\",\n    \"parameters\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"value\": {\n          \"type\": \"number\",\n          \"description\": \"Value in Kelvin\"\n        }\n      },\n      \"required\": [\n        \"value\"\n      ]\n    },\n    \"returns\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"value\": {\n          \"type\": \"number\",\n          \"description\": \"Value in Celsius\"\n        }\n      },\n      \"required\": [\n        \"value\"\n      ]\n    }\n  }\n]"
  },
  {
    "path": "symphony/server/typescript/serve.ts",
    "content": "import { FastifyInstance, fastify } from \"fastify\";\nimport * as fs from \"fs\";\nimport * as chokidar from \"chokidar\";\n\nlet fastifyInstance: FastifyInstance;\n\nconst startServer = async () => {\n  fastifyInstance = fastify({\n    logger: false,\n  });\n\n  const handlerFiles = fs\n    .readdirSync(\"./functions\")\n    .filter((file) => file.endsWith(\".ts\"));\n\n  handlerFiles.forEach(async (file) => {\n    try {\n      const modulePath = require.resolve(`../../../functions/${file}`);\n      delete require.cache[modulePath];\n\n      const { handler } = await import(modulePath);\n      const routePath = `/${file.slice(0, -3)}`;\n\n      fastifyInstance.post(routePath, async (request, reply) => {\n        const payload = request.body;\n        return handler(payload, reply);\n      });\n    } catch (error) {\n      console.error(error);\n    }\n  });\n\n  try {\n    await fastifyInstance.listen({ port: 3003 });\n  } catch (err) {\n    fastifyInstance.log.error(err);\n    process.exit(1);\n  }\n};\n\nconst watcher = chokidar.watch(\"./functions/*.ts\", {\n  persistent: true,\n});\n\nconst restartServer = async () => {\n  if (fastifyInstance) {\n    await fastifyInstance.close();\n  }\n\n  await startServer();\n};\n\nwatcher\n  .on(\"ready\", () => {\n    startServer().catch(console.error);\n  })\n  .on(\"change\", () => {\n    restartServer().catch(console.error);\n  })\n  .on(\"unlink\", () => {\n    restartServer().catch(console.error);\n  });\n"
  },
  {
    "path": "symphony/server/watch.ts",
    "content": "import * as chokidar from \"chokidar\";\nimport { ChildProcess, exec } from \"child_process\";\n\nlet process: ChildProcess;\n\nconst generatePythonDescriptions = () => {\n  if (process) process.kill();\n  process = exec(\"yarn nps describe.py\");\n};\n\nconst pythonWatcher = chokidar.watch(\"./functions/*.py\");\n\npythonWatcher\n  .on(\"ready\", generatePythonDescriptions)\n  .on(\"add\", generatePythonDescriptions)\n  .on(\"change\", generatePythonDescriptions)\n  .on(\"unlink\", generatePythonDescriptions);\n\nconst generateTypescriptDescriptions = () => {\n  if (process) process.kill();\n  process = exec(\"yarn nps describe.ts\");\n};\n\nconst typescriptWatcher = chokidar.watch(\"./functions/*.ts\");\n\ntypescriptWatcher\n  .on(\"ready\", generateTypescriptDescriptions)\n  .on(\"add\", generateTypescriptDescriptions)\n  .on(\"change\", generateTypescriptDescriptions)\n  .on(\"unlink\", generateTypescriptDescriptions);\n\nconst generateInterfaces = () => {\n  if (process) process.kill();\n  process = exec(\"yarn nps jig\");\n};\n\nconst descriptionsWatcher = chokidar.watch(\n  \"./symphony/server/*/descriptions.json\"\n);\n\ndescriptionsWatcher\n  .on(\"ready\", generateInterfaces)\n  .on(\"change\", generateInterfaces);\n"
  },
  {
    "path": "symphony/utils/functions.ts",
    "content": "import { Connection } from \"./types\";\nimport * as O from \"fp-ts/Option\";\nimport * as AR from \"fp-ts/Array\";\nimport { pipe } from \"fp-ts/function\";\n\nexport const encodeFunctionName = (name: string): string => {\n  return name.replace(\".\", \"-\");\n};\n\nexport const decodeFunctionName = (name: string): string => {\n  return name.replace(\"-\", \".\");\n};\n\nexport const getNameFromFunction = (name: string): string => {\n  return name.slice(0, -3);\n};\n\nexport const getColor = (): string => {\n  const colors = [\"#EB5528\", \"#79D760\", \"#EB55F7\", \"#3A063E\"];\n  return colors[Math.floor(Math.random() * colors.length)];\n};\n\nexport const getAssistantFromConnections = (\n  connections: Connection[]\n): O.Option<Connection> =>\n  pipe(\n    connections,\n    AR.findFirst((connection) => connection.name === \"assistant\")\n  );\n\nexport const getUserFromConnections = (\n  connections: Connection[]\n): O.Option<Connection> =>\n  pipe(\n    connections,\n    AR.findFirst((connection) => connection.name === \"user\")\n  );\n\nexport const getDescriptionFromConnection = (\n  connection: O.Option<Connection>\n): O.Option<string> =>\n  pipe(\n    connection,\n    O.map((connection) => connection.description)\n  );\n\nexport const getModelIdFromAssistant = (\n  assistant: O.Option<Connection>\n): string =>\n  pipe(\n    assistant,\n    O.map((assistant) => assistant.modelId),\n    O.getOrElse(() => \"gpt-4\")\n  );\n\nexport const getSystemDescription = (\n  assistantDescription: O.Option<string>,\n  userDescription: O.Option<string>\n): string => {\n  const assistant = pipe(\n    assistantDescription,\n    O.getOrElse(\n      () => \"You are a friendly assistant. Keep your responses short.\"\n    )\n  );\n\n  const user = pipe(\n    userDescription,\n    O.getOrElse(() => \"I'm a user. I'm here to talk to you.\")\n  );\n\n  return `${assistant} ${user}`;\n};\n"
  },
  {
    "path": "symphony/utils/types.ts",
    "content": "import { UUID } from \"crypto\";\n\nexport interface FunctionCall {\n  name: string;\n  arguments: string;\n}\n\nexport interface Tool {\n  id: string;\n  type: string;\n  function: FunctionCall;\n}\n\nexport interface Message {\n  role: string;\n  content: string;\n  name: string;\n  tool_call_id?: string;\n  tool_calls?: Tool[];\n}\n\nexport interface Generation {\n  id: UUID;\n  conversationId: UUID;\n  timestamp: string;\n  message: Message;\n}\n\nexport interface Connection {\n  name: string;\n  color: string;\n  description: string;\n  modelId: string;\n}\n\nexport interface Context {\n  id: UUID;\n  generations: Generation[];\n  connections: Connection[];\n}\n\nexport interface Property {\n  type: string;\n  description?: string;\n  items?: {\n    type: string;\n  };\n}\n\nexport interface Properties {\n  [key: string]: Property;\n}\n\nexport interface Descriptions {\n  name: string;\n  description?: string;\n  parameters?: Properties;\n  returns?: Properties;\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"noEmitOnError\": false,\n    \"resolveJsonModule\": true,\n    \"jsx\": \"react\"\n  }\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { defineConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react-swc\";\n\nexport default defineConfig({\n  plugins: [react()],\n  root: \"./symphony/client\",\n  publicDir: \"./symphony/client/public\",\n  css: {\n    preprocessorOptions: {\n      scss: {\n        quietDeps: true,\n      },\n    },\n  },\n  clearScreen: false,\n});\n"
  }
]