docs: add code playground (#220)
parent
e7f5d38389
commit
fd2adcba8a
|
|
@ -81,6 +81,7 @@ const config = {
|
||||||
position: 'left',
|
position: 'left',
|
||||||
label: 'Docs',
|
label: 'Docs',
|
||||||
},
|
},
|
||||||
|
{to: '/playground', label: 'Playground', position: 'left'},
|
||||||
{to: '/api', label: 'API', position: 'left'},
|
{to: '/api', label: 'API', position: 'left'},
|
||||||
// FIXME(meng): enable blog.
|
// FIXME(meng): enable blog.
|
||||||
// {to: '/blog', label: 'Blog', position: 'left'},
|
// {to: '/blog', label: 'Blog', position: 'left'},
|
||||||
|
|
@ -97,11 +98,19 @@ const config = {
|
||||||
style: 'dark',
|
style: 'dark',
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
title: 'Docs',
|
title: 'Links',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Self Hosting',
|
label: 'Docs',
|
||||||
to: '/docs/self-hosting',
|
to: '/docs/getting-started',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Playground',
|
||||||
|
to: '/playground',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'API',
|
||||||
|
to: '/api',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@
|
||||||
"@docusaurus/core": "2.4.1",
|
"@docusaurus/core": "2.4.1",
|
||||||
"@docusaurus/preset-classic": "2.4.1",
|
"@docusaurus/preset-classic": "2.4.1",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^1.6.22",
|
||||||
|
"@monaco-editor/react": "^4.5.1",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
|
"axios": "^1.4.0",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"docusaurus-preset-openapi": "^0.6.4",
|
"docusaurus-preset-openapi": "^0.6.4",
|
||||||
"postcss": "^8.4.24",
|
"postcss": "^8.4.24",
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
<Editor
|
||||||
|
beforeMount={beforeMount}
|
||||||
|
theme="tabby-light"
|
||||||
|
defaultLanguage="python"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -18,17 +18,14 @@
|
||||||
--ifm-color-primary-lighter: #443f45;
|
--ifm-color-primary-lighter: #443f45;
|
||||||
--ifm-color-primary-lightest: #4d484e;
|
--ifm-color-primary-lightest: #4d484e;
|
||||||
|
|
||||||
--ifm-background-color: #e9e1d4;
|
--ifm-background-color: #FFF8EC;
|
||||||
--ifm-background-surface-color: var(--ifm-background-color);
|
--ifm-background-surface-color: #E9E1D4;
|
||||||
|
|
||||||
--ifm-code-background: #FFF8EC;
|
--ifm-code-background: #FFFEFD;
|
||||||
--openapi-monaco-background-color-light: #FFF8EC !important;
|
--openapi-monaco-background-color-light: var(--ifm-code-background) !important;
|
||||||
--openapi-card-background-color: 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 {
|
.markdown a {
|
||||||
color: var(--ifm-color-success-darkest);
|
color: var(--ifm-color-success-darkest);
|
||||||
|
|
@ -56,3 +53,7 @@
|
||||||
height: 10px;
|
height: 10px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Playground .monaco-editor {
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
<Layout title="Playground">
|
||||||
|
<main className="flex-grow flex relative">
|
||||||
|
<div className="Playground absolute inset-0">
|
||||||
|
<Monaco
|
||||||
|
defaultValue={DEFAULT_CODE}
|
||||||
|
options={{
|
||||||
|
automaticLayout: true,
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
"""`
|
||||||
|
|
@ -1804,7 +1804,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
state-local "^1.0.6"
|
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"
|
version "4.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.5.1.tgz#fbc76c692aee9a33b9ab24ae0c5f219b8f002fdb"
|
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.5.1.tgz#fbc76c692aee9a33b9ab24ae0c5f219b8f002fdb"
|
||||||
integrity sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ==
|
integrity sha512-NNDFdP+2HojtNhCkRfE6/D6ro6pBNihaOzMbGK84lNWzRu+CfBjwzGt4jmnqimLuqp5yE5viHS2vi+QOAnD5FQ==
|
||||||
|
|
@ -2681,6 +2681,15 @@ axios@^0.26.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects "^1.14.8"
|
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:
|
babel-loader@^8.2.5:
|
||||||
version "8.3.0"
|
version "8.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8"
|
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"
|
fbemitter "^3.0.0"
|
||||||
fbjs "^3.0.1"
|
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"
|
version "1.15.2"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||||
|
|
@ -6781,6 +6790,11 @@ proxy-addr@~2.0.7:
|
||||||
forwarded "0.2.0"
|
forwarded "0.2.0"
|
||||||
ipaddr.js "1.9.1"
|
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:
|
pump@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue