Repository: symphony-hq/symphony
Branch: main
Commit: f1f220210368
Files: 35
Total size: 92.3 KB
Directory structure:
gitextract_umptbr62/
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── LICENSE.md
├── README.md
├── functions/
│ ├── getCoordinates.py
│ ├── getWeather.ts
│ └── kelvinToCelsius.ts
├── interfaces/
│ ├── getCoordinates-py.tsx
│ ├── getWeather-ts.tsx
│ └── kelvinToCelsius-ts.tsx
├── package-scripts.js
├── package.json
├── requirements.txt
├── symphony/
│ ├── client/
│ │ ├── colors.scss
│ │ ├── index.html
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── database/
│ │ ├── destroy.sh
│ │ ├── postgrest.conf
│ │ └── setup.sh
│ ├── server/
│ │ ├── jig.ts
│ │ ├── python/
│ │ │ ├── describe.py
│ │ │ ├── descriptions.json
│ │ │ └── serve.py
│ │ ├── service.ts
│ │ ├── typescript/
│ │ │ ├── describe.ts
│ │ │ ├── descriptions.json
│ │ │ └── serve.ts
│ │ └── watch.ts
│ └── utils/
│ ├── functions.ts
│ └── types.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.js
================================================
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"plugin:prettier/recommended",
],
ignorePatterns: [".eslintrc.js", "package-scripts.js"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh", "prettier"],
rules: {
"prettier/prettier": "error",
},
};
================================================
FILE: .gitignore
================================================
/node_modules
/venv
.DS_Store
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*
__pycache__/
training-data.jsonl
symphony/*.js
================================================
FILE: .prettierrc
================================================
{
"endOfLine": "lf",
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"bracketSpacing": true
}
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2023 Jeremy Philemon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================

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