Merge branch 'main' of github.com:placeholder-soft/storytime

This commit is contained in:
Zitao Xiong
2023-12-12 14:48:15 +08:00
45 changed files with 1600 additions and 177 deletions

View File

@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
@@ -46,8 +46,10 @@
"eslint": "^8.53.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"prettier": "^3.1.1",
"typescript": "^5.2.2",
"vite": "^5.0.0",
"vite-plugin-pages": "^0.32.0"
"vite-plugin-pages": "^0.32.0",
"vite-plugin-svgr": "^4.2.0"
}
}
}

606
app/pnpm-lock.yaml generated
View File

@@ -109,6 +109,9 @@ devDependencies:
eslint-plugin-react-refresh:
specifier: ^0.4.4
version: 0.4.5(eslint@8.55.0)
prettier:
specifier: ^3.1.1
version: 3.1.1
typescript:
specifier: ^5.2.2
version: 5.3.3
@@ -118,6 +121,9 @@ devDependencies:
vite-plugin-pages:
specifier: ^0.32.0
version: 0.32.0(vite@5.0.7)
vite-plugin-svgr:
specifier: ^4.2.0
version: 4.2.0(typescript@5.3.3)(vite@5.0.7)
packages:
@@ -126,6 +132,169 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/@ampproject/remapping@2.2.1:
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/gen-mapping': 0.3.3
'@jridgewell/trace-mapping': 0.3.20
dev: true
/@babel/code-frame@7.23.5:
resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/highlight': 7.23.4
chalk: 2.4.2
dev: true
/@babel/compat-data@7.23.5:
resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/core@7.23.6:
resolution: {integrity: sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==}
engines: {node: '>=6.9.0'}
dependencies:
'@ampproject/remapping': 2.2.1
'@babel/code-frame': 7.23.5
'@babel/generator': 7.23.6
'@babel/helper-compilation-targets': 7.23.6
'@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.6)
'@babel/helpers': 7.23.6
'@babel/parser': 7.23.6
'@babel/template': 7.22.15
'@babel/traverse': 7.23.6
'@babel/types': 7.23.6
convert-source-map: 2.0.0
debug: 4.3.4
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
transitivePeerDependencies:
- supports-color
dev: true
/@babel/generator@7.23.6:
resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.23.6
'@jridgewell/gen-mapping': 0.3.3
'@jridgewell/trace-mapping': 0.3.20
jsesc: 2.5.2
dev: true
/@babel/helper-compilation-targets@7.23.6:
resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/compat-data': 7.23.5
'@babel/helper-validator-option': 7.23.5
browserslist: 4.22.2
lru-cache: 5.1.1
semver: 6.3.1
dev: true
/@babel/helper-environment-visitor@7.22.20:
resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-function-name@7.23.0:
resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.22.15
'@babel/types': 7.23.6
dev: true
/@babel/helper-hoist-variables@7.22.5:
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.23.6
dev: true
/@babel/helper-module-imports@7.22.15:
resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.23.6
dev: true
/@babel/helper-module-transforms@7.23.3(@babel/core@7.23.6):
resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
'@babel/core': 7.23.6
'@babel/helper-environment-visitor': 7.22.20
'@babel/helper-module-imports': 7.22.15
'@babel/helper-simple-access': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6
'@babel/helper-validator-identifier': 7.22.20
dev: true
/@babel/helper-simple-access@7.22.5:
resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.23.6
dev: true
/@babel/helper-split-export-declaration@7.22.6:
resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.23.6
dev: true
/@babel/helper-string-parser@7.23.4:
resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-identifier@7.22.20:
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-option@7.23.5:
resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helpers@7.23.6:
resolution: {integrity: sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.22.15
'@babel/traverse': 7.23.6
'@babel/types': 7.23.6
transitivePeerDependencies:
- supports-color
dev: true
/@babel/highlight@7.23.4:
resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-validator-identifier': 7.22.20
chalk: 2.4.2
js-tokens: 4.0.0
dev: true
/@babel/parser@7.23.6:
resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.23.6
dev: true
/@babel/runtime@7.23.5:
resolution: {integrity: sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==}
engines: {node: '>=6.9.0'}
@@ -133,6 +302,42 @@ packages:
regenerator-runtime: 0.14.0
dev: false
/@babel/template@7.22.15:
resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.23.5
'@babel/parser': 7.23.6
'@babel/types': 7.23.6
dev: true
/@babel/traverse@7.23.6:
resolution: {integrity: sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.23.5
'@babel/generator': 7.23.6
'@babel/helper-environment-visitor': 7.22.20
'@babel/helper-function-name': 7.23.0
'@babel/helper-hoist-variables': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6
'@babel/parser': 7.23.6
'@babel/types': 7.23.6
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
- supports-color
dev: true
/@babel/types@7.23.6:
resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.23.4
'@babel/helper-validator-identifier': 7.22.20
to-fast-properties: 2.0.0
dev: true
/@emotion/hash@0.9.1:
resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==}
dev: false
@@ -901,6 +1106,36 @@ packages:
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
dev: true
/@jridgewell/gen-mapping@0.3.3:
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.20
dev: true
/@jridgewell/resolve-uri@3.1.1:
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/set-array@1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec@1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
dev: true
/@jridgewell/trace-mapping@0.3.20:
resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==}
dependencies:
'@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@mapbox/node-pre-gyp@1.0.11:
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
hasBin: true
@@ -2216,6 +2451,20 @@ packages:
engines: {node: '>=14.0.0'}
dev: false
/@rollup/pluginutils@5.1.0:
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
dependencies:
'@types/estree': 1.0.5
estree-walker: 2.0.2
picomatch: 2.3.1
dev: true
/@rollup/rollup-android-arm-eabi@4.7.0:
resolution: {integrity: sha512-rGku10pL1StFlFvXX5pEv88KdGW6DHUghsxyP/aRYb9eH+74jTGJ3U0S/rtlsQ4yYq1Hcc7AMkoJOb1xu29Fxw==}
cpu: [arm]
@@ -2343,6 +2592,132 @@ packages:
resolution: {integrity: sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg==}
dev: false
/@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.23.6):
resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.6
dev: true
/@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.23.6):
resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.6
dev: true
/@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.23.6):
resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.6
dev: true
/@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.23.6):
resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.6
dev: true
/@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.23.6):
resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.6
dev: true
/@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.23.6):
resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.6
dev: true
/@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.23.6):
resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.6
dev: true
/@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.23.6):
resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==}
engines: {node: '>=12'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.6
dev: true
/@svgr/babel-preset@8.1.0(@babel/core@7.23.6):
resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==}
engines: {node: '>=14'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.23.6
'@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.23.6)
'@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.23.6)
'@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.23.6)
'@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.23.6)
'@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.23.6)
'@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.23.6)
'@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.23.6)
'@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.23.6)
dev: true
/@svgr/core@8.1.0(typescript@5.3.3):
resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==}
engines: {node: '>=14'}
dependencies:
'@babel/core': 7.23.6
'@svgr/babel-preset': 8.1.0(@babel/core@7.23.6)
camelcase: 6.3.0
cosmiconfig: 8.3.6(typescript@5.3.3)
snake-case: 3.0.4
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/@svgr/hast-util-to-babel-ast@8.0.0:
resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==}
engines: {node: '>=14'}
dependencies:
'@babel/types': 7.23.6
entities: 4.5.0
dev: true
/@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0):
resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==}
engines: {node: '>=14'}
peerDependencies:
'@svgr/core': '*'
dependencies:
'@babel/core': 7.23.6
'@svgr/babel-preset': 8.1.0(@babel/core@7.23.6)
'@svgr/core': 8.1.0(typescript@5.3.3)
'@svgr/hast-util-to-babel-ast': 8.0.0
svg-parser: 2.0.4
transitivePeerDependencies:
- supports-color
dev: true
/@swc/core-darwin-arm64@1.3.100:
resolution: {integrity: sha512-XVWFsKe6ei+SsDbwmsuRkYck1SXRpO60Hioa4hoLwR8fxbA9eVp6enZtMxzVVMBi8ej5seZ4HZQeAWepbukiBw==}
engines: {node: '>=10'}
@@ -2482,6 +2857,10 @@ packages:
'@types/ms': 0.7.34
dev: true
/@types/estree@1.0.5:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true
/@types/fabric@5.3.6:
resolution: {integrity: sha512-nTP5I68SsGnanIHxCoBX83ghscw9M9DI27iSDcd0Z+cpiQ5cZByH0nzkm4itDR/LgAy253q7B93xHgyOh2+hFQ==}
dev: true
@@ -2820,6 +3199,13 @@ packages:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
/ansi-styles@3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
dependencies:
color-convert: 1.9.3
dev: true
/ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -2911,6 +3297,17 @@ packages:
dev: false
optional: true
/browserslist@4.22.2:
resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001568
electron-to-chromium: 1.4.610
node-releases: 2.0.14
update-browserslist-db: 1.0.13(browserslist@4.22.2)
dev: true
/bs58@5.0.0:
resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==}
dependencies:
@@ -2930,10 +3327,19 @@ packages:
engines: {node: '>=6'}
dev: true
/camelcase@6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
dev: true
/camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
dev: false
/caniuse-lite@1.0.30001568:
resolution: {integrity: sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==}
dev: true
/canvas@2.11.2:
resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==}
engines: {node: '>=6'}
@@ -2948,6 +3354,15 @@ packages:
dev: false
optional: true
/chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
dependencies:
ansi-styles: 3.2.1
escape-string-regexp: 1.0.5
supports-color: 5.5.0
dev: true
/chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -2980,12 +3395,22 @@ packages:
engines: {node: '>=6'}
dev: false
/color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
color-name: 1.1.3
dev: true
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
/color-name@1.1.3:
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
dev: true
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
@@ -3013,6 +3438,26 @@ packages:
dev: false
optional: true
/convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
dev: true
/cosmiconfig@8.3.6(typescript@5.3.3):
resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
engines: {node: '>=14'}
peerDependencies:
typescript: '>=4.9.5'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
import-fresh: 3.3.0
js-yaml: 4.1.0
parse-json: 5.2.0
path-type: 4.0.0
typescript: 5.3.3
dev: true
/cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@@ -3220,10 +3665,32 @@ packages:
dev: false
optional: true
/dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
dependencies:
no-case: 3.0.4
tslib: 2.6.2
dev: true
/electron-to-chromium@1.4.610:
resolution: {integrity: sha512-mqi2oL1mfeHYtOdCxbPQYV/PL7YrQlxbvFEZ0Ee8GbDdShimqt2/S6z2RWqysuvlwdOrQdqvE0KZrBTipAeJzg==}
dev: true
/emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: false
/entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
dev: true
/error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
dependencies:
is-arrayish: 0.2.1
dev: true
/es-get-iterator@1.1.3:
resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==}
dependencies:
@@ -3271,7 +3738,11 @@ packages:
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
dev: false
/escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
dev: true
/escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
@@ -3408,6 +3879,10 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
/estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
/esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -3617,6 +4092,11 @@ packages:
dev: false
optional: true
/gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
dev: true
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
@@ -3660,6 +4140,11 @@ packages:
once: 1.4.0
path-is-absolute: 1.0.1
/globals@11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
dev: true
/globals@13.24.0:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
engines: {node: '>=8'}
@@ -3693,6 +4178,11 @@ packages:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
dev: true
/has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
dev: true
/has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -3850,6 +4340,10 @@ packages:
is-typed-array: 1.1.12
dev: true
/is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
dev: true
/is-bigint@1.0.4:
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
dependencies:
@@ -3984,7 +4478,6 @@ packages:
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
dev: false
/js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
@@ -4038,10 +4531,20 @@ packages:
dev: false
optional: true
/jsesc@2.5.2:
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
engines: {node: '>=4'}
hasBin: true
dev: true
/json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
dev: true
/json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
dev: true
/json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
dev: true
@@ -4079,6 +4582,10 @@ packages:
type-check: 0.4.0
dev: true
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true
/local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
@@ -4113,6 +4620,18 @@ packages:
js-tokens: 4.0.0
dev: false
/lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
dependencies:
tslib: 2.6.2
dev: true
/lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
dependencies:
yallist: 3.1.1
dev: true
/lru-cache@6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@@ -4238,6 +4757,13 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
/no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
dependencies:
lower-case: 2.0.2
tslib: 2.6.2
dev: true
/node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
@@ -4252,6 +4778,10 @@ packages:
dev: false
optional: true
/node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
dev: true
/nopt@5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'}
@@ -4360,6 +4890,16 @@ packages:
engines: {node: '>=6'}
dev: true
/parse-json@5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
dependencies:
'@babel/code-frame': 7.23.5
error-ex: 1.3.2
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
dev: true
/parse5@6.0.1:
resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
requiresBuild: true
@@ -4426,6 +4966,12 @@ packages:
engines: {node: '>= 0.8.0'}
dev: true
/prettier@3.1.1:
resolution: {integrity: sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==}
engines: {node: '>=14'}
hasBin: true
dev: true
/protobufjs@7.2.5:
resolution: {integrity: sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==}
engines: {node: '>=12.0.0'}
@@ -4727,8 +5273,6 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
requiresBuild: true
dev: false
optional: true
/semver@7.5.4:
resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
@@ -4813,6 +5357,13 @@ packages:
engines: {node: '>=8'}
dev: true
/snake-case@3.0.4:
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
dependencies:
dot-case: 3.0.4
tslib: 2.6.2
dev: true
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
@@ -4901,12 +5452,23 @@ packages:
engines: {node: '>=14.0.0'}
dev: false
/supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
dependencies:
has-flag: 3.0.0
dev: true
/supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
/svg-parser@2.0.4:
resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==}
dev: true
/symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
requiresBuild: true
@@ -4931,6 +5493,11 @@ packages:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true
/to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
dev: true
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -4976,7 +5543,6 @@ packages:
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: false
/tweetnacl@1.0.3:
resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
@@ -5038,6 +5604,17 @@ packages:
dev: false
optional: true
/update-browserslist-db@1.0.13(browserslist@4.22.2):
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
dependencies:
browserslist: 4.22.2
escalade: 3.1.1
picocolors: 1.0.0
dev: true
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
@@ -5121,6 +5698,21 @@ packages:
- supports-color
dev: true
/vite-plugin-svgr@4.2.0(typescript@5.3.3)(vite@5.0.7):
resolution: {integrity: sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==}
peerDependencies:
vite: ^2.6.0 || 3 || 4 || 5
dependencies:
'@rollup/pluginutils': 5.1.0
'@svgr/core': 8.1.0(typescript@5.3.3)
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0)
vite: 5.0.7
transitivePeerDependencies:
- rollup
- supports-color
- typescript
dev: true
/vite@5.0.7:
resolution: {integrity: sha512-B4T4rJCDPihrQo2B+h1MbeGL/k/GMAHzhQ8S0LjQ142s6/+l3hHTT095ORvsshj4QCkoWu3Xtmob5mazvakaOw==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -5337,6 +5929,10 @@ packages:
engines: {node: '>=10'}
dev: false
/yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
dev: true
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}

View File

@@ -0,0 +1,23 @@
import { useDispatch, useSelector } from "react-redux";
import { Container, Heading } from "@radix-ui/themes";
import { toScene } from "../modules/story/actions";
import { storySelector } from "../modules/story/selectors";
const Cover = () => {
const { title, currentSceneIndex } = useSelector(storySelector);
const dispatch = useDispatch();
const next = () => {
dispatch(toScene({ index: currentSceneIndex + 1 }));
// move to next scene
};
return (
<Container onClick={next}>
<Heading>{title}</Heading>
<Heading as="h4">image here</Heading>
</Container>
);
};
export default Cover;

View File

@@ -0,0 +1,29 @@
/// <reference types="vite-plugin-svgr/client" />
import styled from "styled-components";
import {FC} from "react";
import LogonIcon from './_/logo.svg?react'
export const StyledHeader = styled.div`
//display: flex;
width: 100vw;
background: #A9F868;;
position: fixed;
top: 0;
padding: 29px 34px;
`
export const Header: FC = () => {
return (
<StyledHeader>
<LogonIcon/>
</StyledHeader>
)
}
export const PageContainer = styled.div`
background: #A9F868;
width: 100vw;
min-height: 100vh;
`

View File

@@ -0,0 +1,4 @@
<svg width="128" height="24" viewBox="0 0 128 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="127" height="23" rx="11.5" stroke="black"/>
<path d="M13.1 19.18C9.7 19.18 8.08 17.44 8 14.8H9.78C9.88 16.5 10.9 17.56 13.1 17.56C15.14 17.56 16.32 16.76 16.32 15.16C16.32 13.8 15.54 13.2 14.02 12.88L11.98 12.44C9.76 11.96 8.48 10.8 8.48 8.66C8.48 6.4 10.22 4.82 13.12 4.82C15.92 4.82 17.88 6.5 17.9 8.98H16.12C16.04 7.46 15.04 6.46 13.08 6.46C11.42 6.46 10.28 7.18 10.28 8.66C10.28 9.8 10.98 10.4 12.46 10.74L14.36 11.18C16.76 11.74 18.14 12.86 18.14 15.16C18.14 17.66 16.14 19.18 13.1 19.18ZM24.0661 19V6.66H19.9461V5H29.9861V6.66H25.8661V19H24.0661ZM37.1184 19.18C33.5984 19.18 31.5984 17.02 31.5984 13.2V10.8C31.5984 6.98 33.5984 4.82 37.1184 4.82C40.6384 4.82 42.6384 6.98 42.6384 10.8V13.2C42.6384 17.02 40.6384 19.18 37.1184 19.18ZM33.3984 13.2C33.3984 15.82 34.4184 17.48 37.1184 17.48C39.8184 17.48 40.8384 15.82 40.8384 13.2V10.8C40.8384 8.18 39.8184 6.52 37.1184 6.52C34.4184 6.52 33.3984 8.18 33.3984 10.8V13.2ZM45.8359 19V5H51.1759C53.7759 5 55.4159 6.38 55.4159 8.76C55.4159 11.4 53.7759 12.62 51.1759 12.62H48.1559L56.1559 19H53.2959L47.5959 14.34V19H45.8359ZM47.5959 10.98H51.0759C52.5759 10.98 53.5959 10.46 53.5959 8.78C53.5959 7.14 52.5759 6.6 51.0759 6.6H47.5959V10.98ZM61.4625 19V12.6H60.7825L56.6625 5H58.6225L62.3825 12.1L66.1225 5H68.0425L63.9425 12.6H63.2625V19H61.4625ZM73.4606 19V6.66H69.3406V5H79.3806V6.66H75.2606V19H73.4606ZM81.4859 19V17.34H84.4059V6.66H81.4859V5H89.1259V6.66H86.2059V17.34H89.1259V19H81.4859ZM97.8152 19L94.2952 5.5V19H92.5352V5H95.8752L99.5152 18.5L103.155 5H106.495V19H104.735V5.5L101.215 19H97.8152ZM110.504 19V5H119.104V6.64H112.304V10.96H118.484V12.6H112.304V17.36H119.104V19H110.504Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,42 @@
import { useDispatch, useSelector } from "react-redux";
import { Container, Heading } from "@radix-ui/themes";
import {
storySelector,
currentSceneSelector,
} from "../modules/story/selectors";
import { updateStory } from "../modules/story/actions";
import { StoryProgressPromptRole } from "../types/story";
const Cover = () => {
// const { currentSceneIndex } = useSelector(storySelector);
const { sceneTitle, sceneDescription, optionPrompt, options } =
useSelector(currentSceneSelector);
const dispatch = useDispatch();
const onOptionClick = (val: string) => {
dispatch(
updateStory({
message: { role: StoryProgressPromptRole.User, content: val },
})
);
};
return (
<Container>
<Heading>{sceneTitle}</Heading>
<Heading as="h4">{sceneDescription}</Heading>
<Heading as="h4">{optionPrompt}</Heading>
{options && options.length && (
<ul>
{options.map((option, oidx) => (
<li key={oidx} onClick={() => onOptionClick(option)}>
{option}
</li>
))}
</ul>
)}
</Container>
);
};
export default Cover;

View File

@@ -13,7 +13,7 @@ export function protectedRoute<T extends { user: User }>(component: FC<T>) {
return <div>Loading...</div>;
}
if (user == null) {
return <Navigate to="/" />;
return <Navigate to="/login" />;
}
return createElement(component, {
...props,

View File

@@ -1,7 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";
import {
getFunctions,
httpsCallable,
connectFunctionsEmulator,
} from "firebase/functions";
import { getAuth, connectAuthEmulator } from "firebase/auth";
import { CloudFunctionsType } from "model/functions";
// Your web app's Firebase configuration
const firebaseConfig = {
@@ -16,3 +23,25 @@ const firebaseConfig = {
export const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth(app);
export const functions = getFunctions(app);
export async function callFunction<Name extends keyof CloudFunctionsType>(
name: Name,
...args: Parameters<CloudFunctionsType[Name]>
): Promise<ReturnType<CloudFunctionsType[Name]>> {
return await httpsCallable(
functions,
"execute",
)({
type: name,
args,
}).then((a) => a.data as any);
}
const useEmulator = import.meta.env.VITE_USE_EMULATOR === "true";
if (useEmulator) {
connectAuthEmulator(auth, "http://localhost:9099", { disableWarnings: true });
connectFirestoreEmulator(db, "localhost", 8080);
connectFunctionsEmulator(functions, "localhost", 5001);
}

10
app/src/global.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
declare module "*.svg" {
import * as React from "react"
export const ReactComponent: React.FunctionComponent<
React.SVGProps<SVGSVGElement> & { title?: string }
>
const src: string
export default src
}

37
app/src/index.css Normal file
View File

@@ -0,0 +1,37 @@
a {
font-weight: 500;
color: black;
text-decoration: none;
}
body {
margin: 0;
display: flex;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
body, div, dl, dt, dd, ul, ol, li, tr, td, th,
h1, h2, h3, h4, h5, h6, hr, br, img, table,
input, form, a, p, textarea {
padding: 0;
margin: 0;
}
ul, ol, li {
list-style: none;
}
a {
text-decoration: none;
display: block;
}
img {
border: 0;
display: block;
}

View File

@@ -1,17 +1,17 @@
// import React from "react";
import ReactDOM from "react-dom/client";
import * as ReactDOM from "react-dom/client";
import "@mysten/dapp-kit/dist/index.css";
import "@radix-ui/themes/styles.css";
import "./index.css";
import { getFullnodeUrl } from "@mysten/sui.js/client";
import {
createNetworkConfig,
SuiClientProvider,
WalletProvider,
createNetworkConfig,
} from "@mysten/dapp-kit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Theme } from "@radix-ui/themes";
import App from "./App.tsx";
import App from "./App";
const queryClient = new QueryClient();
@@ -33,6 +33,6 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
</WalletProvider>
</SuiClientProvider>
</QueryClientProvider>
</Theme>
</Theme>,
// </React.StrictMode>
);

12
app/src/mock.ts Normal file
View File

@@ -0,0 +1,12 @@
export const MOCK_STORY_INIT_RESP = `{
"type": "story-introduction",
"title": "The Quest of Gary the Snail",
"character": "An adventurous and inquisitive snail named Gary with a vibrant, spiral shell painted with a mosaic of forest hues",
"setting": "A diverse medieval realm known as Verdant Kingdom, a lush land composed of enchanted forests, majestic mountains, and serene valleys, where magical creatures coexist and ancient secrets lay hidden beneath the luscious canopy",
"introduction": "Gary the snail, adorned with his magnificent, spiral shell, sets his tiny eyes upon the world beyond his comfortable mushroom abode. The land is alive with the murmur of the trees and the soft whispers of the wind. With a heart filled with wanderlust and antennas tuned to the pulse of adventure, Gary discovers an old scroll half-buried under a willow, sealed with the royal emblem of the long-lost Turritella dynasty. The scroll promises a quest of valor and wisdom that could lead to the fabled relic, The Amulet of Aiolos, which grants the bearer the favor of the winds. Each choice will steer Gary deeper into the heart of Verdant Kingdom's forgotten lore.",
"sceneNumber": 1,
"sceneTitle": "The Willow's Whisper",
"sceneDescription": "As Gary unrolls the ancient scroll under the protective boughs of the willow, a soft glow emanates from the delicate parchment. Lines drawn with ink the color of the midnight sky reveal three distinct paths that lie ahead:\n\n1. The Glimmering Brook: A babbling waterway, lit by the dance of sunbeams piercing through the foliage, home to the wise Koi, keepers of the kingdom's history.\n\n2. The Sunstone Caves: A complex of deep caverns beneath the Amber Hills, where the ceilings glitter with mineral constellations that tell stories of old.\n\n3. The Rosewood Thicket: A dense expanse brimming with roses of various hues and fragrances, said to guard the path to the court of the fae.",
"optionsPrompt": "Which path should Gary embark upon to unravel the mysteries of Verdant Kingdom?",
"options": ["Brook", "Caves", "Thicket"]
}`;

View File

@@ -1,24 +1,57 @@
import { Action } from "redux";
// Action Types
export const CREATE_CHARACTER = "Character/CREATE_CHARACTER";
export const CREATE_CHARACTER_SUCCESS = "Character/CREATE_CHARACTER_SUCCESS";
export const CREATE_CHARACTER_NAME = "Character/CREATE_CHARACTER_NAME";
export const CREATE_CHARACTER_TYPE = "Character/CREATE_CHARACTER_TYPE";
export const CREATE_CHARACTER_IMAGE = "Character/CREATE_CHARACTER_IMAGE";
export const CREATE_CHARACTER_IMAGE_SUCCESS =
"Character/CREATE_CHARACTER_IMAGE_SUCCESS";
// Action Creators
type CreateCharacterBody = { sketchBlob: Blob; prompt: string };
export type CreateCharacterAction = Action & { data: CreateCharacterBody };
export const createCharacter = (
data: CreateCharacterBody
): CreateCharacterAction => ({
type: CREATE_CHARACTER,
type CreateCharacterNameBody = { characterName: string };
export type CreateCharacterNameAction = Action & {
data: CreateCharacterNameBody;
};
export const createCharacterName = (
data: CreateCharacterNameBody
): CreateCharacterNameAction => ({
type: CREATE_CHARACTER_NAME,
data,
});
type CreateCharacterSuccessBody = { blob: Blob; characterName: string };
export type CreateCharacterSuccessAction = Action & {
data: CreateCharacterSuccessBody;
type CreateCharacterTypeBody = { characterType: string };
export type CreateCharacterTypeAction = Action & {
data: CreateCharacterTypeBody;
};
export const createCharacterSuccess = (data: CreateCharacterSuccessBody) => ({
type: CREATE_CHARACTER_SUCCESS,
export const createCharacterType = (
data: CreateCharacterTypeBody
): CreateCharacterTypeAction => ({
type: CREATE_CHARACTER_TYPE,
data,
});
type CreateCharacterImageBody = { sketchBlob: Blob; prompt: string };
export type CreateCharacterImageAction = Action & {
data: CreateCharacterImageBody;
};
export const createCharacterImage = (
data: CreateCharacterImageBody
): CreateCharacterImageAction => ({
type: CREATE_CHARACTER_IMAGE,
data,
});
type CreateCharacterImageSuccessBody = { blob: Blob; characterType: string };
export type CreateCharacterImageSuccessAction = Action & {
data: CreateCharacterImageSuccessBody;
};
export const createCharacterImageSuccess = (
data: CreateCharacterImageSuccessBody
) => ({
type: CREATE_CHARACTER_IMAGE_SUCCESS,
data,
});
export type CharacterAction =
| CreateCharacterImageAction
| CreateCharacterImageSuccessAction;

View File

@@ -1,27 +1,36 @@
import {
CreateCharacterSuccessAction,
CREATE_CHARACTER_SUCCESS,
CREATE_CHARACTER_NAME,
CREATE_CHARACTER_TYPE,
CREATE_CHARACTER_IMAGE_SUCCESS,
CharacterAction,
} from "./actions";
// Initial State
export type CharacterState = {
characterName: string;
characterType: string;
characterImage: Blob;
};
const initialState: CharacterState = {
characterName: "",
characterType: "",
characterImage: new Blob(),
};
// Reducer
const characterReducer = (
state = initialState,
action: CreateCharacterSuccessAction
) => {
const characterReducer = (state = initialState, action: CharacterAction) => {
switch (action.type) {
case CREATE_CHARACTER_SUCCESS: {
const { blob, characterName } = action.data;
return { ...state, characterImage: blob, characterName };
case CREATE_CHARACTER_NAME: {
const { characterName } = action.data;
return { ...state, characterName };
}
case CREATE_CHARACTER_TYPE: {
const { characterType } = action.data;
return { ...state, characterType };
}
case CREATE_CHARACTER_IMAGE_SUCCESS: {
const { blob, characterType } = action.data;
return { ...state, characterImage: blob, characterType };
}
default:
return state;

View File

@@ -1,13 +1,13 @@
import axios from "axios";
import { call, put, takeEvery } from "redux-saga/effects";
import {
CREATE_CHARACTER,
CreateCharacterAction,
createCharacterSuccess,
CREATE_CHARACTER_IMAGE,
CreateCharacterImageAction,
createCharacterImageSuccess,
} from "./actions";
// Sample worker saga
function* createCharacter(action: CreateCharacterAction) {
function* createCharacterImage(action: CreateCharacterImageAction) {
const { sketchBlob, prompt } = action.data;
const formData = new FormData();
formData.append("sketch_file", sketchBlob);
@@ -25,7 +25,9 @@ function* createCharacter(action: CreateCharacterAction) {
responseType: "blob",
}
);
yield put(createCharacterSuccess({ blob: data, characterName: prompt }));
yield put(
createCharacterImageSuccess({ blob: data, characterType: prompt })
);
} catch (e) {
console.error(e);
}
@@ -33,5 +35,5 @@ function* createCharacter(action: CreateCharacterAction) {
// Combine all sagas
export default function* characterSaga() {
yield takeEvery(CREATE_CHARACTER, createCharacter);
yield takeEvery(CREATE_CHARACTER_IMAGE, createCharacterImage);
}

View File

@@ -13,3 +13,8 @@ export const characterNameSelector = createSelector(
[getCharacterBase],
({ characterName }) => characterName
);
export const characterTypeSelector = createSelector(
[getCharacterBase],
({ characterType }) => characterType
);

View File

@@ -2,12 +2,20 @@ import { Action } from "redux";
import { StoryProgress } from "../../types/story";
// Action Types
export const TO_SCENE = "Story/TO_SCENE";
export const INIT_STORY = "Story/INIT_STORY";
export const INIT_STORY_SUCCESS = "Story/INIT_STORY_SUCCESS";
export const UPDATE_STORY = "Story/UPDATE_STORY";
export const UPDATE_STORY_SUCCESS = "Story/UPDATE_STORY_SUCCESS";
// Action Creators
type ToSceneBody = { index: number };
export type ToSceneAction = Action & { data: ToSceneBody };
export const toScene = (data: ToSceneBody): ToSceneAction => ({
type: TO_SCENE,
data,
});
type InitStoryBody = { initMessage: StoryProgress };
export type InitStoryAction = Action & { data: InitStoryBody };
export const initStory = (data: InitStoryBody): InitStoryAction => ({
@@ -24,10 +32,10 @@ export const initStorySuccess = (data: InitStorySuccessBody) => ({
data,
});
type UpdateStoryBody = { initMessage: StoryProgress };
type UpdateStoryBody = { message: StoryProgress };
export type UpdateStoryAction = Action & { data: UpdateStoryBody };
export const UpdateStory = (data: UpdateStoryBody): UpdateStoryAction => ({
type: INIT_STORY,
export const updateStory = (data: UpdateStoryBody): UpdateStoryAction => ({
type: UPDATE_STORY,
data,
});
@@ -41,6 +49,7 @@ export const updateStorySuccess = (data: InitStorySuccessBody) => ({
});
export type StoryAction =
| ToSceneAction
| InitStoryAction
| InitStorySuccessAction
| UpdateStoryAction

View File

@@ -1,6 +1,7 @@
import { StoryProgress, Scene } from "../../types/story";
import {
StoryAction,
TO_SCENE,
INIT_STORY,
INIT_STORY_SUCCESS,
UPDATE_STORY,
@@ -11,22 +12,29 @@ import { parseStoryGuideline } from "./utils";
// Initial State
export type StoryState = {
storyProgressPrompts: StoryProgress[];
storyTitle: string;
storyIntro: string;
scene: number;
title: string;
introduction: string;
currentSceneIndex: number;
scenes: Scene[];
};
const initialState: StoryState = {
scene: 0,
currentSceneIndex: 0,
storyProgressPrompts: [],
storyTitle: "",
storyIntro: "",
title: "",
introduction: "",
scenes: [],
};
// Reducer
const storyReducer = (state = initialState, action: StoryAction) => {
switch (action.type) {
case TO_SCENE: {
const { index } = action.data;
return {
...state,
currentSceneIndex: index,
};
}
case INIT_STORY: {
const { initMessage } = action.data;
return {
@@ -36,22 +44,34 @@ const storyReducer = (state = initialState, action: StoryAction) => {
}
case INIT_STORY_SUCCESS: {
const { progress } = action.data;
const { title, intro, scene } = parseStoryGuideline(progress.content);
const { title, introduction, scene } = parseStoryGuideline(
progress.content
);
return {
...state,
storyTitle: title,
storyIntro: intro,
storyProgressPrompts: [...state.storyProgressPrompts, progress],
title,
introduction,
scenes: [...state.scenes, scene],
storyProgressPrompts: [...state.storyProgressPrompts, progress],
};
}
case UPDATE_STORY: {
// TODO: when users fires their prompt to open AI, add the user role prompt into prompt list
return state;
// when users fires their prompt to open AI, add the user role prompt into prompt list
const { message } = action.data;
return {
...state,
storyProgressPrompts: [...state.storyProgressPrompts, message],
};
}
case UPDATE_STORY_SUCCESS: {
// TODO: when user received resp from open AI, it includes assistant prompt
return state;
// when user received resp from open AI, it includes assistant prompt
const { progress } = action.data;
const { scene } = parseStoryGuideline(progress.content);
return {
...state,
scenes: [...state.scenes, scene],
storyProgressPrompts: [...state.storyProgressPrompts, progress],
};
}
default:
return state;

View File

@@ -1,7 +1,15 @@
import axios from "axios";
import { call, put, takeEvery } from "redux-saga/effects";
import { call, put, select, takeEvery } from "redux-saga/effects";
import { StoryProgress } from "../../types/story";
import { INIT_STORY, InitStoryAction, initStorySuccess } from "./actions";
import {
INIT_STORY,
UPDATE_STORY,
InitStoryAction,
initStorySuccess,
updateStorySuccess,
toScene,
} from "./actions";
import { storyProgressSelector, storySelector } from "./selectors";
// Sample worker saga
function* initStory(action: InitStoryAction) {
@@ -9,6 +17,9 @@ function* initStory(action: InitStoryAction) {
const payload = {
model: "gpt-4-1106-preview",
messages: [initMessage],
temperature: 1,
max_tokens: 1024,
top_p: 1,
};
try {
@@ -29,7 +40,41 @@ function* initStory(action: InitStoryAction) {
}
}
function* updateStory(action: InitStoryAction) {
const { message } = action.data;
const { currentSceneIndex } = yield select(storySelector);
const storyProgress = (yield select(
storyProgressSelector
)) as StoryProgress[];
const payload = {
model: "gpt-4-1106-preview",
messages: [...storyProgress, message],
temperature: 1,
max_tokens: 1024,
top_p: 1,
};
try {
const { data } = yield call(
axios.post,
"https://api.openai.com/v1/chat/completions",
payload,
{
headers: {
Authorization: `Bearer ${import.meta.env.VITE_OPENAI_API_SECRET}`,
},
}
);
const progress = data.choices?.[0].message as StoryProgress;
yield put(updateStorySuccess({ progress }));
} catch (e) {
console.error(e);
} finally {
yield put(toScene({ index: currentSceneIndex + 1 }));
}
}
// Combine all sagas
export default function* storySaga() {
yield takeEvery(INIT_STORY, initStory);
yield takeEvery(UPDATE_STORY, updateStory);
}

View File

@@ -1,9 +1,25 @@
import { Scene } from "model";
import { createSelector } from "reselect";
import { RootState } from "..";
// Assuming your state structure has a counter object
const getStoryBase = (state: RootState) => state.story;
export const storySelector = createSelector(
[getStoryBase],
({ title, introduction, currentSceneIndex }) => ({
title,
introduction,
currentSceneIndex,
})
);
export const currentSceneSelector = createSelector(
[getStoryBase],
({ scenes, currentSceneIndex }) =>
currentSceneIndex > 0 ? scenes[currentSceneIndex - 1] : ({} as Scene)
);
export const storyProgressSelector = createSelector(
[getStoryBase],
({ storyProgressPrompts }) => storyProgressPrompts

View File

@@ -1,22 +1,50 @@
export const parseScene = (content: string) => {
import { RawScene, Scene } from "../../types/story";
export const parseScene = (content: string): Scene => {
// TODO: parse `Scene` type out of AI generated content
console.log(content);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let cont: any = {};
try {
cont = JSON.parse(content) as RawScene;
} catch (e) {
console.log(e);
}
return {
content: "",
options: [],
type: cont.type,
character: cont.character,
setting: cont.setting,
sceneNumber: cont.sceneNumber,
sceneTitle: cont.sceneTitle,
sceneDescription: cont.sceneDescription,
optionPrompt: cont.optionPrompt,
options: cont.options,
};
};
export const parseStoryGuideline = (content: string) => {
export const parseStoryGuideline = (
content: string
): { title: string; introduction: string; scene: Scene } => {
// TODO: parse the story title, coverImage, intro and first `Scene` type out of AI generated content
console.log(content);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let cont: any = {};
try {
cont = JSON.parse(content) as RawScene;
} catch (e) {
console.log(e);
}
return {
title: "",
intro: "",
coverImageUrl: "",
title: cont.title,
introduction: cont.introduction,
// coverImageUrl: "", // TODO: get from other service
scene: {
content: "",
options: [],
type: cont.type,
character: cont.character,
setting: cont.setting,
sceneNumber: cont.sceneNumber,
sceneTitle: cont.sceneTitle,
sceneDescription: cont.sceneDescription,
optionPrompt: cont.optionPrompt,
options: cont.options,
},
};
};

View File

@@ -1,11 +1,14 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import Canvas from "../components/Canvas";
import Preview from "../components/Preview";
import { characterImageSelector } from "../modules/character/selectors";
import { createCharacter } from "../modules/character/actions";
import {
characterImageSelector,
characterTypeSelector,
} from "../modules/character/selectors";
import { createCharacterImage } from "../modules/character/actions";
const Container = styled.div`
display: flex;
@@ -16,11 +19,18 @@ const CanvasPage = () => {
const [sketchBlob, setSketchBlob] = useState<Blob>(new Blob());
const [prompt, setPrompt] = useState("");
const dispatch = useDispatch();
const characterIamge = useSelector(characterImageSelector);
const characterImage = useSelector(characterImageSelector);
const navigate = useNavigate();
const characterType = useSelector(characterTypeSelector);
useEffect(() => {
if (characterType && !prompt) {
setPrompt(characterType);
}
}, [characterType]);
const convert = () => {
dispatch(createCharacter({ sketchBlob, prompt }));
dispatch(createCharacterImage({ sketchBlob, prompt }));
// TODO: refactor later
setTimeout(() => {
@@ -39,7 +49,7 @@ const CanvasPage = () => {
onChange={(e) => setPrompt(e.target.value)}
/>
</div>
<Preview data={characterIamge} />
<Preview data={characterImage} />
</Container>
<div>
<button onClick={convert}>Convert</button>

View File

@@ -0,0 +1,38 @@
import { useState } from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { Container, Heading, Button } from "@radix-ui/themes";
import { createCharacterType } from "../modules/character/actions";
const CHARACTER_BASE = ["cat", "human", "bear"];
const CharacterBase = () => {
const [characterBase, setCharacterBase] = useState("human");
const navigate = useNavigate();
const dispatch = useDispatch();
return (
<Container>
<Heading>Select Character Base</Heading>
<ul>
{CHARACTER_BASE.map((c) => (
<li key={c} onClick={() => setCharacterBase(c)}>
<img src="" alt={c} />
<Heading as="h4">{c}</Heading>
</li>
))}
</ul>
<Button
onClick={() => {
// update default character type
dispatch(createCharacterType({ characterType: characterBase }));
navigate("/canvas");
}}
>
Select Character
</Button>
</Container>
);
};
export default CharacterBase;

View File

@@ -6,16 +6,18 @@ import {
onSnapshot,
QueryDocumentSnapshot,
} from "firebase/firestore";
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import { User } from "firebase/auth";
import { Container, Button } from "@radix-ui/themes";
import { protectedRoute } from "../components/protectedRoute.tsx";
const DashboardPage = protectedRoute(({ user }: { user: User }) => {
const [projects, setProjects] = useState<QueryDocumentSnapshot<Project>[]>();
const navigate = useNavigate();
useEffect(() => {
onSnapshot(collection(db, `users/${user.uid}/projects`), (snapshot) => {
setProjects(
snapshot.docs.map((x) => x as QueryDocumentSnapshot<Project>),
snapshot.docs.map((x) => x as QueryDocumentSnapshot<Project>)
);
});
}, [user.uid]);
@@ -24,10 +26,16 @@ const DashboardPage = protectedRoute(({ user }: { user: User }) => {
}
if (projects.length === 0) {
return (
<div>
Start creating
<Link to="/canvas">Create adventure</Link>
</div>
<Container>
Create Your own Adventure
<Button
onClick={() => {
navigate("/name");
}}
>
Get Started
</Button>
</Container>
);
}
return (

View File

@@ -1,10 +1,19 @@
import { ConnectButton } from "@mysten/dapp-kit";
import { Box, Container, Flex, Heading } from "@radix-ui/themes";
import { WalletStatus } from "../components/WalletStatus.tsx";
import { callFunction } from "../firebase.ts";
export default function App() {
return (
<>
<button
onClick={async () => {
const result = await callFunction("generateImage", "test");
console.log(result);
}}
>
Test Cloudfunction Call
</button>
<Flex
position="sticky"
px="4"

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

@@ -0,0 +1,110 @@
import { FC } from "react";
import { Header, PageContainer } from "../../components/Layout/Layout";
import styled from "styled-components";
import step1 from "./_/step1.png";
import step2 from "./_/step2.png";
import step3 from "./_/step3.png";
import { Link } from "react-router-dom";
const StyledTitle = styled.h1`
color: #000;
font-size: 50px;
font-weight: 600;
line-height: 60px;
letter-spacing: -1px;
margin-left: 34px;
margin-top: 72px;
`;
const StyledStepContainer = styled.div`
display: flex;
justify-content: space-between;
gap: 20px;
padding: 0 20px;
`;
const StyledStepItem = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
`;
const StyledStepItemTitle = styled.div`
color: #000;
font-size: 30px;
font-weight: 500;
line-height: 60px;
`;
const StyledStepItemImage = styled.img`
width: 100%;
border-radius: 5px;
`;
const StyledStepItemDescription = styled.div`
color: #000;
font-size: 18px;
font-weight: 500;
line-height: 20px;
`;
const StepItem: FC<{
title: string;
description: string;
image: string;
}> = (props) => {
return (
<StyledStepItem>
<StyledStepItemTitle>{props.title}</StyledStepItemTitle>
<StyledStepItemImage src={props.image} alt="" />
<StyledStepItemDescription>{props.description}</StyledStepItemDescription>
</StyledStepItem>
);
};
const StyledStartButton = styled(Link)`
padding: 4px 25px;
border-radius: 10px;
background: #000;
color: #fff;
text-align: center;
width: fit-content;
cursor: pointer;
`;
const StyledStartButtonContainer = styled.div`
display: flex;
margin-top: 140px;
justify-content: center;
`;
const HomePage: FC = () => {
return (
<PageContainer>
<Header />
<StyledTitle>Create your own Adventure</StyledTitle>
<StyledStepContainer>
<StepItem
title={"1.Create Character"}
image={step1}
description="Youve got two images, you need two words.Start working out that big brain"
/>
<StepItem
title={"2.Generate Story"}
image={step2}
description="Youve got two images, you need two words.Start working out that big brain"
/>
<StepItem
title={"3.Mint Your Story"}
image={step3}
description="Youve got two images, you need two words.Start working out that big brain"
/>
</StyledStepContainer>
<StyledStartButtonContainer>
<StyledStartButton to="/login">Get started</StyledStartButton>
</StyledStartButtonContainer>
</PageContainer>
);
};
export default HomePage;

View File

@@ -1,36 +1,5 @@
import { useEffect, useState } from "react";
import { Navigate } from "react-router";
import {
GoogleAuthProvider,
onAuthStateChanged,
signInWithPopup,
User,
} from "firebase/auth";
import { auth } from "../firebase.ts";
export default function App() {
const [user, setUser] = useState<User | null>();
useEffect(() => {
onAuthStateChanged(auth, (user) => {
setUser(user);
});
}, []);
if (user === undefined) {
return <div>Loading...</div>;
}
if (user != null) {
return <Navigate to="/dashboard" />;
}
return (
<div>
<button
onClick={async () => {
const provider = new GoogleAuthProvider();
await signInWithPopup(auth, provider);
}}
>
Continue with Google
</button>
</div>
);
return <Navigate to="/home" />;
}

View File

@@ -0,0 +1,110 @@
import { FC, useEffect, useState } from "react";
import { Header, PageContainer } from "../../components/Layout/Layout";
import styled from "styled-components";
import {
GoogleAuthProvider,
onAuthStateChanged,
signInWithPopup,
User,
} from "firebase/auth";
import { auth } from "../../firebase.ts";
import { Navigate } from "react-router";
export const StyledPageContainer = styled(PageContainer)`
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
`;
const StyledCard = styled.div`
width: 353px;
height: 433px;
border-radius: 10px;
background: #ddffc2;
display: flex;
flex-direction: column;
padding: 59px 35px;
align-items: center;
`;
const StyledCardTitle = styled.div`
color: #000;
font-family: N27;
font-size: 20px;
line-height: 24px;
text-align: center;
`;
const StyledDescritionTitle = styled.p`
color: #000;
text-align: center;
font-family: Inter Tight;
font-size: 30px;
font-weight: 500;
line-height: 60px;
margin-top: 74px;
`;
const StyledDescription = styled.p`
color: #000;
text-align: center;
font-family: Inter Tight;
font-size: 12px;
font-weight: 500;
line-height: 20px;
width: 237px;
`;
const StyleSubButton = styled.button`
color: #fff;
font-weight: 600;
text-align: center;
width: 283px;
padding: 10px 25px;
gap: 10px;
border-radius: 10px;
background: #000;
margin-top: 80px;
cursor: pointer;
`;
const LoginPage: FC = () => {
const [user, setUser] = useState<User | null>();
useEffect(() => {
onAuthStateChanged(auth, (user) => {
setUser(user);
});
}, []);
if (user === undefined) {
return <div>Loading...</div>;
}
if (user != null) {
return <Navigate to="/dashboard" />;
}
return (
<>
<Header />
<StyledPageContainer>
<StyledCard>
<StyledCardTitle>STORYTIME</StyledCardTitle>
<StyledDescritionTitle>Start Creating!</StyledDescritionTitle>
<StyledDescription>
Youve got two images, you need two words. Start working out that
big brain
</StyledDescription>
<StyleSubButton
onClick={async () => {
const provider = new GoogleAuthProvider();
await signInWithPopup(auth, provider);
}}
>
Continue with google
</StyleSubButton>
</StyledCard>
</StyledPageContainer>
</>
);
};
export default LoginPage;

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

View File

@@ -0,0 +1,60 @@
import { FC } from "react";
import { Header, PageContainer } from "../../components/Layout/Layout";
import storyImg from "./_/story.png";
import styled from "styled-components";
export const StyledPageContainer = styled(PageContainer)`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100vw;
`;
const StyledMintTitle = styled.div`
color: #000;
text-align: center;
font-family: Inter;
font-size: 60px;
font-weight: 600;
line-height: 60px;
letter-spacing: -1px;
`;
const StyledMintSubTitle = styled.div`
color: #000;
font-family: Inter;
font-size: 25px;
font-weight: 600;
line-height: 24px;
`
const StyledMintImg = styled.img`
margin-top: 47px;
margin-bottom: 29px;
`
const StyledMintButton = styled.div`
padding: 4px 25px;
border-radius: 10px;
background: #000;
cursor: pointer;
color: #fff;
margin-top: 29px;
`
const MinStoryPage: FC = () => {
return (
<div>
<Header />
<StyledPageContainer>
<StyledMintTitle>Mint your story</StyledMintTitle>
<StyledMintImg src={storyImg} alt="" />
<StyledMintSubTitle>The Journey to Dragons Keep</StyledMintSubTitle>
<StyledMintButton>MINT STORY</StyledMintButton>
</StyledPageContainer>
</div>
);
};
export default MinStoryPage;

33
app/src/pages/name.tsx Normal file
View File

@@ -0,0 +1,33 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { Container, Heading, TextField, Button } from "@radix-ui/themes";
import { createCharacterName } from "../modules/character/actions";
const Name = () => {
const [characterName, setCharacterName] = useState("");
const navigate = useNavigate();
const dispatch = useDispatch();
return (
<Container>
<Heading>Enter Character Name</Heading>
<TextField.Root>
<TextField.Input
value={characterName}
onChange={(e) => setCharacterName(e.target.value)}
placeholder="ENTER CHARACTER NAME"
/>
</TextField.Root>
<Button
onClick={() => {
dispatch(createCharacterName({ characterName }));
navigate("/characterBase");
}}
>
Select Character
</Button>
</Container>
);
};
export default Name;

View File

@@ -1,31 +1,40 @@
import { useDispatch, useSelector } from "react-redux";
import { Container } from "@radix-ui/themes";
import { characterNameSelector } from "../../modules/character/selectors";
import { characterTypeSelector } from "../../modules/character/selectors";
import { initStory } from "../../modules/story/actions";
import { getStoryTemplate } from "../../utils";
import { useEffect } from "react";
import { StoryProgressPromptRole } from "../../types/story";
import { storyProgressSelector } from "../../modules/story/selectors";
import {
storySelector,
storyProgressSelector,
} from "../../modules/story/selectors";
import Scene from "../../components/Scene";
import Cover from "../../components/Cover";
const Story: React.FC = () => {
const characterName = useSelector(characterNameSelector);
const template = getStoryTemplate(characterName);
const characterType = useSelector(characterTypeSelector);
const template = getStoryTemplate({ characterName, characterType });
const dispatch = useDispatch();
const { title, currentSceneIndex } = useSelector(storySelector);
const storyProgress = useSelector(storyProgressSelector);
console.log(storyProgress);
useEffect(() => {
dispatch(
initStory({
initMessage: {
role: StoryProgressPromptRole.System,
content: template,
},
})
);
}, []);
if (!title) {
dispatch(
initStory({
initMessage: {
role: StoryProgressPromptRole.System,
content: template,
},
})
);
}
}, [title]);
return <Container>OYOYO</Container>;
return currentSceneIndex > 0 ? <Scene /> : <Cover />;
};
export default Story;

View File

@@ -8,14 +8,28 @@ export type StoryProgress = {
content: string;
};
type SceneOption = {
content: string;
value: string;
export type RawScene = {
title: string; // project
introduction: string; // project
type: "story-introduction" | "story-followup" | "story-ending";
character: string;
setting: string;
sceneNumber: number;
sceneTitle: string;
sceneDescription: string;
optionPrompt: string;
options: string[];
};
export type Scene = {
index: number;
content: string;
options: SceneOption[];
imageUrl: string;
// title: string; // project
// introduction: string; // project
type: "story-introduction" | "story-followup" | "story-ending";
character: string;
setting: string;
sceneNumber: number;
sceneTitle: string;
sceneDescription: string;
optionPrompt: string;
options: string[];
};

View File

@@ -1,40 +1,89 @@
export const getStoryTemplate = (customPrompt: string) =>
export const getStoryTemplate = ({
characterType,
characterName,
}: {
characterType: string;
characterName: string;
}) =>
encodeURI(
`Respond with markdown format and highlight list out each section.
`Respond with JSON format, don't output any other content except the JSON, don't wrap the JSON with markdown syntax
Create a choose your own adventure game with 6 scenes with each scene having 3 choices for the user that are both fun and engaging.
Create a choose-your-own adventure game that lasts 6 scenes where each scene's choice will lead to the next scene. Each scene having 3 choices for the user to choose from that is both fun and engaging. Ensure the the character aesthetic is well defined with tons of description and the setting has the scenery very detailed. Response one scene at a time.
Here is the structure for the storybook and an example:
Here is the structure for the storybook (in typescript interface format) and an example:
Structure
Title
Character
Setting
Introduction
Scenes description followed by user choices. (Scene should be 4-5 sentences max).
Conclusion
\`\`\`
type MarkdownText = string
type Option = string
interface StorybookIntro extends StorybookFollowup {
type: "story-introduction"
title: string
character: string
setting: string
introduction: string
}
interface StorybookFollowup {
type: "story-followup"
sceneNumber: number
sceneTitle: string
sceneDescription: MarkdownText
optionsPrompt: string
options: [
Option,
Option,
Option,
]
}
interface StorybookEnding {
type: "story-ending"
sceneNumber: number
sceneTitle: string
sceneDescription: MarkdownText
}
\`\`\`
Example:
Title: "The Enchanted Forest Adventure"
Example 1:
Character: A clever and resourceful rabbit named Remy.
{
"type": "story-introduction",
"title": "The Enchanted Forest Adventure",
"character": "A clever and resourceful rabbit named Remy with a top hat and a red bowtie",
"setting": "A whimsical world where nature and magic intertwine, filled with talking animals, mystical plants, and hidden paths that brings about a pleasant and warm atmosphere",
"introduction": "In a world where every leaf and stone tells a story, Remy, a clever and resourceful rabbit, is about to embark on an unexpected journey. Known for his wit and curiosity, Remy lives in a cozy burrow near the Enchanted Glade. One morning, he finds a mysterious map leading to an unknown destination. His adventure begins with a choice of where to go first.",
"sceneNumber": 1,
"sceneTitle": "The Mysterious Map",
"sceneDescription": "Remy examines the map and notices three landmarks:\n\n1. The Silver Lake: A shimmering lake known for its reflective waters that show visions.\n\n2. The Whispering Woods: A dense forest where the trees are said to hold ancient wisdom.\n\n3. The Moonlit Meadow: A tranquil meadow that glows under the moon, rumored to be a place of magic.",
"optionsPrompt": "Where should Remy start his adventure?",
"options": ["Lake", "Woods", "Meadow"]
}
Setting: A whimsical world where nature and magic intertwine, filled with talking animals, mystical plants, and hidden paths.
---------- Example 1 end ---------
Introduction: "In a world where every leaf and stone tells a story, Remy, a clever and resourceful rabbit, is about to embark on an unexpected journey. Known for his wit and curiosity, Remy lives in a cozy burrow near the Enchanted Glade. One morning, he finds a mysterious map leading to an unknown destination. His adventure begins with a choice of where to go first."
Example 2:
Scene 1: The Mysterious Map
Remy examines the map and notices three landmarks:
{
"type": "story-followup",
"sceneNumber": 2,
"sceneTitle": "The Mysterious Map",
"sceneDescription": "Remy examines the map and notices three landmarks:\n\n1. The Silver Lake: A shimmering lake known for its reflective waters that show visions.\n\n2. The Whispering Woods: A dense forest where the trees are said to hold ancient wisdom.\n\n3. The Moonlit Meadow: A tranquil meadow that glows under the moon, rumored to be a place of magic.",
"optionsPrompt": "Where should Remy start his adventure?",
"options": ["Lake", "Woods", "Meadow"]
}
1. The Silver Lake: A shimmering lake known for its reflective waters that show visions.
2. The Whispering Woods: A dense forest where the trees are said to hold ancient wisdom.
3. The Moonlit Meadow: A tranquil meadow that glows under the moon, rumored to be a place of magic.
Where should Remy start his adventure? [Type 'Lake', 'Woods', or 'Meadow']
---------- Example 2 end ---------
Let me know your choice, and we'll continue the story from there!
Example 3:
Stop after each scene for user to type their input. As an user, I can play the game here by responding with a choice.
{
"type": "story-ending",
"sceneNumber": 5,
"sceneTitle": "The Mysterious Map",
"sceneDescription": "Gary, reflecting on his experiences throughout the quest, realizes that true treasure lies not in solitary glory, but in sharing triumphs with others. With care, he transports the Golden Lettuce back to his community. His return is celebrated, and the lettuce's seeds bring prosperity to the garden.\n\nGary's act of kindness becomes a tale told from one generation to the next, inspiring countless other snails to live a life of bravery, adventure, and most importantly, generosity."
}
The Character is a ${customPrompt} named Gary for this new story
`.trim()
---------- Example 3 end ---------
Ensure that after each scene the user has to type their input. As a user, I can play the game here by responding with a choice.
The Character is a ${characterType} named ${characterName} for this new story`.trim()
);

View File

@@ -20,6 +20,6 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"include": ["src", "src/global.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -1,8 +1,9 @@
import { defineConfig } from 'vite'
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react-swc'
import Pages from 'vite-plugin-pages'
import svgr from "vite-plugin-svgr";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), Pages()],
plugins: [react(), svgr(), Pages(),],
})

View File

@@ -37,5 +37,26 @@
},
"storage": {
"rules": "storage.rules"
},
"emulators": {
"auth": {
"port": 9099
},
"functions": {
"port": 5001
},
"firestore": {
"port": 8080
},
"hosting": {
"port": 5000
},
"storage": {
"port": 9199
},
"ui": {
"enabled": true
},
"singleProjectMode": true
}
}

View File

@@ -0,0 +1,11 @@
import { CloudFunctionsTypeWithUid } from "./handlersType";
export const generateImage: CloudFunctionsTypeWithUid["generateImage"] = async (
prompt,
uid,
) => {
return {
url: "hello",
finalPrompt: "world" + prompt,
};
};

7
functions/src/handlersType.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import { CloudFunctionsType } from "model/functions";
export type CloudFunctionsTypeWithUid = {
[P in keyof CloudFunctionsType]: (
...args: [...Parameters<CloudFunctionsType[P]>, string | undefined]
) => ReturnType<CloudFunctionsType[P]>;
};

View File

@@ -7,7 +7,7 @@
* See a full list of supported triggers at https://firebase.google.com/docs/functions
*/
import { onCall, onRequest, Request } from "firebase-functions/v2/https";
import { onCall, onRequest, Request, HttpsError } from "firebase-functions/v2/https";
import * as logger from "firebase-functions/logger";
import { OpenAI } from "openai";
import * as express from "express";
@@ -26,10 +26,15 @@ admin.initializeApp({
// https://firebase.google.com/docs/functions/typescript
export const execute = onCall({ region: "asia-east1" }, (request) => {
logger.info("Hello logs!", { structuredData: true });
return {
data: "Hello from Firebase!",
};
const uid = auth?.uid;
logger.info(data, { context: uid });
if (!(data.type in handlers)) {
throw new HttpsError(
"invalid-argument",
`Function ${data.type} does not exist`,
);
}
return await (handlers as any)[data.type](...data.args, uid);
});
const openai = new OpenAI({
apiKey: "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",

3
model/functions.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export type CloudFunctionsType = {
generateImage(prompt: string): Promise<{ url: string; finalPrompt: string }>;
};

15
model/model.d.ts vendored
View File

@@ -40,9 +40,14 @@ export type SceneOption = { content: string; value: string };
export type Scene = {
id: string;
projectId: string;
imageUrl: string;
index: number;
content: string;
options: SceneOption[];
imageUrl: string;
title: string; // project
introduction: string; // project
type: "story-introduction" | "story-followup" | "story-ending";
character: string;
setting: string;
sceneNumber: number;
sceneTitle: string;
sceneDescription: string;
optionPrompt: string;
options: string[];
};