From 2d5a69e85d7b0b2b48ad10983f1ccc9664eb113f Mon Sep 17 00:00:00 2001 From: Stachu Korick Date: Tue, 16 May 2023 16:02:06 -0400 Subject: [PATCH] wip --- backend/src/Wasm/Libs/Editor.fs | 2 +- backend/src/Wasm/Wasm.fsproj | 2 +- canvases/dark-client/client.dark | 479 ++++++++++++++++++ canvases/dark-client/config.yml | 12 + canvases/dark-client/main.dark | 72 +++ canvases/dark-client/system-prompt.txt | 161 ++++++ canvases/dark-client/template.secrets | 3 + .../dark-client/wordmark-dark-transparent.png | Bin 0 -> 9844 bytes canvases/dark-editor-stachu/client.dark | 428 ++++++++++++++++ canvases/dark-editor-stachu/client/_todo.dark | 62 +++ canvases/dark-editor-stachu/client/html.dark | 97 ++++ canvases/dark-editor-stachu/client/main.dark | 43 ++ canvases/dark-editor-stachu/client/model.dark | 16 + .../dark-editor-stachu/client/openai.dark | 50 ++ .../dark-editor-stachu/client/prelude.dark | 28 + .../dark-editor-stachu/client/update.dark | 101 ++++ canvases/dark-editor-stachu/client/view.dark | 2 + canvases/dark-editor-stachu/config.yml | 12 + canvases/dark-editor-stachu/main.dark | 72 +++ canvases/dark-editor-stachu/system-prompt.txt | 161 ++++++ canvases/dark-editor-stachu/template.secrets | 3 + .../wordmark-dark-transparent.png | Bin 0 -> 9844 bytes 22 files changed, 1804 insertions(+), 2 deletions(-) create mode 100644 canvases/dark-client/client.dark create mode 100644 canvases/dark-client/config.yml create mode 100644 canvases/dark-client/main.dark create mode 100644 canvases/dark-client/system-prompt.txt create mode 100644 canvases/dark-client/template.secrets create mode 100644 canvases/dark-client/wordmark-dark-transparent.png create mode 100644 canvases/dark-editor-stachu/client.dark create mode 100644 canvases/dark-editor-stachu/client/_todo.dark create mode 100644 canvases/dark-editor-stachu/client/html.dark create mode 100644 canvases/dark-editor-stachu/client/main.dark create mode 100644 canvases/dark-editor-stachu/client/model.dark create mode 100644 canvases/dark-editor-stachu/client/openai.dark create mode 100644 canvases/dark-editor-stachu/client/prelude.dark create mode 100644 canvases/dark-editor-stachu/client/update.dark create mode 100644 canvases/dark-editor-stachu/client/view.dark create mode 100644 canvases/dark-editor-stachu/config.yml create mode 100644 canvases/dark-editor-stachu/main.dark create mode 100644 canvases/dark-editor-stachu/system-prompt.txt create mode 100644 canvases/dark-editor-stachu/template.secrets create mode 100644 canvases/dark-editor-stachu/wordmark-dark-transparent.png diff --git a/backend/src/Wasm/Libs/Editor.fs b/backend/src/Wasm/Libs/Editor.fs index 9bfde28d9d..07adb87349 100644 --- a/backend/src/Wasm/Libs/Editor.fs +++ b/backend/src/Wasm/Libs/Editor.fs @@ -124,7 +124,7 @@ let fns : List = let source = Json.Vanilla.deserialize sourceJson let stdLib = - LibExecution.StdLib.combine [ StdLibExecution.StdLib.contents ] [] [] + LibExecution.StdLib.combine [ StdLibExecution.StdLib.contents; Wasm.Libs.HttpClient.contents ] [] [] let! result = let expr = exprsCollapsedIntoOne source.exprs diff --git a/backend/src/Wasm/Wasm.fsproj b/backend/src/Wasm/Wasm.fsproj index 2eb4febd5c..cd80dfd74d 100644 --- a/backend/src/Wasm/Wasm.fsproj +++ b/backend/src/Wasm/Wasm.fsproj @@ -30,8 +30,8 @@ - + diff --git a/canvases/dark-client/client.dark b/canvases/dark-client/client.dark new file mode 100644 index 0000000000..1d6d11dd11 --- /dev/null +++ b/canvases/dark-client/client.dark @@ -0,0 +1,479 @@ +// TODO: let ignore (z: ): Unit +module Prelude = + let alert (s: String): Unit = + let _ = WASM.Editor.callJSFunction "alert" [s] + () + + let log (s: String): Unit = + let _ = WASM.Editor.callJSFunction "console.log" [s] + () + + let rUnwrap (result: Result<'a, 'b>): 'a = + match result with + | Ok s -> s + | Error e -> + log e // TODO: this won't work if non-string + alert "Expected OK, got Error - see log" + + let parseAndSerializeProgram (userProgram: String): String = + let response = + WASM.HttpClient.request + "POST" + "http://dark-editor.dlio.localhost:11003/get-program-json" + [] + (String.toBytes userProgram) + + response |> rUnwrap |> (fun r -> r.body) |> String.fromBytes + + +module OpenAI = + // todo: throw this stuff into an OpenAI module? + type OpenAIChatCompletionRequestMessage = + { role: String; content: String } + type OpenAIChatCompletionRequest = + { model: String + max_tokens: Int + temperature: Float + messages : List } + + type OpenAIChatCompletionResponseChoiceMessage = { content: String } + type OpenAIChatCompletionResponseChoice = { message: OpenAIChatCompletionResponseChoiceMessage } + type OpenAIChatCompletionResponse = { choices: List } + + let openAIcompletion (prompt: String): Result = + let apiKey = + let url = "http://dark-editor.dlio.localhost:11003/openai-apikey-yikes" + match WASM.HttpClient.request "get" url [] Bytes.empty with + | Ok response -> response.body |> String.fromBytes + + let openAIRequest = + OpenAIChatCompletionRequest + { model = "gpt-3.5-turbo" + max_tokens = 700 + temperature = 0.7 + messages = [OpenAIChatCompletionRequestMessage {role = "user"; content = prompt}] } + + match Json.serialize openAIRequest with + | Ok reqBody -> + let headers = + [ + ("authorization", "Bearer " ++ apiKey) + ("content-type", "application/json") + ] + + let openAIResponse = + WASM.HttpClient.request "POST" "https://api.openai.com/v1/chat/completions" headers (String.toBytes reqBody) + + match openAIResponse with + | Ok r -> + match Json.parse (String.fromBytes r.body) with + | Ok r -> + match List.head r.choices with + | Just c -> Ok c.message.content + + | Nothing -> Error ("No choices returned") + | Error err -> Error ("Couldn't parse open ai completino response - " ++ err) + | Error e -> Error ("OpenAI API request failed\n" ++ e) + | Error e -> Error ("Couldn't serialize request" ++ e) + + +module Html = + // module to be used in the dark editor to write HTML + type Attribute = { key: String; value: String } + + let attr (key: String) (value: String): Attribute = + { key = key; value = value } + + let br () = tag "br" [] [] + let str (s: String): String = s + let comment (s: String): String = "" + + + + let document (nodes: List): String = + let htmlDoc = "" + let theRest = nodes + |>String::join "" + htmlDoc ++ theRest + + let tag (name: String) (attributes: List) (children: List): String = + let attributesText = + attributes + |> List.map (fun attr -> attr.key ++ "=" ++ attr.value ++ "") + |> String.join " " + let startTag = "<" ++ tag ++ " " ++ attributesText ++ ">" + let endTag = "" + let childHtml = String.join children "" + startTag ++ childHtml ++ endTag + + + let a (attributes: List) (children: List): String = + tag "a" attributes children + + // same for div, span, etc.: + let div (attributes: List) (children: List): String = + tag "div" attributes children + + let span (attributes: List) (children: List): String = + tag "span" attributes children + + let h1 (attributes: List) (children: List): String = + tag "h1" attributes children + + let h2 (attributes: List) (children: List): String = + tag "h2" attributes children + + let h3 (attributes: List) (children: List): String = + tag "h3" attributes children + + let h4 (attributes: List) (children: List): String = + tag "h4" attributes children + + let h5 (attributes: List) (children: List): String = + tag "h5" attributes children + + let h6 (attributes: List) (children: List): String = + tag "h6" attributes children + + let p (attributes: List) (children: List): String = + tag "p" attributes children + + let ul (attributes: List) (children: List): String = + tag "ul" attributes children + + let ol (attributes: List) (children: List): String = + tag "ol" attributes children + + let li (attributes: List) (children: List): String = + tag "li" attributes children + + let table (attributes: List) (children: List): String = + tag "table" attributes children + + let tr (attributes: List) (children: List): String = + tag "tr" attributes children + + let td (attributes: List) (children: List): String = + tag "td" attributes children + + let th (attributes: List) (children: List): String = + tag "th" attributes children + + let tbody (attributes: List) (children: List): String = + tag "tbody" attributes children + + let thead (attributes: List) (children: List): String = + tag "thead" attributes children + + let tfoot (attributes: List) (children: List): String = + tag "tfoot" attributes children + + let caption (attributes: List) (children: List): String = + tag "caption" attributes children + + let colgroup (attributes: List) (children: List): String = + tag "colgroup" attributes children + + let col (attributes: List) (children: List): String = + tag "col" attributes children + + +// 100k token (200pg book) context windows +module Poe = + let foo = "bar" + + + +// TODO: in the prompt, include something like +// "all code snippets returned must be executable without any other context needed" + +// Model + +let view<'a> (a: 'a): String = + """< + """ + +type View<'a> = { view: 'a -> String } // html + +type Code = | Code of code: String + +type UserProgram = + { Id: String + Name: String + CodeSnippets: List // these are concated after being sorted by type + Docs: List + Secrets: List } + +type CodeSnippetType = Type | Function | Script | View | HttpEndpoint +type CodeSnippet = { id: String; name: String; type: CodeSnippetType code: Code } +type Secret = { id: String; key: String; value: String } +type Doc = { id: String; name: String; text: String } + + + + +// call upon "CanvasHack" with a UserProgram +// and refresh their application + + +type EditorFocus = + | Home + | EditCode of relevantCodePieces: List + + +type GptResponsePart = + | UpdatedSnippet of code: String // todo: tidy to smaller 'diff' later + | Text + | Options + +type UIActionAvailable = + | + +type AvailableAction = + | EvalCode + | AddFunction + | AddType + | UpdateType + | Other of prompt: String // resolve later + +type GptResponse = + { UpdatedSnippet: Option + Text: Option + AvailableActions: List } + + +type EditorContext = + { program: UserProgram + focus: EditorFocus } + + +type BotResponseItem = + | Text of text: String + | CodeSnippet of id: String + +type ChatHistoryItem = + | UserPrompt of id: String * prompt: String + | BotResponse of id: String * items: List // todo: add promptId to tie together with relevant UserPrompt + +type CodeSnippet = + { id: String; code: String; eval: Option } + +type Model = + { systemPrompt: String + chatHistory: List + codeSnippets: List } + + +type Msg = + | UserGavePrompt of prompt: String + | UserRequestedCodeEval of id: String * codeSnippet: String + +let update (model: Model) (msg: Msg) : Model = + match msg with + | UserGavePrompt userPrompt -> + // I guess, until we have cmds or something, + // we have to deal with http calls and such in-line, like here + let fullPrompt = model.systemPrompt ++ userPrompt + + match openAIcompletion fullPrompt with + | Ok apiResponse -> + let (newCodeSnippets, botResponseParts) = + (String.split_v1 (" " ++ apiResponse) "```") + |> List.indexedMap (fun i text -> (i, text)) + |> List.fold ([], []) (fun acc curr -> + let (codeSnippets, responseParts) = acc + let (i, text) = curr + let text = String.trim text + + if text == "" then + acc + else + if i % 2 == 1 then + let snippetId = rUnwrap(String.random_v2 5) + let updatedCodeSnippets = List.append codeSnippets [CodeSnippet { id = snippetId; code = text; eval = Nothing }] + let updatedResponseParts = List.append responseParts [BotResponseItem.CodeSnippet snippetId] + (updatedCodeSnippets, updatedResponseParts) + else + codeSnippets, (List.append responseParts [BotResponseItem.Text text]) + ) + + let newChatItemsItems = [ + ChatHistoryItem.UserPrompt (rUnwrap(String.random_v2 5)) userPrompt + ChatHistoryItem.BotResponse (rUnwrap(String.random_v2 5)) botResponseParts + ] + + Model + { systemPrompt = model.systemPrompt + chatHistory = List.append model.chatHistory newChatItemsItems + codeSnippets = List.append model.codeSnippets newCodeSnippets } + + | Error err -> alert err + + | UserRequestedCodeEval id codeSnippet -> + // split this into 2 groups - the one we're updating, and the rest + let (snippetToUpdate, otherSnippets) = + model.codeSnippets |> List.partition (fun cs -> cs.id == id) + + match List.head snippetToUpdate with + | Nothing -> alert "couldn't find snippet" + | Just snippetToUpdate -> + let parsedAndSerialized = parseAndSerializeProgram codeSnippet + + let evalResult = (WASM.Editor.evalUserProgram parsedAndSerialized) |> rUnwrap + + let updatedCodeSnippets = + List.append + [CodeSnippet { id = snippetToUpdate.id; code = codeSnippet; eval = Just evalResult }] + otherSnippets + + Model + { systemPrompt = model.systemPrompt + chatHistory = model.chatHistory + codeSnippets = updatedCodeSnippets } + + + /// Single point of communicating to JS host + /// + /// Let the JS host know that the state has been updated, + /// so that it can update the UI accordingly. + let updateStateInJS (newState: Model): Result = + match Json.serialize newState with + | Ok serialized -> + let _ = WASM.Editor.callJSFunction "window.stateUpdated" [serialized] + Ok () + | Error err -> + let _ = WASM.Editor.callJSFunction "console.warn" ["Couldn't serialize - " ++ err] + Error "Couldn't serialize updated state" + + + /// Single point of communication from JS host + /// + /// Listen for events from the JS host, and update the state accordingly. + let handleEvent (evt: String): Result = + match Json.parse evt with + | Ok msg -> + match WASM.Editor.getState with + | Ok currentState -> + let newState = update currentState msg + + // returns result, but let's assume it worked... + let _ = WASM.Editor.setState newState + + updateStateInJS newState + + | Error err -> Error "Couldn't get current state" + | Error err -> Error "Couldn't parse raw msg" + + +// Init +// (things to run on startup, before accepting any events +// the initial state is set to the result of the final expr) +let wordsAboutDark = + // we need a reasonably-sophisticated way of doing this + WASM.HttpClient.request "get" "http://dark-editor.dlio.localhost:11003/system-prompt" [] Bytes.empty + |> rUnwrap + +let systemPrompt = "" + +let initState = + match systemPrompt with + | Ok response -> + let demoSnippet = + CodeSnippet + { id = (String.random_v2 5) |> rUnwrap + code = "let incr (i: Int): Int = i + 1\n\nincr 5" + eval = Nothing } + + let chatHistory = [ + ChatHistoryItem.UserPrompt + (rUnwrap(String.random_v2 5)) + "Hello, I want a function that increments an integer!" + + ChatHistoryItem.BotResponse + (rUnwrap(String.random_v2 5)) + [ + BotResponseItem.Text "OK - here's some code:" + BotResponseItem.CodeSnippet demoSnippet.id + ] + ] + + Model + { systemPrompt = String.fromBytes response.body + chatHistory = chatHistory + codeSnippets = [demoSnippet] } + + | Error err -> + Model + { systemPrompt = String.fromBytes "nope" + chatHistory = [ ] } + +updateStateInJS initState + +initState + + + + + + + + + +// // Models to interop with JS host +// // (TODO: wrap this in `module JS = `) +// type BotResponseJS + +// type ChatHistoryItemJS = +// | BotResponse of { author : string; body : String } + +// type ModelForJS = +// // should be 1:1 with Model, but nice and serializeable +// { systemPrompt : string +// state : string +// code: String +// chatHistory: List } + +// // TODO: result +// let stateForJS (state: Model) : ModelForJS = +// { systemPrompt = state.systemPrompt +// state = match state.State with +// | WaitingForFirstInput -> "WaitingForFirstInput" +// | WaitingForUserInput -> "WaitingForUserInput" +// | BotIsThinking -> "BotIsThinking" +// chatHistory = +// state.chatHistory +// |> List.map (fun item -> +// { author = match item.author with User -> "User" | Bot -> "Bot" +// prompt = item.text }) } + +// // TODO: result +// let modelFromJS (model: ModelForJS) : Model = +// { SystemPrompt = Prompt model.systemPrompt +// State = +// match model.state with +// | "WaitingForFirstInput" -> WaitingForFirstInput +// | "WaitingForUserInput" -> WaitingForUserInput +// | "BotIsThinking" -> BotIsThinking +// ChatHistory = +// model.chatHistory +// |> List.map (fun item -> +// { Author = match item.author with "User" -> User | "Bot" -> Bot +// Prompt = item.text }) } + + +// type EventJS = { eventName: String; data: String } + +// let msgOfJS (msg: EventJS) : Msg = +// match msg.eventName with +// | "userGavePrompt" -> UserGavePrompt (Prompt msg.data) +// | "botResponded" -> BotResponded (Prompt msg.data) +// | _ -> failwith "Couldn't parse event name" + +// let msgToJS (msg: Msg) : EventJS = +// match msg with +// | UserGavePrompt prompt -> +// { eventName = "userGavePrompt" +// data = match prompt with Prompt p -> p } +// | BotResponded prompt -> +// { eventName = "botResponded" +// data = match prompt with Prompt p -> p } \ No newline at end of file diff --git a/canvases/dark-client/config.yml b/canvases/dark-client/config.yml new file mode 100644 index 0000000000..d00b3b6c83 --- /dev/null +++ b/canvases/dark-client/config.yml @@ -0,0 +1,12 @@ +version: 2 +id: 11111111-1111-1111-1111-111111111111 +main: main +client: + - client/prelude.dark + - client/html.dark + - client/openai.dark + - client/model.dark + - client/update.dark + - client/view.dark + - client/main.dark + diff --git a/canvases/dark-client/main.dark b/canvases/dark-client/main.dark new file mode 100644 index 0000000000..e1fe879216 --- /dev/null +++ b/canvases/dark-client/main.dark @@ -0,0 +1,72 @@ +[] +let _handler _req = + Http.response (String.toBytes OPENAI_API_KEY) 200 + +[] +let _handler _req = + let filePath = "dark-editor-vue/dist/index.html" + let file = Experiments.readFromCanvases filePath + let body = + match file with + | Ok f -> f + | Error _ -> Bytes.empty + + Http.responseWithHeaders + (body) + (Dict.fromListOverwritingDuplicates [ ("Content-Type", "text/html") ]) + 200 + +[] +let _handler _req = + match Experiments.readFromCanvases ("dark-editor-vue/dist/assets/" ++ path) with + | Ok fileContents -> + let contentType = + match (String.split_v1 path ".") |> List.last_v2 |> Option.withDefault "" with + | "js" -> "application/javascript" + | "json" -> "application/json" + | "css" -> "text/css" + | _ -> "text/plain" + + let headers = + Dict.fromListOverwritingDuplicates [ ("content-type", contentType) ] + + Http.responseWithHeaders fileContents headers 200 + + | Error _e -> Http.response (String.toBytes "Couldn't load asset") 400 + + +[] +let _handler _req = + match Experiments.readFromCanvases "dark-editor/client.dark" with + | Ok sourceInBytes -> Http.response sourceInBytes 200 + | Error _err -> Http.response (String.toBytes "Couldn't load client.dark from disk") 500 + + +// TODO: remove once a parser can run in WASM runtime +[] +let _handler _req = + let sourceInBytes = request.body + let program = + Experiments.parseAndSerializeProgram (String.fromBytes sourceInBytes) "user-code.dark" + + match program with + | Ok program -> + let types = Option.withDefault (Dict.get_v2 program "types") "[]" + let fns = Option.withDefault (Dict.get_v2 program "fns") "[]" + let exprs = Option.withDefault (Dict.get_v2 program "exprs") "[]" + + let json = "{ \"types\": " ++ types ++ ", \"fns\": " ++ fns ++ ", \"exprs\": " ++ exprs ++ "}" + + Http.responseWithHeaders + (String.toBytes json) + (Dict.fromListOverwritingDuplicates [ ("content-type", "application-json")]) + 200 + + | Error err -> Http.response (String.toBytes ("Couldn't parse the program \n" ++ err)) 400 + + +[] +let _handler _req = + match Experiments.readFromCanvases "dark-editor/system-prompt.txt" with + | Ok prompt -> Http.response prompt 200 + | Error _e -> Http.response (String.toBytes "Couldn't load prompt from disk") 500 diff --git a/canvases/dark-client/system-prompt.txt b/canvases/dark-client/system-prompt.txt new file mode 100644 index 0000000000..fcc3fbbf44 --- /dev/null +++ b/canvases/dark-client/system-prompt.txt @@ -0,0 +1,161 @@ +You are a tool that is used by Dark developers to write and manipulate their Dark +programs. + +Dark is a programming language used to write backends. In addition to the language, +it has built-in support for DBs, REPLs, CRONs, Workers, etc. The term "toplevels" is +used to refer to these distinct parts of a user's program - a user DB, a REPL, a +CRON, etc. + +Dark is very similar to F#, including syntax and semantics, with these exceptions: + +- Dark does not support "record deconstruction" in `match` expressions +- field and function names use camelCase in Dark, not snake_case +- Dark does not support mutation. +- all names must be fully-qualified (eg `String.trimLeft`) and imports (or `open`) are not supported +- string interpolation replaces `sprintf`, for example `$"My name is {name}"` +--- + +# Dark features: + +## Types: + +Dark is statically typed, and supports type definitions. Types are good and should be used often. +- records are supported, such as `type MyType = { x : int }` +- enums are supported, such as `type MyType = | OptionOne of Int | SecondOption | Another of Int * String * Bool` + +## Functions: + +Dark supports functions at the top level, but not within other functions. Functions are good and should be used often. +- functions are defined using `let` +- all parameter types must be specified (i.e. `String` or `Int`) and are capitalized +- the return type must also be specified + +for example: + +let increment (x: Int) : Int = + x + 1 + +## HTTP handlers + +HTTP handlers are expressions, with each handler stored in a .dark file. + +A request has a `request` variable available: +`{ url: String; headers: List; body: Bytes }` + +The expression should return a response with this type: +`{ statusCode: int; headers: List; body: byte[] }` + +With each response display a list a variables that needs to be filled by the user + +For example: + +hello.dark +``` +[] +let _handler _req = + let body = ("hello " ++ user )|> String.toBytes + Http.response body 200 +``` +variables: +user + + +## Standard library + +type HttpResponse = { + statusCode: int + headers: List + body: Bytes +} + +Here are some functions available in Dark: +- DB.set (dbName: String) (item: 'a) : Uuid +- DB.get (dbName: String) (key: String) : 'a +- HttpClient.request (method: String) (url: String) (headers: List) (body: Bytes) : HttpResponse +- String.fromBytes (bytes: Bytes): String +- String.toBytes (text: String): Bytes +- Json.toString<'a> (v: 'a): String +- Json.parse<'a> (j: String) : 'a + +These are the available standard library modules: +- Int +- Character +- String +- Float +- Bool +- List +- Option +- Result +- DB +- HttpClient +- Json + +## Json + +To use Json, create types that match the JSON request and response, and use the types +to create and parse json. For example: + +``` +type TwilioRequest = { + message : String + from: String + to: String + retries: Int +} + +type TwilioResponse = { + error : Option + successCode : Int +} + +let request = ... +let requestBody = Json.serialize(request) +... +let response = Json.parse(responseBody) +``` + +Only use type names, not anonymous type definitions. + +## Secrets + +Secrets are supported. Secrets are string variables named using ALL_CAPS, such as +`TWILIO_APIKEY` or `AWS_OIDC_FEDERATION_ID`. They are already available, you do not +need to call any functions. + +--- + +Your job is to assist in building an application in Dark. +When you reply, ensure all code samples are executable without any other context. +Below all types and function defined, include at least one expression to be evaluated. +Within the code snippets, do not add code comments. +All code snippets should be wrapped in ``` tags. + +Here's an example of a prompt/response combination: + +User: build a small program that models a list of People, +a function that returns the age of a Person, then hardcode a list of people +and return the age of each person + +You: +Ok, no problem. +``` +type Person = { + name: String + age: Int +} + +let getAge (p: Person) : Int = + p.age + +let people = [ + { name = "Alice"; age = 20 } + { name = "Bob"; age = 30 } +] + +people |> List.map getAge +``` + +Hope that helps! + +--- + diff --git a/canvases/dark-client/template.secrets b/canvases/dark-client/template.secrets new file mode 100644 index 0000000000..a7c42eae8d --- /dev/null +++ b/canvases/dark-client/template.secrets @@ -0,0 +1,3 @@ +#copy this to just .secrets and fill in the values + +OPENAI_API_KEY=TEST \ No newline at end of file diff --git a/canvases/dark-client/wordmark-dark-transparent.png b/canvases/dark-client/wordmark-dark-transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..ff5232a4aca3d3d6dacedf6a716fb9d4b303414b GIT binary patch literal 9844 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 literal 0 HcmV?d00001 diff --git a/canvases/dark-editor-stachu/client.dark b/canvases/dark-editor-stachu/client.dark new file mode 100644 index 0000000000..dd1d9144e6 --- /dev/null +++ b/canvases/dark-editor-stachu/client.dark @@ -0,0 +1,428 @@ +// TODO: let ignore (z: ): Unit +module Prelude = + let alert (s: String): Unit = + let _ = WASM.Editor.callJSFunction "alert" [s] + () + + let log (s: String): Unit = + let _ = WASM.Editor.callJSFunction "console.log" [s] + () + + let rUnwrap (result: Result<'a, 'b>): 'a = + match result with + | Ok s -> s + | Error e -> + log e // TODO: this won't work if non-string + alert "Expected OK, got Error - see log" + + let parseAndSerializeProgram (userProgram: String): String = + let response = + WASM.HttpClient.request + "POST" + "http://dark-editor.dlio.localhost:11003/get-program-json" + [] + (String.toBytes userProgram) + + response |> rUnwrap |> (fun r -> r.body) |> String.fromBytes + + +module OpenAI = + type ChatCompletionRequestMessage = + { role: String; content: String } + type ChatCompletionRequest = + { model: String + max_tokens: Int + temperature: Float + messages : List } + + type ChatCompletionResponseChoiceMessage = { content: String } + type ChatCompletionResponseChoice = { message: ChatCompletionResponseChoiceMessage } + type ChatCompletionResponse = { choices: List } + + let completion (prompt: String): Result = + let apiKey = + let url = "http://dark-editor.dlio.localhost:11003/openai-apikey-yikes" + match WASM.HttpClient.request "get" url [] Bytes.empty with + | Ok response -> response.body |> String.fromBytes + + let request = + ChatCompletionRequest + { model = "gpt-3.5-turbo" + max_tokens = 700 + temperature = 0.7 + messages = [ChatCompletionRequestMessage {role = "user"; content = prompt}] } + + match Json.serialize Request with + | Ok reqBody -> + let headers = + [ + ("authorization", "Bearer " ++ apiKey) + ("content-type", "application/json") + ] + + let response = + WASM.HttpClient.request "POST" "https://api.openai.com/v1/chat/completions" headers (String.toBytes reqBody) + + match response with + | Ok r -> + match Json.parse (String.fromBytes r.body) with + | Ok r -> + match List.head r.choices with + | Just c -> Ok c.message.content + + | Nothing -> Error ("No choices returned") + | Error err -> Error ("Couldn't parse open ai completino response - " ++ err) + | Error e -> Error ("OpenAI API request failed\n" ++ e) + | Error e -> Error ("Couldn't serialize request" ++ e) + + +module Html = + // module to be used in the dark editor to write HTML + type Attribute = { key: String; value: String } + + let attr (key: String) (value: String): Attribute = + { key = key; value = value } + + let br () = tag "br" [] [] + let str (s: String): String = s + let comment (s: String): String = "" + + + + let document (nodes: List): String = + let htmlDoc = "" + let theRest = nodes + |>String::join "" + htmlDoc ++ theRest + + let tag (name: String) (attributes: List) (children: List): String = + let attributesText = + attributes + |> List.map (fun attr -> attr.key ++ "=" ++ attr.value ++ "") + |> String.join " " + let startTag = "<" ++ tag ++ " " ++ attributesText ++ ">" + let endTag = "" + let childHtml = String.join children "" + startTag ++ childHtml ++ endTag + + + let a (attributes: List) (children: List): String = + tag "a" attributes children + + // same for div, span, etc.: + let div (attributes: List) (children: List): String = + tag "div" attributes children + + let span (attributes: List) (children: List): String = + tag "span" attributes children + + let h1 (attributes: List) (children: List): String = + tag "h1" attributes children + + let h2 (attributes: List) (children: List): String = + tag "h2" attributes children + + let h3 (attributes: List) (children: List): String = + tag "h3" attributes children>) (children: List): String = + tag "h6" attributes children + + let p (attributes: List) (children: List): String = + tag "p" attributes children + + let ul (attributes: List) (children: List): String = + tag "ul" attributes children + + let ol (attributes: List) (children: List): String = + tag "ol" attributes children + + let li (attributes: List) (children: List): String = + tag "li" attributes children + + + +// 100k token (~200pg book) context windows +module Poe = + let foo = "bar" + + + +// TODO: in the prompt, include something like +// "all code snippets returned must be executable without any other context needed" + +// Model + +let view<'a> (a: 'a): String = + """< + """ + +type View<'a> = { view: 'a -> String } // html + +type Code = | Code of code: String + +type UserProgram = + { Id: String + Name: String + Functions: List + Types: List + HttpEndpoints: List + Data: List + Context: List + Scripts: List