feat: interop through app actions

This commit is contained in:
BetaHuhn 2023-09-21 15:16:27 +02:00
parent 09c01e4b38
commit 9f305f3203
18 changed files with 862 additions and 188 deletions

3
.gitignore vendored
View file

@ -2,4 +2,5 @@ node_modules
.parcel-cache
dist
notes.txt
lib
lib
.env

View file

@ -22,3 +22,7 @@ micros:
public_routes:
- "/public/*"
provide_actions: true
presets:
env:
- name: SPACE_ACCESS_TOKEN
description: Space Access Token used for optional interoperability features

View file

@ -11,6 +11,8 @@
"dependencies": {
"deta": "^2.0.0",
"deta-space-actions": "^0.1.9",
"deta-space-client": "^2.2.2",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"nanoid": "^3.3.6"
},
@ -512,11 +514,18 @@
"ajv": "^6.9.1"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@ -745,11 +754,23 @@
"node": ">=6.0"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@ -760,8 +781,7 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/commander": {
"version": "2.20.3",
@ -814,6 +834,14 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"node_modules/cross-fetch": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
"dependencies": {
"node-fetch": "^2.6.12"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@ -888,6 +916,18 @@
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/deta-space-client": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/deta-space-client/-/deta-space-client-2.2.2.tgz",
"integrity": "sha512-xGficxfyJBHfQydr3qmNjaadmQfbYPfJWS/6qXM/hGLS8a8OQuJAhaASBPJTW5DXKopRHZAafqNTEu8IGgevyw==",
"dependencies": {
"cross-fetch": "^4.0.0",
"yargs": "^17.7.1"
},
"bin": {
"space-client": "dist/cli/index.js"
}
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@ -897,6 +937,17 @@
"node": ">=0.3.1"
}
},
"node_modules/dotenv": {
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/motdotla/dotenv?sponsor=1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -909,6 +960,11 @@
"dev": true,
"peer": true
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -941,8 +997,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6"
}
@ -1169,6 +1223,14 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@ -1316,6 +1378,14 @@
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@ -1799,6 +1869,14 @@
"node": ">=8.10.0"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -1968,6 +2046,30 @@
"node": ">= 0.8"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -2338,12 +2440,61 @@
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"engines": {
"node": ">=12"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
@ -2814,11 +2965,15 @@
"peer": true,
"requires": {}
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
@ -2968,11 +3123,20 @@
"dev": true,
"peer": true
},
"cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
@ -2980,8 +3144,7 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"commander": {
"version": "2.20.3",
@ -3025,6 +3188,14 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"cross-fetch": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
"requires": {
"node-fetch": "^2.6.12"
}
},
"data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@ -3076,12 +3247,26 @@
}
}
},
"deta-space-client": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/deta-space-client/-/deta-space-client-2.2.2.tgz",
"integrity": "sha512-xGficxfyJBHfQydr3qmNjaadmQfbYPfJWS/6qXM/hGLS8a8OQuJAhaASBPJTW5DXKopRHZAafqNTEu8IGgevyw==",
"requires": {
"cross-fetch": "^4.0.0",
"yargs": "^17.7.1"
}
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
"dotenv": {
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ=="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -3094,6 +3279,11 @@
"dev": true,
"peer": true
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -3119,9 +3309,7 @@
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"dev": true,
"peer": true
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"escape-html": {
"version": "1.0.3",
@ -3291,6 +3479,11 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@ -3399,6 +3592,11 @@
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@ -3741,6 +3939,11 @@
"picomatch": "^2.2.1"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -3867,6 +4070,24 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
},
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
}
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"requires": {
"ansi-regex": "^5.0.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -4103,12 +4324,46 @@
"webidl-conversions": "^3.0.0"
}
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"requires": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
}
},
"yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",

View file

@ -15,6 +15,8 @@
"dependencies": {
"deta": "^2.0.0",
"deta-space-actions": "^0.1.9",
"deta-space-client": "^2.2.2",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"nanoid": "^3.3.6"
},

View file

@ -1,8 +1,6 @@
import { Deta } from 'deta'
import { nanoid } from "nanoid"
export * from '../../src/db/types.js'
const deta = Deta()
export const chats = deta.Base('chats')

View file

@ -1,12 +1,16 @@
import express from 'express'
import { CardType, createActions, Inputs } from 'deta-space-actions'
import dotenv from 'dotenv'
dotenv.config()
import { chats, messages, prompts, settings, generateKey } from './db.js'
import type { Chat, Prompt, Settings } from './db.js'
import type { Chat, Prompt, Settings } from './types.js'
import { createChatWithMessage } from './openai.js'
import { useInterop } from './interop.js'
const app = express()
const actions = createActions()
const interop = useInterop()
const domain = process.env.DETA_SPACE_APP_HOSTNAME
@ -35,6 +39,36 @@ app.get('/public/chats/:key/messages', async (req, res) => {
res.json(items)
})
app.get('/space/actions/config', async (req, res) => {
res.json({
isSetup: interop.isSetup
})
})
app.get('/space/actions', async (req, res) => {
const actions = await interop.listActions()
console.log(actions)
const filtered = actions.filter(action => {
const inputs = action.input
if (!inputs || inputs.length < 1) return false
const textInputs = inputs.filter(input => input.type === 'string')
return textInputs.length > 0
})
res.json(filtered)
})
app.post('/space/actions/invoke', async (req, res) => {
const { instanceId, actionName, payload } = req.body
const invocationRes = await interop.invokeAction(instanceId, actionName, payload)
res.json(invocationRes)
})
actions.add<{ prompt?: string }>({
name: 'create_chat',
title: 'Create Chat',

36
backend/src/interop.ts Normal file
View file

@ -0,0 +1,36 @@
import { fetchFn } from "deta-space-client"
import { AppAction } from "./types"
export const useInterop = (token?: string) => {
const authToken = token || process.env.SPACE_ACCESS_TOKEN || ''
const fetch = fetchFn(authToken)
const request = async (path: string, body?: any, method = 'GET') => {
const res = await fetch(path, {
method,
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json'
}
})
return res.json()
}
const listActions = async () => {
const data = await request('/actions')
return data.actions as AppAction[]
}
const invokeAction = async (instanceId: string, actionName: string, payload?: any) => {
const data = await request(`/actions/${instanceId}/${actionName}`, payload, 'POST')
return data
}
return {
request,
listActions,
invokeAction,
isSetup: !!authToken
}
}

View file

@ -1,4 +1,5 @@
import { chats, messages, prompts, generateKey, Prompt, Message, Chat, Settings } from './db.js'
import { chats, messages, prompts, generateKey } from './db.js'
import type { Prompt, Message, Chat, Settings } from './types.js'
import { getSystemMessage, createChatCompletion } from '../../src/utils/openai.js'

1
backend/src/types.ts Normal file
View file

@ -0,0 +1 @@
export * from '../../src/db/types.js'

23
package-lock.json generated
View file

@ -12,6 +12,7 @@
"@mantine/hooks": "^6.0.1",
"@mantine/next": "^6.0.1",
"@mantine/notifications": "^6.0.1",
"@mantine/spotlight": "^6.0.2",
"@tabler/icons-react": "^2.9.0",
"@tanstack/react-location": "^3.7.4",
"@types/node": "18.15.0",
@ -1275,6 +1276,20 @@
"react-dom": ">=16.8.0"
}
},
"node_modules/@mantine/spotlight": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@mantine/spotlight/-/spotlight-6.0.2.tgz",
"integrity": "sha512-MdC6qXivhrjiKPyM1WPwcaWIo8thfGpKMv9lNuzSKJRzF5n3Uiexucyrb1B+Ca6JqEWWMMuDJ4iNIijAQRhc/Q==",
"dependencies": {
"@mantine/utils": "6.0.2"
},
"peerDependencies": {
"@mantine/core": "6.0.2",
"@mantine/hooks": "6.0.2",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@mantine/ssr": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@mantine/ssr/-/ssr-6.0.2.tgz",
@ -8427,6 +8442,14 @@
"react-transition-group": "4.4.2"
}
},
"@mantine/spotlight": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@mantine/spotlight/-/spotlight-6.0.2.tgz",
"integrity": "sha512-MdC6qXivhrjiKPyM1WPwcaWIo8thfGpKMv9lNuzSKJRzF5n3Uiexucyrb1B+Ca6JqEWWMMuDJ4iNIijAQRhc/Q==",
"requires": {
"@mantine/utils": "6.0.2"
}
},
"@mantine/ssr": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@mantine/ssr/-/ssr-6.0.2.tgz",

View file

@ -26,6 +26,7 @@
"@emotion/react": "^11.10.6",
"@emotion/server": "^11.10.0",
"@mantine/core": "^6.0.1",
"@mantine/spotlight": "^6.0.2",
"@mantine/hooks": "^6.0.1",
"@mantine/next": "^6.0.1",
"@mantine/notifications": "^6.0.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -14,7 +14,7 @@ import {
} from "@mantine/core";
import { useClickOutside } from "@mantine/hooks";
import { IconArrowForward, IconCopy, IconTrash, IconUser } from "@tabler/icons-react";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Message, detaDB } from "../db";
@ -22,6 +22,8 @@ import "../styles/markdown.scss";
import { CreatePromptModal } from "./CreatePromptModal";
import { LogoIcon } from "./Logo";
import { notifications } from "@mantine/notifications";
import { useSpaceAppActions } from "../hooks/contexts";
import { SelectIntegrationModal } from "./SelectIntegrationModal";
export function MessageItem({ message, readOnly = false, onDeleted, handleUseMessage }: { message: Message, readOnly?: boolean, onDeleted?: (key: string) => void, handleUseMessage?: (key: string) => void }) {
const wordCount = useMemo(() => {
@ -29,9 +31,18 @@ export function MessageItem({ message, readOnly = false, onDeleted, handleUseMes
return matches ? matches.length : 0;
}, [message.content]);
const [isExpanded, setExpanded] = useState(false);
const ref = useClickOutside(() => setExpanded(false));
const theme = useMantineTheme();
const markdownRef = useRef<HTMLDivElement>(null)
const { spaceAppActions } = useSpaceAppActions()
const [isExpanded, setExpanded] = useState(false);
const [html, setHtml] = useState('')
useEffect(() => {
setHtml(markdownRef.current?.innerHTML || '')
}, [ref])
const handleDelete = async () => {
await detaDB.messages.delete(message.key);
@ -66,7 +77,7 @@ export function MessageItem({ message, readOnly = false, onDeleted, handleUseMes
</ThemeIcon>
)}
{message.role === "assistant" && <LogoIcon style={{ height: 32 }} color1={theme.colors[theme.primaryColor][3]} color2={theme.colors[theme.primaryColor][7]} />}
<Box sx={{ flex: 1, width: 0 }} className="markdown">
<Box sx={{ flex: 1, width: 0 }} className="markdown" ref={markdownRef}>
<ReactMarkdown
children={message.content}
remarkPlugins={[remarkGfm]}
@ -111,6 +122,17 @@ export function MessageItem({ message, readOnly = false, onDeleted, handleUseMes
</Box>
<Box style={{ display: 'flex' }}>
{spaceAppActions !== null && (
<SelectIntegrationModal contentHtml={html} contentText={message.content}>
<Tooltip label="Send to Space" position="top">
<ActionIcon >
{/* <IconBolt opacity={0.5} size={20} /> */}
<img src="/assets/integrations/deta.png" height={20} style={{ opacity: 0.75 }} />
</ActionIcon>
</Tooltip>
</SelectIntegrationModal>
)}
{!readOnly && handleUseMessage && (
<>
<CreatePromptModal content={message.content || ''} />

View file

@ -0,0 +1,231 @@
import { notifications } from "@mantine/notifications";
import { SpotlightProvider, spotlight } from '@mantine/spotlight';
import { cloneElement, ReactElement, useEffect, useState } from "react";
import { useChat, useSpaceAppActions } from "../hooks/contexts";
import { IconBolt, IconSearch } from "@tabler/icons-react";
import { AppAction } from "../db";
export function SelectIntegrationModal({
contentText,
contentHtml,
children,
}: {
contentText: string;
contentHtml: string;
children: ReactElement;
}) {
const [open, setOpen] = useState(false);
const { chat } = useChat()
const { setSpaceAppActions } = useSpaceAppActions()
const [spotlightActions, setSpotlightActions] = useState<any[]>([]);
useEffect(() => {
const dataFetch = async () => {
const res = await fetch(`/api/space/actions`)
const items = await res.json()
const actions = items as unknown as AppAction[]
setSpaceAppActions(actions)
const spotlightActionsItems = actions.map(action => ({
title: action.title,
description: action.instance_alias,
icon: action.icon_url ? <img width={28} style={{ borderRadius: 5 }} src={action.icon_url} alt={action.title} /> : <IconBolt size={28} />,
onTrigger: async () => {
try {
if (action) {
const titleKeys = ['title', 'name']
const contentKeys = ['content', 'description', 'text']
let payload: { [key: string]: string } = {}
for (const input of action.input!) {
if (titleKeys.includes(input.name)) {
payload[input.name] = chat?.description || 'Dialogue AI Chat'
} else if (contentKeys.includes(input.name)) {
payload[input.name] = action.app_name === 'Minima' ? contentHtml : contentText
} else if (input.name === 'html') {
payload[input.name] = contentHtml
}
}
const res = await fetch(`/api/space/actions/invoke`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
instanceId: action.instance_id,
actionName: action.name,
payload: payload
})
})
const invocation = await res.json()
console.log(invocation)
if (invocation.type === '@deta/raw') {
navigator.clipboard.writeText(invocation.data)
} else if (invocation.type === '@deta/detail') {
navigator.clipboard.writeText(invocation.data.ref || invocation.data.url || invocation.data.title || invocation.data.text)
} else {
navigator.clipboard.writeText(JSON.stringify(invocation.data))
}
notifications.show({
title: "Success",
color: "green",
message: `Action "${action.title}" invoked successfully.`,
})
} else {
notifications.show({
title: "Error",
color: "red",
message: "Integration not found.",
});
}
} catch (error: any) {
if (error.toJSON().message === "Network Error") {
notifications.show({
title: "Error",
color: "red",
message: "No internet connection.",
});
}
const message = error.response?.data?.error?.message;
if (message) {
notifications.show({
title: "Error",
color: "red",
message,
});
}
}
},
}))
setSpotlightActions(spotlightActionsItems)
spotlight.open()
}
if (open) {
dataFetch()
}
}, [open])
const handleOpen = () => {
setOpen(true)
}
return (
<>
{cloneElement(children, { onClick: handleOpen })}
{open && (
<SpotlightProvider
actions={spotlightActions}
searchIcon={<IconSearch size="1.2rem" />}
searchPlaceholder="Search..."
shortcut={null}
nothingFoundMessage="Nothing found..."
onSpotlightOpen={() => setOpen(true)}
onSpotlightClose={() => setOpen(false)}
limit={Infinity}
>
</SpotlightProvider>
)}
{/* <Modal opened={opened} onClose={close} title="Space App Actions" withinPortal size="lg">
<form
onSubmit={async (event) => {
try {
setSubmitting(true);
event.preventDefault();
const action = spaceAppActions?.find(i => {
const appName = selectedAppAction.split('__')[0]
const actionName = selectedAppAction.split('__')[1]
return i.app_name === appName && i.name === actionName
})
if (action) {
const titleKeys = ['title', 'name']
const contentKeys = ['content', 'description', 'text']
let payload: { [key: string]: string } = {}
for (const input of action.input!) {
if (titleKeys.includes(input.name)) {
payload[input.name] = chat?.description || 'Dialogue AI Chat'
} else if (contentKeys.includes(input.name)) {
payload[input.name] = contentHtml
}
}
const res = await fetch(`/api/space/actions/invoke`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
instanceId: action.instance_id,
actionName: action.name,
payload: payload
})
})
console.log(res)
navigator.clipboard.writeText('noteUrl')
notifications.show({
title: "Success",
color: "green",
message: "Message stored in Minima and link copied to clipboard.",
})
} else {
notifications.show({
title: "Error",
color: "red",
message: "Integration not found.",
});
}
} catch (error: any) {
if (error.toJSON().message === "Network Error") {
notifications.show({
title: "Error",
color: "red",
message: "No internet connection.",
});
}
const message = error.response?.data?.error?.message;
if (message) {
notifications.show({
title: "Error",
color: "red",
message,
});
}
} finally {
setSubmitting(false);
setSelectedAppAction('');
close();
}
}}
>
<Stack>
<Select
value={selectedAppAction}
onChange={(value) => setSelectedAppAction(value ?? '')}
data={(spaceAppActions || []).map(appAction => ({ value: `${appAction.app_name}__${appAction.name}`, label: `${appAction.app_name} ${appAction.title} - ${appAction.instance_alias}` }))}
placeholder="Select App Action"
variant="filled"
searchable
/>
<Button type="submit" loading={submitting}>
Send to App
</Button>
</Stack>
</form>
</Modal> */}
</>
);
}

View file

@ -47,4 +47,37 @@ export interface Chat {
key: string;
instance: string
apiKey: string
}
export enum ViewType {
DETAIL = '@deta/detail',
RAW = '@deta/raw',
FILE = '@deta/file',
LIST = '@deta/list',
TABLE = '@deta/table',
}
export type ActionInput = {
name: string
title?: string
type?: string
optional: boolean
}
export type AppAction = {
title: string
name: string
description: string
local?: boolean
output: ViewType
app_name: string
instance_id: string
instance_alias: string
channel: string
version: string
input?: ActionInput[]
icon_url?: string
placeholder_icon_config?: {
css_background: string
}
}

View file

@ -1,5 +1,5 @@
import React, { useContext } from "react";
import type { Chat, Prompt, Settings } from "../db";
import type { AppAction, Chat, Prompt, Settings } from "../db";
export const ChatContext = React.createContext<{
chat: Chat | null,
@ -44,4 +44,13 @@ export const IncognitoModeContext = React.createContext<{
export function useIncognitoMode() {
return useContext(IncognitoModeContext);
}
export const SpaceAppActionsContext = React.createContext<{
spaceAppActions: AppAction[] | null,
setSpaceAppActions: React.Dispatch<React.SetStateAction<AppAction[] | null>>
}>({ spaceAppActions: null, setSpaceAppActions: () => {} });
export function useSpaceAppActions() {
return useContext(SpaceAppActionsContext);
}

View file

@ -33,7 +33,7 @@ import { CreatePromptModal } from "../components/CreatePromptModal";
import { LogoIcon } from "../components/Logo";
import { SettingsModal } from "../components/SettingsModal";
import { config } from "../utils/config";
import { ChatContext, ChatsContext, IncognitoModeContext, PromptsContext, SettingsContext } from "../hooks/contexts";
import { ChatContext, ChatsContext, IncognitoModeContext, PromptsContext, SettingsContext, SpaceAppActionsContext } from "../hooks/contexts";
import { ChatHeader } from "../components/ChatHeader";
import { useLocalStorage } from "@mantine/hooks";
import { CreateChatButton } from "../components/CreateChatButton";
@ -81,6 +81,20 @@ export function BaseLayout({ children }: { children: React.ReactNode }) {
dataFetch();
}, []);
const [spaceAppActions, setSpaceAppActions] = useState<any[] | null>(null);
useEffect(() => {
const dataFetch = async () => {
const res = await fetch(`/api/space/actions/config`)
const config = await res.json()
if (config.isSetup) {
setSpaceAppActions([])
}
};
dataFetch();
}, []);
const [chat, setChat] = useState<Chat | null>(null);
useEffect(() => {
const dataFetch = async () => {
@ -163,180 +177,182 @@ export function BaseLayout({ children }: { children: React.ReactNode }) {
return (
<SettingsContext.Provider value={{ settings: settings, setSettings: setSettings }}>
<ChatContext.Provider value={{ chat: chat, setChat: setChat }}>
<ChatsContext.Provider value={{ chats: chats, setChats: setChats }}>
<PromptsContext.Provider value={{ prompts: prompts, setPrompts: setPrompts }}>
<IncognitoModeContext.Provider value={{ incognitoMode: incognitoMode, setIncognitoMode: setIncognitoMode }}>
<AppShell
className={`${colorScheme}-theme`}
navbarOffsetBreakpoint="sm"
navbar={
<Navbar width={{ md: 300 }} hiddenBreakpoint="md" hidden={!opened}>
<Navbar.Section className="app-region-drag">
<Box
style={{
height: 60,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: 10,
borderBottom: border,
}}
>
<Link
to="/"
className="app-region-no-drag"
style={{ padding: 4, textDecoration: 'none', color: 'inherit', display: 'flex', alignItems: 'center', gap: 8 }}
<SpaceAppActionsContext.Provider value={{ spaceAppActions: spaceAppActions, setSpaceAppActions: setSpaceAppActions }}>
<ChatContext.Provider value={{ chat: chat, setChat: setChat }}>
<ChatsContext.Provider value={{ chats: chats, setChats: setChats }}>
<PromptsContext.Provider value={{ prompts: prompts, setPrompts: setPrompts }}>
<IncognitoModeContext.Provider value={{ incognitoMode: incognitoMode, setIncognitoMode: setIncognitoMode }}>
<AppShell
className={`${colorScheme}-theme`}
navbarOffsetBreakpoint="sm"
navbar={
<Navbar width={{ md: 300 }} hiddenBreakpoint="md" hidden={!opened}>
<Navbar.Section className="app-region-drag">
<Box
style={{
height: 60,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: 10,
borderBottom: border,
}}
>
<LogoIcon style={{ height: 22, display: "block", }} color1={theme.colors[theme.primaryColor][3]} color2={theme.colors[theme.primaryColor][7]} />
<Text size='1.25rem' weight={600}>
Dialogue AI
</Text>
</Link>
<Box style={{ display: 'flex', alignItems: 'center' }}>
{incognitoMode ? (
<Tooltip label="Disable Incognito Mode">
<ActionIcon
size="xl"
onClick={handleIncognito}
>
<IconSpyOff size={20} />
</ActionIcon>
</Tooltip>
) : (
<Tooltip label="Incognito Mode">
<ActionIcon
size="xl"
onClick={handleIncognito}
>
<IconSpy size={20} />
</ActionIcon>
</Tooltip>
)}
<SettingsModal>
<Tooltip label="Settings">
<ActionIcon
size="xl"
sx={(theme) => ({
[theme.fn.smallerThan('md')]: {
marginRight: '2.5rem',
},
})}
<Link
to="/"
className="app-region-no-drag"
style={{ padding: 4, textDecoration: 'none', color: 'inherit', display: 'flex', alignItems: 'center', gap: 8 }}
>
<LogoIcon style={{ height: 22, display: "block", }} color1={theme.colors[theme.primaryColor][3]} color2={theme.colors[theme.primaryColor][7]} />
<Text size='1.25rem' weight={600}>
Dialogue AI
</Text>
</Link>
<Box style={{ display: 'flex', alignItems: 'center' }}>
{incognitoMode ? (
<Tooltip label="Disable Incognito Mode">
<ActionIcon
size="xl"
onClick={handleIncognito}
>
<IconSettings size={20} />
</ActionIcon>
</Tooltip>
</SettingsModal>
<IconSpyOff size={20} />
</ActionIcon>
</Tooltip>
) : (
<Tooltip label="Incognito Mode">
<ActionIcon
size="xl"
onClick={handleIncognito}
>
<IconSpy size={20} />
</ActionIcon>
</Tooltip>
)}
<SettingsModal>
<Tooltip label="Settings">
<ActionIcon
size="xl"
sx={(theme) => ({
[theme.fn.smallerThan('md')]: {
marginRight: '2.5rem',
},
})}
>
<IconSettings size={20} />
</ActionIcon>
</Tooltip>
</SettingsModal>
</Box>
</Box>
</Box>
</Navbar.Section>
<Navbar.Section
sx={(theme) => ({
padding: rem(4),
background:
theme.colorScheme === "dark"
? theme.colors.dark[8]
: theme.colors.gray[1],
borderBottom: border,
})}
>
<SegmentedControl
fullWidth
value={tab}
onChange={(value) => setTab(value as typeof tab)}
data={["Chats", "Prompts"]}
/>
</Navbar.Section>
<Navbar.Section
sx={(theme) => ({
padding: rem(4),
background:
theme.colorScheme === "dark"
? theme.colors.dark[7]
: theme.white,
borderBottom: border,
})}
>
<TextInput
variant="unstyled"
radius={0}
placeholder="Search"
value={search}
onChange={(event) =>
setSearch(event.currentTarget.value.toLowerCase())
}
sx={{ paddingInline: 4 }}
icon={<IconSearch opacity={0.8} size={20} />}
rightSection={
!!search && (
<ActionIcon onClick={() => setSearch("")}>
<IconX opacity={0.5} size={20} />{" "}
</ActionIcon>
)
}
/>
</Navbar.Section>
<Navbar.Section grow component={ScrollArea} id="chats">
{tab === "Chats" && (fetchingChats ? (
<Stack spacing="xs" mt={8} px={10}>
<Skeleton height="2rem" />
<Skeleton height="2rem" />
<Skeleton height="2rem" />
</Stack>
) : (<Chats search={search} />))}
{tab === "Prompts" && (
<Suspense fallback={
</Navbar.Section>
<Navbar.Section
sx={(theme) => ({
padding: rem(4),
background:
theme.colorScheme === "dark"
? theme.colors.dark[8]
: theme.colors.gray[1],
borderBottom: border,
})}
>
<SegmentedControl
fullWidth
value={tab}
onChange={(value) => setTab(value as typeof tab)}
data={["Chats", "Prompts"]}
/>
</Navbar.Section>
<Navbar.Section
sx={(theme) => ({
padding: rem(4),
background:
theme.colorScheme === "dark"
? theme.colors.dark[7]
: theme.white,
borderBottom: border,
})}
>
<TextInput
variant="unstyled"
radius={0}
placeholder="Search"
value={search}
onChange={(event) =>
setSearch(event.currentTarget.value.toLowerCase())
}
sx={{ paddingInline: 4 }}
icon={<IconSearch opacity={0.8} size={20} />}
rightSection={
!!search && (
<ActionIcon onClick={() => setSearch("")}>
<IconX opacity={0.5} size={20} />{" "}
</ActionIcon>
)
}
/>
</Navbar.Section>
<Navbar.Section grow component={ScrollArea} id="chats">
{tab === "Chats" && (fetchingChats ? (
<Stack spacing="xs" mt={8} px={10}>
<Skeleton height="2rem" />
<Skeleton height="2rem" />
<Skeleton height="2rem" />
</Stack>
}>
<Prompts search={search} onPlay={() => setTab("Chats")} />
</Suspense>
)}
</Navbar.Section>
<Navbar.Section>
<Flex direction="column" p={10} gap="xs">
{tab === "Chats" && (
<>
{ incognitoMode && (
<DeleteChatsModal />
)}
<CreateChatButton fullWidth>
{incognitoMode ? "New Private Chat" : "New Chat"}
</CreateChatButton>
</>
) : (<Chats search={search} />))}
{tab === "Prompts" && (
<Suspense fallback={
<Stack spacing="xs" mt={8} px={10}>
<Skeleton height="2rem" />
<Skeleton height="2rem" />
<Skeleton height="2rem" />
</Stack>
}>
<Prompts search={search} onPlay={() => setTab("Chats")} />
</Suspense>
)}
{tab === "Prompts" && <CreatePromptModal />}
</Flex>
</Navbar.Section>
</Navbar>
}
header={
chat ? (
<ChatHeader />
) : undefined
}
layout="alt"
padding={0}
>
<MediaQuery largerThan="md" styles={{ display: "none" }}>
<Burger
opened={opened}
onClick={() => setOpened((o) => !o)}
size="sm"
color={theme.colors.gray[6]}
className="app-region-no-drag"
sx={{ position: "fixed", top: 16, right: 16, zIndex: 100 }}
/>
</MediaQuery>
{children}
</AppShell>
</IncognitoModeContext.Provider>
</PromptsContext.Provider>
</ChatsContext.Provider>
</ChatContext.Provider>
</Navbar.Section>
<Navbar.Section>
<Flex direction="column" p={10} gap="xs">
{tab === "Chats" && (
<>
{ incognitoMode && (
<DeleteChatsModal />
)}
<CreateChatButton fullWidth>
{incognitoMode ? "New Private Chat" : "New Chat"}
</CreateChatButton>
</>
)}
{tab === "Prompts" && <CreatePromptModal />}
</Flex>
</Navbar.Section>
</Navbar>
}
header={
chat ? (
<ChatHeader />
) : undefined
}
layout="alt"
padding={0}
>
<MediaQuery largerThan="md" styles={{ display: "none" }}>
<Burger
opened={opened}
onClick={() => setOpened((o) => !o)}
size="sm"
color={theme.colors.gray[6]}
className="app-region-no-drag"
sx={{ position: "fixed", top: 16, right: 16, zIndex: 100 }}
/>
</MediaQuery>
{children}
</AppShell>
</IncognitoModeContext.Provider>
</PromptsContext.Provider>
</ChatsContext.Provider>
</ChatContext.Provider>
</SpaceAppActionsContext.Provider>
</SettingsContext.Provider>
);
}

View file

@ -470,6 +470,13 @@
"@mantine/utils" "6.0.2"
"react-transition-group" "4.4.2"
"@mantine/spotlight@^6.0.2":
"integrity" "sha512-MdC6qXivhrjiKPyM1WPwcaWIo8thfGpKMv9lNuzSKJRzF5n3Uiexucyrb1B+Ca6JqEWWMMuDJ4iNIijAQRhc/Q=="
"resolved" "https://registry.npmjs.org/@mantine/spotlight/-/spotlight-6.0.2.tgz"
"version" "6.0.2"
dependencies:
"@mantine/utils" "6.0.2"
"@mantine/ssr@6.0.2":
"integrity" "sha512-s7F0LtKVdGflbV/2ofCUcdoEQlCpnspBIOPW2IORk7Ysab/XE/Ea2nphtfStNZcYZXk9haOVp50Yi8yAxzBOVg=="
"resolved" "https://registry.npmjs.org/@mantine/ssr/-/ssr-6.0.2.tgz"