diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index 3834200..de5e524 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -81,6 +81,7 @@ const config = {
position: 'left',
label: 'Docs',
},
+ {to: '/playground', label: 'Playground', position: 'left'},
{to: '/api', label: 'API', position: 'left'},
// FIXME(meng): enable blog.
// {to: '/blog', label: 'Blog', position: 'left'},
@@ -97,11 +98,19 @@ const config = {
style: 'dark',
links: [
{
- title: 'Docs',
+ title: 'Links',
items: [
{
- label: 'Self Hosting',
- to: '/docs/self-hosting',
+ label: 'Docs',
+ to: '/docs/getting-started',
+ },
+ {
+ label: 'Playground',
+ to: '/playground',
+ },
+ {
+ label: 'API',
+ to: '/api',
},
],
},
diff --git a/website/package.json b/website/package.json
index e11c6cd..4d7b385 100644
--- a/website/package.json
+++ b/website/package.json
@@ -17,7 +17,9 @@
"@docusaurus/core": "2.4.1",
"@docusaurus/preset-classic": "2.4.1",
"@mdx-js/react": "^1.6.22",
+ "@monaco-editor/react": "^4.5.1",
"autoprefixer": "^10.4.14",
+ "axios": "^1.4.0",
"clsx": "^1.2.1",
"docusaurus-preset-openapi": "^0.6.4",
"postcss": "^8.4.24",
diff --git a/website/src/components/Monaco/index.js b/website/src/components/Monaco/index.js
new file mode 100644
index 0000000..fb72168
--- /dev/null
+++ b/website/src/components/Monaco/index.js
@@ -0,0 +1,185 @@
+import axios from "axios"
+
+import React, { useRef, useEffect } from "react"
+import Editor, { useMonaco } from "@monaco-editor/react"
+
+let TabbyServerURL = "https://app.tabbyml.com/api/workspace/tabbyml/tabby";
+
+export default function MonacoEditor(props) {
+ function beforeMount(monaco) {
+ // Setup theming
+ const LIGHT_BACKGROUND = getColor(
+ "--ifm-background-color"
+ );
+
+ monaco.editor.defineTheme("tabby-light", {
+ base: "vs",
+ inherit: true,
+ rules: [],
+ colors: {
+ "editor.background": LIGHT_BACKGROUND,
+ }
+ });
+
+ monaco.languages.registerInlineCompletionsProvider(
+ { pattern: "**" },
+ new CompletionProvider(monaco)
+ )
+
+ monaco.editor.registerCommand(
+ "acceptTabbyCompletion",
+ (accessor, id, index) => {
+ logAction(id, index, "select")
+ }
+ )
+ }
+
+ return (
+
+ )
+}
+
+class CompletionProvider {
+ constructor(monaco) {
+ this.monaco = monaco
+ this.latestTimestamp = 0
+ }
+
+ async provideInlineCompletions(document, position, context, token) {
+ 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
+ }
+
+ let response
+ try {
+ response = await this.callTabbyApi(currentTimestamp, prompt)
+ } catch (err) {
+ console.error("error", err)
+ return emptyResponse
+ }
+ 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({ data: response.data, items })
+ }
+
+ handleItemDidShow(completions, item) {
+ logAction(completions.data.id, item.choice.index, "view")
+ }
+
+ freeInlineCompletions() {}
+
+ getPrompt(document, position) {
+ 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) {
+ return value === undefined || value === null || value.length === 0
+ }
+
+ sleep(milliseconds) {
+ return new Promise((r) => setTimeout(r, milliseconds))
+ }
+
+ async callTabbyApi(timestamp, prompt) {
+ const request = (this.pendingRequest = axios.post(
+ `${TabbyServerURL}/v1/completions`,
+ {
+ language: "python",
+ prompt,
+ }
+ ))
+ const response = await request
+ this.pendingRequest = null
+ return response
+ }
+
+ toInlineCompletions(value, range) {
+ return (
+ value.choices.map((choice) => ({
+ range,
+ insertText: choice.text,
+ choice,
+ command: {
+ id: "acceptTabbyCompletion",
+ arguments: [value.id, choice.index],
+ },
+ })) || []
+ )
+ }
+
+ hasSuffixParen(document, position) {
+ const suffix = document.getValueInRange(
+ new this.monaco.Range(
+ position.lineNumber,
+ position.column,
+ position.lineNumber,
+ position.column + 1
+ )
+ )
+ return ")]}".indexOf(suffix) > -1
+ }
+}
+
+function logAction(completion_id, choice_index, type) {
+ axios.post(`${TabbyServerURL}/v1/events`, {
+ type,
+ completion_id,
+ choice_index,
+ })
+}
+
+function getColor(property) {
+ const styles = getComputedStyle(document.documentElement);
+ // Weird chrome bug, returns " #ffffff " instead of "#ffffff", see: https://github.com/cloud-annotations/docusaurus-openapi/issues/144
+ const color = styles.getPropertyValue(property).trim();
+ if (color.length === 4) {
+ // change hex short codes like "#fff" to "#ffffff"
+ // to fix: https://github.com/cloud-annotations/docusaurus-openapi/issues/183
+ let res = "#"; // prepend #
+ for (const c of color.substring(1)) {
+ res += c + c; // duplicate each char
+ }
+ return res;
+ }
+ return color;
+}
+
diff --git a/website/src/css/custom.css b/website/src/css/custom.css
index f00f930..41ec67e 100644
--- a/website/src/css/custom.css
+++ b/website/src/css/custom.css
@@ -18,17 +18,14 @@
--ifm-color-primary-lighter: #443f45;
--ifm-color-primary-lightest: #4d484e;
- --ifm-background-color: #e9e1d4;
- --ifm-background-surface-color: var(--ifm-background-color);
+ --ifm-background-color: #FFF8EC;
+ --ifm-background-surface-color: #E9E1D4;
- --ifm-code-background: #FFF8EC;
- --openapi-monaco-background-color-light: #FFF8EC !important;
+ --ifm-code-background: #FFFEFD;
+ --openapi-monaco-background-color-light: var(--ifm-code-background) !important;
--openapi-card-background-color: var(--ifm-code-background) !important;
}
-.monaco-editor{
- background-color: var(--openapi-monaco-background-color-light) !important;
-}
.markdown a {
color: var(--ifm-color-success-darkest);
@@ -56,3 +53,7 @@
height: 10px;
font-weight: 600;
}
+
+.Playground .monaco-editor {
+ padding-top: 15px;
+}
diff --git a/website/src/pages/playground.js b/website/src/pages/playground.js
new file mode 100644
index 0000000..85c92aa
--- /dev/null
+++ b/website/src/pages/playground.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import Layout from '@theme/Layout';
+import Monaco from "@site/src/components/Monaco";
+
+export default function Home() {
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+const DEFAULT_CODE = `import datetime
+
+def parse_expenses(expenses_string):
+ """Parse the list of expenses and return the list of triples (date, value, currency).
+ Ignore lines starting with #.
+ Parse the date using datetime.
+ Example expenses_string:
+ 2016-01-02 -34.01 USD
+ 2016-01-03 2.59 DKK
+ 2016-01-03 -2.72 EUR
+ """`
diff --git a/website/yarn.lock b/website/yarn.lock
index 27f2e86..cbf6595 100644
--- a/website/yarn.lock
+++ b/website/yarn.lock
@@ -1804,7 +1804,7 @@
dependencies:
state-local "^1.0.6"
-"@monaco-editor/react@^4.3.1":
+"@monaco-editor/react@^4.3.1", "@monaco-editor/react@^4.5.1":
version "4.5.1"
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.5.1.tgz#fbc76c692aee9a33b9ab24ae0c5f219b8f002fdb"
integrity sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ==
@@ -2681,6 +2681,15 @@ axios@^0.26.1:
dependencies:
follow-redirects "^1.14.8"
+axios@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
+ integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==
+ dependencies:
+ follow-redirects "^1.15.0"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
babel-loader@^8.2.5:
version "8.3.0"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8"
@@ -4257,7 +4266,7 @@ flux@^4.0.1:
fbemitter "^3.0.0"
fbjs "^3.0.1"
-follow-redirects@^1.0.0, follow-redirects@^1.14.7, follow-redirects@^1.14.8:
+follow-redirects@^1.0.0, follow-redirects@^1.14.7, follow-redirects@^1.14.8, follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
@@ -6781,6 +6790,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"