diff --git a/.gitignore b/.gitignore index eca4b26..b50e56f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules .parcel-cache dist notes.txt -lib \ No newline at end of file +lib +.env \ No newline at end of file diff --git a/Spacefile b/Spacefile index 6fc37b9..06fc891 100644 --- a/Spacefile +++ b/Spacefile @@ -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 diff --git a/backend/package-lock.json b/backend/package-lock.json index ff134a8..aab44cb 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -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", diff --git a/backend/package.json b/backend/package.json index e1321ee..f7f69e2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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" }, diff --git a/backend/src/db.ts b/backend/src/db.ts index f3eb89d..63dbd8f 100644 --- a/backend/src/db.ts +++ b/backend/src/db.ts @@ -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') diff --git a/backend/src/index.ts b/backend/src/index.ts index 7810617..2829940 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -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', diff --git a/backend/src/interop.ts b/backend/src/interop.ts new file mode 100644 index 0000000..be88309 --- /dev/null +++ b/backend/src/interop.ts @@ -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 + } +} diff --git a/backend/src/openai.ts b/backend/src/openai.ts index a1e8ce5..a498714 100644 --- a/backend/src/openai.ts +++ b/backend/src/openai.ts @@ -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' diff --git a/backend/src/types.ts b/backend/src/types.ts new file mode 100644 index 0000000..9d97991 --- /dev/null +++ b/backend/src/types.ts @@ -0,0 +1 @@ +export * from '../../src/db/types.js' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8e64a63..689ff86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 2bc408c..43bc331 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/assets/integrations/deta.png b/public/assets/integrations/deta.png new file mode 100644 index 0000000..b997654 Binary files /dev/null and b/public/assets/integrations/deta.png differ diff --git a/src/components/MessageItem.tsx b/src/components/MessageItem.tsx index 8bac9fc..9a79713 100644 --- a/src/components/MessageItem.tsx +++ b/src/components/MessageItem.tsx @@ -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(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 )} {message.role === "assistant" && } - + + {spaceAppActions !== null && ( + + + + {/* */} + + + + + )} + {!readOnly && handleUseMessage && ( <> diff --git a/src/components/SelectIntegrationModal.tsx b/src/components/SelectIntegrationModal.tsx new file mode 100644 index 0000000..8d90552 --- /dev/null +++ b/src/components/SelectIntegrationModal.tsx @@ -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([]); + + 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 ? {action.title} : , + 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 && ( + } + searchPlaceholder="Search..." + shortcut={null} + nothingFoundMessage="Nothing found..." + onSpotlightOpen={() => setOpen(true)} + onSpotlightClose={() => setOpen(false)} + limit={Infinity} + > + + )} + {/* +
{ + 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(); + } + }} + > + +