├── .github └── workflows │ └── main.yml ├── .gitignore ├── .vscode ├── cspell.json └── settings.json ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── cmd ├── create.go ├── integrations.go ├── keys.go ├── lab.go ├── root.go └── serve.go ├── docs └── img │ ├── api-key.png │ ├── chat.png │ ├── header.png │ └── templates.png ├── environment ├── environment.go └── integration.go ├── go.mod ├── go.sum ├── jupyterlab_extension ├── .eslintignore ├── .eslintrc.js ├── .github │ └── workflows │ │ ├── binder-on-pr.yml │ │ ├── build.yml │ │ ├── check-release.yml │ │ └── update-integration-tests.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .stylelintrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASE.md ├── babel.config.js ├── binder │ ├── environment.yml │ └── postBuild ├── custom.d.ts ├── install.json ├── jest.config.js ├── langforge │ └── __init__.py ├── notebooks │ ├── api-agent.ipynb │ ├── baby-agi.ipynb │ ├── chat-creative.ipynb │ ├── chat-deterministic.ipynb │ ├── code.ipynb │ ├── qa-pdf.ipynb │ └── qa-txt.ipynb ├── package.json ├── pyproject.toml ├── setup.py ├── src │ ├── __tests__ │ │ └── langforge.spec.ts │ ├── chat.tsx │ ├── index.ts │ ├── interfaces.ts │ ├── jupyter.ts │ ├── launcher-utils.ts │ ├── markdown-utils.ts │ ├── monkey-patch.ts │ ├── notebook.ts │ ├── sidebar.tsx │ └── svg.ts ├── style │ ├── base.css │ ├── index.css │ └── index.js ├── tsconfig.json ├── ui-tests │ ├── README.md │ ├── jupyter_server_test_config.py │ ├── package.json │ ├── playwright.config.js │ └── tests │ │ └── langforge.spec.ts ├── webpack.config.js └── yarn.lock ├── main.go ├── npm ├── langforge.js └── package.json ├── pypi ├── langforge │ ├── __init__.py │ └── main.py └── setup.py ├── python ├── env.go ├── files.go ├── files │ ├── integrations.yaml │ ├── server.py │ └── startup │ │ ├── 00-dotenv.py │ │ ├── 10-extension-support.py │ │ └── 20-utilities.py ├── handler.go ├── integration.go ├── jupyter.go └── package.go ├── system ├── dotenv.go └── utils.go ├── test_cli.py └── tui ├── editor.go ├── prompt.go └── utils.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build, Test, and Release 2 | on: 3 | push: 4 | branches: 5 | - release 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Install upx 11 | run: sudo apt-get install -y upx 12 | 13 | - name: Install jq 14 | run: sudo apt-get install -y jq 15 | 16 | - name: Checkout repository 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Setup Python 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: "3.x" 25 | 26 | - name: Install Python packages 27 | run: python3 -m pip install jupyterlab build 28 | 29 | - name: Install python packaging tools 30 | run: pip install setuptools wheel twine 31 | 32 | - name: Setup Go 33 | uses: actions/setup-go@v2 34 | with: 35 | go-version: "1.x" 36 | 37 | - name: Setup Node 38 | uses: actions/setup-node@v2 39 | with: 40 | node-version: "14.x" 41 | registry-url: "https://registry.npmjs.org" 42 | 43 | - name: Run bump target 44 | run: make bump 45 | 46 | - name: Build and archive 47 | run: make 48 | 49 | - name: Upload build artifacts 50 | uses: actions/upload-artifact@v2 51 | with: 52 | name: build-artifacts 53 | path: build 54 | 55 | test-macos: 56 | runs-on: macos-latest 57 | needs: build 58 | steps: 59 | - name: Check out repository 60 | uses: actions/checkout@v2 61 | 62 | - name: Download build artifacts 63 | uses: actions/download-artifact@v2 64 | with: 65 | name: build-artifacts 66 | path: build 67 | 68 | - name: Setup Python 69 | uses: actions/setup-python@v2 70 | with: 71 | python-version: "3.x" 72 | 73 | - name: Install the package 74 | run: pip install build/pypi/dist/langforge_ai*.whl 75 | 76 | - name: Run CLI tests 77 | run: python -m unittest test_cli.py 78 | 79 | test-linux: 80 | runs-on: ubuntu-latest 81 | needs: build 82 | steps: 83 | - name: Check out repository 84 | uses: actions/checkout@v2 85 | 86 | - name: Download build artifacts 87 | uses: actions/download-artifact@v2 88 | with: 89 | name: build-artifacts 90 | path: build 91 | 92 | - name: Setup Python 93 | uses: actions/setup-python@v2 94 | with: 95 | python-version: "3.x" 96 | 97 | - name: Install the package 98 | run: pip install build/pypi/dist/langforge_ai*.whl 99 | 100 | - name: Run CLI tests 101 | run: python -m unittest test_cli.py 102 | 103 | test-windows: 104 | runs-on: windows-latest 105 | needs: build 106 | steps: 107 | - name: Check out repository 108 | uses: actions/checkout@v2 109 | 110 | - name: Download build artifacts 111 | uses: actions/download-artifact@v2 112 | with: 113 | name: build-artifacts 114 | path: build 115 | 116 | - name: Setup Python 117 | uses: actions/setup-python@v2 118 | with: 119 | python-version: "3.x" 120 | 121 | - name: Set up WSL 122 | uses: Vampire/setup-wsl@v2 123 | with: 124 | distribution: Ubuntu-20.04 125 | update: "true" 126 | additional-packages: python3 python3-pip 127 | 128 | - name: Install the package 129 | run: | 130 | $whl_file = Get-ChildItem -Path build/pypi/dist -Filter langforge_ai*.whl | Select-Object -First 1 131 | $whl_path = Join-Path -Path "build/pypi/dist" -ChildPath $whl_file.Name 132 | pip install $whl_path 133 | shell: powershell 134 | 135 | - name: Run CLI tests in PowerShell 136 | run: python -m unittest test_cli.py 137 | shell: powershell 138 | 139 | - name: Run CLI tests in cmd.exe 140 | run: python -m unittest test_cli.py 141 | shell: cmd 142 | 143 | - name: Install the package in WSL 144 | run: pip install build/pypi/dist/langforge_ai*.whl 145 | shell: wsl-bash_Ubuntu-20.04 {0} 146 | 147 | - name: Run CLI tests in wsl 148 | run: python3 -m unittest test_cli.py 149 | shell: wsl-bash_Ubuntu-20.04 {0} 150 | 151 | release: 152 | runs-on: ubuntu-latest 153 | needs: [test-macos, test-linux, test-windows] 154 | steps: 155 | - name: Check out repository 156 | uses: actions/checkout@v2 157 | with: 158 | fetch-depth: 0 159 | 160 | - name: Download build artifacts 161 | uses: actions/download-artifact@v2 162 | with: 163 | name: build-artifacts 164 | path: build 165 | 166 | - name: Setup Python 167 | uses: actions/setup-python@v2 168 | with: 169 | python-version: "3.x" 170 | 171 | - name: Setup Node 172 | uses: actions/setup-node@v2 173 | with: 174 | node-version: "14.x" 175 | registry-url: "https://registry.npmjs.org" 176 | 177 | - name: Run bump target again 178 | run: make bump 179 | 180 | - name: Install python packaging tools 181 | run: pip install setuptools wheel twine 182 | 183 | - name: Publish on PyPI and NPM 184 | run: make release 185 | env: 186 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 187 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 188 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 189 | 190 | - name: Commit and push changes 191 | run: | 192 | git config --global user.name "GitHub Actions" 193 | git config --global user.email "actions@github.com" 194 | git add . 195 | git commit -m "Bump version and publish" 196 | git push https://${{ secrets.GH_TOKEN }}@github.com/mme/langforge.git 197 | 198 | - name: Merge to main branch 199 | run: | 200 | git checkout main 201 | git merge --no-ff release 202 | git push https://${{ secrets.GH_TOKEN }}@github.com/mme/langforge.git 203 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | # *.exe 6 | # *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | .DS_Store 24 | 25 | langforge.egg-info 26 | 27 | 28 | /build/ 29 | pypi/langforge_ai.egg-info 30 | 31 | pypi/langforge/bin 32 | pypi/langforge_cli.egg-info 33 | pypi/build/ 34 | pypi/dist 35 | npm/bin 36 | python/files/langforge-0.1.0-py3-none-any.whl 37 | 38 | .venv 39 | 40 | npm/langforge*.tgz 41 | 42 | *.pyc -------------------------------------------------------------------------------- /.vscode/cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "words": [ 5 | "langforge", 6 | "langchain", 7 | "myapp", 8 | "promptui", 9 | "chzyer", 10 | "manifoldco", 11 | "venv", 12 | "openai", 13 | "LAVIS", 14 | "wolframalpha", 15 | "AlecAivazis", 16 | "cerebrium", 17 | "gooseai", 18 | "cerebriumai", 19 | "huggingface", 20 | "promptlayer", 21 | "HUGGINGFACEHUB", 22 | "serpapi", 23 | "googlesearchapi", 24 | "natbot", 25 | "faiss", 26 | "opensearch", 27 | "deeplake", 28 | "spacy", 29 | "nltk", 30 | "punkt", 31 | "detectron", 32 | "libmagic", 33 | "poppler", 34 | "tesseract", 35 | "nomic", 36 | "tiktoken", 37 | "weaviate", 38 | "atlasdb", 39 | "chromadb", 40 | "deepinfra", 41 | "forefrontai", 42 | "graphsignal", 43 | "hazyresearch", 44 | "helicone", 45 | "nlpcloud", 46 | "runhouse", 47 | "stochasticai", 48 | "markus", 49 | "ecker", 50 | "stochasticx", 51 | "pterm", 52 | "atomicgo", 53 | "putils", 54 | "pterm", 55 | "checkmark", 56 | "torchaudio", 57 | "torchvision", 58 | "layoutparser", 59 | "layoutmodels", 60 | "joho", 61 | "godotenv", 62 | "ipykernel", 63 | "jupyterlab", 64 | "dotenv", 65 | "apputils", 66 | "labextension", 67 | "ipython", 68 | "IPYTHONDIR", 69 | "extensionmanager", 70 | "lumino", 71 | "svgstr", 72 | "Autosize", 73 | "evalue", 74 | "ename", 75 | "linkify", 76 | "hljs", 77 | "monkeypatch", 78 | "testutils", 79 | "ipynb", 80 | "ipywidgets", 81 | "jsonify", 82 | "mkmsg", 83 | "docmanager", 84 | "aarch", 85 | "cmdclass", 86 | "setuptools", 87 | "pypi" 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.pylintArgs": ["--ignore=__pycache__"], 3 | "files.exclude": { 4 | "**/__pycache__": true, 5 | "**/.ipynb_checkpoints": true 6 | }, 7 | "[go]": { 8 | "editor.insertSpaces": true, 9 | "editor.formatOnSave": true, 10 | "editor.defaultFormatter": "golang.go", 11 | "rewrap.autoWrap.enabled": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2023 Markus Ecker 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := all 2 | 3 | # Project Variables 4 | VERSION := $(file < VERSION) 5 | 6 | # JupyterLab Variables 7 | JL_WHEEL_FILE = python/files/langforge-0.1.0-py3-none-any.whl 8 | JL_IPYNB_FILES = $(wildcard jupyterlab_extension/notebooks/*.ipynb) 9 | JL_TS_FILES = $(wildcard jupyterlab_extension/src/*.ts) 10 | JL_CSS_JS_FILES = $(wildcard jupyterlab_extension/style/*.css) $(wildcard jupyterlab_extension/style/*.js) 11 | JL_JSON_FILES = jupyterlab_extension/package.json jupyterlab_extension/tsconfig.json 12 | JL_WEBPACK_CONFIG = jupyterlab_extension/webpack.config.js 13 | 14 | # Go Variables 15 | GO_BUILD_FILE=build/golang/.done 16 | GO_SOURCES = $(shell find cmd environment python system tui -type f) 17 | 18 | # Python Variables 19 | PY_SOURCES = $(wildcard pypi/*.py) $(wildcard pypi/langforge/*.py) 20 | 21 | # JS Variables 22 | JS_SOURCES = $(wildcard npm/*.js) 23 | JS_PACKAGE_JSON = npm/package.json 24 | 25 | all: jupyterlab go compress copy pypi npm archive 26 | 27 | $(JL_WHEEL_FILE): $(JL_IPYNB_FILES) $(JL_TS_FILES) $(JL_CSS_JS_FILES) $(JL_JSON_FILES) $(JL_WEBPACK_CONFIG) 28 | cd jupyterlab_extension && \ 29 | jlpm install && \ 30 | jlpm run build && \ 31 | python -m build && \ 32 | mv dist/langforge-0.1.0-py3-none-any.whl ../python/files/langforge-0.1.0-py3-none-any.whl && \ 33 | rm -rf dist 34 | 35 | jupyterlab: $(JL_WHEEL_FILE) 36 | 37 | $(GO_BUILD_FILE): $(GO_SOURCES) 38 | mkdir -p build/golang && \ 39 | GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o build/golang/langforge-windows-amd64.exe && \ 40 | GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o build/golang/langforge-macos-amd64 && \ 41 | GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o build/golang/langforge-macos-arm64 && \ 42 | CGO=0 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o build/golang/langforge-linux-amd64 && \ 43 | CGO=0 CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o build/golang/langforge-linux-arm64 && \ 44 | touch $(GO_BUILD_FILE) 45 | 46 | go: $(GO_BUILD_FILE) 47 | 48 | compress: $(GO_BUILD_FILE) 49 | upx --brute build/golang/langforge-windows-amd64.exe && \ 50 | upx --brute build/golang/langforge-macos-amd64 && \ 51 | upx --brute build/golang/langforge-linux-amd64 && \ 52 | upx --brute build/golang/langforge-linux-arm64 && \ 53 | touch $(GO_BUILD_FILE) 54 | 55 | copy: $(GO_BUILD_FILE) 56 | mkdir -p pypi/langforge/bin && \ 57 | mkdir -p npm/bin && \ 58 | cp build/golang/* pypi/langforge/bin && \ 59 | cp build/golang/* npm/bin && \ 60 | touch $(GO_BUILD_FILE) 61 | 62 | pypi: $(GO_BUILD_FILE) $(PY_SOURCES) 63 | cd pypi && \ 64 | python setup.py bdist_wheel && \ 65 | cd .. && touch $(GO_BUILD_FILE) 66 | 67 | npm: $(GO_BUILD_FILE) $(JS_SOURCES) $(JS_PACKAGE_JSON) 68 | cd npm && \ 69 | npm pack && \ 70 | cd .. && touch $(GO_BUILD_FILE) 71 | 72 | archive: $(GO_BUILD_FILE) 73 | rm -rf build/pypi && \ 74 | rm -rf build/npm && \ 75 | cp -r pypi build/pypi && \ 76 | cp -r npm build/npm && \ 77 | touch $(GO_BUILD_FILE) 78 | 79 | clean: 80 | rm -rf build 81 | rm -rf pypi/build 82 | rm -rf pypi/dist 83 | rm -rf pypi/langforge_ai.egg-info 84 | rm -rf pypi/.venv 85 | rm -rf python/files/langforge-0.1.0-py3-none-any.whl 86 | rm -rf jupyterlab_extension/lib 87 | rm -rf jupyterlab_extension/node_modules 88 | rm -rf jupyterlab_extension/tsconfig.tsbuildinfo 89 | rm -rf pypi/langforge/bin 90 | rm -rf npm/bin 91 | rm -rf npm/langforge*.tgz 92 | 93 | bump: 94 | $(eval VERSION := $(shell cat VERSION)) 95 | @echo "Current version: $(VERSION)" 96 | $(eval NEW_VERSION := $(shell echo "$(VERSION)" | awk -F. '{print $$1 "." $$2 "." $$3+1}')) 97 | @echo "New version: $(NEW_VERSION)" 98 | @jq '.version = "$(NEW_VERSION)"' npm/package.json > npm/package.json.tmp && mv npm/package.json.tmp npm/package.json 99 | @echo "$(NEW_VERSION)" > VERSION 100 | @echo "Updated version in VERSION file" 101 | 102 | jupyterlab_dev: 103 | cd jupyterlab_extension && \ 104 | pip install -ve . && \ 105 | jupyter labextension develop --overwrite . 106 | 107 | release: 108 | cd build/npm && npm publish langforge-$$(cat ../../VERSION | tr -d '[:space:]').tgz 109 | cd build/pypi && python -m twine upload dist/* 110 | 111 | .SUFFIXES: 112 | 113 | .PHONY: all jupyterlab go compress copy pypi archive npm clean bump jupyterlab_dev release-npm 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![LangForge](https://github.com/mme/langforge/raw/main/docs/img/header.png "LangForge") 2 | 3 | LangForge is an **open-source toolkit** designed to make it easy to create and deploy **_LangChain applications_**. 4 | 5 | ## Features 6 | 7 | - Simplified environment setup and API key management 8 | - Predefined notebooks for various use cases to help you get started quickly 9 | - Instantly chat with your chains using the Jupyter integration 10 | - Automatic REST interface generation for your app 11 | 12 | ## Installation 13 | 14 | To install LangForge, simply run the following command: 15 | 16 | ```bash 17 | pip install langforge-ai 18 | ``` 19 | 20 | ## Getting Started 21 | 22 | Use the create command to generate a new LangChain app. 23 | 24 | LangForge will ask you a couple of questions, then set up a virtual environment, install required packages, and configure API keys, providing a ready-to-use foundation for your app. 25 | 26 | ```bash 27 | langforge create myapp 28 | ``` 29 | 30 | When prompted to edit your API keys, input your OpenAI API key. 31 | 32 | ### Launch JupyterLab 33 | 34 | Next, run the langforge lab command to launch Jupyter Lab. 35 | 36 | ```bash 37 | cd myapp 38 | langforge lab 39 | ``` 40 | 41 | Your project comes with ready-to-use templates for various use cases and an integration that allows you to chat with your chains directly within Jupyter. 42 | 43 | In this example, we select the "Creative ChatGPT" template. 44 | 45 | ![Templates](https://github.com/mme/langforge/raw/main/docs/img/templates.png "Templates") 46 | 47 | ### Develop your LangChain app 48 | 49 | Now that we have our notebook open, let's run the code. 50 | 51 | Select `Kernel > Restart Kernel and Run All Cells...` 52 | 53 | This template will make ChatGPT behave like an old school adventure game. To play with it, click the smiling robot face on the upper right to open a chat window. 54 | 55 | ![Chat](https://github.com/mme/langforge/raw/main/docs/img/chat.png "Chat") 56 | 57 | Great! Note that upon running the first cell, a table displaying your API keys will appear. If your OpenAI key was not set during app creation, simply click the edit button and input your key. 58 | 59 | ```python 60 | # make sure all packages are installed and environment variables are set 61 | %setup langchain openai 62 | ``` 63 | 64 | ![API Key](https://github.com/mme/langforge/raw/main/docs/img/api-key.png "API Key") 65 | 66 | Let's change the prompt to customize our adventure. You can come up with any scenario you want. In this tutorial, we will go for a space adventure. 67 | 68 | ```python 69 | template = """This is a conversation between a human and a system called AdventureGPT. 70 | 71 | AdventureGPT is designed to create immersive and engaging text-based adventure games. 72 | 73 | AdventureGPT is capable of understanding both simple commands, such as 'look,' and more 74 | complex sentences, allowing it to effectively interpret the player's intent. 75 | 76 | This adventure takes place in space. The player steps into the role of Captain Bravado, 77 | a fearless and charismatic leader of the starship 'Infinity Chaser'. 78 | Tasked with navigating the uncharted reaches of the cosmos, Captain Bravado and their 79 | loyal crew must overcome various challenges, solve intricate puzzles, and make critical 80 | decisions that will shape the fate of their mission and the future of interstellar 81 | exploration. 82 | """ 83 | ``` 84 | 85 | Now rerun the cell and find yourself in an immersive space adventure! 86 | 87 | ### Serve your app 88 | 89 | LangForge automatically generates a REST interface for your app, making it easy to deploy and share with others. When you are happy with your app, use the `serve` command followed by the name of your notebook to start serving your app. 90 | 91 | ```bash 92 | langforge serve chat-creative.ipynb 93 | ``` 94 | 95 | We can now use curl to send HTTP requests to our app: 96 | 97 | ``` 98 | curl -X POST -H "Content-Type: application/json" -d '{"input": "look", "memory": []}' http://localhost:2204/chat/gpt_adventure 99 | ``` 100 | 101 | Note that we include two keys in the JSON: input, which represents the user's command or message, and memory, which holds the conversation history to maintain context and continuity in the interaction. 102 | 103 | ## Contributing 104 | 105 | We welcome contributions from the community! If you'd like to contribute to LangForge, please feel free to submit pull requests or open issues on our GitHub repository. 106 | 107 | ## License 108 | 109 | LangForge is released under the MIT License. 110 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.6 -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © Markus Ecker 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "langforge/python" 9 | "langforge/system" 10 | "langforge/tui" 11 | "os" 12 | "path/filepath" 13 | 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | // createCmd represents the create command 18 | var createCmd = &cobra.Command{ 19 | Use: "create [app-name]", 20 | Short: "Create a new langchain application", 21 | Long: `The create command generates a new LangChain application with LangForge. 22 | 23 | It sets up a virtual environment, installs dependencies, 24 | and configures API keys, allowing you to get started quickly`, 25 | Args: func(cmd *cobra.Command, args []string) error { 26 | if len(args) < 1 { 27 | return fmt.Errorf("app name is missing") 28 | } 29 | return nil 30 | }, 31 | Run: func(cmd *cobra.Command, args []string) { 32 | createAppCmd(args[0]) 33 | }, 34 | } 35 | 36 | func init() { 37 | rootCmd.AddCommand(createCmd) 38 | } 39 | 40 | func createAppCmd(appName string) { 41 | 42 | currentDir, err := os.Getwd() 43 | if err != nil { 44 | fmt.Println("Error getting current directory:", err) 45 | return 46 | } 47 | 48 | dir := filepath.Join(currentDir, appName) 49 | 50 | handler := python.NewPythonHandler(dir) 51 | 52 | // Check if a file with the specified app name already exists 53 | if _, err := os.Stat(dir); err == nil { 54 | panic(fmt.Errorf("file with name '%s' already exists", dir)) 55 | } 56 | 57 | tui.DisplayBanner() 58 | 59 | // Check if a virtual environment should be created 60 | shouldCreateEnvironment, err := tui.PromptYesNo("Create a virtual environment for your 🦜️🔗LangChain app?", true) 61 | if err != nil { 62 | panic(err) 63 | } 64 | 65 | tui.EmptyLine() 66 | if shouldCreateEnvironment { 67 | fmt.Println("Yes, create a virtual environment.") 68 | } else { 69 | fmt.Println("No, install dependencies in this environment.") 70 | } 71 | tui.EmptyLine() 72 | 73 | if !shouldCreateEnvironment { 74 | err := handler.DetermineInstalledIntegrations() 75 | if err != nil { 76 | panic(err) 77 | } 78 | } 79 | 80 | // Create the app directory 81 | if err := os.Mkdir(appName, 0755); err != nil { 82 | panic(err) 83 | } 84 | 85 | if shouldCreateEnvironment { 86 | fmt.Println("Creating virtual environment...") 87 | tui.EmptyLine() 88 | 89 | // Create the virtual environment 90 | if err := python.CreateVirtualEnv(".venv", appName); err != nil { 91 | panic(err) 92 | } 93 | 94 | // activate the virtual environment 95 | if err := python.ActivateEnvironment(".venv", appName); err != nil { 96 | panic(err) 97 | } 98 | } 99 | 100 | err = tui.EditAndUpdateIntegrations(handler, true, false) 101 | if err != nil { 102 | panic(err) 103 | } 104 | 105 | // Ensure the environment has all required keys in the .env file 106 | dotEnvPath := filepath.Join(appName, ".env") 107 | apiKeys := handler.InstalledIntegrationsApiKeys() 108 | err = system.EnsureEnv(dotEnvPath, apiKeys) 109 | if err != nil { 110 | panic(err) 111 | } 112 | 113 | if len(apiKeys) > 0 { 114 | unsetKeys, err := system.UnsetAPIKeys(dotEnvPath, apiKeys) 115 | if err != nil { 116 | panic(err) 117 | } 118 | 119 | unsetKeysMap := make(map[string]bool) 120 | for _, key := range unsetKeys { 121 | unsetKeysMap[key] = true 122 | } 123 | 124 | tui.EmptyLine() 125 | nAPIKeys := len(apiKeys) 126 | if nAPIKeys == 1 { 127 | fmt.Println(tui.Bold("We found 1 API key associated with your installed integration:")) 128 | } else { 129 | fmt.Println(tui.Bold("We found %d API keys associated with your installed integrations:", nAPIKeys)) 130 | } 131 | for _, key := range apiKeys { 132 | if unsetKeysMap[key] { 133 | fmt.Printf("- %s\n", key) 134 | } else { 135 | fmt.Printf("- %s (set)\n", key) 136 | } 137 | } 138 | 139 | // Load the environment from the .env file 140 | env, err := system.ReadEnv(dotEnvPath) 141 | if err != nil { 142 | panic(err) 143 | } 144 | 145 | // Set the default values for the API keys 146 | env = system.SetDefaultEnv(apiKeys, env) 147 | 148 | tui.EmptyLine() 149 | shouldEditApiKeys, err := tui.PromptYesNo("Would you like to edit your API keys now?", true) 150 | if err != nil { 151 | panic(err) 152 | } 153 | 154 | if shouldEditApiKeys { 155 | tui.EmptyLine() 156 | 157 | // Edit the environment (only API keys) 158 | editedEnv, err := tui.EditApiKeys(apiKeys, env) 159 | if err != nil { 160 | panic(err) 161 | } 162 | 163 | env = editedEnv 164 | 165 | } else { 166 | tui.EmptyLine() 167 | } 168 | 169 | // Save the edited environment back to the .env file 170 | err = system.WriteEnv(dotEnvPath, env) 171 | if err != nil { 172 | panic(err) 173 | } 174 | } 175 | 176 | fmt.Printf("Successfully created 🦜️🔗LangChain application '%s'.\n", appName) 177 | } 178 | -------------------------------------------------------------------------------- /cmd/integrations.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "langforge/python" 6 | "langforge/tui" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var integrationsCmd = &cobra.Command{ 14 | Use: "integrations", 15 | Short: "Edit the integrations for your langchain application", 16 | Long: `The integrations command allows you to view and edit the integrations 17 | used in your langchain application.`, 18 | Run: func(cmd *cobra.Command, args []string) { 19 | editIntegrations() 20 | }, 21 | } 22 | 23 | func init() { 24 | rootCmd.AddCommand(integrationsCmd) 25 | } 26 | 27 | func editIntegrations() { 28 | cwd, err := os.Getwd() 29 | if err != nil { 30 | fmt.Println("Error getting current directory:", err) 31 | return 32 | } 33 | 34 | venvDir := filepath.Join(cwd, ".venv") 35 | if _, err := os.Stat(venvDir); err == nil { 36 | // Activate the virtual environment 37 | err = python.ActivateEnvironment(venvDir) 38 | if err != nil { 39 | fmt.Println("Error activating virtual environment:", err) 40 | return 41 | } 42 | } else { 43 | fmt.Println("No virtual environment found. Continuing in the current environment.") 44 | } 45 | 46 | handler := python.NewPythonHandler(cwd) 47 | 48 | err = tui.EditAndUpdateIntegrations(handler, false, true) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | fmt.Println("Successfully updated integrations.") 54 | } 55 | -------------------------------------------------------------------------------- /cmd/keys.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "langforge/python" 6 | "langforge/system" 7 | "langforge/tui" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | // keysCmd represents the keys command 15 | var keysCmd = &cobra.Command{ 16 | Use: "keys", 17 | Short: "Edit API keys for a LangChain application", 18 | Long: `The keys command allows you to edit the API keys for a LangChain application.`, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | editApiKeysCmd() 21 | }, 22 | } 23 | 24 | func init() { 25 | rootCmd.AddCommand(keysCmd) 26 | } 27 | 28 | func editApiKeysCmd() { 29 | 30 | currentDir, err := os.Getwd() 31 | if err != nil { 32 | fmt.Println("Error getting current directory:", err) 33 | return 34 | } 35 | 36 | handler := python.NewPythonHandler(currentDir) 37 | 38 | venvDir := filepath.Join(currentDir, ".venv") 39 | if _, err := os.Stat(venvDir); err == nil { 40 | // Activate the virtual environment 41 | err = python.ActivateEnvironment(venvDir) 42 | if err != nil { 43 | fmt.Println("Error activating virtual environment:", err) 44 | return 45 | } 46 | } 47 | 48 | err = handler.DetermineInstalledIntegrations() 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | // Ensure the environment has all required keys in the .env file 54 | dotEnvPath := filepath.Join(currentDir, ".env") 55 | 56 | apiKeys := handler.InstalledIntegrationsApiKeys() 57 | err = system.EnsureEnv(dotEnvPath, apiKeys) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | // Load the environment from the .env file 63 | env, err := system.ReadEnv(dotEnvPath) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | editedEnv, err := tui.EditApiKeys(apiKeys, env) 69 | if err != nil { 70 | panic(err) 71 | } 72 | 73 | env = editedEnv 74 | 75 | // Save the edited environment back to the .env file 76 | err = system.WriteEnv(dotEnvPath, env) 77 | if err != nil { 78 | panic(err) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /cmd/lab.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "langforge/python" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var jupyterLabCmd = &cobra.Command{ 15 | Use: "lab", 16 | Short: " launches JupyterLab for instant coding in your virtual environment.", 17 | Long: `The lab command starts a JupyterLab server within your virtual environment and 18 | launches a browser window, enabling you to begin coding immediately in an 19 | interactive workspace.`, 20 | Run: func(cmd *cobra.Command, args []string) { 21 | startJupyterLabCmd() 22 | }, 23 | } 24 | 25 | func init() { 26 | rootCmd.AddCommand(jupyterLabCmd) 27 | } 28 | 29 | func startJupyterLabCmd() { 30 | cwd, err := os.Getwd() 31 | if err != nil { 32 | fmt.Println("Error getting current directory:", err) 33 | return 34 | } 35 | 36 | venvDir := filepath.Join(cwd, ".venv") 37 | if _, err := os.Stat(venvDir); err == nil { 38 | // Activate the virtual environment 39 | err = python.ActivateEnvironment(venvDir) 40 | if err != nil { 41 | fmt.Println("Error activating virtual environment:", err) 42 | return 43 | } 44 | } else { 45 | fmt.Println("No virtual environment found. Continuing in the current environment.") 46 | } 47 | 48 | err = python.SetJupyterEnvironmentVariables(cwd) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | // Start the Jupyter Notebook server 54 | labCmd := exec.Command("jupyter", "lab") 55 | var labOutput bytes.Buffer 56 | labCmd.Stdout = &labOutput 57 | labCmd.Stderr = os.Stderr 58 | 59 | err = labCmd.Run() 60 | if err != nil { 61 | panic(err) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © Markus Ecker 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // rootCmd represents the base command when called without any subcommands 14 | var rootCmd = &cobra.Command{ 15 | Use: "langforge", 16 | Short: "LangForge: A Toolkit for Creating and Deploying LangChain Apps", 17 | Long: `LangForge is a toolkit for creating and deploying LangChain apps. 18 | 19 | It simplifies the process by handling dependencies, providing 20 | Jupyter notebooks for experimentation, and enabling you to 21 | interact with your chains via a REST API.`, 22 | // Uncomment the following line if your bare application 23 | // has an action associated with it: 24 | // Run: func(cmd *cobra.Command, args []string) { }, 25 | } 26 | 27 | // Execute adds all child commands to the root command and sets flags appropriately. 28 | // This is called by main.main(). It only needs to happen once to the rootCmd. 29 | func Execute() { 30 | defer recoverFromPanic() 31 | err := rootCmd.Execute() 32 | if err != nil { 33 | os.Exit(1) 34 | } 35 | } 36 | 37 | func init() { 38 | // Here you will define your flags and configuration settings. 39 | // Cobra supports persistent flags, which, if defined here, 40 | // will be global for your application. 41 | 42 | // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.langforge.yaml)") 43 | 44 | // Cobra also supports local flags, which will only run 45 | // when this action is called directly. 46 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 47 | } 48 | 49 | func recoverFromPanic() { 50 | if r := recover(); r != nil { 51 | fmt.Fprintf(os.Stderr, "Error: %s\n", r) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cmd/serve.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © Markus Ecker 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "langforge/python" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "strconv" 14 | "strings" 15 | 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | var serveCmd = &cobra.Command{ 20 | Use: "serve [notebook.ipynb]", 21 | Short: "Serve a LangChain application", 22 | Long: `The serve command serves a LangChain application from a Jupyter notebook.`, 23 | Args: func(cmd *cobra.Command, args []string) error { 24 | if len(args) < 1 { 25 | return fmt.Errorf("notebook is missing") 26 | } 27 | return nil 28 | }, 29 | Run: func(cmd *cobra.Command, args []string) { 30 | // Directly get the port value without checking for the flag's change 31 | port, err := cmd.Flags().GetInt("port") 32 | if err != nil { 33 | fmt.Printf("Error parsing port: %v\n", err) 34 | return 35 | } 36 | serveAppCmd(args[0], port) 37 | }, 38 | } 39 | 40 | func init() { 41 | rootCmd.AddCommand(serveCmd) 42 | serveCmd.Flags().Int("port", 2204, "port number to serve LangChain application") 43 | } 44 | 45 | func serveAppCmd(notebookPath string, port int) { 46 | 47 | cwd, err := os.Getwd() 48 | if err != nil { 49 | fmt.Println("Error getting current directory:", err) 50 | return 51 | } 52 | 53 | venvDir := filepath.Join(cwd, ".venv") 54 | if _, err := os.Stat(venvDir); err == nil { 55 | // Activate the virtual environment 56 | err = python.ActivateEnvironment(venvDir) 57 | if err != nil { 58 | fmt.Println("Error activating virtual environment:", err) 59 | return 60 | } 61 | } else { 62 | fmt.Println("No virtual environment found. Continuing in the current environment.") 63 | } 64 | 65 | err = python.SetJupyterEnvironmentVariables(cwd) 66 | if err != nil { 67 | panic(err) 68 | } 69 | 70 | // Add filename and --port arguments to the command 71 | cmd := exec.Command("python", "-", notebookPath, "--port", strconv.Itoa(port)) 72 | stdin, err := cmd.StdinPipe() 73 | if err != nil { 74 | panic(err) 75 | } 76 | 77 | pythonScript, err := python.ServerPy() 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | // Set Stdout and Stderr to stream the output 83 | cmd.Stdout = os.Stdout 84 | cmd.Stderr = os.Stderr 85 | 86 | err = cmd.Start() 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | io.WriteString(stdin, strings.TrimSpace(string(pythonScript))) 92 | stdin.Close() 93 | 94 | err = cmd.Wait() 95 | if err != nil { 96 | panic(err) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /docs/img/api-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mme/langforge/293057082a6da6d1797f5b1c6a792acaed6f83c3/docs/img/api-key.png -------------------------------------------------------------------------------- /docs/img/chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mme/langforge/293057082a6da6d1797f5b1c6a792acaed6f83c3/docs/img/chat.png -------------------------------------------------------------------------------- /docs/img/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mme/langforge/293057082a6da6d1797f5b1c6a792acaed6f83c3/docs/img/header.png -------------------------------------------------------------------------------- /docs/img/templates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mme/langforge/293057082a6da6d1797f5b1c6a792acaed6f83c3/docs/img/templates.png -------------------------------------------------------------------------------- /environment/environment.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | type EnvironmentHandler interface { 4 | DetermineInstalledIntegrations() error 5 | NamesOfIntegrationsToInstall() []string 6 | NamesOfIntegrationsToUninstall() []string 7 | ExecuteChanges() error 8 | InstalledIntegrationsApiKeys() []string 9 | GetIntegrations() []*Integration 10 | } 11 | -------------------------------------------------------------------------------- /environment/integration.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | type SelectableItem interface { 4 | GetTitle() string 5 | IsSelected() bool 6 | SetSelected(bool) 7 | } 8 | 9 | type Integration struct { 10 | Name string `yaml:"name"` 11 | Title string `yaml:"title"` 12 | Selected bool `yaml:"selected"` 13 | Installed bool `yaml:"installed"` 14 | Packages []string `yaml:"packages"` 15 | ApiKeys []string `yaml:"apiKeys"` 16 | PreInstallCommands []string `yaml:"preInstallCommands"` 17 | PostInstallCommands []string `yaml:"postInstallCommands"` 18 | } 19 | 20 | func (i *Integration) GetTitle() string { 21 | return i.Title 22 | } 23 | 24 | func (i *Integration) IsSelected() bool { 25 | return i.Selected 26 | } 27 | 28 | func (i *Integration) SetSelected(selected bool) { 29 | i.Selected = selected 30 | } 31 | 32 | func (i *Integration) Copy() *Integration { 33 | return &Integration{ 34 | Name: i.Name, 35 | Title: i.Title, 36 | Selected: i.Selected, 37 | Installed: i.Installed, 38 | Packages: i.Packages, 39 | ApiKeys: i.ApiKeys, 40 | PreInstallCommands: i.PreInstallCommands, 41 | PostInstallCommands: i.PostInstallCommands, 42 | } 43 | } 44 | 45 | func CopyIntegrations(integrations []*Integration) []*Integration { 46 | result := []*Integration{} 47 | for _, integration := range integrations { 48 | result = append(result, integration.Copy()) 49 | } 50 | return result 51 | } 52 | 53 | func IntegrationsToSelectableItems(integrations []*Integration) []SelectableItem { 54 | selectableItems := make([]SelectableItem, len(integrations)) 55 | for i, integration := range integrations { 56 | selectableItems[i] = integration 57 | } 58 | return selectableItems 59 | } 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module langforge 2 | 3 | go 1.19 4 | 5 | require ( 6 | atomicgo.dev/keyboard v0.2.9 7 | github.com/AlecAivazis/survey/v2 v2.3.6 8 | github.com/joho/godotenv v1.5.1 9 | github.com/pterm/pterm v0.12.55 10 | github.com/spf13/cobra v1.6.1 11 | gopkg.in/yaml.v3 v3.0.1 12 | ) 13 | 14 | require ( 15 | atomicgo.dev/cursor v0.1.1 // indirect 16 | github.com/containerd/console v1.0.3 // indirect 17 | github.com/gookit/color v1.5.2 // indirect 18 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 19 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 20 | github.com/lithammer/fuzzysearch v1.1.5 // indirect 21 | github.com/mattn/go-colorable v0.1.2 // indirect 22 | github.com/mattn/go-isatty v0.0.8 // indirect 23 | github.com/mattn/go-runewidth v0.0.14 // indirect 24 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect 25 | github.com/rivo/uniseg v0.4.4 // indirect 26 | github.com/spf13/pflag v1.0.5 // indirect 27 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 28 | golang.org/x/sys v0.6.0 // indirect 29 | golang.org/x/term v0.5.0 // indirect 30 | golang.org/x/text v0.8.0 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= 2 | atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4= 3 | atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= 4 | atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= 5 | atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= 6 | github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw= 7 | github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= 8 | github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= 9 | github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= 10 | github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= 11 | github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= 12 | github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= 13 | github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= 14 | github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= 15 | github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= 16 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= 17 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= 18 | github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= 19 | github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= 20 | github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= 21 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 22 | github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= 23 | github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 24 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 25 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 26 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 27 | github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= 28 | github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= 29 | github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= 30 | github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= 31 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= 32 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= 33 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 34 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 35 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 36 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 37 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 38 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 39 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 40 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 41 | github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= 42 | github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= 43 | github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= 44 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 45 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 46 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 47 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 48 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 49 | github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= 50 | github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= 51 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 52 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 53 | github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= 54 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 55 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 56 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 57 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 58 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= 59 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 60 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 61 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 62 | github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= 63 | github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= 64 | github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= 65 | github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= 66 | github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= 67 | github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= 68 | github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= 69 | github.com/pterm/pterm v0.12.55 h1:+yVQi8lyCi5Zwg5VyZWkLV/sJl3HCmqji9cyWzcThSU= 70 | github.com/pterm/pterm v0.12.55/go.mod h1:7rswprkyxYOse1IMh79w42jvReNHxro4z9oHfqjIdzM= 71 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 72 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 73 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 74 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 75 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 76 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 77 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 78 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 79 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 80 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 81 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 82 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 83 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 84 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 85 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 86 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 87 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 88 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 89 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= 90 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 91 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 92 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= 93 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 94 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 95 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 96 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 98 | golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 99 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 100 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 102 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 104 | golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= 105 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 106 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 107 | golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= 108 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 109 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 110 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 111 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 112 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 113 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 114 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 115 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 116 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 117 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 118 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 119 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 120 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 121 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 122 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 123 | -------------------------------------------------------------------------------- /jupyterlab_extension/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | **/*.d.ts 5 | tests 6 | 7 | **/__tests__ 8 | ui-tests 9 | -------------------------------------------------------------------------------- /jupyterlab_extension/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended' 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | project: 'tsconfig.json', 11 | sourceType: 'module' 12 | }, 13 | plugins: ['@typescript-eslint'], 14 | rules: { 15 | '@typescript-eslint/naming-convention': [ 16 | 'error', 17 | { 18 | selector: 'interface', 19 | format: ['PascalCase'], 20 | custom: { 21 | regex: '^I[A-Z]', 22 | match: true 23 | } 24 | } 25 | ], 26 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 27 | '@typescript-eslint/no-explicit-any': 'off', 28 | '@typescript-eslint/no-namespace': 'off', 29 | '@typescript-eslint/no-use-before-define': 'off', 30 | '@typescript-eslint/quotes': [ 31 | 'error', 32 | 'single', 33 | { avoidEscape: true, allowTemplateLiterals: false } 34 | ], 35 | curly: ['error', 'all'], 36 | eqeqeq: 'error', 37 | 'prefer-arrow-callback': 'error' 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /jupyterlab_extension/.github/workflows/binder-on-pr.yml: -------------------------------------------------------------------------------- 1 | name: Binder Badge 2 | on: 3 | pull_request_target: 4 | types: [opened] 5 | 6 | jobs: 7 | binder: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | pull-requests: write 11 | steps: 12 | - uses: jupyterlab/maintainer-tools/.github/actions/binder-link@v1 13 | with: 14 | 15 | github_token: ${{ secrets.github_token }} 16 | 17 | -------------------------------------------------------------------------------- /jupyterlab_extension/.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: '*' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Base Setup 18 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 19 | 20 | - name: Install dependencies 21 | run: python -m pip install -U jupyterlab~=3.1 22 | 23 | - name: Lint the extension 24 | run: | 25 | set -eux 26 | jlpm 27 | jlpm run lint:check 28 | 29 | - name: Test the extension 30 | run: | 31 | set -eux 32 | jlpm run test 33 | 34 | - name: Build the extension 35 | run: | 36 | set -eux 37 | python -m pip install .[test] 38 | 39 | jupyter labextension list 40 | jupyter labextension list 2>&1 | grep -ie "langforge.*OK" 41 | python -m jupyterlab.browser_check 42 | 43 | - name: Package the extension 44 | run: | 45 | set -eux 46 | 47 | pip install build 48 | python -m build 49 | pip uninstall -y "langforge" jupyterlab 50 | 51 | - name: Upload extension packages 52 | uses: actions/upload-artifact@v3 53 | with: 54 | name: extension-artifacts 55 | path: dist/langforge* 56 | if-no-files-found: error 57 | 58 | test_isolated: 59 | needs: build 60 | runs-on: ubuntu-latest 61 | 62 | steps: 63 | - name: Checkout 64 | uses: actions/checkout@v3 65 | - name: Install Python 66 | uses: actions/setup-python@v4 67 | with: 68 | python-version: '3.9' 69 | architecture: 'x64' 70 | - uses: actions/download-artifact@v3 71 | with: 72 | name: extension-artifacts 73 | - name: Install and Test 74 | run: | 75 | set -eux 76 | # Remove NodeJS, twice to take care of system and locally installed node versions. 77 | sudo rm -rf $(which node) 78 | sudo rm -rf $(which node) 79 | 80 | pip install "jupyterlab~=3.1" langforge*.whl 81 | 82 | 83 | jupyter labextension list 84 | jupyter labextension list 2>&1 | grep -ie "langforge.*OK" 85 | python -m jupyterlab.browser_check --no-chrome-test 86 | 87 | integration-tests: 88 | name: Integration tests 89 | needs: build 90 | runs-on: ubuntu-latest 91 | 92 | env: 93 | PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/pw-browsers 94 | 95 | steps: 96 | - name: Checkout 97 | uses: actions/checkout@v3 98 | 99 | - name: Base Setup 100 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 101 | 102 | - name: Download extension package 103 | uses: actions/download-artifact@v3 104 | with: 105 | name: extension-artifacts 106 | 107 | - name: Install the extension 108 | run: | 109 | set -eux 110 | python -m pip install "jupyterlab~=3.1" langforge*.whl 111 | 112 | - name: Install dependencies 113 | working-directory: ui-tests 114 | env: 115 | PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 116 | run: jlpm install 117 | 118 | - name: Set up browser cache 119 | uses: actions/cache@v3 120 | with: 121 | path: | 122 | ${{ github.workspace }}/pw-browsers 123 | key: ${{ runner.os }}-${{ hashFiles('ui-tests/yarn.lock') }} 124 | 125 | - name: Install browser 126 | run: jlpm playwright install chromium 127 | working-directory: ui-tests 128 | 129 | - name: Execute integration tests 130 | working-directory: ui-tests 131 | run: | 132 | jlpm playwright test 133 | 134 | - name: Upload Playwright Test report 135 | if: always() 136 | uses: actions/upload-artifact@v3 137 | with: 138 | name: langforge-playwright-tests 139 | path: | 140 | ui-tests/test-results 141 | ui-tests/playwright-report 142 | 143 | check_links: 144 | name: Check Links 145 | runs-on: ubuntu-latest 146 | timeout-minutes: 15 147 | steps: 148 | - uses: actions/checkout@v3 149 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 150 | - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 151 | -------------------------------------------------------------------------------- /jupyterlab_extension/.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | branches: ["*"] 7 | 8 | jobs: 9 | check_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Base Setup 15 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 16 | - name: Install Dependencies 17 | run: | 18 | pip install -e . 19 | - name: Check Release 20 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 21 | with: 22 | 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | - name: Upload Distributions 26 | uses: actions/upload-artifact@v3 27 | with: 28 | name: langforge-releaser-dist-${{ github.run_number }} 29 | path: .jupyter_releaser_checkout/dist 30 | -------------------------------------------------------------------------------- /jupyterlab_extension/.github/workflows/update-integration-tests.yml: -------------------------------------------------------------------------------- 1 | name: Update Playwright Snapshots 2 | 3 | on: 4 | issue_comment: 5 | types: [created, edited] 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | 13 | 14 | update-snapshots: 15 | if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, 'please update playwright snapshots') }} 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Configure git to use https 25 | run: git config --global hub.protocol https 26 | 27 | - name: Checkout the branch from the PR that triggered the job 28 | run: hub pr checkout ${{ github.event.issue.number }} 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Base Setup 33 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 34 | 35 | - name: Install dependencies 36 | run: python -m pip install -U jupyterlab~=3.1 37 | 38 | - name: Install extension 39 | run: | 40 | set -eux 41 | jlpm 42 | python -m pip install . 43 | 44 | - uses: jupyterlab/maintainer-tools/.github/actions/update-snapshots@v1 45 | with: 46 | github_token: ${{ secrets.GITHUB_TOKEN }} 47 | # Playwright knows how to start JupyterLab server 48 | start_server_script: 'null' 49 | test_folder: ui-tests 50 | 51 | -------------------------------------------------------------------------------- /jupyterlab_extension/.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | *.log 5 | .eslintcache 6 | .stylelintcache 7 | *.egg-info/ 8 | .ipynb_checkpoints 9 | *.tsbuildinfo 10 | langforge/labextension 11 | # Version file is handled by hatchling 12 | langforge/_version.py 13 | 14 | # Integration tests 15 | ui-tests/test-results/ 16 | ui-tests/playwright-report/ 17 | 18 | # Created by https://www.gitignore.io/api/python 19 | # Edit at https://www.gitignore.io/?templates=python 20 | 21 | ### Python ### 22 | # Byte-compiled / optimized / DLL files 23 | __pycache__/ 24 | *.py[cod] 25 | *$py.class 26 | 27 | # C extensions 28 | *.so 29 | 30 | # Distribution / packaging 31 | .Python 32 | build/ 33 | develop-eggs/ 34 | dist/ 35 | downloads/ 36 | eggs/ 37 | .eggs/ 38 | lib/ 39 | lib64/ 40 | parts/ 41 | sdist/ 42 | var/ 43 | wheels/ 44 | pip-wheel-metadata/ 45 | share/python-wheels/ 46 | .installed.cfg 47 | *.egg 48 | MANIFEST 49 | 50 | # PyInstaller 51 | # Usually these files are written by a python script from a template 52 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 53 | *.manifest 54 | *.spec 55 | 56 | # Installer logs 57 | pip-log.txt 58 | pip-delete-this-directory.txt 59 | 60 | # Unit test / coverage reports 61 | htmlcov/ 62 | .tox/ 63 | .nox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage/ 69 | coverage.xml 70 | *.cover 71 | .hypothesis/ 72 | .pytest_cache/ 73 | 74 | # Translations 75 | *.mo 76 | *.pot 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | 84 | # PyBuilder 85 | target/ 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # celery beat schedule file 91 | celerybeat-schedule 92 | 93 | # SageMath parsed files 94 | *.sage.py 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # Mr Developer 104 | .mr.developer.cfg 105 | .project 106 | .pydevproject 107 | 108 | # mkdocs documentation 109 | /site 110 | 111 | # mypy 112 | .mypy_cache/ 113 | .dmypy.json 114 | dmypy.json 115 | 116 | # Pyre type checker 117 | .pyre/ 118 | 119 | # End of https://www.gitignore.io/api/python 120 | 121 | # OSX files 122 | .DS_Store 123 | -------------------------------------------------------------------------------- /jupyterlab_extension/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | !/package.json 6 | langforge 7 | -------------------------------------------------------------------------------- /jupyterlab_extension/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid", 5 | "endOfLine": "auto" 6 | } 7 | -------------------------------------------------------------------------------- /jupyterlab_extension/.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-recommended", 4 | "stylelint-config-standard", 5 | "stylelint-prettier/recommended" 6 | ], 7 | "rules": { 8 | "property-no-vendor-prefix": null, 9 | "selector-no-vendor-prefix": null, 10 | "value-no-vendor-prefix": null 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /jupyterlab_extension/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /jupyterlab_extension/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Markus Ecker 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /jupyterlab_extension/README.md: -------------------------------------------------------------------------------- 1 | # langforge 2 | 3 | [![Github Actions Status](https://github.com/mme/langforge/workflows/Build/badge.svg)](https://github.com/mme/langforge/actions/workflows/build.yml)[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/mme/langforge/main?urlpath=lab) 4 | LangForge JupyterLab extension 5 | 6 | ## Requirements 7 | 8 | - JupyterLab >= 3.0 9 | 10 | ## Install 11 | 12 | To install the extension, execute: 13 | 14 | ```bash 15 | pip install langforge 16 | ``` 17 | 18 | ## Uninstall 19 | 20 | To remove the extension, execute: 21 | 22 | ```bash 23 | pip uninstall langforge 24 | ``` 25 | 26 | ## Contributing 27 | 28 | ### Development install 29 | 30 | Note: You will need NodeJS to build the extension package. 31 | 32 | The `jlpm` command is JupyterLab's pinned version of 33 | [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use 34 | `yarn` or `npm` in lieu of `jlpm` below. 35 | 36 | ```bash 37 | # Clone the repo to your local environment 38 | # Change directory to the langforge directory 39 | # Install package in development mode 40 | pip install -e "." 41 | # Link your development version of the extension with JupyterLab 42 | jupyter labextension develop . --overwrite 43 | # Rebuild extension Typescript source after making changes 44 | jlpm build 45 | ``` 46 | 47 | You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. 48 | 49 | ```bash 50 | # Watch the source directory in one terminal, automatically rebuilding when needed 51 | jlpm watch 52 | # Run JupyterLab in another terminal 53 | jupyter lab 54 | ``` 55 | 56 | With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). 57 | 58 | By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command: 59 | 60 | ```bash 61 | jupyter lab build --minimize=False 62 | ``` 63 | 64 | ### Development uninstall 65 | 66 | ```bash 67 | pip uninstall langforge 68 | ``` 69 | 70 | In development mode, you will also need to remove the symlink created by `jupyter labextension develop` 71 | command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions` 72 | folder is located. Then you can remove the symlink named `langforge` within that folder. 73 | 74 | ### Testing the extension 75 | 76 | #### Frontend tests 77 | 78 | This extension is using [Jest](https://jestjs.io/) for JavaScript code testing. 79 | 80 | To execute them, execute: 81 | 82 | ```sh 83 | jlpm 84 | jlpm test 85 | ``` 86 | 87 | #### Integration tests 88 | 89 | This extension uses [Playwright](https://playwright.dev/docs/intro/) for the integration tests (aka user level tests). 90 | More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle testing the extension in JupyterLab. 91 | 92 | More information are provided within the [ui-tests](./ui-tests/README.md) README. 93 | 94 | ### Packaging the extension 95 | 96 | See [RELEASE](RELEASE.md) 97 | -------------------------------------------------------------------------------- /jupyterlab_extension/RELEASE.md: -------------------------------------------------------------------------------- 1 | # Making a new release of langforge 2 | 3 | The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). 4 | 5 | ## Manual release 6 | 7 | ### Python package 8 | 9 | This extension can be distributed as Python 10 | packages. All of the Python 11 | packaging instructions in the `pyproject.toml` file to wrap your extension in a 12 | Python package. Before generating a package, we first need to install `build`. 13 | 14 | ```bash 15 | pip install build twine hatch 16 | ``` 17 | 18 | Bump the version using `hatch`. By default this will create a tag. 19 | See the docs on [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version#semver) for details. 20 | 21 | ```bash 22 | hatch version 23 | ``` 24 | 25 | To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do: 26 | 27 | ```bash 28 | python -m build 29 | ``` 30 | 31 | > `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. 32 | 33 | Then to upload the package to PyPI, do: 34 | 35 | ```bash 36 | twine upload dist/* 37 | ``` 38 | 39 | ### NPM package 40 | 41 | To publish the frontend part of the extension as a NPM package, do: 42 | 43 | ```bash 44 | npm login 45 | npm publish --access public 46 | ``` 47 | 48 | ## Automated releases with the Jupyter Releaser 49 | 50 | The extension repository should already be compatible with the Jupyter Releaser. 51 | 52 | Check out the [workflow documentation](https://github.com/jupyter-server/jupyter_releaser#typical-workflow) for more information. 53 | 54 | Here is a summary of the steps to cut a new release: 55 | 56 | - Fork the [`jupyter-releaser` repo](https://github.com/jupyter-server/jupyter_releaser) 57 | - Add `ADMIN_GITHUB_TOKEN`, `PYPI_TOKEN` and `NPM_TOKEN` to the Github Secrets in the fork 58 | - Go to the Actions panel 59 | - Run the "Draft Changelog" workflow 60 | - Merge the Changelog PR 61 | - Run the "Draft Release" workflow 62 | - Run the "Publish Release" workflow 63 | 64 | ## Publishing to `conda-forge` 65 | 66 | If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html 67 | 68 | Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically. 69 | -------------------------------------------------------------------------------- /jupyterlab_extension/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@jupyterlab/testutils/lib/babel.config'); 2 | -------------------------------------------------------------------------------- /jupyterlab_extension/binder/environment.yml: -------------------------------------------------------------------------------- 1 | # a mybinder.org-ready environment for demoing langforge 2 | # this environment may also be used locally on Linux/MacOS/Windows, e.g. 3 | # 4 | # conda env update --file binder/environment.yml 5 | # conda activate langforge-demo 6 | # 7 | name: langforge-demo 8 | 9 | channels: 10 | - conda-forge 11 | 12 | dependencies: 13 | # runtime dependencies 14 | - python >=3.8,<3.9.0a0 15 | - jupyterlab >=3,<4.0.0a0 16 | # labextension build dependencies 17 | - nodejs >=18,<19 18 | - pip 19 | - wheel 20 | # additional packages for demos 21 | # - ipywidgets 22 | -------------------------------------------------------------------------------- /jupyterlab_extension/binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ perform a development install of langforge 3 | 4 | On Binder, this will run _after_ the environment has been fully created from 5 | the environment.yml in this directory. 6 | 7 | This script should also run locally on Linux/MacOS/Windows: 8 | 9 | python3 binder/postBuild 10 | """ 11 | import subprocess 12 | import sys 13 | from pathlib import Path 14 | 15 | 16 | ROOT = Path.cwd() 17 | 18 | def _(*args, **kwargs): 19 | """ Run a command, echoing the args 20 | 21 | fails hard if something goes wrong 22 | """ 23 | print("\n\t", " ".join(args), "\n") 24 | return_code = subprocess.call(args, **kwargs) 25 | if return_code != 0: 26 | print("\nERROR", return_code, " ".join(args)) 27 | sys.exit(return_code) 28 | 29 | # verify the environment is self-consistent before even starting 30 | _(sys.executable, "-m", "pip", "check") 31 | 32 | # install the labextension 33 | _(sys.executable, "-m", "pip", "install", "-e", ".") 34 | _(sys.executable, "-m", "jupyter", "labextension", "develop", "--overwrite", ".") 35 | 36 | # verify the environment the extension didn't break anything 37 | _(sys.executable, "-m", "pip", "check") 38 | 39 | # list the extensions 40 | _("jupyter", "server", "extension", "list") 41 | 42 | # initially list installed extensions to determine if there are any surprises 43 | _("jupyter", "labextension", "list") 44 | 45 | 46 | print("JupyterLab with langforge is ready to run with:\n") 47 | print("\tjupyter lab\n") 48 | -------------------------------------------------------------------------------- /jupyterlab_extension/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.ipynb' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /jupyterlab_extension/install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "langforge", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package langforge" 5 | } 6 | -------------------------------------------------------------------------------- /jupyterlab_extension/jest.config.js: -------------------------------------------------------------------------------- 1 | const jestJupyterLab = require('@jupyterlab/testutils/lib/jest-config'); 2 | 3 | const esModules = [ 4 | '@jupyterlab/', 5 | 'lib0', 6 | 'y\\-protocols', 7 | 'y\\-websocket', 8 | 'yjs' 9 | ].join('|'); 10 | 11 | const jlabConfig = jestJupyterLab(__dirname); 12 | 13 | const { 14 | moduleFileExtensions, 15 | moduleNameMapper, 16 | preset, 17 | setupFilesAfterEnv, 18 | setupFiles, 19 | testPathIgnorePatterns, 20 | transform 21 | } = jlabConfig; 22 | 23 | module.exports = { 24 | moduleFileExtensions, 25 | moduleNameMapper, 26 | preset, 27 | setupFilesAfterEnv, 28 | setupFiles, 29 | testPathIgnorePatterns, 30 | transform, 31 | automock: false, 32 | collectCoverageFrom: [ 33 | 'src/**/*.{ts,tsx}', 34 | '!src/**/*.d.ts', 35 | '!src/**/.ipynb_checkpoints/*' 36 | ], 37 | coverageDirectory: 'coverage', 38 | coverageReporters: ['lcov', 'text'], 39 | globals: { 40 | 'ts-jest': { 41 | tsconfig: 'tsconfig.json' 42 | } 43 | }, 44 | testRegex: 'src/.*/.*.spec.ts[x]?$', 45 | transformIgnorePatterns: [`/node_modules/(?!${esModules}).+`] 46 | }; 47 | -------------------------------------------------------------------------------- /jupyterlab_extension/langforge/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ 2 | 3 | 4 | def _jupyter_labextension_paths(): 5 | return [{ 6 | "src": "labextension", 7 | "dest": "langforge" 8 | }] 9 | 10 | -------------------------------------------------------------------------------- /jupyterlab_extension/notebooks/api-agent.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1c045b86-ee4d-4101-aff3-0928e3171023", 6 | "metadata": {}, 7 | "source": [ 8 | "# Create an API agent\n", 9 | "\n", 10 | "This is template sets up an agent that uses an API via natural language, using the Klarna API as an example." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "9b76c5e8-6809-470b-b534-68fd33f8e116", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "%setup langchain openai" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "f9d53a01-11da-472d-9446-58c2d041ea0d", 27 | "metadata": { 28 | "tags": [] 29 | }, 30 | "outputs": [], 31 | "source": [ 32 | "from typing import List, Optional\n", 33 | "from langchain.chains import LLMChain\n", 34 | "from langchain.chat_models import ChatOpenAI\n", 35 | "from langchain.prompts import PromptTemplate\n", 36 | "from langchain.requests import Requests\n", 37 | "from langchain.tools import APIOperation, OpenAPISpec\n", 38 | "from langchain.agents import AgentType, Tool, initialize_agent\n", 39 | "from langchain.agents.agent_toolkits import NLAToolkit\n", 40 | "from langchain.chains.api import open_meteo_docs\n", 41 | "from langchain.tools import OpenAPISpec" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "id": "ecb4452d-9b01-4097-b4c2-88a703626f7d", 48 | "metadata": { 49 | "tags": [] 50 | }, 51 | "outputs": [], 52 | "source": [ 53 | "llm = ChatOpenAI(temperature=0) \n", 54 | "klarna_toolkit = NLAToolkit.from_llm_and_url(llm, \"https://www.klarna.com/us/shopping/public/openai/v0/api-docs/\")\n", 55 | "\n", 56 | "openapi_format_instructions = \"\"\"Use the following format:\n", 57 | "\n", 58 | "Question: the input question you must answer\n", 59 | "Thought: you should always think about what to do\n", 60 | "Action: the action to take, should be one of [{tool_names}]\n", 61 | "Action Input: what to instruct the AI Action representative.\n", 62 | "Observation: The Agent's response\n", 63 | "... (this Thought/Action/Action Input/Observation can repeat N times)\n", 64 | "Thought: I now know the final answer. User can't see any of my observations, API responses, links, or tools.\n", 65 | "Final Answer: the final answer to the original input question with the right amount of detail\n", 66 | "\n", 67 | "When responding with your Final Answer, remember that the person you are responding to CANNOT see any of your Thought/Action/Action Input/Observations, so if there is any relevant information there you need to include it explicitly in your response.\"\"\"\n", 68 | "\n", 69 | "mrkl = initialize_agent(klarna_toolkit.get_tools(), llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, agent_kwargs={\"format_instructions\":openapi_format_instructions})" 70 | ] 71 | } 72 | ], 73 | "metadata": { 74 | "kernelspec": { 75 | "display_name": "Python 3 (ipykernel)", 76 | "language": "python", 77 | "name": "python3" 78 | }, 79 | "language_info": { 80 | "codemirror_mode": { 81 | "name": "ipython", 82 | "version": 3 83 | }, 84 | "file_extension": ".py", 85 | "mimetype": "text/x-python", 86 | "name": "python", 87 | "nbconvert_exporter": "python", 88 | "pygments_lexer": "ipython3", 89 | "version": "3.9.6" 90 | } 91 | }, 92 | "nbformat": 4, 93 | "nbformat_minor": 5 94 | } 95 | -------------------------------------------------------------------------------- /jupyterlab_extension/notebooks/chat-creative.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "093477b5-c4ed-4e93-99ec-e29bcfe103fa", 6 | "metadata": {}, 7 | "source": [ 8 | "# Create a Custom ChatGPT (creative)\n", 9 | "\n", 10 | "This is a quick template for creating a custom ChatGPT version of ChatGPT using 🦜🔗 LangChain.\n", 11 | "\n", 12 | "In this notebook, we assign the large language model (LLM) the role of a text-based adventure game. You can modify the prompt to suit any creative writing task.\n", 13 | "\n", 14 | "Observe that we set the temperature to its maximum value (1) to enhance the model's creative output.\n" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "60008d25-d32a-466c-be48-addaa6bf57e7", 21 | "metadata": { 22 | "tags": [] 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "# make sure all packages are installed and environment variables are set\n", 27 | "%setup langchain openai" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "id": "f6092f6a-2efe-4eab-a868-1b58e6c36c50", 34 | "metadata": { 35 | "tags": [] 36 | }, 37 | "outputs": [], 38 | "source": [ 39 | "from langchain.prompts import (\n", 40 | " ChatPromptTemplate, \n", 41 | " MessagesPlaceholder, \n", 42 | " SystemMessagePromptTemplate, \n", 43 | " HumanMessagePromptTemplate\n", 44 | ")\n", 45 | "from langchain.chains import ConversationChain\n", 46 | "from langchain.chat_models import ChatOpenAI\n", 47 | "from langchain.memory import ConversationBufferMemory" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "id": "fcf9ee57-4423-43ae-8645-4aeb7d8b6a85", 54 | "metadata": { 55 | "tags": [] 56 | }, 57 | "outputs": [], 58 | "source": [ 59 | "template = \"\"\"This is a conversation between a human and a system called AdventureGPT.\n", 60 | "\n", 61 | "AdventureGPT is designed to create immersive and engaging text-based adventure games.\n", 62 | "\n", 63 | "AdventureGPT is capable of understanding both simple commands, such as 'look,' and more \n", 64 | "complex sentences, allowing it to effectively interpret the player's intent.\n", 65 | "\"\"\"\n", 66 | "\n", 67 | "prompt = ChatPromptTemplate.from_messages([\n", 68 | " SystemMessagePromptTemplate.from_template(template),\n", 69 | " MessagesPlaceholder(variable_name=\"history\"),\n", 70 | " HumanMessagePromptTemplate.from_template(\"{input}\")\n", 71 | "])\n", 72 | "\n", 73 | "llm = ChatOpenAI(temperature=1)\n", 74 | "# if you want GPT-4: \n", 75 | "# llm = ChatOpenAI(temperature=1, model_name=\"gpt-4\")\n", 76 | "\n", 77 | "memory = ConversationBufferMemory(return_messages=True)\n", 78 | "gpt_adventure = ConversationChain(memory=memory, prompt=prompt, llm=llm)" 79 | ] 80 | } 81 | ], 82 | "metadata": { 83 | "kernelspec": { 84 | "display_name": "Python 3 (ipykernel)", 85 | "language": "python", 86 | "name": "python3" 87 | }, 88 | "language_info": { 89 | "codemirror_mode": { 90 | "name": "ipython", 91 | "version": 3 92 | }, 93 | "file_extension": ".py", 94 | "mimetype": "text/x-python", 95 | "name": "python", 96 | "nbconvert_exporter": "python", 97 | "pygments_lexer": "ipython3", 98 | "version": "3.9.6" 99 | } 100 | }, 101 | "nbformat": 4, 102 | "nbformat_minor": 5 103 | } 104 | -------------------------------------------------------------------------------- /jupyterlab_extension/notebooks/chat-deterministic.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "093477b5-c4ed-4e93-99ec-e29bcfe103fa", 6 | "metadata": {}, 7 | "source": [ 8 | "# Create a Custom ChatGPT (deterministic)\n", 9 | "\n", 10 | "This is a quick template for creating a custom ChatGPT version of ChatGPT using 🦜🔗 LangChain.\n", 11 | "\n", 12 | "In this notebook, we assign the large language model (LLM) the role of a life coach. You can modify the prompt to suit any other task that calls for more consistent and focused output.\n", 13 | "\n", 14 | "Observe that we set the temperature to its minimum value (0) to make the output more deterministic.\n" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "60008d25-d32a-466c-be48-addaa6bf57e7", 21 | "metadata": { 22 | "tags": [] 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "# make sure all packages are installed and environment variables are set\n", 27 | "%setup langchain openai" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "id": "f6092f6a-2efe-4eab-a868-1b58e6c36c50", 34 | "metadata": { 35 | "tags": [] 36 | }, 37 | "outputs": [], 38 | "source": [ 39 | "from langchain.prompts import (\n", 40 | " ChatPromptTemplate, \n", 41 | " MessagesPlaceholder, \n", 42 | " SystemMessagePromptTemplate, \n", 43 | " HumanMessagePromptTemplate\n", 44 | ")\n", 45 | "from langchain.chains import ConversationChain\n", 46 | "from langchain.chat_models import ChatOpenAI\n", 47 | "from langchain.memory import ConversationBufferMemory" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "id": "fcf9ee57-4423-43ae-8645-4aeb7d8b6a85", 54 | "metadata": { 55 | "tags": [] 56 | }, 57 | "outputs": [], 58 | "source": [ 59 | "template = \"\"\"This is a conversation between a human and a system called CoachGPT.\n", 60 | "\n", 61 | "CoachGPT is designed to ask the user for their age and name.\n", 62 | "\n", 63 | "Once it knows age and name, CoachGPT will give some good life advices. It will refuse\n", 64 | "to give any advice without knowing the humans age and name.\n", 65 | "\n", 66 | "After CoachGPT has given advice, it will ask the user to visit the website \n", 67 | "\"https://example.com/signup\" to sign up for free and end the conversation.\n", 68 | "\"\"\"\n", 69 | "\n", 70 | "prompt = ChatPromptTemplate.from_messages([\n", 71 | " SystemMessagePromptTemplate.from_template(template),\n", 72 | " MessagesPlaceholder(variable_name=\"history\"),\n", 73 | " HumanMessagePromptTemplate.from_template(\"{input}\")\n", 74 | "])\n", 75 | "\n", 76 | "llm = ChatOpenAI(temperature=0)\n", 77 | "# if you want GPT-4: \n", 78 | "# llm = ChatOpenAI(temperature=0, model_name=\"gpt-4\")\n", 79 | "\n", 80 | "memory = ConversationBufferMemory(return_messages=True)\n", 81 | "gpt_adventure = ConversationChain(memory=memory, prompt=prompt, llm=llm)" 82 | ] 83 | } 84 | ], 85 | "metadata": { 86 | "kernelspec": { 87 | "display_name": "Python 3 (ipykernel)", 88 | "language": "python", 89 | "name": "python3" 90 | }, 91 | "language_info": { 92 | "codemirror_mode": { 93 | "name": "ipython", 94 | "version": 3 95 | }, 96 | "file_extension": ".py", 97 | "mimetype": "text/x-python", 98 | "name": "python", 99 | "nbconvert_exporter": "python", 100 | "pygments_lexer": "ipython3", 101 | "version": "3.9.6" 102 | } 103 | }, 104 | "nbformat": 4, 105 | "nbformat_minor": 5 106 | } 107 | -------------------------------------------------------------------------------- /jupyterlab_extension/notebooks/code.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "9b88091e-2cdc-485c-93be-8601e633175e", 6 | "metadata": { 7 | "tags": [] 8 | }, 9 | "source": [ 10 | "# Code QA\n", 11 | "Use this template to ask questions about any GitHub repository" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "2f3f9a1e-6fa9-4e13-8816-da6b086370c7", 18 | "metadata": { 19 | "tags": [] 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "%setup langchain deeplake openai tiktoken" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "c078f31b-8735-4de6-990d-42feb1704909", 30 | "metadata": { 31 | "tags": [] 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "import os\n", 36 | "from langchain.embeddings.openai import OpenAIEmbeddings\n", 37 | "from langchain.vectorstores import DeepLake\n", 38 | "from langchain.document_loaders import TextLoader\n", 39 | "from langchain.text_splitter import CharacterTextSplitter\n", 40 | "from langchain.chat_models import ChatOpenAI\n", 41 | "from langchain.chains import ConversationalRetrievalChain\n", 42 | "from langchain.memory import ConversationBufferMemory" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "id": "adcd34b8-5f85-4be6-83f2-b6a8112fa1ad", 49 | "metadata": { 50 | "tags": [] 51 | }, 52 | "outputs": [], 53 | "source": [ 54 | "!git clone https://github.com/twitter/the-algorithm # replace any repository of your choice " 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "id": "48db6914-8c61-417e-9932-e4173a2ed555", 61 | "metadata": { 62 | "tags": [] 63 | }, 64 | "outputs": [], 65 | "source": [ 66 | "root_dir = './the-algorithm'\n", 67 | "docs = []\n", 68 | "for dirpath, dirnames, filenames in os.walk(root_dir):\n", 69 | " for file in filenames:\n", 70 | " try: \n", 71 | " loader = TextLoader(os.path.join(dirpath, file), encoding='utf-8')\n", 72 | " docs.extend(loader.load_and_split())\n", 73 | " except Exception as e: \n", 74 | " pass\n", 75 | "text_splitter = CharacterTextSplitter(chunk_size=5000, chunk_overlap=0)\n", 76 | "texts = text_splitter.split_documents(docs)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "id": "c047943f-5432-4d5f-afd4-f32f9bead90e", 83 | "metadata": { 84 | "tags": [] 85 | }, 86 | "outputs": [], 87 | "source": [ 88 | "embeddings = OpenAIEmbeddings()\n", 89 | "db = DeepLake.from_documents(texts, embeddings)" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "id": "98a74173-15b2-49b0-b789-0fe8da65f9a4", 96 | "metadata": { 97 | "tags": [] 98 | }, 99 | "outputs": [], 100 | "source": [ 101 | "retriever = db.as_retriever()\n", 102 | "retriever.search_kwargs['distance_metric'] = 'cos'\n", 103 | "retriever.search_kwargs['fetch_k'] = 100\n", 104 | "retriever.search_kwargs['maximal_marginal_relevance'] = True\n", 105 | "retriever.search_kwargs['k'] = 20\n", 106 | "\n", 107 | "def filter(x):\n", 108 | " # filter based on source code\n", 109 | " if 'com.google' in x['text'].data()['value']:\n", 110 | " return False\n", 111 | " \n", 112 | " # filter based on path e.g. extension\n", 113 | " metadata = x['metadata'].data()['value']\n", 114 | " return 'scala' in metadata['source'] or 'py' in metadata['source']" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "id": "a6217adb-275b-4e2c-bdb1-738f70d278fe", 121 | "metadata": { 122 | "tags": [] 123 | }, 124 | "outputs": [], 125 | "source": [ 126 | "memory = ConversationBufferMemory(memory_key=\"chat_history\", input_key=\"question\")\n", 127 | "model = ChatOpenAI(model='gpt-4') # 'gpt-3.5-turbo',\n", 128 | "qa = ConversationalRetrievalChain.from_llm(model,retriever=retriever, memory=memory, get_chat_history=lambda inputs: inputs)" 129 | ] 130 | } 131 | ], 132 | "metadata": { 133 | "kernelspec": { 134 | "display_name": "Python 3 (ipykernel)", 135 | "language": "python", 136 | "name": "python3" 137 | }, 138 | "language_info": { 139 | "codemirror_mode": { 140 | "name": "ipython", 141 | "version": 3 142 | }, 143 | "file_extension": ".py", 144 | "mimetype": "text/x-python", 145 | "name": "python", 146 | "nbconvert_exporter": "python", 147 | "pygments_lexer": "ipython3", 148 | "version": "3.9.6" 149 | } 150 | }, 151 | "nbformat": 4, 152 | "nbformat_minor": 5 153 | } 154 | -------------------------------------------------------------------------------- /jupyterlab_extension/notebooks/qa-pdf.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "c67820df-cf47-4f39-8745-9656448564af", 6 | "metadata": { 7 | "tags": [] 8 | }, 9 | "source": [ 10 | "# Create a Question Answering Chat with a PDF\n", 11 | "\n", 12 | "This is a quick template for creating a question answering chat with ChatGPT and 🦜🔗 LangChain using a PDF.\n", 13 | "\n", 14 | "We load an example document and create an index using OpenAI text embeddings. Then, we can chat about the contents of this document." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "67048376-0aa6-4eab-a130-7cca75e556a3", 21 | "metadata": { 22 | "tags": [] 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "%setup langchain openai chromadb pypdf" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "b4fb6ab3-ce1f-4828-bb94-c12be9a31366", 33 | "metadata": { 34 | "tags": [] 35 | }, 36 | "outputs": [], 37 | "source": [ 38 | "from langchain.embeddings.openai import OpenAIEmbeddings\n", 39 | "from langchain.vectorstores import Chroma\n", 40 | "from langchain.text_splitter import CharacterTextSplitter\n", 41 | "from langchain.llms import OpenAI\n", 42 | "from langchain.chat_models import ChatOpenAI\n", 43 | "from langchain.chains import ConversationalRetrievalChain\n", 44 | "from langchain.document_loaders import PyPDFLoader\n", 45 | "from langchain.memory import ConversationBufferMemory\n", 46 | "import urllib.request\n", 47 | "\n", 48 | "# retrieve the \"Attention Is All You Need\" paper\n", 49 | "urllib.request.urlretrieve(\"https://arxiv.org/pdf/1706.03762\", \"attention.pdf\")\n", 50 | "# retrieve \"Language Models are Few-Shot Learners\"\n", 51 | "urllib.request.urlretrieve(\"https://arxiv.org/pdf/2005.14165v4\", \"gpt3.pdf\")\n", 52 | "\n", 53 | "\n", 54 | "pdfs = [\n", 55 | " \"attention.pdf\",\n", 56 | " \"gpt3.pdf\"\n", 57 | "];\n", 58 | "\n", 59 | "documents = []\n", 60 | "\n", 61 | "for pdf in pdfs:\n", 62 | " loader = PyPDFLoader(pdf)\n", 63 | " docs = loader.load()\n", 64 | " documents.extend(docs)\n", 65 | " \n", 66 | "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", 67 | "documents = text_splitter.split_documents(documents)\n", 68 | "embeddings = OpenAIEmbeddings()\n", 69 | "vectorstore = Chroma.from_documents(documents, embeddings)" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "id": "d7e799f0-ff67-44d7-a141-7d378ae19179", 76 | "metadata": { 77 | "tags": [] 78 | }, 79 | "outputs": [], 80 | "source": [ 81 | "memory = ConversationBufferMemory(memory_key=\"chat_history\", input_key=\"question\")\n", 82 | "llm = ChatOpenAI(temperature=0)\n", 83 | "# if you want GPT-4: \n", 84 | "# llm = ChatOpenAI(temperature=0, model_name=\"gpt-4\")\n", 85 | "\n", 86 | "qa = ConversationalRetrievalChain.from_llm(llm, vectorstore.as_retriever(), memory=memory, get_chat_history=lambda inputs: inputs)" 87 | ] 88 | } 89 | ], 90 | "metadata": { 91 | "kernelspec": { 92 | "display_name": "Python 3 (ipykernel)", 93 | "language": "python", 94 | "name": "python3" 95 | }, 96 | "language_info": { 97 | "codemirror_mode": { 98 | "name": "ipython", 99 | "version": 3 100 | }, 101 | "file_extension": ".py", 102 | "mimetype": "text/x-python", 103 | "name": "python", 104 | "nbconvert_exporter": "python", 105 | "pygments_lexer": "ipython3", 106 | "version": "3.9.6" 107 | } 108 | }, 109 | "nbformat": 4, 110 | "nbformat_minor": 5 111 | } 112 | -------------------------------------------------------------------------------- /jupyterlab_extension/notebooks/qa-txt.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "c67820df-cf47-4f39-8745-9656448564af", 6 | "metadata": { 7 | "tags": [] 8 | }, 9 | "source": [ 10 | "# Create a Question Answering Chat\n", 11 | "\n", 12 | "This is a quick template for creating a question answering chat with ChatGPT and 🦜🔗 LangChain.\n", 13 | "\n", 14 | "We load an example document and create an index using OpenAI text embeddings. Then, we can chat about the contents of this document." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "67048376-0aa6-4eab-a130-7cca75e556a3", 21 | "metadata": { 22 | "tags": [] 23 | }, 24 | "outputs": [], 25 | "source": [ 26 | "%setup langchain openai chromadb" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "b4fb6ab3-ce1f-4828-bb94-c12be9a31366", 33 | "metadata": { 34 | "tags": [] 35 | }, 36 | "outputs": [], 37 | "source": [ 38 | "from langchain.embeddings.openai import OpenAIEmbeddings\n", 39 | "from langchain.vectorstores import Chroma\n", 40 | "from langchain.text_splitter import CharacterTextSplitter\n", 41 | "from langchain.llms import OpenAI\n", 42 | "from langchain.chat_models import ChatOpenAI\n", 43 | "from langchain.chains import ConversationalRetrievalChain\n", 44 | "from langchain.document_loaders import TextLoader\n", 45 | "from langchain.memory import ConversationBufferMemory\n", 46 | "import urllib.request\n", 47 | "\n", 48 | "# retrieve the state of the union speech\n", 49 | "urllib.request.urlretrieve(\"https://raw.githubusercontent.com/hwchase17/chat-your-data/master/state_of_the_union.txt\", \"state_of_the_union.txt\")\n", 50 | "\n", 51 | "txts = [\n", 52 | " \"state_of_the_union.txt\"\n", 53 | "];\n", 54 | "\n", 55 | "documents = []\n", 56 | "\n", 57 | "for txt in txts:\n", 58 | " loader = TextLoader(txt)\n", 59 | " docs = loader.load()\n", 60 | " documents.extend(docs)\n", 61 | "\n", 62 | "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", 63 | "documents = text_splitter.split_documents(documents)\n", 64 | "embeddings = OpenAIEmbeddings()\n", 65 | "vectorstore = Chroma.from_documents(documents, embeddings)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "id": "d7e799f0-ff67-44d7-a141-7d378ae19179", 72 | "metadata": { 73 | "tags": [] 74 | }, 75 | "outputs": [], 76 | "source": [ 77 | "memory = ConversationBufferMemory(memory_key=\"chat_history\", input_key=\"question\")\n", 78 | "llm = ChatOpenAI(temperature=0)\n", 79 | "# if you want GPT-4: \n", 80 | "# llm = ChatOpenAI(temperature=0, model_name=\"gpt-4\")\n", 81 | "\n", 82 | "qa = ConversationalRetrievalChain.from_llm(llm, vectorstore.as_retriever(), memory=memory, get_chat_history=lambda inputs: inputs)" 83 | ] 84 | } 85 | ], 86 | "metadata": { 87 | "kernelspec": { 88 | "display_name": "Python 3 (ipykernel)", 89 | "language": "python", 90 | "name": "python3" 91 | }, 92 | "language_info": { 93 | "codemirror_mode": { 94 | "name": "ipython", 95 | "version": 3 96 | }, 97 | "file_extension": ".py", 98 | "mimetype": "text/x-python", 99 | "name": "python", 100 | "nbconvert_exporter": "python", 101 | "pygments_lexer": "ipython3", 102 | "version": "3.9.6" 103 | } 104 | }, 105 | "nbformat": 4, 106 | "nbformat_minor": 5 107 | } 108 | -------------------------------------------------------------------------------- /jupyterlab_extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "langforge", 3 | "version": "0.1.0", 4 | "description": "LangForge JupyterLab extension", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/mme/langforge", 11 | "bugs": { 12 | "url": "https://github.com/mme/langforge/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": { 16 | "name": "Markus Ecker", 17 | "email": "markus.ecker@gmail.com" 18 | }, 19 | "files": [ 20 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 21 | "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}" 22 | ], 23 | "main": "lib/index.js", 24 | "types": "lib/index.d.ts", 25 | "style": "style/index.css", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/mme/langforge.git" 29 | }, 30 | "scripts": { 31 | "build": "jlpm build:lib && jlpm build:labextension:dev", 32 | "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension", 33 | "build:labextension": "jupyter labextension build .", 34 | "build:labextension:dev": "jupyter labextension build --development True .", 35 | "build:lib": "tsc --sourceMap", 36 | "build:lib:prod": "tsc", 37 | "clean": "jlpm clean:lib", 38 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 39 | "clean:lintcache": "rimraf .eslintcache .stylelintcache", 40 | "clean:labextension": "rimraf langforge/labextension langforge/_version.py", 41 | "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache", 42 | "eslint": "jlpm eslint:check --fix", 43 | "eslint:check": "eslint . --cache --ext .ts,.tsx", 44 | "install:extension": "jlpm build", 45 | "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", 46 | "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", 47 | "prettier": "jlpm prettier:base --write --list-different", 48 | "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 49 | "prettier:check": "jlpm prettier:base --check", 50 | "stylelint": "jlpm stylelint:check --fix", 51 | "stylelint:check": "stylelint --cache \"style/**/*.css\"", 52 | "test": "jest --coverage", 53 | "watch": "run-p watch:src watch:labextension", 54 | "watch:src": "tsc -w", 55 | "watch:labextension": "jupyter labextension watch ." 56 | }, 57 | "dependencies": { 58 | "@jupyterlab/application": "^3.6.1", 59 | "@jupyterlab/launcher": "^3.6.2", 60 | "@types/markdown-it": "^12.2.3", 61 | "markdown-it": "^13.0.1", 62 | "react-textarea-autosize": "^8.4.0" 63 | }, 64 | "devDependencies": { 65 | "@babel/core": "^7.0.0", 66 | "@babel/preset-env": "^7.0.0", 67 | "@jupyterlab/builder": "^3.1.0", 68 | "@jupyterlab/testutils": "^3.0.0", 69 | "@types/jest": "^26.0.0", 70 | "@typescript-eslint/eslint-plugin": "^4.8.1", 71 | "@typescript-eslint/parser": "^4.8.1", 72 | "eslint": "^7.14.0", 73 | "eslint-config-prettier": "^6.15.0", 74 | "eslint-plugin-prettier": "^3.1.4", 75 | "jest": "^26.0.0", 76 | "npm-run-all": "^4.1.5", 77 | "prettier": "^2.1.1", 78 | "raw-loader": "^4.0.2", 79 | "rimraf": "^3.0.2", 80 | "stylelint": "^14.3.0", 81 | "stylelint-config-prettier": "^9.0.4", 82 | "stylelint-config-recommended": "^6.0.0", 83 | "stylelint-config-standard": "~24.0.0", 84 | "stylelint-prettier": "^2.0.0", 85 | "ts-jest": "^26.0.0", 86 | "typescript": "~4.1.3" 87 | }, 88 | "sideEffects": [ 89 | "style/*.css", 90 | "style/index.js" 91 | ], 92 | "styleModule": "style/index.js", 93 | "publishConfig": { 94 | "access": "public" 95 | }, 96 | "jupyterlab": { 97 | "extension": true, 98 | "outputDir": "langforge/labextension", 99 | "webpackConfig": "./webpack.config.js" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /jupyterlab_extension/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling>=1.4.0", "jupyterlab>=3.4.7,<4.0.0", "hatch-nodejs-version"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "langforge" 7 | readme = "README.md" 8 | license = { file = "LICENSE" } 9 | requires-python = ">=3.7" 10 | classifiers = [ 11 | "Framework :: Jupyter", 12 | "Framework :: Jupyter :: JupyterLab", 13 | "Framework :: Jupyter :: JupyterLab :: 3", 14 | "Framework :: Jupyter :: JupyterLab :: Extensions", 15 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 16 | "License :: OSI Approved :: BSD License", 17 | "Programming Language :: Python", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.7", 20 | "Programming Language :: Python :: 3.8", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | "Programming Language :: Python :: 3.11", 24 | ] 25 | dependencies = [ 26 | ] 27 | dynamic = ["version", "description", "authors", "urls", "keywords"] 28 | 29 | [tool.hatch.version] 30 | source = "nodejs" 31 | 32 | [tool.hatch.metadata.hooks.nodejs] 33 | fields = ["description", "authors", "urls"] 34 | 35 | [tool.hatch.build.targets.sdist] 36 | artifacts = ["langforge/labextension"] 37 | exclude = [".github", "binder"] 38 | 39 | [tool.hatch.build.targets.wheel.shared-data] 40 | "langforge/labextension" = "share/jupyter/labextensions/langforge" 41 | "install.json" = "share/jupyter/labextensions/langforge/install.json" 42 | 43 | [tool.hatch.build.hooks.version] 44 | path = "langforge/_version.py" 45 | 46 | [tool.hatch.build.hooks.jupyter-builder] 47 | dependencies = ["hatch-jupyter-builder>=0.5"] 48 | build-function = "hatch_jupyter_builder.npm_builder" 49 | ensured-targets = [ 50 | "langforge/labextension/static/style.js", 51 | "langforge/labextension/package.json", 52 | ] 53 | skip-if-exists = ["langforge/labextension/static/style.js"] 54 | 55 | [tool.hatch.build.hooks.jupyter-builder.build-kwargs] 56 | build_cmd = "build:prod" 57 | npm = ["jlpm"] 58 | 59 | [tool.hatch.build.hooks.jupyter-builder.editable-build-kwargs] 60 | build_cmd = "install:extension" 61 | npm = ["jlpm"] 62 | source_dir = "src" 63 | build_dir = "langforge/labextension" 64 | 65 | [tool.jupyter-releaser.options] 66 | version_cmd = "hatch version" 67 | 68 | [tool.jupyter-releaser.hooks] 69 | before-build-npm = ["python -m pip install jupyterlab~=3.1", "jlpm", "jlpm build:prod"] 70 | before-build-python = ["jlpm clean:all"] 71 | 72 | [tool.check-wheel-contents] 73 | ignore = ["W002"] 74 | -------------------------------------------------------------------------------- /jupyterlab_extension/setup.py: -------------------------------------------------------------------------------- 1 | __import__('setuptools').setup() 2 | -------------------------------------------------------------------------------- /jupyterlab_extension/src/__tests__/langforge.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests 3 | */ 4 | 5 | describe('langforge', () => { 6 | it('should be tested', () => { 7 | expect(1 + 1).toEqual(2); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /jupyterlab_extension/src/chat.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from 'react'; 2 | import * as React from 'react'; 3 | import TextareaAutosize from 'react-textarea-autosize'; 4 | import { LlmVar, Turn } from './interfaces'; 5 | import { svgAvatarUserStr, svgAvatarLlmStr, svgThinkingStr } from './svg'; 6 | 7 | interface ChatProps { 8 | llmVars: LlmVar[]; 9 | currentLlmVar: LlmVar; 10 | history: Turn[]; 11 | thinking: boolean; 12 | onSendMessage: (message: string) => void; 13 | onSelectLlmVar: (botId: string) => void; 14 | } 15 | 16 | export function Chat(props: ChatProps): JSX.Element { 17 | const [newMessage, setNewMessage] = useState(''); 18 | 19 | const formRef = useRef(null); 20 | const chatMessagesRef = useRef(null); 21 | const chatInputTextAreaRef = useRef(null); 22 | 23 | const handleInputChange = (event: React.ChangeEvent) => { 24 | setNewMessage(event.target.value); 25 | }; 26 | 27 | const handleFormSubmit = (event: React.FormEvent) => { 28 | event.preventDefault(); 29 | submit(); 30 | }; 31 | 32 | const submit = () => { 33 | if (props.thinking) { 34 | return; 35 | } 36 | props.onSendMessage(newMessage); 37 | setNewMessage(''); 38 | chatInputTextAreaRef.current!.focus(); 39 | }; 40 | 41 | const handleSelectLlmVar = (event: React.ChangeEvent) => { 42 | const selectedId = event.target.value; 43 | props.onSelectLlmVar(selectedId); 44 | }; 45 | 46 | useEffect(() => { 47 | if (chatMessagesRef.current) { 48 | chatMessagesRef.current.scrollTop = chatMessagesRef.current.scrollHeight; 49 | } 50 | }, [ 51 | props.currentLlmVar.id + '_' + props.currentLlmVar.input, 52 | props.history.length 53 | ]); 54 | 55 | const handleKeyDown: React.KeyboardEventHandler< 56 | HTMLTextAreaElement 57 | > = event => { 58 | if (event.key === 'Enter' && !event.shiftKey) { 59 | event.preventDefault(); 60 | submit(); 61 | } 62 | }; 63 | 64 | return ( 65 |
66 |
67 |
68 | 77 |
78 |
79 |
80 | {props.history.map((turn, index) => { 81 | let rowClass = 'jp-LangForge-chat-message-row'; 82 | if (turn.type === 'input') { 83 | rowClass += ' jp-LangForge-chat-message-row-user'; 84 | } else if (turn.type === 'output') { 85 | rowClass += ' jp-LangForge-chat-message-row-llm'; 86 | } 87 | 88 | let avatarDiv =
; 89 | 90 | if (turn.type === 'input') { 91 | avatarDiv = ( 92 |
96 | ); 97 | } else if (turn.type === 'output') { 98 | avatarDiv = ( 99 |
103 | ); 104 | } 105 | return ( 106 |
107 |
108 |
{avatarDiv}
109 |
113 |
114 |
115 | ); 116 | })} 117 | {props.thinking === true && ( 118 |
119 |
120 |
121 |
125 |
126 |
131 |
132 |
133 | )} 134 |
135 |
140 |
141 | 150 | 168 |
169 |
170 |
171 | ); 172 | } 173 | -------------------------------------------------------------------------------- /jupyterlab_extension/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ILabShell, 3 | ILayoutRestorer, 4 | JupyterFrontEnd, 5 | JupyterFrontEndPlugin 6 | } from '@jupyterlab/application'; 7 | import { LangForgeSidebarWidget } from './sidebar'; 8 | import { NotebookPanel } from '@jupyterlab/notebook'; 9 | import { Widget } from '@lumino/widgets'; 10 | // import { executeCode } from './notebook'; 11 | import { Jupyter } from './jupyter'; 12 | import { newJupyterState } from './interfaces'; 13 | import { ILauncher } from '@jupyterlab/launcher'; 14 | import { addLaunchers } from './launcher-utils'; 15 | import { monkeyPatchLauncher } from './monkey-patch'; 16 | 17 | /** 18 | * Initialization data for the langforge extension. 19 | */ 20 | const plugin: JupyterFrontEndPlugin = { 21 | id: 'langforge:plugin', 22 | autoStart: true, 23 | requires: [ILayoutRestorer, ILabShell, ILauncher], 24 | activate: ( 25 | app: JupyterFrontEnd, 26 | restorer: ILayoutRestorer, 27 | labShell: ILabShell, 28 | launcher: ILauncher 29 | ) => { 30 | const { commands } = app; 31 | monkeyPatchLauncher(); 32 | addLaunchers(commands, launcher); 33 | 34 | const state = Jupyter.getInstance(); 35 | // Instantiate the widget 36 | const sidebarWidget = new LangForgeSidebarWidget(state); 37 | 38 | // Add the widget to the right sidebar 39 | app.shell.add(sidebarWidget, 'right', { rank: 1 }); 40 | 41 | // Track the widget state 42 | restorer.add(sidebarWidget, sidebarWidget.id); 43 | 44 | app.restored.then(() => { 45 | currentChanged(labShell.currentWidget); 46 | 47 | labShell.currentChanged.connect(async (sender, args) => { 48 | currentChanged(args.newValue); 49 | }); 50 | }); 51 | } 52 | }; 53 | 54 | async function currentChanged(newValue: Widget | null) { 55 | const state = Jupyter.getInstance(); 56 | if (newValue instanceof NotebookPanel) { 57 | await newValue.sessionContext.ready; 58 | let kernelName = newValue.sessionContext.session?.kernel?.name; 59 | let isPythonNotebook = 60 | kernelName !== null && 61 | kernelName !== undefined && 62 | kernelName.startsWith('python'); 63 | state.state = newJupyterState(isPythonNotebook, false); 64 | if (isPythonNotebook) { 65 | state.currentNotebookPanel = newValue; 66 | } else { 67 | state.currentNotebookPanel = null; 68 | } 69 | } else { 70 | state.state = newJupyterState(false, false); 71 | state.currentNotebookPanel = null; 72 | } 73 | } 74 | 75 | export default plugin; 76 | -------------------------------------------------------------------------------- /jupyterlab_extension/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Turn { 2 | type: 'input' | 'output'; 3 | text: string; 4 | } 5 | 6 | export interface LlmVar { 7 | id: string; 8 | name: string; 9 | type: string; 10 | input: string; 11 | } 12 | 13 | export interface JupyterState { 14 | isPythonNotebook: boolean; 15 | llmVars: LlmVar[]; 16 | currentLlmVar: LlmVar | null; 17 | history: Turn[]; 18 | initializing: boolean; 19 | thinking: boolean; 20 | } 21 | 22 | export function newJupyterState( 23 | isPythonNotebook: boolean, 24 | initializing: boolean = true 25 | ): JupyterState { 26 | return { 27 | isPythonNotebook: isPythonNotebook, 28 | llmVars: [], 29 | currentLlmVar: null, 30 | history: [], 31 | initializing: initializing, 32 | thinking: false 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /jupyterlab_extension/src/jupyter.ts: -------------------------------------------------------------------------------- 1 | import { NotebookPanel } from '@jupyterlab/notebook'; 2 | import { executeCode } from './notebook'; 3 | import { newJupyterState, JupyterState } from './interfaces'; 4 | import { markdown } from './markdown-utils'; 5 | 6 | export class Jupyter { 7 | private _jupyterState: JupyterState = newJupyterState(false); 8 | private _jupyterStateChangedCallbacks: ((state: JupyterState) => void)[] = []; 9 | private _currentNotebookPanel: NotebookPanel | null = null; 10 | private _isTicking: boolean = false; 11 | private _updateStatus: 'none' | 'sending' | 'discard' = 'none'; 12 | 13 | private static _instance: Jupyter | null = null; 14 | 15 | static getInstance(): Jupyter { 16 | if (this._instance === null) { 17 | this._instance = new Jupyter(); 18 | } 19 | return this._instance; 20 | } 21 | 22 | private constructor() { 23 | setInterval(() => this._tick(), 1000); 24 | } 25 | 26 | private async _tick() { 27 | if (this._isTicking) { 28 | return; 29 | } 30 | 31 | if (this._updateStatus === 'sending') { 32 | return; 33 | } 34 | 35 | if (this._updateStatus === 'discard') { 36 | this._updateStatus = 'none'; 37 | } 38 | 39 | if (this._jupyterState.isPythonNotebook && this._currentNotebookPanel) { 40 | this._isTicking = true; 41 | try { 42 | let newState = { ...this._jupyterState }; 43 | 44 | await this._queryLlmVars(newState); 45 | this._setCurrentLlmVar(newState); 46 | await this._queryHistory(newState); 47 | 48 | newState.initializing = false; 49 | 50 | if ((this._updateStatus as any) === 'sending') { 51 | return; 52 | } 53 | 54 | if ((this._updateStatus as any) === 'discard') { 55 | this._updateStatus = 'none'; 56 | return; 57 | } 58 | 59 | this.state = newState; 60 | } catch (e) { 61 | console.log('error', e); 62 | } finally { 63 | this._isTicking = false; 64 | } 65 | } 66 | } 67 | 68 | private async _exec(output: { [key: string]: string[] }) { 69 | let payload: { [key: string]: string } = {}; 70 | for (let key in output) { 71 | let fun = output[key][0]; 72 | let vars = output[key].slice(1); 73 | 74 | vars = vars.map( 75 | v => 76 | "'" + 77 | v.replace(/'/g, "\\'").replace(/\r/g, '').replace(/\n/g, '\\n') + 78 | "'" 79 | ); 80 | let funCall = 81 | '__langforge_jupyterlab__helpers__instance__.' + 82 | fun + 83 | '(' + 84 | vars.join(',') + 85 | ')'; 86 | payload[key] = funCall; 87 | } 88 | let pythonResult = await executeCode( 89 | this._currentNotebookPanel!, 90 | '', 91 | payload 92 | ); 93 | let result: { [key: string]: any } = {}; 94 | for (let key in pythonResult) { 95 | result[key] = this._resultToJson(pythonResult[key]); 96 | } 97 | return result; 98 | } 99 | 100 | private async _queryLlmVars(newState: JupyterState) { 101 | try { 102 | newState.llmVars = ( 103 | await this._exec({ llmVars: ['get_llm_vars'] }) 104 | ).llmVars; 105 | } catch (e) { 106 | const error = e as Error; 107 | error.message = 'queryLlmVars: ' + error.message; 108 | throw error; 109 | } 110 | } 111 | 112 | private _setCurrentLlmVar(newState: JupyterState) { 113 | if ( 114 | newState.currentLlmVar !== null && 115 | newState.llmVars.filter( 116 | llmVar => llmVar.name === newState.currentLlmVar!.name 117 | ).length === 0 118 | ) { 119 | newState.currentLlmVar = null; 120 | } 121 | 122 | if (!newState.currentLlmVar && newState.llmVars.length > 0) { 123 | newState.currentLlmVar = newState.llmVars[0]; 124 | } 125 | } 126 | 127 | private async _queryHistory(newState: JupyterState) { 128 | try { 129 | if (!newState.currentLlmVar) { 130 | return; 131 | } 132 | newState.history = ( 133 | await this._exec({ 134 | history: [ 135 | 'get_history', 136 | newState.currentLlmVar.name, 137 | newState.currentLlmVar.input 138 | ] 139 | }) 140 | ).history; 141 | newState.history = newState.history.map(h => markdown(h)); 142 | } catch (e) { 143 | const error = e as Error; 144 | error.message = 'queryHistory: ' + error.message; 145 | throw error; 146 | } 147 | } 148 | 149 | private _resultToJson(result: any): any { 150 | if (result && result.status === 'ok') { 151 | let dataTextPlain = result.data['text/plain']; 152 | 153 | // remove leading and trailing single quotes if present 154 | if (dataTextPlain.startsWith("'") && dataTextPlain.endsWith("'")) { 155 | dataTextPlain = dataTextPlain.substring(1, dataTextPlain.length - 1); 156 | // replace escaped single quotes 157 | dataTextPlain = dataTextPlain.replace(/\\'/g, "'"); 158 | // escape double quotes 159 | dataTextPlain = dataTextPlain.replace(/\\\\"/g, '\\"'); 160 | } 161 | result = JSON.parse(dataTextPlain); 162 | 163 | return result; 164 | } 165 | return null; 166 | } 167 | 168 | onStateChanged(callback: (state: JupyterState) => void) { 169 | this._jupyterStateChangedCallbacks.push(callback); 170 | } 171 | 172 | get state() { 173 | return this._jupyterState; 174 | } 175 | 176 | set state(state: JupyterState) { 177 | this._jupyterState = state; 178 | this._jupyterStateChangedCallbacks.forEach(callback => callback(state)); 179 | } 180 | 181 | get currentNotebookPanel() { 182 | return this._currentNotebookPanel; 183 | } 184 | 185 | set currentNotebookPanel(currentNotebookPanel: NotebookPanel | null) { 186 | this._currentNotebookPanel = currentNotebookPanel; 187 | } 188 | 189 | async sendMessage(msg: string) { 190 | if ( 191 | !this._jupyterState.isPythonNotebook || 192 | !this._currentNotebookPanel || 193 | !this._jupyterState.currentLlmVar 194 | ) { 195 | return; 196 | } 197 | this._updateStatus = 'sending'; 198 | 199 | try { 200 | const newState = { ...this._jupyterState }; 201 | newState.history.push(markdown({ text: msg, type: 'input' })); 202 | newState.thinking = true; 203 | 204 | this.state = newState; 205 | 206 | newState.history = ( 207 | await this._exec({ 208 | history: [ 209 | 'send_message', 210 | newState.currentLlmVar!.name, 211 | newState.currentLlmVar!.input, 212 | msg 213 | ] 214 | }) 215 | ).history; 216 | 217 | newState.history = newState.history.map(h => markdown(h)); 218 | newState.thinking = false; 219 | 220 | this.state = newState; 221 | } finally { 222 | this._updateStatus = 'discard'; 223 | } 224 | } 225 | 226 | selectLlmVar(id: string) { 227 | const newState = { ...this._jupyterState }; 228 | newState.currentLlmVar = newState.llmVars.filter( 229 | llmVar => llmVar.id === id 230 | )[0]; 231 | this.state = newState; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /jupyterlab_extension/src/launcher-utils.ts: -------------------------------------------------------------------------------- 1 | import { ILauncher } from '@jupyterlab/launcher'; 2 | import { CommandRegistry } from '@lumino/commands'; 3 | import chatCreativeNotebook from 'chat-creative.ipynb'; 4 | import chatDeterministicNotebook from 'chat-deterministic.ipynb'; 5 | import apiAgentNotebook from 'api-agent.ipynb'; 6 | import qaTextNotebook from 'qa-txt.ipynb'; 7 | import qaPdfNotebook from 'qa-pdf.ipynb'; 8 | import codeNotebook from 'code.ipynb'; 9 | import babyAgiNotebook from 'baby-agi.ipynb'; 10 | 11 | import { Contents } from '@jupyterlab/services'; 12 | import { NotebookPanel } from '@jupyterlab/notebook'; 13 | 14 | async function openNotebookWithContent( 15 | commands: CommandRegistry, 16 | content: string, 17 | desiredName: string 18 | ): Promise { 19 | const model: Contents.IModel = await commands.execute( 20 | 'docmanager:new-untitled', 21 | { 22 | path: '', 23 | type: 'notebook', 24 | ext: '.ipynb' 25 | } 26 | ); 27 | 28 | const doc: NotebookPanel = await commands.execute('docmanager:open', { 29 | path: model.path 30 | }); 31 | 32 | await doc.context.ready; 33 | doc.context.model.fromString(content); 34 | doc.context.model.initialize(); 35 | 36 | let renamed = false; 37 | let index = 0; 38 | while (!renamed) { 39 | try { 40 | await doc.context.rename( 41 | `${desiredName}${index === 0 ? '' : '-' + index}.ipynb` 42 | ); 43 | renamed = true; 44 | } catch { 45 | index += 1; 46 | } 47 | } 48 | 49 | await doc.context.save(); 50 | await doc.sessionContext.ready; 51 | doc.content.activeCellIndex = 1; 52 | } 53 | 54 | function addItem( 55 | commands: CommandRegistry, 56 | launcher: ILauncher, 57 | category: string, 58 | command: string, 59 | label: string, 60 | caption: string, 61 | iconClass: string, 62 | rank: number, 63 | notebook: string, 64 | desiredName: string 65 | ) { 66 | commands.addCommand(command, { 67 | label: label, 68 | caption: caption, 69 | iconClass: iconClass, 70 | execute: () => { 71 | openNotebookWithContent(commands, notebook, desiredName); 72 | } 73 | }); 74 | launcher.add({ 75 | command, 76 | category, 77 | rank 78 | }); 79 | } 80 | 81 | export function addLaunchers(commands: CommandRegistry, launcher: ILauncher) { 82 | addItem( 83 | commands, 84 | launcher, 85 | 'Templates', 86 | 'chat-creative:create', 87 | 'Creative ChatGPT', 88 | 'Open a notebook for creative chat', 89 | 'jp-NotebookIcon', 90 | 1, 91 | chatCreativeNotebook, 92 | 'chat-creative' 93 | ); 94 | 95 | addItem( 96 | commands, 97 | launcher, 98 | 'Templates', 99 | 'chat-deterministic:create', 100 | 'Deterministic ChatGPT', 101 | 'Open a notebook for deterministic chat', 102 | 'jp-NotebookIcon', 103 | 2, 104 | chatDeterministicNotebook, 105 | 'chat-deterministic' 106 | ); 107 | 108 | addItem( 109 | commands, 110 | launcher, 111 | 'Templates', 112 | 'api-agent:create', 113 | 'API Agent', 114 | 'Open a notebook for an API agent', 115 | 'jp-NotebookIcon', 116 | 3, 117 | apiAgentNotebook, 118 | 'api-agent' 119 | ); 120 | 121 | addItem( 122 | commands, 123 | launcher, 124 | 'Templates', 125 | 'qa-txt:create', 126 | 'QA Text', 127 | 'Open a notebook for QA text', 128 | 'jp-NotebookIcon', 129 | 4, 130 | qaTextNotebook, 131 | 'qa-txt' 132 | ); 133 | 134 | addItem( 135 | commands, 136 | launcher, 137 | 'Templates', 138 | 'qa-pdf:create', 139 | 'QA PDF', 140 | 'Open a notebook for QA PDF', 141 | 'jp-NotebookIcon', 142 | 5, 143 | qaPdfNotebook, 144 | 'qa-pdf' 145 | ); 146 | 147 | addItem( 148 | commands, 149 | launcher, 150 | 'Templates', 151 | 'code:create', 152 | 'QA Code', 153 | 'Open a notebook for QA code', 154 | 'jp-NotebookIcon', 155 | 6, 156 | codeNotebook, 157 | 'code' 158 | ); 159 | 160 | addItem( 161 | commands, 162 | launcher, 163 | 'Templates', 164 | 'baby-agi:create', 165 | 'Baby AGI', 166 | 'Open a notebook for Baby AGI', 167 | 'jp-NotebookIcon', 168 | 7, 169 | babyAgiNotebook, 170 | 'baby-agi' 171 | ); 172 | } 173 | -------------------------------------------------------------------------------- /jupyterlab_extension/src/markdown-utils.ts: -------------------------------------------------------------------------------- 1 | import MarkdownIt from 'markdown-it'; 2 | import { Turn } from './interfaces'; 3 | 4 | const md = new MarkdownIt({ 5 | html: true, 6 | linkify: true, 7 | typographer: true 8 | }); 9 | 10 | export function markdown(turn: Turn): Turn { 11 | let text = turn.text; 12 | try { 13 | // escape double quotes 14 | text = text.replace(/"/g, '\\"'); 15 | text = JSON.parse(`"${text}"`); 16 | } catch (err) { 17 | console.error('Invalid string format:', err); 18 | console.error(text); 19 | return turn; 20 | } 21 | 22 | if (turn.type === 'input') { 23 | return { type: turn.type, text: text.replace(/\n/g, '
') }; 24 | } else { 25 | text = text.trim(); 26 | text = md.render(text); 27 | text = text 28 | .replace(/
/g, '