firefly-compiler 0.4.40 → 0.4.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.hintrc +4 -4
- package/.vscode/settings.json +4 -4
- package/bin/Release.ff +99 -71
- package/bin/firefly.mjs +1 -1
- package/compiler/Builder.ff +257 -257
- package/compiler/Compiler.ff +227 -227
- package/compiler/Dependencies.ff +186 -186
- package/compiler/DependencyLock.ff +17 -17
- package/compiler/JsEmitter.ff +946 -946
- package/compiler/LspHook.ff +202 -202
- package/compiler/ModuleCache.ff +178 -178
- package/compiler/Workspace.ff +88 -88
- package/core/.firefly/include/package-lock.json +394 -394
- package/core/.firefly/include/package.json +5 -5
- package/core/.firefly/include/prepare.sh +1 -1
- package/core/.firefly/package.ff +2 -2
- package/core/Array.ff +265 -265
- package/core/Atomic.ff +64 -64
- package/core/Box.ff +7 -7
- package/core/BrowserSystem.ff +40 -40
- package/core/BuildSystem.ff +148 -148
- package/core/Crypto.ff +96 -102
- package/core/Equal.ff +36 -36
- package/core/HttpClient.ff +87 -87
- package/core/JsSystem.ff +69 -69
- package/core/Json.ff +434 -434
- package/core/List.ff +415 -415
- package/core/Lock.ff +144 -144
- package/core/NodeSystem.ff +189 -189
- package/core/Ordering.ff +161 -161
- package/core/Path.ff +401 -401
- package/core/Random.ff +134 -134
- package/core/RbMap.ff +216 -216
- package/core/Show.ff +43 -43
- package/core/SourceLocation.ff +68 -68
- package/core/Task.ff +141 -141
- package/experimental/benchmarks/ListGrab.ff +23 -23
- package/experimental/benchmarks/ListGrab.java +55 -55
- package/experimental/benchmarks/Pyrotek45.ff +30 -30
- package/experimental/benchmarks/Pyrotek45.java +64 -64
- package/experimental/bidirectional/Bidi.ff +88 -88
- package/experimental/random/Index.ff +53 -53
- package/experimental/random/Process.ff +120 -120
- package/experimental/random/Scrape.ff +51 -51
- package/experimental/random/Symbols.ff +73 -73
- package/experimental/random/Tensor.ff +52 -52
- package/experimental/random/Units.ff +36 -36
- package/experimental/s3/S3TestAuthorizationHeader.ff +38 -38
- package/experimental/s3/S3TestPut.ff +15 -15
- package/experimental/tests/TestJson.ff +26 -26
- package/firefly.sh +0 -0
- package/fireflysite/Main.ff +13 -13
- package/lsp/.firefly/package.ff +1 -1
- package/lsp/CompletionHandler.ff +811 -811
- package/lsp/Handler.ff +714 -714
- package/lsp/HoverHandler.ff +79 -79
- package/lsp/LanguageServer.ff +272 -272
- package/lsp/SignatureHelpHandler.ff +55 -55
- package/lsp/SymbolHandler.ff +181 -181
- package/lsp/TestReferences.ff +16 -16
- package/lsp/TestReferencesCase.ff +7 -7
- package/lsp/stderr.txt +1 -1
- package/lsp/stdout.txt +34 -34
- package/lux/.firefly/package.ff +1 -1
- package/lux/Css.ff +648 -648
- package/lux/CssTest.ff +48 -48
- package/lux/Lux.ff +487 -487
- package/lux/LuxEvent.ff +116 -116
- package/lux/Main.ff +128 -128
- package/lux/Main2.ff +144 -144
- package/output/js/ff/compiler/Builder.mjs +43 -43
- package/output/js/ff/compiler/Dependencies.mjs +3 -3
- package/output/js/ff/core/Array.mjs +59 -59
- package/output/js/ff/core/Atomic.mjs +36 -36
- package/output/js/ff/core/BrowserSystem.mjs +11 -11
- package/output/js/ff/core/BuildSystem.mjs +30 -30
- package/output/js/ff/core/Crypto.mjs +58 -62
- package/output/js/ff/core/HttpClient.mjs +24 -24
- package/output/js/ff/core/Json.mjs +147 -147
- package/output/js/ff/core/List.mjs +50 -50
- package/output/js/ff/core/Lock.mjs +97 -97
- package/output/js/ff/core/NodeSystem.mjs +77 -77
- package/output/js/ff/core/Ordering.mjs +8 -8
- package/output/js/ff/core/Path.mjs +231 -231
- package/output/js/ff/core/Random.mjs +56 -56
- package/output/js/ff/core/Task.mjs +31 -31
- package/package.json +29 -29
- package/rpc/.firefly/package.ff +1 -1
- package/rpc/Rpc.ff +69 -69
- package/s3/.firefly/package.ff +1 -1
- package/s3/S3.ff +92 -92
- package/unsafejs/UnsafeJs.ff +19 -19
- package/vscode/LICENSE.txt +21 -21
- package/vscode/Prepublish.ff +15 -15
- package/vscode/README.md +16 -16
- package/vscode/client/package.json +22 -22
- package/vscode/client/src/extension.ts +104 -104
- package/vscode/icons/firefly-icon.svg +10 -10
- package/vscode/language-configuration.json +61 -61
- package/vscode/package-lock.json +3623 -3623
- package/vscode/package.json +160 -160
- package/vscode/snippets.json +241 -241
- package/webserver/.firefly/include/package-lock.json +16 -16
- package/webserver/.firefly/include/package.json +5 -5
- package/webserver/.firefly/package.ff +2 -2
- package/webserver/WebServer.ff +685 -685
- package/websocket/.firefly/package.ff +1 -1
- package/websocket/WebSocket.ff +131 -131
package/lux/LuxEvent.ff
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
class LuxEvent {}
|
|
2
|
-
|
|
3
|
-
extend self: LuxEvent {
|
|
4
|
-
|
|
5
|
-
preventDefault(): Unit {unsupported()}
|
|
6
|
-
target js sync """self_.preventDefault()"""
|
|
7
|
-
|
|
8
|
-
stopPropagation(): Unit {unsupported()}
|
|
9
|
-
target js sync """self_.stopPropagation()"""
|
|
10
|
-
|
|
11
|
-
target(): JsValue {unsupported()}
|
|
12
|
-
target js sync """return self_.target"""
|
|
13
|
-
|
|
14
|
-
currentTarget(): JsValue {unsupported()}
|
|
15
|
-
target js sync """return self_.currentTarget"""
|
|
16
|
-
|
|
17
|
-
relatedTarget(): JsValue {unsupported()}
|
|
18
|
-
target js sync """return self_.relatedTarget"""
|
|
19
|
-
|
|
20
|
-
nativeEvent(): JsValue {unsupported()}
|
|
21
|
-
target js sync """return self_"""
|
|
22
|
-
|
|
23
|
-
type(): String {unsupported()}
|
|
24
|
-
target js sync """return self_.type"""
|
|
25
|
-
|
|
26
|
-
altKey(): Bool {unsupported()}
|
|
27
|
-
target js sync """return self_.altKey || false"""
|
|
28
|
-
|
|
29
|
-
ctrlKey(): Bool {unsupported()}
|
|
30
|
-
target js sync """return self_.ctrlKey || false"""
|
|
31
|
-
|
|
32
|
-
shiftKey(): Bool {unsupported()}
|
|
33
|
-
target js sync """return self_.shiftKey || false"""
|
|
34
|
-
|
|
35
|
-
metaKey(): Bool {unsupported()}
|
|
36
|
-
target js sync """return self_.metaKey || false"""
|
|
37
|
-
|
|
38
|
-
button(): Int {unsupported()}
|
|
39
|
-
target js sync """return self_.button || 0"""
|
|
40
|
-
|
|
41
|
-
primaryButton(): Bool {unsupported()}
|
|
42
|
-
target js sync """return !!(self_.buttons & 1)"""
|
|
43
|
-
|
|
44
|
-
secondaryButton(): Bool {unsupported()}
|
|
45
|
-
target js sync """return !!(self_.buttons & 2)"""
|
|
46
|
-
|
|
47
|
-
middleButton(): Bool {unsupported()}
|
|
48
|
-
target js sync """return !!(self_.buttons & 4)"""
|
|
49
|
-
|
|
50
|
-
clientX(): Float {unsupported()}
|
|
51
|
-
target js sync """return self_.clientX || 0"""
|
|
52
|
-
|
|
53
|
-
clientY(): Float {unsupported()}
|
|
54
|
-
target js sync """return self_.clientY || 0"""
|
|
55
|
-
|
|
56
|
-
movementX(): Float {unsupported()}
|
|
57
|
-
target js sync """return self_.movementX || 0"""
|
|
58
|
-
|
|
59
|
-
movementY(): Float {unsupported()}
|
|
60
|
-
target js sync """return self_.movementY || 0"""
|
|
61
|
-
|
|
62
|
-
offsetX(): Float {unsupported()}
|
|
63
|
-
target js sync """return self_.offsetX || 0"""
|
|
64
|
-
|
|
65
|
-
offsetY(): Float {unsupported()}
|
|
66
|
-
target js sync """return self_.offsetY || 0"""
|
|
67
|
-
|
|
68
|
-
pageX(): Float {unsupported()}
|
|
69
|
-
target js sync """return self_.opageX || 0"""
|
|
70
|
-
|
|
71
|
-
pageY(): Float {unsupported()}
|
|
72
|
-
target js sync """return self_.pageY || 0"""
|
|
73
|
-
|
|
74
|
-
screenX(): Float {unsupported()}
|
|
75
|
-
target js sync """return self_.screenX || 0"""
|
|
76
|
-
|
|
77
|
-
screenY(): Float {unsupported()}
|
|
78
|
-
target js sync """return self_.screenY || 0"""
|
|
79
|
-
|
|
80
|
-
key(): String {unsupported()}
|
|
81
|
-
target js sync """return self_.key || ''"""
|
|
82
|
-
|
|
83
|
-
code(): String {unsupported()}
|
|
84
|
-
target js sync """return self_.code || ''"""
|
|
85
|
-
|
|
86
|
-
repeat(): Bool {unsupported()}
|
|
87
|
-
target js sync """return !!self_.repeat"""
|
|
88
|
-
|
|
89
|
-
leftKeyLocation(): Bool {unsupported()}
|
|
90
|
-
target js sync """return self_.location === 1"""
|
|
91
|
-
|
|
92
|
-
rightKeyLocation(): Bool {unsupported()}
|
|
93
|
-
target js sync """return self_.location === 2"""
|
|
94
|
-
|
|
95
|
-
numpadKeyLocation(): Bool {unsupported()}
|
|
96
|
-
target js sync """return self_.location === 3"""
|
|
97
|
-
|
|
98
|
-
isComposing(): Bool {unsupported()}
|
|
99
|
-
target js sync """return !!self_.isComposing"""
|
|
100
|
-
|
|
101
|
-
data(): String {unsupported()}
|
|
102
|
-
target js sync """return self_.data || ''"""
|
|
103
|
-
|
|
104
|
-
text(): String {unsupported()}
|
|
105
|
-
target js sync """return '' + (self_.target.value || '')"""
|
|
106
|
-
|
|
107
|
-
inputType(): String {unsupported()}
|
|
108
|
-
target js sync """return self_.inputType || ''"""
|
|
109
|
-
|
|
110
|
-
dataTransfer(): JsValue {unsupported()}
|
|
111
|
-
target js sync """return self_.dataTransfer"""
|
|
112
|
-
|
|
113
|
-
getTargetRanges(): JsValue {unsupported()}
|
|
114
|
-
target js sync """return self_.getTargetRanges()"""
|
|
115
|
-
|
|
116
|
-
}
|
|
1
|
+
class LuxEvent {}
|
|
2
|
+
|
|
3
|
+
extend self: LuxEvent {
|
|
4
|
+
|
|
5
|
+
preventDefault(): Unit {unsupported()}
|
|
6
|
+
target js sync """self_.preventDefault()"""
|
|
7
|
+
|
|
8
|
+
stopPropagation(): Unit {unsupported()}
|
|
9
|
+
target js sync """self_.stopPropagation()"""
|
|
10
|
+
|
|
11
|
+
target(): JsValue {unsupported()}
|
|
12
|
+
target js sync """return self_.target"""
|
|
13
|
+
|
|
14
|
+
currentTarget(): JsValue {unsupported()}
|
|
15
|
+
target js sync """return self_.currentTarget"""
|
|
16
|
+
|
|
17
|
+
relatedTarget(): JsValue {unsupported()}
|
|
18
|
+
target js sync """return self_.relatedTarget"""
|
|
19
|
+
|
|
20
|
+
nativeEvent(): JsValue {unsupported()}
|
|
21
|
+
target js sync """return self_"""
|
|
22
|
+
|
|
23
|
+
type(): String {unsupported()}
|
|
24
|
+
target js sync """return self_.type"""
|
|
25
|
+
|
|
26
|
+
altKey(): Bool {unsupported()}
|
|
27
|
+
target js sync """return self_.altKey || false"""
|
|
28
|
+
|
|
29
|
+
ctrlKey(): Bool {unsupported()}
|
|
30
|
+
target js sync """return self_.ctrlKey || false"""
|
|
31
|
+
|
|
32
|
+
shiftKey(): Bool {unsupported()}
|
|
33
|
+
target js sync """return self_.shiftKey || false"""
|
|
34
|
+
|
|
35
|
+
metaKey(): Bool {unsupported()}
|
|
36
|
+
target js sync """return self_.metaKey || false"""
|
|
37
|
+
|
|
38
|
+
button(): Int {unsupported()}
|
|
39
|
+
target js sync """return self_.button || 0"""
|
|
40
|
+
|
|
41
|
+
primaryButton(): Bool {unsupported()}
|
|
42
|
+
target js sync """return !!(self_.buttons & 1)"""
|
|
43
|
+
|
|
44
|
+
secondaryButton(): Bool {unsupported()}
|
|
45
|
+
target js sync """return !!(self_.buttons & 2)"""
|
|
46
|
+
|
|
47
|
+
middleButton(): Bool {unsupported()}
|
|
48
|
+
target js sync """return !!(self_.buttons & 4)"""
|
|
49
|
+
|
|
50
|
+
clientX(): Float {unsupported()}
|
|
51
|
+
target js sync """return self_.clientX || 0"""
|
|
52
|
+
|
|
53
|
+
clientY(): Float {unsupported()}
|
|
54
|
+
target js sync """return self_.clientY || 0"""
|
|
55
|
+
|
|
56
|
+
movementX(): Float {unsupported()}
|
|
57
|
+
target js sync """return self_.movementX || 0"""
|
|
58
|
+
|
|
59
|
+
movementY(): Float {unsupported()}
|
|
60
|
+
target js sync """return self_.movementY || 0"""
|
|
61
|
+
|
|
62
|
+
offsetX(): Float {unsupported()}
|
|
63
|
+
target js sync """return self_.offsetX || 0"""
|
|
64
|
+
|
|
65
|
+
offsetY(): Float {unsupported()}
|
|
66
|
+
target js sync """return self_.offsetY || 0"""
|
|
67
|
+
|
|
68
|
+
pageX(): Float {unsupported()}
|
|
69
|
+
target js sync """return self_.opageX || 0"""
|
|
70
|
+
|
|
71
|
+
pageY(): Float {unsupported()}
|
|
72
|
+
target js sync """return self_.pageY || 0"""
|
|
73
|
+
|
|
74
|
+
screenX(): Float {unsupported()}
|
|
75
|
+
target js sync """return self_.screenX || 0"""
|
|
76
|
+
|
|
77
|
+
screenY(): Float {unsupported()}
|
|
78
|
+
target js sync """return self_.screenY || 0"""
|
|
79
|
+
|
|
80
|
+
key(): String {unsupported()}
|
|
81
|
+
target js sync """return self_.key || ''"""
|
|
82
|
+
|
|
83
|
+
code(): String {unsupported()}
|
|
84
|
+
target js sync """return self_.code || ''"""
|
|
85
|
+
|
|
86
|
+
repeat(): Bool {unsupported()}
|
|
87
|
+
target js sync """return !!self_.repeat"""
|
|
88
|
+
|
|
89
|
+
leftKeyLocation(): Bool {unsupported()}
|
|
90
|
+
target js sync """return self_.location === 1"""
|
|
91
|
+
|
|
92
|
+
rightKeyLocation(): Bool {unsupported()}
|
|
93
|
+
target js sync """return self_.location === 2"""
|
|
94
|
+
|
|
95
|
+
numpadKeyLocation(): Bool {unsupported()}
|
|
96
|
+
target js sync """return self_.location === 3"""
|
|
97
|
+
|
|
98
|
+
isComposing(): Bool {unsupported()}
|
|
99
|
+
target js sync """return !!self_.isComposing"""
|
|
100
|
+
|
|
101
|
+
data(): String {unsupported()}
|
|
102
|
+
target js sync """return self_.data || ''"""
|
|
103
|
+
|
|
104
|
+
text(): String {unsupported()}
|
|
105
|
+
target js sync """return '' + (self_.target.value || '')"""
|
|
106
|
+
|
|
107
|
+
inputType(): String {unsupported()}
|
|
108
|
+
target js sync """return self_.inputType || ''"""
|
|
109
|
+
|
|
110
|
+
dataTransfer(): JsValue {unsupported()}
|
|
111
|
+
target js sync """return self_.dataTransfer"""
|
|
112
|
+
|
|
113
|
+
getTargetRanges(): JsValue {unsupported()}
|
|
114
|
+
target js sync """return self_.getTargetRanges()"""
|
|
115
|
+
|
|
116
|
+
}
|
package/lux/Main.ff
CHANGED
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
import HttpServer from ff:httpserver
|
|
2
|
-
|
|
3
|
-
import Lux
|
|
4
|
-
import LuxEvent
|
|
5
|
-
import Css
|
|
6
|
-
|
|
7
|
-
data ChatEntry(
|
|
8
|
-
question: String
|
|
9
|
-
answer: Option[String]
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
mainComponent(lux: Lux, http: HttpClient) {
|
|
13
|
-
lux.useState([]): chat, setChat =>
|
|
14
|
-
lux.useState(""): message, setMessage =>
|
|
15
|
-
lux.div {
|
|
16
|
-
lux.div {
|
|
17
|
-
chat.each {questionComponent(lux, http, _)}
|
|
18
|
-
}
|
|
19
|
-
lux.form {
|
|
20
|
-
lux.input {
|
|
21
|
-
//.with("autofocus", "true")
|
|
22
|
-
lux.setValue(message)
|
|
23
|
-
lux.onInput {event => setMessage(event.text())}
|
|
24
|
-
}
|
|
25
|
-
lux.on("submit") {event =>
|
|
26
|
-
event.preventDefault()
|
|
27
|
-
setMessage("")
|
|
28
|
-
setChat([...chat, message])
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
questionCss = CssClass([Css.marginBottom("10px")], [], [])
|
|
35
|
-
|
|
36
|
-
questionComponent(lux: Lux, http: HttpClient, question: String) {
|
|
37
|
-
lux.div {
|
|
38
|
-
lux.cssClass(questionCss)
|
|
39
|
-
lux.div {lux.text("User: " + question)}
|
|
40
|
-
lux.useLazy1(question): _ =>
|
|
41
|
-
lux.useSuspense {lux.div {lux.text("Assistant typing...")}}: lux =>
|
|
42
|
-
let answer = http.fetch(
|
|
43
|
-
url = "/chat"
|
|
44
|
-
method = "POST"
|
|
45
|
-
body = Some(HttpClient.bodyText(question))
|
|
46
|
-
).readText()
|
|
47
|
-
lux.div {lux.text("Assistant: " + answer)}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
browserMain(system: BrowserSystem) {
|
|
52
|
-
Lux.renderById(system, "main") {lux => mainComponent(lux, system.httpClient())}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
nodeMain(system: NodeSystem) {
|
|
56
|
-
let openAiKey = system.arguments().first()
|
|
57
|
-
HttpServer.listen(system, "localhost", 8080) {request, response =>
|
|
58
|
-
if(request.path() == "/") {
|
|
59
|
-
response.setHeader("Content-Type", ["text/html; charset=UTF-8"])
|
|
60
|
-
response.writeText("<!doctype html>")
|
|
61
|
-
response.writeText("<div id='main'></div>")
|
|
62
|
-
response.writeText("<script type='module' src='/js/script/script/Main.mjs'></script>")
|
|
63
|
-
} elseIf {request.path().startsWith("/js/") && !request.path().contains("..")} {
|
|
64
|
-
response.setHeader("Content-Type", ["text/javascript; charset=UTF-8"])
|
|
65
|
-
response.writeText(system.assets().readText(request.path()))
|
|
66
|
-
} elseIf {request.path() == "/chat"} {
|
|
67
|
-
let question = request.readText()
|
|
68
|
-
response.setHeader("Content-Type", ["text/plain; charset=UTF-8"])
|
|
69
|
-
openAiKey.{
|
|
70
|
-
| None =>
|
|
71
|
-
system.mainTask().sleep(Duration(question.size().toFloat()))
|
|
72
|
-
response.writeText("Hi! You need to give an OpenAI secret key at the command line.")
|
|
73
|
-
| Some(key) =>
|
|
74
|
-
let chatJson = encodeChat(question)
|
|
75
|
-
let answer = fetchAnswer(system.httpClient(), key, chatJson)
|
|
76
|
-
response.writeText(answer)
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
response.writeStatus(404, Some("Not found"))
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
fetchAnswer(httpClient: HttpClient, key: String, question: Json): String {
|
|
85
|
-
let json = httpClient.fetch(
|
|
86
|
-
url = "https://api.openai.com/v1/chat/completions"
|
|
87
|
-
method = "POST"
|
|
88
|
-
headers = [
|
|
89
|
-
Pair("Authorization", "Bearer " + key)
|
|
90
|
-
Pair("Content-Type", "application/json")
|
|
91
|
-
]
|
|
92
|
-
body = Some(HttpClient.bodyText(question.write()))
|
|
93
|
-
).readJson()
|
|
94
|
-
json.field("choices").index(0).field("message").field("content").grabString()
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
encodeChat(question: String): Json {
|
|
98
|
-
let systemJson = Json.object()
|
|
99
|
-
.with("role", "system")
|
|
100
|
-
.with("content", "You are a helpful assistant.")
|
|
101
|
-
let questionJson = Json.object()
|
|
102
|
-
.with("role", "user")
|
|
103
|
-
.with("content", question)
|
|
104
|
-
Json.object()
|
|
105
|
-
.with("model", "gpt-3.5-turbo")
|
|
106
|
-
.with("messages", [systemJson, questionJson])
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/*
|
|
111
|
-
let
|
|
112
|
-
encodeMessage role content =
|
|
113
|
-
E.object [ ("role", E.string role), ("content", E.string content) ]
|
|
114
|
-
encodeEntry question maybeAnswer = case maybeAnswer of
|
|
115
|
-
Nothing -> [ encodeMessage "user" question ]
|
|
116
|
-
Just answer -> [ encodeMessage "user" question, encodeMessage "assistant" answer ]
|
|
117
|
-
encodedMessages = encodeMessage "system" "You are a helpful assistant."
|
|
118
|
-
:: List.concatMap (\entry -> encodeEntry entry.question entry.answer) chat
|
|
119
|
-
in E.object
|
|
120
|
-
[ ("model", E.string "gpt-3.5-turbo")
|
|
121
|
-
, ("messages", E.list (\x -> x) encodedMessages)
|
|
122
|
-
]
|
|
123
|
-
*/
|
|
124
|
-
buildMain(system: BuildSystem) {
|
|
125
|
-
let browser = system.compileForBrowser("Main.ff")
|
|
126
|
-
let assets = AssetSystem.create().addAssets("/js", browser.assets())
|
|
127
|
-
system.setAssets(assets)
|
|
128
|
-
}
|
|
1
|
+
import HttpServer from ff:httpserver
|
|
2
|
+
|
|
3
|
+
import Lux
|
|
4
|
+
import LuxEvent
|
|
5
|
+
import Css
|
|
6
|
+
|
|
7
|
+
data ChatEntry(
|
|
8
|
+
question: String
|
|
9
|
+
answer: Option[String]
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
mainComponent(lux: Lux, http: HttpClient) {
|
|
13
|
+
lux.useState([]): chat, setChat =>
|
|
14
|
+
lux.useState(""): message, setMessage =>
|
|
15
|
+
lux.div {
|
|
16
|
+
lux.div {
|
|
17
|
+
chat.each {questionComponent(lux, http, _)}
|
|
18
|
+
}
|
|
19
|
+
lux.form {
|
|
20
|
+
lux.input {
|
|
21
|
+
//.with("autofocus", "true")
|
|
22
|
+
lux.setValue(message)
|
|
23
|
+
lux.onInput {event => setMessage(event.text())}
|
|
24
|
+
}
|
|
25
|
+
lux.on("submit") {event =>
|
|
26
|
+
event.preventDefault()
|
|
27
|
+
setMessage("")
|
|
28
|
+
setChat([...chat, message])
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
questionCss = CssClass([Css.marginBottom("10px")], [], [])
|
|
35
|
+
|
|
36
|
+
questionComponent(lux: Lux, http: HttpClient, question: String) {
|
|
37
|
+
lux.div {
|
|
38
|
+
lux.cssClass(questionCss)
|
|
39
|
+
lux.div {lux.text("User: " + question)}
|
|
40
|
+
lux.useLazy1(question): _ =>
|
|
41
|
+
lux.useSuspense {lux.div {lux.text("Assistant typing...")}}: lux =>
|
|
42
|
+
let answer = http.fetch(
|
|
43
|
+
url = "/chat"
|
|
44
|
+
method = "POST"
|
|
45
|
+
body = Some(HttpClient.bodyText(question))
|
|
46
|
+
).readText()
|
|
47
|
+
lux.div {lux.text("Assistant: " + answer)}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
browserMain(system: BrowserSystem) {
|
|
52
|
+
Lux.renderById(system, "main") {lux => mainComponent(lux, system.httpClient())}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
nodeMain(system: NodeSystem) {
|
|
56
|
+
let openAiKey = system.arguments().first()
|
|
57
|
+
HttpServer.listen(system, "localhost", 8080) {request, response =>
|
|
58
|
+
if(request.path() == "/") {
|
|
59
|
+
response.setHeader("Content-Type", ["text/html; charset=UTF-8"])
|
|
60
|
+
response.writeText("<!doctype html>")
|
|
61
|
+
response.writeText("<div id='main'></div>")
|
|
62
|
+
response.writeText("<script type='module' src='/js/script/script/Main.mjs'></script>")
|
|
63
|
+
} elseIf {request.path().startsWith("/js/") && !request.path().contains("..")} {
|
|
64
|
+
response.setHeader("Content-Type", ["text/javascript; charset=UTF-8"])
|
|
65
|
+
response.writeText(system.assets().readText(request.path()))
|
|
66
|
+
} elseIf {request.path() == "/chat"} {
|
|
67
|
+
let question = request.readText()
|
|
68
|
+
response.setHeader("Content-Type", ["text/plain; charset=UTF-8"])
|
|
69
|
+
openAiKey.{
|
|
70
|
+
| None =>
|
|
71
|
+
system.mainTask().sleep(Duration(question.size().toFloat()))
|
|
72
|
+
response.writeText("Hi! You need to give an OpenAI secret key at the command line.")
|
|
73
|
+
| Some(key) =>
|
|
74
|
+
let chatJson = encodeChat(question)
|
|
75
|
+
let answer = fetchAnswer(system.httpClient(), key, chatJson)
|
|
76
|
+
response.writeText(answer)
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
response.writeStatus(404, Some("Not found"))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fetchAnswer(httpClient: HttpClient, key: String, question: Json): String {
|
|
85
|
+
let json = httpClient.fetch(
|
|
86
|
+
url = "https://api.openai.com/v1/chat/completions"
|
|
87
|
+
method = "POST"
|
|
88
|
+
headers = [
|
|
89
|
+
Pair("Authorization", "Bearer " + key)
|
|
90
|
+
Pair("Content-Type", "application/json")
|
|
91
|
+
]
|
|
92
|
+
body = Some(HttpClient.bodyText(question.write()))
|
|
93
|
+
).readJson()
|
|
94
|
+
json.field("choices").index(0).field("message").field("content").grabString()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
encodeChat(question: String): Json {
|
|
98
|
+
let systemJson = Json.object()
|
|
99
|
+
.with("role", "system")
|
|
100
|
+
.with("content", "You are a helpful assistant.")
|
|
101
|
+
let questionJson = Json.object()
|
|
102
|
+
.with("role", "user")
|
|
103
|
+
.with("content", question)
|
|
104
|
+
Json.object()
|
|
105
|
+
.with("model", "gpt-3.5-turbo")
|
|
106
|
+
.with("messages", [systemJson, questionJson])
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
let
|
|
112
|
+
encodeMessage role content =
|
|
113
|
+
E.object [ ("role", E.string role), ("content", E.string content) ]
|
|
114
|
+
encodeEntry question maybeAnswer = case maybeAnswer of
|
|
115
|
+
Nothing -> [ encodeMessage "user" question ]
|
|
116
|
+
Just answer -> [ encodeMessage "user" question, encodeMessage "assistant" answer ]
|
|
117
|
+
encodedMessages = encodeMessage "system" "You are a helpful assistant."
|
|
118
|
+
:: List.concatMap (\entry -> encodeEntry entry.question entry.answer) chat
|
|
119
|
+
in E.object
|
|
120
|
+
[ ("model", E.string "gpt-3.5-turbo")
|
|
121
|
+
, ("messages", E.list (\x -> x) encodedMessages)
|
|
122
|
+
]
|
|
123
|
+
*/
|
|
124
|
+
buildMain(system: BuildSystem) {
|
|
125
|
+
let browser = system.compileForBrowser("Main.ff")
|
|
126
|
+
let assets = AssetSystem.create().addAssets("/js", browser.assets())
|
|
127
|
+
system.setAssets(assets)
|
|
128
|
+
}
|