Add monaco editor for streamlit (#12)

* Init monaco component

* Add monaco editor
add-more-languages
Meng Zhang 2023-03-25 19:13:42 +08:00 committed by GitHub
parent b622bd6762
commit d8f2d03636
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 31342 additions and 2 deletions

View File

@ -7,7 +7,6 @@ repos:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:

View File

@ -1,4 +1,5 @@
import streamlit as st
from components import tabby
from utils.service_info import ServiceInfo
SERVICES = [
@ -12,5 +13,7 @@ def make_badge_markdown(x: ServiceInfo):
return f"![{x.label}]({x.badge_url})"
st.markdown("## Status")
st.markdown("## Tabby")
st.markdown(" ".join(map(make_badge_markdown, SERVICES)))
tabby.editor()

View File

@ -0,0 +1,44 @@
import os
import streamlit.components.v1 as components
_RELEASE = __name__ != "__main_"
# Declare a Streamlit component. `declare_component` returns a function
if not _RELEASE:
_editor_func = components.declare_component(
# We give the component a simple, descriptive name ("my_component"
# does not fit this bill, so please choose something better for your
# own component :)
"tabby-editor",
# Pass `url` here to tell Streamlit that the component will be served
# by the local dev server that you run via `npm run start`.
# (This is useful while your component is in development.)
url="http://localhost:3001",
)
else:
# When we're distributing a production version of the component, we'll
# replace the `url` param with `path`, and point it to to the component's
# build directory:
parent_dir = os.path.dirname(os.path.abspath(__file__))
build_dir = os.path.join(parent_dir, "frontend/build")
_editor_func = components.declare_component("tabby-editor", path=build_dir)
# Create a wrapper function for the component. This is an optional
# best practice - we could simply expose the component function returned by
# `declare_component` and call it done. The wrapper allows us to customize
# our component's API: we can pre-process its input args, post-process its
# output value, and add a docstring for users.
def editor(tabby_server_url="http://localhost:5000", key=None):
_editor_func(tabby_server_url=tabby_server_url, key=key)
# Add some test code to play with the component while it's in development.
# During development, we can run this just as we would any other Streamlit
# app: `$ streamlit run my_component/__init__.py`
if not _RELEASE:
import streamlit as st
tabby_server_url = st.text_input("Tabby Server URL", value="http://localhost:5000")
tabby(tabby_server_url)

View File

@ -0,0 +1,6 @@
# Run the component's dev server on :3001
# (The Streamlit dev server already runs on :3000)
PORT=3001
# Don't automatically open the web browser on `npm run start`.
BROWSER=none

View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,5 @@
{
"endOfLine": "lf",
"semi": false,
"trailingComma": "es5"
}

View File

@ -0,0 +1,20 @@
{
"files": {
"main.js": "./static/js/main.3a9265b0.chunk.js",
"main.js.map": "./static/js/main.3a9265b0.chunk.js.map",
"runtime-main.js": "./static/js/runtime-main.44d30fc2.js",
"runtime-main.js.map": "./static/js/runtime-main.44d30fc2.js.map",
"static/js/2.ae80075f.chunk.js": "./static/js/2.ae80075f.chunk.js",
"static/js/2.ae80075f.chunk.js.map": "./static/js/2.ae80075f.chunk.js.map",
"index.html": "./index.html",
"precache-manifest.1d3088fc135428c2ea5392e379f82710.js": "./precache-manifest.1d3088fc135428c2ea5392e379f82710.js",
"service-worker.js": "./service-worker.js",
"static/js/2.ae80075f.chunk.js.LICENSE.txt": "./static/js/2.ae80075f.chunk.js.LICENSE.txt",
"static/js/main.3a9265b0.chunk.js.LICENSE.txt": "./static/js/main.3a9265b0.chunk.js.LICENSE.txt"
},
"entrypoints": [
"static/js/runtime-main.44d30fc2.js",
"static/js/2.ae80075f.chunk.js",
"static/js/main.3a9265b0.chunk.js"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
<!doctype html><html lang="en"><head><title>Streamlit Component</title><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Streamlit Component"/><link rel="stylesheet" href="bootstrap.min.css"/></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function t(t){for(var n,l,a=t[0],p=t[1],i=t[2],c=0,s=[];c<a.length;c++)l=a[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in p)Object.prototype.hasOwnProperty.call(p,n)&&(e[n]=p[n]);for(f&&f(t);s.length;)s.shift()();return u.push.apply(u,i||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,a=1;a<r.length;a++){var p=r[a];0!==o[p]&&(n=!1)}n&&(u.splice(t--,1),e=l(l.s=r[0]))}return e}var n={},o={1:0},u=[];function l(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,l),r.l=!0,r.exports}l.m=e,l.c=n,l.d=function(e,t,r){l.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,t){if(1&t&&(e=l(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(l.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)l.d(r,n,function(t){return e[t]}.bind(null,n));return r},l.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(t,"a",t),t},l.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},l.p="./";var a=this.webpackJsonpstreamlit_component_template=this.webpackJsonpstreamlit_component_template||[],p=a.push.bind(a);a.push=t,a=a.slice();for(var i=0;i<a.length;i++)t(a[i]);var f=p;r()}([])</script><script src="./static/js/2.ae80075f.chunk.js"></script><script src="./static/js/main.3a9265b0.chunk.js"></script></body></html>

View File

@ -0,0 +1,26 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "42fedf3d1999ef9b28619146c4e415cd",
"url": "./index.html"
},
{
"revision": "409a462d1d8f6189ac9a",
"url": "./static/js/2.ae80075f.chunk.js"
},
{
"revision": "df087e3b51cab167e93df344458ea8f1",
"url": "./static/js/2.ae80075f.chunk.js.LICENSE.txt"
},
{
"revision": "ab4b2d8e1d0c358de5ef",
"url": "./static/js/main.3a9265b0.chunk.js"
},
{
"revision": "4e0e34f265fae8f33b01b27ae29d9d6f",
"url": "./static/js/main.3a9265b0.chunk.js.LICENSE.txt"
},
{
"revision": "7e9d84e346ce158d1e50",
"url": "./static/js/runtime-main.44d30fc2.js"
}
]);

View File

@ -0,0 +1,39 @@
/**
* Welcome to your Workbox-powered service worker!
*
* You'll need to register this file in your web app and you should
* disable HTTP caching for this file too.
* See https://goo.gl/nhQhGp
*
* The rest of the code is auto-generated. Please don't update this file
* directly; instead, make changes to your Workbox build configuration
* and re-run your build process.
* See https://goo.gl/2aRDsh
*/
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
"./precache-manifest.1d3088fc135428c2ea5392e379f82710.js"
);
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
workbox.core.clientsClaim();
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("./index.html"), {
blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,78 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <http://feross.org>
* @license MIT
*/
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license
* Copyright 2018-2021 Streamlit Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @license React v0.19.1
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.14.0
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.14.0
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.14.0
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
!function(e){function t(t){for(var n,l,a=t[0],p=t[1],i=t[2],c=0,s=[];c<a.length;c++)l=a[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in p)Object.prototype.hasOwnProperty.call(p,n)&&(e[n]=p[n]);for(f&&f(t);s.length;)s.shift()();return u.push.apply(u,i||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,a=1;a<r.length;a++){var p=r[a];0!==o[p]&&(n=!1)}n&&(u.splice(t--,1),e=l(l.s=r[0]))}return e}var n={},o={1:0},u=[];function l(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,l),r.l=!0,r.exports}l.m=e,l.c=n,l.d=function(e,t,r){l.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},l.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,t){if(1&t&&(e=l(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(l.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)l.d(r,n,function(t){return e[t]}.bind(null,n));return r},l.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(t,"a",t),t},l.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},l.p="./";var a=this.webpackJsonpstreamlit_component_template=this.webpackJsonpstreamlit_component_template||[],p=a.push.bind(a);a.push=t,a=a.slice();for(var i=0;i<a.length;i++)t(a[i]);var f=p;r()}([]);
//# sourceMappingURL=runtime-main.44d30fc2.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
{
"name": "streamlit_component_template",
"version": "1.0.3",
"private": true,
"dependencies": {
"@monaco-editor/react": "^4.4.6",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"axios": "^1.3.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"streamlit-component-lib-react-hooks": "^1.0.3"
},
"devDependencies": {
"typescript": "^4.6.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"homepage": "."
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Streamlit Component</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Streamlit Component" />
<link rel="stylesheet" href="bootstrap.min.css" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,145 @@
import axios from "axios"
import { useRenderData } from "streamlit-component-lib-react-hooks"
import React, { useEffect } from "react"
import Monaco, { useMonaco } from "@monaco-editor/react"
const PythonParseJSON = `def parse_json_lines(filename: str) -> List[Any]:
output = []
with open(filename, "r", encoding="utf-8") as f:
`
export default function TabbyEditor() {
const renderData = useRenderData()
;(window as any).tabbyServerURL = renderData.args.tabby_server_url
const monaco = useMonaco()
useEffect(() => {
if (!monaco) return
monaco.languages.registerInlineCompletionsProvider(
{ pattern: "**" },
new CompletionProvider(monaco)
)
}, [monaco])
return (
<div style={{ height: 400 }}>
<Monaco
theme="vs-dark"
defaultLanguage="python"
defaultValue={PythonParseJSON}
/>
</div>
)
}
class CompletionProvider {
private monaco: any
private latestTimestamp: number
private pendingRequest: any
constructor(monaco: any) {
this.monaco = monaco
this.latestTimestamp = 0
}
async provideInlineCompletions(
document: any,
position: any,
context: any,
token: any
) {
const prompt = this.getPrompt(document, position)
const emptyResponse = Promise.resolve({ items: [] })
if (this.isNil(prompt)) {
console.debug("Prompt is empty, skipping")
return emptyResponse
}
const currentTimestamp = Date.now()
this.latestTimestamp = currentTimestamp
await this.sleep(500)
if (this.pendingRequest) await this.pendingRequest
if (currentTimestamp < this.latestTimestamp) {
return emptyResponse
}
const response = await this.callTabbyApi(currentTimestamp, prompt)
const hasSuffixParen = this.hasSuffixParen(document, position)
const replaceRange = hasSuffixParen
? new this.monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column + 1
)
: new this.monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column
)
const items = this.toInlineCompletions(response.data, replaceRange)
return Promise.resolve({ items })
}
freeInlineCompletions() {}
getPrompt(document: any, position: any): string {
const firstLine = Math.max(position.lineNumber - 120, 0)
const range = new this.monaco.Range(
firstLine,
0,
position.lineNumber,
position.column
)
return document.getValueInRange(range)
}
isNil(value: any) {
return value === undefined || value === null || value.length === 0
}
sleep(milliseconds: number) {
return new Promise((r) => setTimeout(r, milliseconds))
}
async callTabbyApi(timestamp: number, prompt: string) {
const request = (this.pendingRequest = axios.post(
`${(window as any).tabbyServerURL}/v1/completions`,
{
prompt,
}
))
const response = await request
this.pendingRequest = null
return response
}
toInlineCompletions(value: any, range: any) {
return (
value.choices
?.map((choice: any) => choice.text)
.map((text: string) => ({
range,
text,
})) || []
)
}
hasSuffixParen(document: any, position: any): boolean {
const suffix = document.getValueInRange(
new this.monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column + 1
)
)
return ")]}".indexOf(suffix) > -1
}
}

View File

@ -0,0 +1,13 @@
import React from "react"
import ReactDOM from "react-dom"
import { StreamlitProvider } from "streamlit-component-lib-react-hooks"
import Tabby from "./Tabby"
ReactDOM.render(
<React.StrictMode>
<StreamlitProvider>
<Tabby />
</StreamlitProvider>
</React.StrictMode>,
document.getElementById("root")
)

View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"]
}

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ import os
import uvicorn
from fastapi import FastAPI, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from . import events
@ -16,6 +17,14 @@ app = FastAPI(
docs_url="/",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
MODEL_NAME = os.environ.get("MODEL_NAME")
MODEL_BACKEND = os.environ.get("MODEL_BACKEND", "python")