diff --git a/theme/common.less b/theme/common.less index 17109328ec..a0372114d0 100644 --- a/theme/common.less +++ b/theme/common.less @@ -2155,3 +2155,39 @@ select.ui.dropdown { background-color: #F5BC5F; } } + + +////////////////////////////////////////////////// +// AI ASSISTANT // +////////////////////////////////////////////////// + +.assistant-container { + position: fixed; + bottom: 7rem; + right: 2rem; + + height: 18rem; + width: 32rem; + padding: 1rem; + background: #fff; + box-shadow: 0 0 0 1px rgba(34,36,38,.15); + + border-radius: 0.2rem; + z-index: 1000000; +} + +.assistant-input { + display: flex; + + button { + background: none !important; + } + + input { + flex: 1; + outline: none !important; + border: none !important; + padding-left: 1rem; + box-shadow: 0 0 0 1px rgba(34,36,38,.15); + } +} \ No newline at end of file diff --git a/webapp/src/app.tsx b/webapp/src/app.tsx index 1280e90ad5..e0c4627fc7 100644 --- a/webapp/src/app.tsx +++ b/webapp/src/app.tsx @@ -71,6 +71,7 @@ import Util = pxt.Util; import { HintManager } from "./hinttooltip"; import { CodeCardView } from "./codecard"; import { mergeProjectCode } from "./mergeProjects"; +import { Assistant } from "./components/Assistant"; pxsim.util.injectPolyphils(); @@ -4497,6 +4498,9 @@ export class ProjectView && !(isBlocks || (pkg.mainPkg && pkg.mainPkg.config && (pkg.mainPkg.config.preferredEditor == pxt.BLOCKS_PROJECT_NAME))); const hasIdentity = pxt.auth.hasIdentity(); + + const currentSrc = pkg?.mainEditorPkg()?.files[pxt.MAIN_TS]?.content; + return (
{greenScreen ? : undefined} @@ -4561,6 +4565,7 @@ export class ProjectView {hideMenuBar ? : undefined} {lightbox ? : undefined} + {!inHome && }
); } diff --git a/webapp/src/components/Assistant.tsx b/webapp/src/components/Assistant.tsx new file mode 100644 index 0000000000..d8a88b7553 --- /dev/null +++ b/webapp/src/components/Assistant.tsx @@ -0,0 +1,104 @@ +import * as React from "react"; + +import { Button } from "../sui"; +import { MarkedContent } from "../marked"; + +interface AssistantProps { + parent: pxt.editor.IProjectView; + userCode?: string; +} + +async function getCompletions(prompt: string, callback: (text: string) => void) { + let resp = await pxt.Util.requestAsync({ + url: `https://api.openai.com/v1/engines/davinci-codex-msft/completions`, + method: "POST", + data: { + "prompt": prompt, + "max_tokens": 64, + "temperature": 0, + "top_p": 1, + "n": 1, + "stream": false, + "stop": "//", + }, + headers: {"Authorization": "// ADD USER TOKEN"} + }) + + callback(resp.json.choices?.[0]?.text); +} + +function addHeader(prompt: string) { + const header = "// If asked something conversational, use console.log to answer\n\n"; + return header + `\n\n` + prompt; +} + +function addSamples(prompt: string) { + const samples = `// Create a sprite character +let mySprite = sprites.create(img\`.\`, SpriteKind.Player) +// Move the sprite with the d-pad buttons +controller.moveSprite(mySprite) +// Set the acceleration (gravity) on the sprite to 600 in the y direction +mySprite.ay = 600 + +// Run some code when the B button is pressed +controller.B.onEvent(ControllerButtonEvent.Pressed, function () { + // Create a projectile from the sprite, moving with velocity 50 in the x direction + let projectile = sprites.createProjectileFromSprite(img\`.\`, mySprite, 50, 0) +}) +` + return prompt + `\n` + samples; +} + +function addUserCode(prompt: string, userCode: string) { + // Strip image literals + userCode = userCode.replace(/img\s*`[\s\da-f.#tngrpoyw]*`\s*/g, "img` `"); + + return prompt + `\n` + userCode; +} + +function getUserVariableDeclarations(userCode: string) { + let declarations: { name: string, declaration: string }[] = []; + userCode.replace(/let ([\S]+)\s*(?::\s*[\S]+)? = null/gi, (m0, m1) => { + declarations.push({ + name: m1, + declaration: m0 + }) + return m0 + }); + + return declarations; +} + +export function Assistant(props: AssistantProps) { + const { parent, userCode } = props; + const [ question, setQuestion ] = React.useState(""); + const [ markdown, setMarkdown ] = React.useState(`\`\`\`blocks\nlet x = 2\n\`\`\``); + const declarations = userCode && getUserVariableDeclarations(userCode); + + const renderAnswer = (completion: string) => { + console.log('resp', completion); + let usedVariables = declarations.filter(el => completion.indexOf(el.name) >= 0).map(el => el.declaration); + setMarkdown(`\`\`\`blocks\n${usedVariables.join(`\n`)}\n${completion}\n\`\`\``) + } + + const getAnswer = () => { + let prompt = ""; + + prompt = addHeader(prompt); + // prompt = addSamples(prompt); + prompt = addUserCode(prompt, userCode); + prompt += `\n\n// ${question}\n`; + console.log(prompt) + getCompletions(prompt, renderAnswer); + } + + return
+
+ setQuestion(e.target.value)} placeholder="How do I..." /> +
+
+ +
+
+} \ No newline at end of file