slidedeck shape installed, still minor difficulty in keyboard arrow transition between slides (last slide + wraparound)
This commit is contained in:
parent
2590a86352
commit
a0e73b0f9e
|
|
@ -9,6 +9,7 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.33.1",
|
||||
"@daily-co/daily-js": "^0.60.0",
|
||||
"@daily-co/daily-react": "^0.20.0",
|
||||
"@tldraw/assets": "^3.6.0",
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
"@types/markdown-it": "^14.1.1",
|
||||
"@types/marked": "^5.0.2",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"ai": "^4.1.0",
|
||||
"cherry-markdown": "^0.8.57",
|
||||
"cloudflare-workers-unfurl": "^0.0.7",
|
||||
"gray-matter": "^4.0.3",
|
||||
|
|
@ -28,6 +30,7 @@
|
|||
"jspdf": "^2.5.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"marked": "^15.0.4",
|
||||
"openai": "^4.79.3",
|
||||
"rbush": "^4.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
@ -53,6 +56,90 @@
|
|||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.0.4.tgz",
|
||||
"integrity": "sha512-lJi5zwDosvvZER3e/pB8lj1MN3o3S7zJliQq56BRr4e9V3fcRyFtwP0JRxaRS5vHYX3OJ154VezVoQNrk0eaKw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"json-schema": "^0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.1.0.tgz",
|
||||
"integrity": "sha512-rBUabNoyB25PBUjaiMSk86fHNSCqTngNZVvXxv8+6mvw47JX5OexW+ZHRsEw8XKTE8+hqvNFVzctaOrRZ2i9Zw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "1.0.4",
|
||||
"eventsource-parser": "^3.0.0",
|
||||
"nanoid": "^3.3.8",
|
||||
"secure-json-parse": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/react": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.1.0.tgz",
|
||||
"integrity": "sha512-U5lBbLyf1pw79xsk5dgHSkBv9Jta3xzWlOLpxsmHlxh1X94QOH3e1gm+nioQ/JvTuHLm23j2tz3i4MpMdchwXQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider-utils": "2.1.0",
|
||||
"@ai-sdk/ui-utils": "1.1.0",
|
||||
"swr": "^2.2.5",
|
||||
"throttleit": "2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/ui-utils": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.1.0.tgz",
|
||||
"integrity": "sha512-ETXwdHaHwzC7NIehbthDFGwsTFk+gNtRL/lm85nR4WDFvvYQptoM/7wTANs0p0H7zumB3Ep5hKzv0Encu8vSRw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "1.0.4",
|
||||
"@ai-sdk/provider-utils": "2.1.0",
|
||||
"zod-to-json-schema": "^3.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||
|
|
@ -67,6 +154,30 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.33.1",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.33.1.tgz",
|
||||
"integrity": "sha512-VrlbxiAdVRGuKP2UQlCnsShDHJKWepzvfRCkZMpU+oaUdKLpOfmylLMRojGrAgebV+kDtPjewCVP0laHXg+vsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
"abort-controller": "^3.0.0",
|
||||
"agentkeepalive": "^4.2.1",
|
||||
"form-data-encoder": "1.7.2",
|
||||
"formdata-node": "^4.3.2",
|
||||
"node-fetch": "^2.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
|
||||
"version": "18.19.71",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.71.tgz",
|
||||
"integrity": "sha512-evXpcgtZm8FY4jqBSN8+DmOTcVkkvTmAayeo4Wf3m1xAruyVGzGuDh/Fb/WWX2yLItUiho42ozyJjB0dw//Tkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||
|
|
@ -1318,6 +1429,15 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/api": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/number": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
||||
|
|
@ -2765,6 +2885,12 @@
|
|||
"integrity": "sha512-VgnAj6tIAhJhZdJ8/IpxdatM8G4OD3VWGlp6xIxUGENZlpbob9Ty4VVdC1FIEp0aK6DBscDDjyzy5FB60TuNqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/diff-match-patch": {
|
||||
"version": "1.0.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
|
||||
"integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/dompurify": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz",
|
||||
|
|
@ -2837,6 +2963,16 @@
|
|||
"integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
|
||||
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-forge": {
|
||||
"version": "1.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
|
||||
|
|
@ -3387,6 +3523,18 @@
|
|||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
|
|
@ -3451,6 +3599,47 @@
|
|||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agentkeepalive": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
|
||||
"integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"humanize-ms": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ai": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ai/-/ai-4.1.0.tgz",
|
||||
"integrity": "sha512-95nI9hBSSAKPrMnpJbaB3yqvh+G8BS4/EtFz3HR0HgEDJpxC0R6JAlB8+B/BXHd/roNGBrS08Z3Zain/6OFSYA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "1.0.4",
|
||||
"@ai-sdk/provider-utils": "2.1.0",
|
||||
"@ai-sdk/react": "1.1.0",
|
||||
"@ai-sdk/ui-utils": "1.1.0",
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
"jsondiffpatch": "0.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
|
|
@ -4742,6 +4931,12 @@
|
|||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/diff-match-patch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
||||
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/domexception": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
|
||||
|
|
@ -5280,6 +5475,15 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
|
|
@ -5301,6 +5505,15 @@
|
|||
"integrity": "sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eventsource-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-3.2.0.tgz",
|
||||
|
|
@ -5438,6 +5651,25 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data-encoder": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
|
||||
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/formdata-node": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
|
||||
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-domexception": "1.0.0",
|
||||
"web-streams-polyfill": "4.0.0-beta.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/fp-ts": {
|
||||
"version": "2.16.9",
|
||||
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.9.tgz",
|
||||
|
|
@ -5800,6 +6032,15 @@
|
|||
"node": ">=8.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/humanize-ms": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
|
||||
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
|
|
@ -6076,6 +6317,12 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
||||
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/json-schema-to-ts": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-1.6.4.tgz",
|
||||
|
|
@ -6106,6 +6353,35 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsondiffpatch": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
|
||||
"integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
"chalk": "^5.3.0",
|
||||
"diff-match-patch": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"jsondiffpatch": "bin/jsondiffpatch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsondiffpatch/node_modules/chalk": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
|
|
@ -6529,7 +6805,6 @@
|
|||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
@ -6544,6 +6819,25 @@
|
|||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
|
|
@ -6707,6 +7001,45 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/openai": {
|
||||
"version": "4.79.3",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-4.79.3.tgz",
|
||||
"integrity": "sha512-0yAnr6oxXAyVrYwLC1jA0KboyU7DjEmrfTXQX+jSpE+P4i72AI/Lxx5pvR3r9i5X7G33835lL+ZrnQ+MDvyuUg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
"abort-controller": "^3.0.0",
|
||||
"agentkeepalive": "^4.2.1",
|
||||
"form-data-encoder": "1.7.2",
|
||||
"formdata-node": "^4.3.2",
|
||||
"node-fetch": "^2.6.7"
|
||||
},
|
||||
"bin": {
|
||||
"openai": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/openai/node_modules/@types/node": {
|
||||
"version": "18.19.71",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.71.tgz",
|
||||
"integrity": "sha512-evXpcgtZm8FY4jqBSN8+DmOTcVkkvTmAayeo4Wf3m1xAruyVGzGuDh/Fb/WWX2yLItUiho42ozyJjB0dw//Tkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/os-paths": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/os-paths/-/os-paths-4.4.0.tgz",
|
||||
|
|
@ -7498,6 +7831,12 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/secure-json-parse": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
|
||||
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/selfsigned": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz",
|
||||
|
|
@ -7803,6 +8142,19 @@
|
|||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/swr": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.0.tgz",
|
||||
"integrity": "sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
|
|
@ -7836,6 +8188,18 @@
|
|||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/throttleit": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
|
||||
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/time-span": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/time-span/-/time-span-4.0.0.tgz",
|
||||
|
|
@ -8066,6 +8430,12 @@
|
|||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unenv": {
|
||||
"name": "unenv-nightly",
|
||||
"version": "2.0.0-20241204-140205-a5d5190",
|
||||
|
|
@ -8191,6 +8561,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
|
@ -8388,6 +8767,15 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "4.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/web-vitals": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-0.2.4.tgz",
|
||||
|
|
@ -9179,6 +9567,15 @@
|
|||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-to-json-schema": {
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz",
|
||||
"integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"author": "Jeff Emmett",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.33.1",
|
||||
"@daily-co/daily-js": "^0.60.0",
|
||||
"@daily-co/daily-react": "^0.20.0",
|
||||
"@tldraw/assets": "^3.6.0",
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
"@types/markdown-it": "^14.1.1",
|
||||
"@types/marked": "^5.0.2",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"ai": "^4.1.0",
|
||||
"cherry-markdown": "^0.8.57",
|
||||
"cloudflare-workers-unfurl": "^0.0.7",
|
||||
"gray-matter": "^4.0.3",
|
||||
|
|
@ -34,6 +36,7 @@
|
|||
"jspdf": "^2.5.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"marked": "^15.0.4",
|
||||
"openai": "^4.79.3",
|
||||
"rbush": "^4.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
import { atom } from 'tldraw'
|
||||
import { SYSTEM_PROMPT } from '@/prompt'
|
||||
|
||||
export const PROVIDERS = [
|
||||
{
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo'], // 'o1-preview', 'o1-mini'],
|
||||
help: 'https://tldraw.notion.site/Make-Real-Help-93be8b5273d14f7386e14eb142575e6e#a9b75e58b1824962a1a69a2f29ace9be',
|
||||
validate: (key: string) => key.startsWith('sk-'),
|
||||
},
|
||||
{
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
models: [
|
||||
'claude-3-5-sonnet-20241022',
|
||||
'claude-3-5-sonnet-20240620',
|
||||
'claude-3-opus-20240229',
|
||||
'claude-3-sonnet-20240229',
|
||||
'claude-3-haiku-20240307',
|
||||
],
|
||||
help: 'https://tldraw.notion.site/Make-Real-Help-93be8b5273d14f7386e14eb142575e6e#3444b55a2ede405286929956d0be6e77',
|
||||
validate: (key: string) => key.startsWith('sk-'),
|
||||
},
|
||||
// { id: 'google', name: 'Google', model: 'Gemeni 1.5 Flash', validate: (key: string) => true },
|
||||
]
|
||||
|
||||
export const makeRealSettings = atom('make real settings', {
|
||||
provider: 'openai' as (typeof PROVIDERS)[number]['id'] | 'all',
|
||||
models: Object.fromEntries(PROVIDERS.map((provider) => [provider.id, provider.models[0]])),
|
||||
keys: {
|
||||
openai: '',
|
||||
anthropic: '',
|
||||
google: '',
|
||||
},
|
||||
prompts: {
|
||||
system: SYSTEM_PROMPT,
|
||||
},
|
||||
})
|
||||
|
||||
export function applySettingsMigrations(settings: any) {
|
||||
const { keys, prompts, ...rest } = settings
|
||||
|
||||
const settingsWithModelsProperty = {
|
||||
provider: 'openai',
|
||||
models: Object.fromEntries(PROVIDERS.map((provider) => [provider.id, provider.models[0]])),
|
||||
keys: {
|
||||
openai: '',
|
||||
anthropic: '',
|
||||
google: '',
|
||||
...keys,
|
||||
},
|
||||
prompts: {
|
||||
system: SYSTEM_PROMPT,
|
||||
...prompts,
|
||||
},
|
||||
...rest,
|
||||
}
|
||||
|
||||
return settingsWithModelsProperty
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
export const SYSTEM_PROMPT = `You are an expert web developer who specializes in building working website prototypes from low-fidelity wireframes. Your job is to accept low-fidelity designs and turn them into high-fidelity interactive and responsive working prototypes. When sent new designs, you should reply with a high-fidelity working prototype as a single HTML file.
|
||||
|
||||
- Use tailwind (via \`cdn.tailwindcss.com\`) for styling.
|
||||
- Put any JavaScript in a script tag with \`type="module"\`.
|
||||
- Use unpkg or skypack to import any required JavaScript dependencies.
|
||||
- Use Google fonts to pull in any open source fonts you require.
|
||||
- If you have any images, load them from Unsplash or use solid colored rectangles as placeholders.
|
||||
- Create SVGs as needed for any icons.
|
||||
|
||||
The designs may include flow charts, diagrams, labels, arrows, sticky notes, screenshots of other applications, or even previous designs. Treat all of these as references for your prototype.
|
||||
|
||||
The designs may include structural elements (such as boxes that represent buttons or content) as well as annotations or figures that describe interactions, behavior, or appearance. Use your best judgement to determine what is an annotation and what should be included in the final result. Annotations are commonly made in the color red. Do NOT include any of those annotations in your final result.
|
||||
|
||||
If there are any questions or underspecified features, use what you know about applications, user experience, and website design patterns to "fill in the blanks". If you're unsure of how the designs should work, take a guess—it's better for you to get it wrong than to leave things incomplete.
|
||||
|
||||
Your prototype should look and feel much more complete and advanced than the wireframes provided. Flesh it out, make it real!
|
||||
|
||||
Remember: you love your designers and want them to be happy. The more complete and impressive your prototype, the happier they will be. You are evaluated on 1) whether your prototype resembles the designs, 2) whether your prototype is interactive and responsive, and 3) whether your prototype is complete and impressive.`
|
||||
|
||||
export const USER_PROMPT =
|
||||
'Here are the latest wireframes. Please reply with a high-fidelity working prototype as a single HTML file.'
|
||||
|
||||
export const USER_PROMPT_WITH_PREVIOUS_DESIGN =
|
||||
"Here are the latest wireframes. There are also some previous outputs here. We have run their code through an 'HTML to screenshot' library to generate a screenshot of the page. The generated screenshot may have some inaccuracies so please use your knowledge of HTML and web development to figure out what any annotations are referring to, which may be different to what is visible in the generated screenshot. Make a new high-fidelity prototype based on your previous work and any new designs or annotations. Again, you should reply with a high-fidelity working prototype as a single HTML file."
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useSync } from "@tldraw/sync"
|
||||
import { useMemo } from "react"
|
||||
import { Tldraw, Editor } from "tldraw"
|
||||
import { Tldraw, Editor, useTools, useIsToolSelected, DefaultToolbar, TldrawUiMenuItem, DefaultToolbarContent, DefaultKeyboardShortcutsDialog, DefaultKeyboardShortcutsDialogContent, defaultTools } from "tldraw"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { ChatBoxTool } from "@/tools/ChatBoxTool"
|
||||
import { ChatBoxShape } from "@/shapes/ChatBoxShapeUtil"
|
||||
|
|
@ -20,25 +20,122 @@ import { handleInitialPageLoad } from "@/utils/handleInitialPageLoad"
|
|||
import { MycrozineTemplateTool } from "@/tools/MycrozineTemplateTool"
|
||||
import { MycrozineTemplateShape } from "@/shapes/MycrozineTemplateShapeUtil"
|
||||
import { registerPropagators, ChangePropagator, TickPropagator, ClickPropagator } from "@/propagators/ScopedPropagators"
|
||||
import { SlideShapeTool } from "@/tools/SlideShapeTool"
|
||||
import { ISlideShape, SlideShapeUtil } from "@/shapes/SlideShapeUtil"
|
||||
import { SlidesPanel } from "@/slides/SlidesPanel"
|
||||
import { moveToSlide } from "@/slides/useSlides"
|
||||
|
||||
// Default to production URL if env var isn't available
|
||||
export const WORKER_URL = "https://jeffemmett-canvas.jeffemmett.workers.dev"
|
||||
|
||||
const shapeUtils = [
|
||||
const updatedComponents = {
|
||||
...components,
|
||||
HelperButtons: SlidesPanel,
|
||||
Minimap: null,
|
||||
Toolbar: (props: any) => {
|
||||
const tools = useTools()
|
||||
const slideTool = tools['Slide']
|
||||
const isSlideSelected = slideTool ? useIsToolSelected(slideTool) : false
|
||||
return (
|
||||
<DefaultToolbar {...props}>
|
||||
{slideTool && <TldrawUiMenuItem {...slideTool} isSelected={isSlideSelected} />}
|
||||
<DefaultToolbarContent />
|
||||
</DefaultToolbar>
|
||||
)
|
||||
},
|
||||
KeyboardShortcutsDialog: (props: any) => {
|
||||
const tools = useTools()
|
||||
return (
|
||||
<DefaultKeyboardShortcutsDialog {...props}>
|
||||
<TldrawUiMenuItem {...tools['Slide']} />
|
||||
<DefaultKeyboardShortcutsDialogContent />
|
||||
</DefaultKeyboardShortcutsDialog>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const customShapeUtils = [
|
||||
ChatBoxShape,
|
||||
VideoChatShape,
|
||||
EmbedShape,
|
||||
// MycrozineTemplateShape,
|
||||
// MarkdownShape
|
||||
SlideShapeUtil,
|
||||
MycrozineTemplateShape,
|
||||
MarkdownShape
|
||||
]
|
||||
const tools = [
|
||||
const customTools = [
|
||||
ChatBoxTool,
|
||||
VideoChatTool,
|
||||
EmbedTool,
|
||||
// MycrozineTemplateTool,
|
||||
// MarkdownTool
|
||||
SlideShapeTool,
|
||||
MycrozineTemplateTool,
|
||||
MarkdownTool
|
||||
]
|
||||
|
||||
const updatedOverrides = {
|
||||
...overrides,
|
||||
actions(editor: Editor, actions: any) {
|
||||
return {
|
||||
...actions,
|
||||
'next-slide': {
|
||||
id: 'next-slide',
|
||||
label: 'Next slide',
|
||||
kbd: 'right',
|
||||
onSelect() {
|
||||
const slides = editor.getCurrentPageShapes().filter(shape => shape.type === 'Slide')
|
||||
if (slides.length === 0) return
|
||||
|
||||
const currentSlide = editor.getSelectedShapes().find(shape => shape.type === 'Slide')
|
||||
const currentIndex = currentSlide
|
||||
? slides.findIndex(slide => slide.id === currentSlide.id)
|
||||
: -1
|
||||
|
||||
console.log('Current index:', currentIndex)
|
||||
console.log('Current slide:', currentSlide)
|
||||
|
||||
|
||||
// Calculate next index with wraparound
|
||||
const nextIndex = currentIndex === -1
|
||||
? 0
|
||||
: currentIndex >= slides.length - 1
|
||||
? 0
|
||||
: currentIndex + 1
|
||||
|
||||
const nextSlide = slides[nextIndex]
|
||||
|
||||
editor.select(nextSlide.id)
|
||||
editor.stopCameraAnimation()
|
||||
moveToSlide(editor, nextSlide as ISlideShape)
|
||||
},
|
||||
},
|
||||
'previous-slide': {
|
||||
id: 'previous-slide',
|
||||
label: 'Previous slide',
|
||||
kbd: 'left',
|
||||
onSelect() {
|
||||
const slides = editor.getCurrentPageShapes().filter(shape => shape.type === 'Slide')
|
||||
if (slides.length === 0) return
|
||||
|
||||
const currentSlide = editor.getSelectedShapes().find(shape => shape.type === 'Slide')
|
||||
const currentIndex = currentSlide
|
||||
? slides.findIndex(slide => slide.id === currentSlide.id)
|
||||
: -1
|
||||
|
||||
// Calculate previous index with wraparound
|
||||
const previousIndex = currentIndex <= 0
|
||||
? slides.length - 1
|
||||
: currentIndex - 1
|
||||
|
||||
const previousSlide = slides[previousIndex]
|
||||
|
||||
editor.select(previousSlide.id)
|
||||
editor.stopCameraAnimation()
|
||||
moveToSlide(editor, previousSlide as ISlideShape)
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export function Board() {
|
||||
const { slug } = useParams<{ slug: string }>()
|
||||
const roomId = slug || "default-room"
|
||||
|
|
@ -47,7 +144,7 @@ export function Board() {
|
|||
() => ({
|
||||
uri: `${WORKER_URL}/connect/${roomId}`,
|
||||
assets: multiplayerAssetStore,
|
||||
shapeUtils: [...shapeUtils, ...defaultShapeUtils],
|
||||
shapeUtils: [...defaultShapeUtils, ...customShapeUtils],
|
||||
bindingUtils: [...defaultBindingUtils],
|
||||
}),
|
||||
[roomId],
|
||||
|
|
@ -56,14 +153,17 @@ export function Board() {
|
|||
const store = useSync(storeConfig)
|
||||
const [editor, setEditor] = useState<Editor | null>(null)
|
||||
|
||||
//console.log("store:", store)
|
||||
//console.log("store.store:",store.store)
|
||||
|
||||
return (
|
||||
<div style={{ position: "fixed", inset: 0 }}>
|
||||
<Tldraw
|
||||
store={store.store}
|
||||
shapeUtils={shapeUtils}
|
||||
tools={tools}
|
||||
components={components}
|
||||
overrides={overrides}
|
||||
shapeUtils={customShapeUtils}
|
||||
tools={customTools}
|
||||
components={updatedComponents}
|
||||
overrides={updatedOverrides}
|
||||
cameraOptions={{
|
||||
zoomSteps: [
|
||||
0.001, // Min zoom
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
import { useCallback } from 'react'
|
||||
import {
|
||||
BaseBoxShapeUtil,
|
||||
Geometry2d,
|
||||
RecordProps,
|
||||
Rectangle2d,
|
||||
SVGContainer,
|
||||
ShapeUtil,
|
||||
T,
|
||||
TLBaseShape,
|
||||
getPerfectDashProps,
|
||||
resizeBox,
|
||||
useValue,
|
||||
} from 'tldraw'
|
||||
import { moveToSlide, useSlides } from '@/slides/useSlides'
|
||||
|
||||
export type ISlideShape = TLBaseShape<
|
||||
'Slide',
|
||||
{
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
>
|
||||
|
||||
export class SlideShapeUtil extends BaseBoxShapeUtil<ISlideShape> {
|
||||
static override type = "Slide"
|
||||
|
||||
// static override props = {
|
||||
// w: T.number,
|
||||
// h: T.number,
|
||||
// }
|
||||
|
||||
override canBind = () => false
|
||||
override hideRotateHandle = () => true
|
||||
|
||||
getDefaultProps(): ISlideShape["props"] {
|
||||
return {
|
||||
w: 720,
|
||||
h: 480,
|
||||
}
|
||||
}
|
||||
|
||||
getGeometry(shape: ISlideShape): Geometry2d {
|
||||
return new Rectangle2d({
|
||||
width: shape.props.w,
|
||||
height: shape.props.h,
|
||||
isFilled: false,
|
||||
})
|
||||
}
|
||||
|
||||
override onRotate = (initial: ISlideShape) => initial
|
||||
override onResize(shape: ISlideShape, info: any) {
|
||||
return resizeBox(shape, info)
|
||||
}
|
||||
|
||||
override onDoubleClick = (shape: ISlideShape) => {
|
||||
moveToSlide(this.editor, shape)
|
||||
this.editor.selectNone()
|
||||
}
|
||||
|
||||
override onDoubleClickEdge = (shape: ISlideShape) => {
|
||||
moveToSlide(this.editor, shape)
|
||||
this.editor.selectNone()
|
||||
}
|
||||
|
||||
component(shape: ISlideShape) {
|
||||
const bounds = this.editor.getShapeGeometry(shape).bounds
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const zoomLevel = useValue('zoom level', () => this.editor.getZoomLevel(), [this.editor])
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const slides = useSlides()
|
||||
const index = slides.findIndex((s) => s.id === shape.id)
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const handleLabelPointerDown = useCallback(() => this.editor.select(shape.id), [shape.id])
|
||||
|
||||
if (!bounds) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<div onPointerDown={handleLabelPointerDown} className="slide-shape-label">
|
||||
{`Slide ${index + 1}`}
|
||||
</div>
|
||||
<SVGContainer>
|
||||
<g
|
||||
style={{
|
||||
stroke: 'var(--color-text)',
|
||||
strokeWidth: 'calc(1px * var(--tl-scale))',
|
||||
opacity: 0.25,
|
||||
}}
|
||||
pointerEvents="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
{bounds.sides.map((side, i) => {
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||
side[0].dist(side[1]),
|
||||
1 / zoomLevel,
|
||||
{
|
||||
style: 'dashed',
|
||||
lengthRatio: 6,
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<line
|
||||
key={i}
|
||||
x1={side[0].x}
|
||||
y1={side[0].y}
|
||||
x2={side[1].x}
|
||||
y2={side[1].y}
|
||||
strokeDasharray={strokeDasharray}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</g>
|
||||
</SVGContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
indicator(shape: ISlideShape) {
|
||||
return <rect width={shape.props.w} height={shape.props.h} />
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { TldrawUiButton, stopEventPropagation, track, useEditor, useValue } from 'tldraw'
|
||||
import { moveToSlide, useCurrentSlide, useSlides } from '@/slides/useSlides'
|
||||
|
||||
export const SlidesPanel = track(() => {
|
||||
const editor = useEditor()
|
||||
const slides = useSlides()
|
||||
const currentSlide = useCurrentSlide()
|
||||
const selectedShapes = useValue('selected shapes', () => editor.getSelectedShapes(), [editor])
|
||||
|
||||
if (slides.length === 0) return null
|
||||
return (
|
||||
<div className="slides-panel scroll-light" onPointerDown={(e) => stopEventPropagation(e)}>
|
||||
{slides.map((slide, i) => {
|
||||
const isSelected = selectedShapes.includes(slide)
|
||||
return (
|
||||
<TldrawUiButton
|
||||
key={'slides-panel-button:' + slide.id}
|
||||
type="normal"
|
||||
className="slides-panel-button"
|
||||
onClick={() => {
|
||||
moveToSlide(editor, slide)
|
||||
// Switch to select tool and select the slide shape
|
||||
editor.setCurrentTool('select')
|
||||
editor.select(slide)
|
||||
}}
|
||||
style={{
|
||||
background: currentSlide?.id === slide.id ? 'var(--color-background)' : 'transparent',
|
||||
outline: isSelected ? 'var(--color-selection-stroke) solid 1.5px' : 'none',
|
||||
}}
|
||||
>
|
||||
{`Slide ${i + 1}`}
|
||||
</TldrawUiButton>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
.slides-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
max-height: calc(100% - 110px);
|
||||
margin: 50px 0px;
|
||||
padding: 4px;
|
||||
background-color: var(--color-low);
|
||||
pointer-events: all;
|
||||
border-top-right-radius: var(--radius-4);
|
||||
border-bottom-right-radius: var(--radius-4);
|
||||
overflow: auto;
|
||||
border-right: 2px solid var(--color-background);
|
||||
border-bottom: 2px solid var(--color-background);
|
||||
border-top: 2px solid var(--color-background);
|
||||
}
|
||||
|
||||
.slides-panel-button {
|
||||
border-radius: var(--radius-4);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.slide-shape-label {
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
background: var(--color-low);
|
||||
padding: calc(12px * var(--tl-scale));
|
||||
border-bottom-right-radius: calc(var(--radius-4) * var(--tl-scale));
|
||||
font-size: calc(12px * var(--tl-scale));
|
||||
color: var(--color-text);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { EASINGS, Editor, atom, useEditor, useValue } from 'tldraw'
|
||||
import { ISlideShape } from '@/shapes/SlideShapeUtil'
|
||||
|
||||
export const $currentSlide = atom<ISlideShape | null>('current slide', null)
|
||||
|
||||
export function moveToSlide(editor: Editor, slide: ISlideShape) {
|
||||
const bounds = editor.getShapePageBounds(slide.id)
|
||||
if (!bounds) return
|
||||
$currentSlide.set(slide)
|
||||
editor.selectNone()
|
||||
editor.zoomToBounds(bounds, {
|
||||
animation: { duration: 500, easing: EASINGS.easeInOutCubic },
|
||||
inset: 0,
|
||||
})
|
||||
}
|
||||
|
||||
export function useSlides() {
|
||||
const editor = useEditor()
|
||||
return useValue<ISlideShape[]>('slide shapes', () => getSlides(editor), [editor])
|
||||
}
|
||||
|
||||
export function useCurrentSlide() {
|
||||
return useValue($currentSlide)
|
||||
}
|
||||
|
||||
export function getSlides(editor: Editor) {
|
||||
return editor
|
||||
.getSortedChildIdsForParent(editor.getCurrentPageId())
|
||||
.map((id) => editor.getShape(id))
|
||||
.filter((s) => s?.type === 'Slide') as ISlideShape[]
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { BaseBoxShapeTool } from 'tldraw'
|
||||
|
||||
export class SlideShapeTool extends BaseBoxShapeTool {
|
||||
static override id = 'Slide'
|
||||
static override initial = 'idle'
|
||||
override shapeType = 'Slide'
|
||||
|
||||
constructor(editor: any) {
|
||||
super(editor)
|
||||
console.log('SlideShapeTool constructed', { id: this.id, shapeType: this.shapeType })
|
||||
}
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
|
|||
{/* Creation Tools Group */}
|
||||
<TldrawUiMenuGroup id="creation-tools">
|
||||
<TldrawUiMenuItem
|
||||
id="video-chat"
|
||||
id="VideoChat"
|
||||
label="Create Video Chat"
|
||||
icon="video"
|
||||
kbd="alt+v"
|
||||
|
|
@ -112,7 +112,7 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
|
|||
}}
|
||||
/>
|
||||
<TldrawUiMenuItem
|
||||
id="chat-box"
|
||||
id="ChatBox"
|
||||
label="Create Chat Box"
|
||||
icon="chat"
|
||||
kbd="alt+c"
|
||||
|
|
@ -122,7 +122,7 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
|
|||
}}
|
||||
/>
|
||||
<TldrawUiMenuItem
|
||||
id="embed"
|
||||
id="Embed"
|
||||
label="Create Embed"
|
||||
icon="embed"
|
||||
kbd="alt+e"
|
||||
|
|
@ -131,7 +131,16 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
|
|||
editor.setCurrentTool("Embed")
|
||||
}}
|
||||
/>
|
||||
{/*
|
||||
<TldrawUiMenuItem
|
||||
id="Slide"
|
||||
label="Create Slide"
|
||||
icon="slides"
|
||||
kbd="alt+s"
|
||||
disabled={hasSelection}
|
||||
onSelect={() => {
|
||||
editor.setCurrentTool("Slide")
|
||||
}}
|
||||
/>
|
||||
<TldrawUiMenuItem
|
||||
id="mycrozine-template"
|
||||
label="Create Mycrozine Template"
|
||||
|
|
@ -151,8 +160,7 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
|
|||
onSelect={() => {
|
||||
editor.setCurrentTool("Markdown")
|
||||
}}
|
||||
/>
|
||||
*/}
|
||||
/>
|
||||
</TldrawUiMenuGroup>
|
||||
|
||||
{/* Frame Controls */}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,14 @@ export function CustomToolbar() {
|
|||
isSelected={tools["Embed"].id === editor.getCurrentToolId()}
|
||||
/>
|
||||
)}
|
||||
{tools["SlideShape"] && (
|
||||
<TldrawUiMenuItem
|
||||
{...tools["SlideShape"]}
|
||||
icon="slides"
|
||||
label="Slide"
|
||||
isSelected={tools["SlideShape"].id === editor.getCurrentToolId()}
|
||||
/>
|
||||
)}
|
||||
{/*
|
||||
{tools["Markdown"] && (
|
||||
<TldrawUiMenuItem
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
import {
|
||||
TLUiDialogProps,
|
||||
TldrawUiButton,
|
||||
TldrawUiButtonLabel,
|
||||
TldrawUiDialogBody,
|
||||
TldrawUiDialogCloseButton,
|
||||
TldrawUiDialogFooter,
|
||||
TldrawUiDialogHeader,
|
||||
TldrawUiDialogTitle,
|
||||
TldrawUiIcon,
|
||||
TldrawUiInput,
|
||||
useReactor,
|
||||
useValue,
|
||||
} from 'tldraw'
|
||||
import { PROVIDERS, makeRealSettings } from '../lib/settings'
|
||||
import { SYSTEM_PROMPT } from '@/prompt'
|
||||
|
||||
export function SettingsDialog({ onClose }: TLUiDialogProps) {
|
||||
const settings = useValue('settings', () => makeRealSettings.get(), [])
|
||||
|
||||
useReactor(
|
||||
'update settings local storage',
|
||||
() => {
|
||||
localStorage.setItem('makereal_settings_2', JSON.stringify(makeRealSettings.get()))
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<TldrawUiDialogHeader>
|
||||
<TldrawUiDialogTitle>Settings</TldrawUiDialogTitle>
|
||||
<TldrawUiDialogCloseButton />
|
||||
</TldrawUiDialogHeader>
|
||||
<TldrawUiDialogBody
|
||||
style={{ maxWidth: 350, display: 'flex', flexDirection: 'column', gap: 8 }}
|
||||
>
|
||||
<p>
|
||||
To use Make Real, enter your API key for each model provider that you wish to use. Draw
|
||||
some shapes, then select the shapes and click Make Real.{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://tldraw.notion.site/Make-Real-FAQs-93be8b5273d14f7386e14eb142575e6e?pvs=4"
|
||||
>
|
||||
<u>Read our guide.</u>
|
||||
</a>
|
||||
</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginTop: 8 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', gap: 4 }}>
|
||||
<label style={{ flexGrow: 2 }}>Provider</label>
|
||||
</div>
|
||||
<select
|
||||
className="apikey_select"
|
||||
value={settings.provider}
|
||||
onChange={(e) => {
|
||||
makeRealSettings.update((s) => ({ ...s, provider: e.target.value as any }))
|
||||
}}
|
||||
>
|
||||
{PROVIDERS.map((provider) => {
|
||||
return (
|
||||
<option key={provider.id + 'option'} value={provider.id}>
|
||||
{provider.name}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
<option value="all">All</option>
|
||||
</select>
|
||||
{settings.provider !== 'all' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', gap: 4 }}>
|
||||
<label style={{ flexGrow: 2 }}>Model</label>
|
||||
</div>
|
||||
<select
|
||||
className="apikey_select"
|
||||
value={settings.models[settings.provider]}
|
||||
onChange={(e) => {
|
||||
makeRealSettings.update((s) => ({
|
||||
...s,
|
||||
models: { ...s.models, [settings.provider]: e.target.value as any },
|
||||
}))
|
||||
}}
|
||||
>
|
||||
{PROVIDERS.find((p) => p.id === settings.provider)!.models.map((model) => {
|
||||
return (
|
||||
<option key={model + 'option'} value={model}>
|
||||
{model}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<hr style={{ margin: '12px 0px' }} />
|
||||
{PROVIDERS.map((provider) => {
|
||||
if (provider.id === 'google') return null
|
||||
const value = settings.keys[provider.id as keyof typeof settings.keys]
|
||||
return (
|
||||
<ApiKeyInput
|
||||
provider={provider}
|
||||
key={provider.name + 'key'}
|
||||
value={value}
|
||||
warning={
|
||||
value === '' && (settings.provider === provider.id || settings.provider === 'all')
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{/* <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', gap: 4 }}>
|
||||
<label style={{ flexGrow: 2 }}>Google</label>
|
||||
</div>
|
||||
<TldrawUiInput
|
||||
className="apikey_input"
|
||||
value={settings.keys.google}
|
||||
placeholder="risky but cool"
|
||||
onValueChange={(value) => {
|
||||
const next = { ...settings, keys: { ...settings.keys, google: value } }
|
||||
localStorage.setItem('makereal_settings_2', JSON.stringify(next))
|
||||
makeRealSettings.set(next)
|
||||
}}
|
||||
/>
|
||||
</div> */}
|
||||
<hr style={{ margin: '12px 0px' }} />
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', gap: 4 }}>
|
||||
<label style={{ flexGrow: 2 }}>System prompt</label>
|
||||
<button
|
||||
style={{ all: 'unset', cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
makeRealSettings.update((s) => ({
|
||||
...s,
|
||||
prompts: { ...s.prompts, system: SYSTEM_PROMPT },
|
||||
}))
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
<TldrawUiInput
|
||||
className="apikey_input"
|
||||
value={settings.prompts.system}
|
||||
onValueChange={(value) => {
|
||||
makeRealSettings.update((s) => ({ ...s, prompts: { ...s.prompts, system: value } }))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TldrawUiDialogBody>
|
||||
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
|
||||
<TldrawUiButton
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
<TldrawUiButtonLabel>Save</TldrawUiButtonLabel>
|
||||
</TldrawUiButton>
|
||||
</TldrawUiDialogFooter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function ApiKeyInput({
|
||||
provider,
|
||||
value,
|
||||
warning,
|
||||
}: {
|
||||
provider: (typeof PROVIDERS)[number]
|
||||
value: string
|
||||
warning: boolean
|
||||
}) {
|
||||
const isValid = value.length === 0 || provider.validate(value)
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', gap: 4, alignItems: 'center' }}>
|
||||
<label style={{ flexGrow: 2, color: warning ? 'red' : 'var(--color-text)' }}>
|
||||
{provider.name} API key
|
||||
</label>
|
||||
<a style={{ cursor: 'pointer', pointerEvents: 'all' }} target="_blank" href={provider.help}>
|
||||
<TldrawUiIcon
|
||||
className="apikey_help_icon"
|
||||
small
|
||||
icon={provider.validate(value) ? 'check' : 'question-mark-circle'}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<TldrawUiInput
|
||||
className={`apikey_input ${isValid ? '' : 'apikey_input__invalid'}`}
|
||||
value={value}
|
||||
placeholder="risky but cool"
|
||||
onValueChange={(value) => {
|
||||
makeRealSettings.update((s) => ({ ...s, keys: { ...s.keys, [provider.id]: value } }))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { TLUiOverrides } from "tldraw"
|
||||
import { shapeIdValidator, TLUiOverrides } from "tldraw"
|
||||
import {
|
||||
cameraHistory,
|
||||
copyLinkToCurrentView,
|
||||
|
|
@ -68,6 +68,7 @@ export const overrides: TLUiOverrides = {
|
|||
label: "Video Chat",
|
||||
kbd: "alt+v",
|
||||
readonlyOk: true,
|
||||
type: "VideoChat",
|
||||
onSelect: () => editor.setCurrentTool("VideoChat"),
|
||||
},
|
||||
ChatBox: {
|
||||
|
|
@ -76,6 +77,7 @@ export const overrides: TLUiOverrides = {
|
|||
label: "Chat",
|
||||
kbd: "alt+c",
|
||||
readonlyOk: true,
|
||||
type: "ChatBox",
|
||||
onSelect: () => editor.setCurrentTool("ChatBox"),
|
||||
},
|
||||
Embed: {
|
||||
|
|
@ -84,26 +86,41 @@ export const overrides: TLUiOverrides = {
|
|||
label: "Embed",
|
||||
kbd: "alt+e",
|
||||
readonlyOk: true,
|
||||
type: "Embed",
|
||||
onSelect: () => editor.setCurrentTool("Embed"),
|
||||
},
|
||||
/*
|
||||
SlideShape: {
|
||||
id: "Slide",
|
||||
icon: "slides",
|
||||
label: "Slide",
|
||||
kbd: "alt+s",
|
||||
type: "Slide",
|
||||
readonlyOk: true,
|
||||
onSelect: () => {
|
||||
console.log('SlideShape tool selected from menu')
|
||||
console.log('Current tool before:', editor.getCurrentToolId())
|
||||
editor.setCurrentTool("Slide")
|
||||
console.log('Current tool after:', editor.getCurrentToolId())
|
||||
},
|
||||
},
|
||||
Markdown: {
|
||||
id: "Markdown",
|
||||
icon: "markdown",
|
||||
label: "Markdown",
|
||||
kbd: "alt+m",
|
||||
readonlyOk: true,
|
||||
type: "Markdown",
|
||||
onSelect: () => editor.setCurrentTool("Markdown"),
|
||||
},
|
||||
MycrozineTemplate: {
|
||||
id: "MycrozineTemplate",
|
||||
icon: "rectangle",
|
||||
label: "Mycrozine Template",
|
||||
type: "MycrozineTemplate",
|
||||
kbd: "m",
|
||||
readonlyOk: true,
|
||||
onSelect: () => editor.setCurrentTool("MycrozineTemplate"),
|
||||
},
|
||||
*/
|
||||
hand: {
|
||||
...tools.hand,
|
||||
onDoubleClick: (info: any) => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
createTLSchema,
|
||||
defaultBindingSchemas,
|
||||
defaultShapeSchemas,
|
||||
shapeIdValidator,
|
||||
} from "@tldraw/tlschema"
|
||||
import { AutoRouter, IRequest, error } from "itty-router"
|
||||
import throttle from "lodash.throttle"
|
||||
|
|
@ -16,6 +17,8 @@ import { VideoChatShape } from "@/shapes/VideoChatShapeUtil"
|
|||
import { EmbedShape } from "@/shapes/EmbedShapeUtil"
|
||||
import { MarkdownShape } from "@/shapes/MarkdownShapeUtil"
|
||||
import { MycrozineTemplateShape } from "@/shapes/MycrozineTemplateShapeUtil"
|
||||
import { T } from "@tldraw/tldraw"
|
||||
import { SlideShapeUtil } from "@/shapes/SlideShapeUtil"
|
||||
|
||||
// add custom shapes and bindings here if needed:
|
||||
export const customSchema = createTLSchema({
|
||||
|
|
@ -41,6 +44,10 @@ export const customSchema = createTLSchema({
|
|||
props: MycrozineTemplateShape.props,
|
||||
migrations: MycrozineTemplateShape.migrations,
|
||||
},
|
||||
Slide: {
|
||||
props: SlideShapeUtil.props,
|
||||
migrations: SlideShapeUtil.migrations,
|
||||
},
|
||||
},
|
||||
bindings: defaultBindingSchemas,
|
||||
})
|
||||
|
|
@ -206,7 +213,7 @@ export class TldrawDurableObject {
|
|||
initialSnapshot.documents = initialSnapshot.documents.filter(
|
||||
(record) => {
|
||||
const shape = record.state as TLShape
|
||||
return shape.type !== "chatBox"
|
||||
return shape.type !== "ChatBox"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue