idoubi commited on
Commit
a6c9d88
2 Parent(s): 8f1d248 7943df4

Merge remote-tracking branch 'upstream/main'

Browse files
.env CHANGED
@@ -1,40 +1,81 @@
1
- # ------------- IMAGE API CONFIG --------------
2
  # Supported values:
3
  # - VIDEOCHAIN
4
  # - REPLICATE
5
- RENDERING_ENGINE="REPLICATE"
6
-
7
- VIDEOCHAIN_API_URL="http://localhost:7860"
8
- VIDEOCHAIN_API_TOKEN=
9
-
10
- REPLICATE_API_TOKEN=
11
- REPLICATE_API_MODEL="stabilityai/sdxl"
12
- REPLICATE_API_MODEL_VERSION="da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf"
13
 
14
- # ------------- LLM API CONFIG ----------------
15
  # Supported values:
16
  # - INFERENCE_ENDPOINT
17
  # - INFERENCE_API
18
- LLM_ENGINE="INFERENCE_ENDPOINT"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- # Hugging Face token (if you choose to use a custom Inference Endpoint or an Inference API model)
21
- HF_API_TOKEN=
22
 
23
- # URL to a custom text-generation Inference Endpoint of your choice
24
- # -> You can leave it empty if you decide to use an Inference API Model instead
25
- HF_INFERENCE_ENDPOINT_URL=
26
 
27
- # You can also use a model from the Inference API (not a custom inference endpoint)
28
- # -> You can leave it empty if you decide to use an Inference Endpoint URL instead
29
- HF_INFERENCE_API_MODEL="codellama/CodeLlama-7b-hf"
30
 
31
- # Not supported yet
32
- OPENAI_TOKEN=
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  # ----------- COMMUNITY SHARING (OPTIONAL) -----------
35
- NEXT_PUBLIC_ENABLE_COMMUNITY_SHARING="false"
36
  # You don't need those community sharing options to run the AI Comic Factory
37
  # locally or on your own server (they are meant to be used by the Hugging Face team)
 
38
  COMMUNITY_API_URL=
39
  COMMUNITY_API_TOKEN=
40
  COMMUNITY_API_ID=
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Supported values:
2
  # - VIDEOCHAIN
3
  # - REPLICATE
4
+ # - INFERENCE_ENDPOINT
5
+ # - INFERENCE_API
6
+ RENDERING_ENGINE="INFERENCE_API"
 
 
 
 
 
7
 
 
8
  # Supported values:
9
  # - INFERENCE_ENDPOINT
10
  # - INFERENCE_API
11
+ # - OPENAI
12
+ LLM_ENGINE="INFERENCE_API"
13
+
14
+ # Not implemented for the Inference API yet - you can submit a PR if you have some ideas
15
+ NEXT_PUBLIC_CAN_UPSCALE="false"
16
+
17
+ # Not implemented for the Inference API yet - you can submit a PR if you have some ideas
18
+ NEXT_PUBLIC_CAN_REDRAW="false"
19
+
20
+ # Set to "true" to create artificial delays and smooth out traffic
21
+ NEXT_PUBLIC_ENABLE_RATE_LIMITER="false"
22
+
23
+ # ------------- PROVIDER AUTH ------------
24
+ # You only need to configure the access token(s) for the provider(s) you want to use
25
+
26
+ # HuggingFace.co token: available for the LLM engine and the RENDERING engine
27
+ AUTH_HF_API_TOKEN=
28
+
29
+ # Replicate.com token: available for the RENDERING engine
30
+ AUTH_REPLICATE_API_TOKEN=
31
+
32
+ # OpenAI.dom token: available for the LLM engine and the RENDERING engine
33
+ AUTH_OPENAI_TOKEN=
34
 
35
+ # An experimental RENDERING engine (sorry it is not very documented yet, so you can use one of the other engines)
36
+ AUTH_VIDEOCHAIN_API_TOKEN=
37
 
38
+ # ------------- RENDERING API CONFIG --------------
 
 
39
 
40
+ # If you decided to use Replicate for the RENDERING engine
41
+ RENDERING_REPLICATE_API_MODEL="stabilityai/sdxl"
42
+ RENDERING_REPLICATE_API_MODEL_VERSION="da77bc59ee60423279fd632efb4795ab731d9e3ca9705ef3341091fb989b7eaf"
43
 
44
+ # If you decided to use a private Hugging Face Inference Endpoint for the RENDERING engine
45
+ RENDERING_HF_INFERENCE_ENDPOINT_URL="https://XXXXXXXXXX.endpoints.huggingface.cloud"
46
+
47
+ # If you decided to use a Hugging Face Inference API model for the RENDERING engine
48
+ RENDERING_HF_INFERENCE_API_MODEL="stabilityai/stable-diffusion-xl-base-1.0"
49
+
50
+ # An experimental RENDERING engine (sorry it is not very documented yet, so you can use one of the other engines)
51
+ RENDERING_VIDEOCHAIN_API_URL="http://localhost:7860"
52
+
53
+ # ------------- LLM API CONFIG ----------------
54
+
55
+ # If you decided to use OpenAI for the LLM engine
56
+ LLM_OPENAI_API_BASE_URL="https://api.openai.com/v1"
57
+ LLM_OPENAI_API_MODEL="gpt-3.5-turbo"
58
+
59
+ # If you decided to use a private Hugging Face Inference Endpoint for the LLM engine
60
+ LLM_HF_INFERENCE_ENDPOINT_URL=""
61
+
62
+ # If you decided to use a Hugging Face Inference API model for the LLM engine
63
+ LLM_HF_INFERENCE_API_MODEL="meta-llama/Llama-2-70b-chat-hf"
64
 
65
  # ----------- COMMUNITY SHARING (OPTIONAL) -----------
 
66
  # You don't need those community sharing options to run the AI Comic Factory
67
  # locally or on your own server (they are meant to be used by the Hugging Face team)
68
+ NEXT_PUBLIC_ENABLE_COMMUNITY_SHARING="false"
69
  COMMUNITY_API_URL=
70
  COMMUNITY_API_TOKEN=
71
  COMMUNITY_API_ID=
72
+
73
+ # ----------- CENSORSHIP (OPTIONAL) -----------
74
+ # censorship is currently disabled, but will be required when we create a "community roll"
75
+ # (a public repositoruy of user-generated comic strips)
76
+ ENABLE_CENSORSHIP="false"
77
+
78
+ # Due to the sensitive nature of some of keywords we want to ban (users try all kind of crazy illegal things)
79
+ # the words are are not put in clear in the source code, but behind an encryption key
80
+ # (I don't want the project to be flagged by an AI robot police on GitHub or something)
81
+ SECRET_FINGERPRINT=""
CONTRIBUTORS.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ This project was developed by Julian Bilcke (@jbilcke-hf), as part of his work at Hugging Face.
2
+
3
+ ------------------------------------------
4
+
5
+ A huge thanks to external developers for their contributions!
6
+
7
+ 艾逗笔 (@idoubi):
8
+ - Added support for OpenAI: https://github.com/jbilcke-hf/ai-comic-factory/pull/6
9
+
LICENCE.md ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ ==============
3
+
4
+ _Version 2.0, January 2004_
5
+ _&lt;<http://www.apache.org/licenses/>&gt;_
6
+
7
+ ### Terms and Conditions for use, reproduction, and distribution
8
+
9
+ #### 1. Definitions
10
+
11
+ “License” shall mean the terms and conditions for use, reproduction, and
12
+ distribution as defined by Sections 1 through 9 of this document.
13
+
14
+ “Licensor” shall mean the copyright owner or entity authorized by the copyright
15
+ owner that is granting the License.
16
+
17
+ “Legal Entity” shall mean the union of the acting entity and all other entities
18
+ that control, are controlled by, or are under common control with that entity.
19
+ For the purposes of this definition, “control” means **(i)** the power, direct or
20
+ indirect, to cause the direction or management of such entity, whether by
21
+ contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or **(iii)** beneficial ownership of such entity.
23
+
24
+ “You” (or “Your”) shall mean an individual or Legal Entity exercising
25
+ permissions granted by this License.
26
+
27
+ “Source” form shall mean the preferred form for making modifications, including
28
+ but not limited to software source code, documentation source, and configuration
29
+ files.
30
+
31
+ “Object” form shall mean any form resulting from mechanical transformation or
32
+ translation of a Source form, including but not limited to compiled object code,
33
+ generated documentation, and conversions to other media types.
34
+
35
+ “Work” shall mean the work of authorship, whether in Source or Object form, made
36
+ available under the License, as indicated by a copyright notice that is included
37
+ in or attached to the work (an example is provided in the Appendix below).
38
+
39
+ “Derivative Works” shall mean any work, whether in Source or Object form, that
40
+ is based on (or derived from) the Work and for which the editorial revisions,
41
+ annotations, elaborations, or other modifications represent, as a whole, an
42
+ original work of authorship. For the purposes of this License, Derivative Works
43
+ shall not include works that remain separable from, or merely link (or bind by
44
+ name) to the interfaces of, the Work and Derivative Works thereof.
45
+
46
+ “Contribution” shall mean any work of authorship, including the original version
47
+ of the Work and any modifications or additions to that Work or Derivative Works
48
+ thereof, that is intentionally submitted to Licensor for inclusion in the Work
49
+ by the copyright owner or by an individual or Legal Entity authorized to submit
50
+ on behalf of the copyright owner. For the purposes of this definition,
51
+ “submitted” means any form of electronic, verbal, or written communication sent
52
+ to the Licensor or its representatives, including but not limited to
53
+ communication on electronic mailing lists, source code control systems, and
54
+ issue tracking systems that are managed by, or on behalf of, the Licensor for
55
+ the purpose of discussing and improving the Work, but excluding communication
56
+ that is conspicuously marked or otherwise designated in writing by the copyright
57
+ owner as “Not a Contribution.”
58
+
59
+ “Contributor” shall mean Licensor and any individual or Legal Entity on behalf
60
+ of whom a Contribution has been received by Licensor and subsequently
61
+ incorporated within the Work.
62
+
63
+ #### 2. Grant of Copyright License
64
+
65
+ Subject to the terms and conditions of this License, each Contributor hereby
66
+ grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
67
+ irrevocable copyright license to reproduce, prepare Derivative Works of,
68
+ publicly display, publicly perform, sublicense, and distribute the Work and such
69
+ Derivative Works in Source or Object form.
70
+
71
+ #### 3. Grant of Patent License
72
+
73
+ Subject to the terms and conditions of this License, each Contributor hereby
74
+ grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
75
+ irrevocable (except as stated in this section) patent license to make, have
76
+ made, use, offer to sell, sell, import, and otherwise transfer the Work, where
77
+ such license applies only to those patent claims licensable by such Contributor
78
+ that are necessarily infringed by their Contribution(s) alone or by combination
79
+ of their Contribution(s) with the Work to which such Contribution(s) was
80
+ submitted. If You institute patent litigation against any entity (including a
81
+ cross-claim or counterclaim in a lawsuit) alleging that the Work or a
82
+ Contribution incorporated within the Work constitutes direct or contributory
83
+ patent infringement, then any patent licenses granted to You under this License
84
+ for that Work shall terminate as of the date such litigation is filed.
85
+
86
+ #### 4. Redistribution
87
+
88
+ You may reproduce and distribute copies of the Work or Derivative Works thereof
89
+ in any medium, with or without modifications, and in Source or Object form,
90
+ provided that You meet the following conditions:
91
+
92
+ * **(a)** You must give any other recipients of the Work or Derivative Works a copy of
93
+ this License; and
94
+ * **(b)** You must cause any modified files to carry prominent notices stating that You
95
+ changed the files; and
96
+ * **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
97
+ all copyright, patent, trademark, and attribution notices from the Source form
98
+ of the Work, excluding those notices that do not pertain to any part of the
99
+ Derivative Works; and
100
+ * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
101
+ Derivative Works that You distribute must include a readable copy of the
102
+ attribution notices contained within such NOTICE file, excluding those notices
103
+ that do not pertain to any part of the Derivative Works, in at least one of the
104
+ following places: within a NOTICE text file distributed as part of the
105
+ Derivative Works; within the Source form or documentation, if provided along
106
+ with the Derivative Works; or, within a display generated by the Derivative
107
+ Works, if and wherever such third-party notices normally appear. The contents of
108
+ the NOTICE file are for informational purposes only and do not modify the
109
+ License. You may add Your own attribution notices within Derivative Works that
110
+ You distribute, alongside or as an addendum to the NOTICE text from the Work,
111
+ provided that such additional attribution notices cannot be construed as
112
+ modifying the License.
113
+
114
+ You may add Your own copyright statement to Your modifications and may provide
115
+ additional or different license terms and conditions for use, reproduction, or
116
+ distribution of Your modifications, or for any such Derivative Works as a whole,
117
+ provided Your use, reproduction, and distribution of the Work otherwise complies
118
+ with the conditions stated in this License.
119
+
120
+ #### 5. Submission of Contributions
121
+
122
+ Unless You explicitly state otherwise, any Contribution intentionally submitted
123
+ for inclusion in the Work by You to the Licensor shall be under the terms and
124
+ conditions of this License, without any additional terms or conditions.
125
+ Notwithstanding the above, nothing herein shall supersede or modify the terms of
126
+ any separate license agreement you may have executed with Licensor regarding
127
+ such Contributions.
128
+
129
+ #### 6. Trademarks
130
+
131
+ This License does not grant permission to use the trade names, trademarks,
132
+ service marks, or product names of the Licensor, except as required for
133
+ reasonable and customary use in describing the origin of the Work and
134
+ reproducing the content of the NOTICE file.
135
+
136
+ #### 7. Disclaimer of Warranty
137
+
138
+ Unless required by applicable law or agreed to in writing, Licensor provides the
139
+ Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
140
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
141
+ including, without limitation, any warranties or conditions of TITLE,
142
+ NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
143
+ solely responsible for determining the appropriateness of using or
144
+ redistributing the Work and assume any risks associated with Your exercise of
145
+ permissions under this License.
146
+
147
+ #### 8. Limitation of Liability
148
+
149
+ In no event and under no legal theory, whether in tort (including negligence),
150
+ contract, or otherwise, unless required by applicable law (such as deliberate
151
+ and grossly negligent acts) or agreed to in writing, shall any Contributor be
152
+ liable to You for damages, including any direct, indirect, special, incidental,
153
+ or consequential damages of any character arising as a result of this License or
154
+ out of the use or inability to use the Work (including but not limited to
155
+ damages for loss of goodwill, work stoppage, computer failure or malfunction, or
156
+ any and all other commercial damages or losses), even if such Contributor has
157
+ been advised of the possibility of such damages.
158
+
159
+ #### 9. Accepting Warranty or Additional Liability
160
+
161
+ While redistributing the Work or Derivative Works thereof, You may choose to
162
+ offer, and charge a fee for, acceptance of support, warranty, indemnity, or
163
+ other liability obligations and/or rights consistent with this License. However,
164
+ in accepting such obligations, You may act only on Your own behalf and on Your
165
+ sole responsibility, not on behalf of any other Contributor, and only if You
166
+ agree to indemnify, defend, and hold each Contributor harmless for any liability
167
+ incurred by, or claims asserted against, such Contributor by reason of your
168
+ accepting any such warranty or additional liability.
169
+
170
+ _END OF TERMS AND CONDITIONS_
README.md CHANGED
@@ -6,6 +6,7 @@ colorTo: yellow
6
  sdk: docker
7
  pinned: true
8
  app_port: 3000
 
9
  ---
10
 
11
  # AI Comic Factory
@@ -17,17 +18,29 @@ First, I would like to highlight that everything is open-source (see [here](http
17
  However the project isn't a monolithic Space that can be duplicated and ran immediately:
18
  it requires various components to run for the frontend, backend, LLM, SDXL etc.
19
 
20
- If you try to duplicate the project and open the `.env` you will see it requires some variables:
21
-
22
- - `LLM_ENGINE`: can be either "INFERENCE_API" or "INFERENCE_ENDPOINT"
23
- - `HF_API_TOKEN`: necessary if you decide to use an inference api model or a custom inference endpoint
24
- - `HF_INFERENCE_ENDPOINT_URL`: necessary if you decide to use a custom inference endpoint
25
- - `RENDERING_ENGINE`: can only be "VIDEOCHAIN" or "REPLICATE" for now, unless you code your custom solution
26
- - `VIDEOCHAIN_API_URL`: url to the VideoChain API server
27
- - `VIDEOCHAIN_API_TOKEN`: secret token to access the VideoChain API server
28
- - `REPLICATE_API_TOKEN`: in case you want to use Replicate.com
29
- - `REPLICATE_API_MODEL`: optional, defaults to "stabilityai/sdxl"
30
- - `REPLICATE_API_MODEL_VERSION`: optional, in case you want to change the version
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  In addition, there are some community sharing variables that you can just ignore.
33
  Those variables are not required to run the AI Comic Factory on your own website or computer
 
6
  sdk: docker
7
  pinned: true
8
  app_port: 3000
9
+ disable_embedding: true
10
  ---
11
 
12
  # AI Comic Factory
 
18
  However the project isn't a monolithic Space that can be duplicated and ran immediately:
19
  it requires various components to run for the frontend, backend, LLM, SDXL etc.
20
 
21
+ If you try to duplicate the project, open the `.env` you will see it requires some variables.
22
+
23
+ Provider config:
24
+ - `LLM_ENGINE`: can be one of: "INFERENCE_API", "INFERENCE_ENDPOINT", "OPENAI"
25
+ - `RENDERING_ENGINE`: can be one of: "INFERENCE_API", "INFERENCE_ENDPOINT", "REPLICATE", "VIDEOCHAIN" for now, unless you code your custom solution
26
+
27
+ Auth config:
28
+ - `AUTH_HF_API_TOKEN`: only if you decide to use OpenAI for the LLM engine necessary if you decide to use an inference api model or a custom inference endpoint
29
+ - `AUTH_OPENAI_TOKEN`: only if you decide to use OpenAI for the LLM engine
30
+ - `AITH_VIDEOCHAIN_API_TOKEN`: secret token to access the VideoChain API server
31
+ - `AUTH_REPLICATE_API_TOKEN`: in case you want to use Replicate.com
32
+
33
+ Rendering config:
34
+ - `RENDERING_HF_INFERENCE_ENDPOINT_URL`: necessary if you decide to use a custom inference endpoint
35
+ - `RENDERING_REPLICATE_API_MODEL_VERSION`: url to the VideoChain API server
36
+ - `RENDERING_HF_INFERENCE_ENDPOINT_URL`: optional, default to nothing
37
+ - `RENDERING_HF_INFERENCE_API_MODEL`: optional, defaults to "stabilityai/stable-diffusion-xl-base-1.0"
38
+ - `RENDERING_REPLICATE_API_MODEL`: optional, defaults to "stabilityai/sdxl"
39
+ - `RENDERING_REPLICATE_API_MODEL_VERSION`: optional, in case you want to change the version
40
+
41
+ Language model config:
42
+ - `LLM_HF_INFERENCE_ENDPOINT_URL`: "https://llama-v2-70b-chat.ngrok.io"
43
+ - `LLM_HF_INFERENCE_API_MODEL`: "codellama/CodeLlama-7b-hf"
44
 
45
  In addition, there are some community sharing variables that you can just ignore.
46
  Those variables are not required to run the AI Comic Factory on your own website or computer
package-lock.json CHANGED
@@ -37,16 +37,19 @@
37
  "cmdk": "^0.2.0",
38
  "cookies-next": "^2.1.2",
39
  "date-fns": "^2.30.0",
 
40
  "eslint": "8.45.0",
41
  "eslint-config-next": "13.4.10",
42
  "html2canvas": "^1.4.1",
43
  "lucide-react": "^0.260.0",
44
  "next": "13.4.10",
 
45
  "pick": "^0.0.1",
46
  "postcss": "8.4.26",
47
  "react": "18.2.0",
48
  "react-circular-progressbar": "^2.1.0",
49
  "react-dom": "18.2.0",
 
50
  "react-virtualized-auto-sizer": "^1.0.20",
51
  "replicate": "^0.17.0",
52
  "sbd": "^1.0.19",
@@ -3691,6 +3694,15 @@
3691
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz",
3692
  "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw=="
3693
  },
 
 
 
 
 
 
 
 
 
3694
  "node_modules/@types/prop-types": {
3695
  "version": "15.7.5",
3696
  "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -3847,6 +3859,17 @@
3847
  "url": "https://opencollective.com/typescript-eslint"
3848
  }
3849
  },
 
 
 
 
 
 
 
 
 
 
 
3850
  "node_modules/abs-svg-path": {
3851
  "version": "0.1.1",
3852
  "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
@@ -3879,6 +3902,17 @@
3879
  "node": ">=0.4.0"
3880
  }
3881
  },
 
 
 
 
 
 
 
 
 
 
 
3882
  "node_modules/ajv": {
3883
  "version": "6.12.6",
3884
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -4096,6 +4130,11 @@
4096
  "has-symbols": "^1.0.3"
4097
  }
4098
  },
 
 
 
 
 
4099
  "node_modules/autoprefixer": {
4100
  "version": "10.4.14",
4101
  "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
@@ -4209,6 +4248,11 @@
4209
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
4210
  "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
4211
  },
 
 
 
 
 
4212
  "node_modules/base64-arraybuffer": {
4213
  "version": "1.0.2",
4214
  "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
@@ -4430,6 +4474,14 @@
4430
  "url": "https://github.com/chalk/chalk?sponsor=1"
4431
  }
4432
  },
 
 
 
 
 
 
 
 
4433
  "node_modules/chokidar": {
4434
  "version": "3.5.3",
4435
  "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -4795,6 +4847,17 @@
4795
  "simple-swizzle": "^0.2.2"
4796
  }
4797
  },
 
 
 
 
 
 
 
 
 
 
 
4798
  "node_modules/command-score": {
4799
  "version": "0.1.2",
4800
  "resolved": "https://registry.npmjs.org/command-score/-/command-score-0.1.2.tgz",
@@ -4879,6 +4942,14 @@
4879
  "node": ">= 8"
4880
  }
4881
  },
 
 
 
 
 
 
 
 
4882
  "node_modules/crypto-js": {
4883
  "version": "4.1.1",
4884
  "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
@@ -5012,6 +5083,14 @@
5012
  "url": "https://github.com/sponsors/ljharb"
5013
  }
5014
  },
 
 
 
 
 
 
 
 
5015
  "node_modules/dequal": {
5016
  "version": "2.0.3",
5017
  "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -5051,6 +5130,15 @@
5051
  "node": ">=0.3.1"
5052
  }
5053
  },
 
 
 
 
 
 
 
 
 
5054
  "node_modules/dir-glob": {
5055
  "version": "3.0.1",
5056
  "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -5139,6 +5227,14 @@
5139
  "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz",
5140
  "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA=="
5141
  },
 
 
 
 
 
 
 
 
5142
  "node_modules/end-of-stream": {
5143
  "version": "1.4.4",
5144
  "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -5697,6 +5793,14 @@
5697
  "node": ">=0.10.0"
5698
  }
5699
  },
 
 
 
 
 
 
 
 
5700
  "node_modules/events": {
5701
  "version": "3.3.0",
5702
  "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -5846,6 +5950,36 @@
5846
  "is-callable": "^1.1.3"
5847
  }
5848
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5849
  "node_modules/fraction.js": {
5850
  "version": "4.2.1",
5851
  "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.1.tgz",
@@ -6197,11 +6331,30 @@
6197
  "entities": "^4.4.0"
6198
  }
6199
  },
 
 
 
 
 
 
 
 
6200
  "node_modules/hyphen": {
6201
  "version": "1.6.6",
6202
  "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.6.6.tgz",
6203
  "integrity": "sha512-XtqmnT+b9n5MX+MsqluFAVTIenbtC25iskW0Z+jLd+awfhA+ZbWKWQMIvLJccGoa2bM1R6juWJ27cZxIFOmkWw=="
6204
  },
 
 
 
 
 
 
 
 
 
 
 
6205
  "node_modules/idb-keyval": {
6206
  "version": "6.2.1",
6207
  "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
@@ -6366,6 +6519,11 @@
6366
  "url": "https://github.com/sponsors/ljharb"
6367
  }
6368
  },
 
 
 
 
 
6369
  "node_modules/is-callable": {
6370
  "version": "1.2.7",
6371
  "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -6851,6 +7009,16 @@
6851
  "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
6852
  "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
6853
  },
 
 
 
 
 
 
 
 
 
 
6854
  "node_modules/media-engine": {
6855
  "version": "1.0.3",
6856
  "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz",
@@ -6876,6 +7044,25 @@
6876
  "node": ">=8.6"
6877
  }
6878
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6879
  "node_modules/mimic-response": {
6880
  "version": "3.1.0",
6881
  "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
@@ -7050,6 +7237,24 @@
7050
  "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
7051
  "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
7052
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7053
  "node_modules/node-fetch": {
7054
  "version": "2.7.0",
7055
  "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -7223,6 +7428,29 @@
7223
  "wrappy": "1"
7224
  }
7225
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7226
  "node_modules/opencollective-postinstall": {
7227
  "version": "2.0.3",
7228
  "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
@@ -7681,6 +7909,14 @@
7681
  "loose-envify": "^1.1.0"
7682
  }
7683
  },
 
 
 
 
 
 
 
 
7684
  "node_modules/react-is": {
7685
  "version": "16.13.1",
7686
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -8043,6 +8279,11 @@
8043
  "url": "https://github.com/sponsors/ljharb"
8044
  }
8045
  },
 
 
 
 
 
8046
  "node_modules/sanitize-html": {
8047
  "version": "2.11.0",
8048
  "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.11.0.tgz",
@@ -9067,6 +9308,14 @@
9067
  "node": ">=10.13.0"
9068
  }
9069
  },
 
 
 
 
 
 
 
 
9070
  "node_modules/webidl-conversions": {
9071
  "version": "3.0.1",
9072
  "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
 
37
  "cmdk": "^0.2.0",
38
  "cookies-next": "^2.1.2",
39
  "date-fns": "^2.30.0",
40
+ "encoding": "^0.1.13",
41
  "eslint": "8.45.0",
42
  "eslint-config-next": "13.4.10",
43
  "html2canvas": "^1.4.1",
44
  "lucide-react": "^0.260.0",
45
  "next": "13.4.10",
46
+ "openai": "^4.10.0",
47
  "pick": "^0.0.1",
48
  "postcss": "8.4.26",
49
  "react": "18.2.0",
50
  "react-circular-progressbar": "^2.1.0",
51
  "react-dom": "18.2.0",
52
+ "react-icons": "^4.11.0",
53
  "react-virtualized-auto-sizer": "^1.0.20",
54
  "replicate": "^0.17.0",
55
  "sbd": "^1.0.19",
 
3694
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz",
3695
  "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw=="
3696
  },
3697
+ "node_modules/@types/node-fetch": {
3698
+ "version": "2.6.6",
3699
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.6.tgz",
3700
+ "integrity": "sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==",
3701
+ "dependencies": {
3702
+ "@types/node": "*",
3703
+ "form-data": "^4.0.0"
3704
+ }
3705
+ },
3706
  "node_modules/@types/prop-types": {
3707
  "version": "15.7.5",
3708
  "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
 
3859
  "url": "https://opencollective.com/typescript-eslint"
3860
  }
3861
  },
3862
+ "node_modules/abort-controller": {
3863
+ "version": "3.0.0",
3864
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
3865
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
3866
+ "dependencies": {
3867
+ "event-target-shim": "^5.0.0"
3868
+ },
3869
+ "engines": {
3870
+ "node": ">=6.5"
3871
+ }
3872
+ },
3873
  "node_modules/abs-svg-path": {
3874
  "version": "0.1.1",
3875
  "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
 
3902
  "node": ">=0.4.0"
3903
  }
3904
  },
3905
+ "node_modules/agentkeepalive": {
3906
+ "version": "4.5.0",
3907
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
3908
+ "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
3909
+ "dependencies": {
3910
+ "humanize-ms": "^1.2.1"
3911
+ },
3912
+ "engines": {
3913
+ "node": ">= 8.0.0"
3914
+ }
3915
+ },
3916
  "node_modules/ajv": {
3917
  "version": "6.12.6",
3918
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
 
4130
  "has-symbols": "^1.0.3"
4131
  }
4132
  },
4133
+ "node_modules/asynckit": {
4134
+ "version": "0.4.0",
4135
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
4136
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
4137
+ },
4138
  "node_modules/autoprefixer": {
4139
  "version": "10.4.14",
4140
  "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
 
4248
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
4249
  "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
4250
  },
4251
+ "node_modules/base-64": {
4252
+ "version": "0.1.0",
4253
+ "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
4254
+ "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
4255
+ },
4256
  "node_modules/base64-arraybuffer": {
4257
  "version": "1.0.2",
4258
  "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
 
4474
  "url": "https://github.com/chalk/chalk?sponsor=1"
4475
  }
4476
  },
4477
+ "node_modules/charenc": {
4478
+ "version": "0.0.2",
4479
+ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
4480
+ "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
4481
+ "engines": {
4482
+ "node": "*"
4483
+ }
4484
+ },
4485
  "node_modules/chokidar": {
4486
  "version": "3.5.3",
4487
  "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
 
4847
  "simple-swizzle": "^0.2.2"
4848
  }
4849
  },
4850
+ "node_modules/combined-stream": {
4851
+ "version": "1.0.8",
4852
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
4853
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
4854
+ "dependencies": {
4855
+ "delayed-stream": "~1.0.0"
4856
+ },
4857
+ "engines": {
4858
+ "node": ">= 0.8"
4859
+ }
4860
+ },
4861
  "node_modules/command-score": {
4862
  "version": "0.1.2",
4863
  "resolved": "https://registry.npmjs.org/command-score/-/command-score-0.1.2.tgz",
 
4942
  "node": ">= 8"
4943
  }
4944
  },
4945
+ "node_modules/crypt": {
4946
+ "version": "0.0.2",
4947
+ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
4948
+ "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
4949
+ "engines": {
4950
+ "node": "*"
4951
+ }
4952
+ },
4953
  "node_modules/crypto-js": {
4954
  "version": "4.1.1",
4955
  "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
 
5083
  "url": "https://github.com/sponsors/ljharb"
5084
  }
5085
  },
5086
+ "node_modules/delayed-stream": {
5087
+ "version": "1.0.0",
5088
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
5089
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
5090
+ "engines": {
5091
+ "node": ">=0.4.0"
5092
+ }
5093
+ },
5094
  "node_modules/dequal": {
5095
  "version": "2.0.3",
5096
  "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
 
5130
  "node": ">=0.3.1"
5131
  }
5132
  },
5133
+ "node_modules/digest-fetch": {
5134
+ "version": "1.3.0",
5135
+ "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz",
5136
+ "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==",
5137
+ "dependencies": {
5138
+ "base-64": "^0.1.0",
5139
+ "md5": "^2.3.0"
5140
+ }
5141
+ },
5142
  "node_modules/dir-glob": {
5143
  "version": "3.0.1",
5144
  "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
 
5227
  "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz",
5228
  "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA=="
5229
  },
5230
+ "node_modules/encoding": {
5231
+ "version": "0.1.13",
5232
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
5233
+ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
5234
+ "dependencies": {
5235
+ "iconv-lite": "^0.6.2"
5236
+ }
5237
+ },
5238
  "node_modules/end-of-stream": {
5239
  "version": "1.4.4",
5240
  "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
 
5793
  "node": ">=0.10.0"
5794
  }
5795
  },
5796
+ "node_modules/event-target-shim": {
5797
+ "version": "5.0.1",
5798
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
5799
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
5800
+ "engines": {
5801
+ "node": ">=6"
5802
+ }
5803
+ },
5804
  "node_modules/events": {
5805
  "version": "3.3.0",
5806
  "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
 
5950
  "is-callable": "^1.1.3"
5951
  }
5952
  },
5953
+ "node_modules/form-data": {
5954
+ "version": "4.0.0",
5955
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
5956
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
5957
+ "dependencies": {
5958
+ "asynckit": "^0.4.0",
5959
+ "combined-stream": "^1.0.8",
5960
+ "mime-types": "^2.1.12"
5961
+ },
5962
+ "engines": {
5963
+ "node": ">= 6"
5964
+ }
5965
+ },
5966
+ "node_modules/form-data-encoder": {
5967
+ "version": "1.7.2",
5968
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
5969
+ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
5970
+ },
5971
+ "node_modules/formdata-node": {
5972
+ "version": "4.4.1",
5973
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
5974
+ "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
5975
+ "dependencies": {
5976
+ "node-domexception": "1.0.0",
5977
+ "web-streams-polyfill": "4.0.0-beta.3"
5978
+ },
5979
+ "engines": {
5980
+ "node": ">= 12.20"
5981
+ }
5982
+ },
5983
  "node_modules/fraction.js": {
5984
  "version": "4.2.1",
5985
  "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.1.tgz",
 
6331
  "entities": "^4.4.0"
6332
  }
6333
  },
6334
+ "node_modules/humanize-ms": {
6335
+ "version": "1.2.1",
6336
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
6337
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
6338
+ "dependencies": {
6339
+ "ms": "^2.0.0"
6340
+ }
6341
+ },
6342
  "node_modules/hyphen": {
6343
  "version": "1.6.6",
6344
  "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.6.6.tgz",
6345
  "integrity": "sha512-XtqmnT+b9n5MX+MsqluFAVTIenbtC25iskW0Z+jLd+awfhA+ZbWKWQMIvLJccGoa2bM1R6juWJ27cZxIFOmkWw=="
6346
  },
6347
+ "node_modules/iconv-lite": {
6348
+ "version": "0.6.3",
6349
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
6350
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
6351
+ "dependencies": {
6352
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
6353
+ },
6354
+ "engines": {
6355
+ "node": ">=0.10.0"
6356
+ }
6357
+ },
6358
  "node_modules/idb-keyval": {
6359
  "version": "6.2.1",
6360
  "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
 
6519
  "url": "https://github.com/sponsors/ljharb"
6520
  }
6521
  },
6522
+ "node_modules/is-buffer": {
6523
+ "version": "1.1.6",
6524
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
6525
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
6526
+ },
6527
  "node_modules/is-callable": {
6528
  "version": "1.2.7",
6529
  "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
 
7009
  "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
7010
  "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
7011
  },
7012
+ "node_modules/md5": {
7013
+ "version": "2.3.0",
7014
+ "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
7015
+ "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
7016
+ "dependencies": {
7017
+ "charenc": "0.0.2",
7018
+ "crypt": "0.0.2",
7019
+ "is-buffer": "~1.1.6"
7020
+ }
7021
+ },
7022
  "node_modules/media-engine": {
7023
  "version": "1.0.3",
7024
  "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz",
 
7044
  "node": ">=8.6"
7045
  }
7046
  },
7047
+ "node_modules/mime-db": {
7048
+ "version": "1.52.0",
7049
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
7050
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
7051
+ "engines": {
7052
+ "node": ">= 0.6"
7053
+ }
7054
+ },
7055
+ "node_modules/mime-types": {
7056
+ "version": "2.1.35",
7057
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
7058
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
7059
+ "dependencies": {
7060
+ "mime-db": "1.52.0"
7061
+ },
7062
+ "engines": {
7063
+ "node": ">= 0.6"
7064
+ }
7065
+ },
7066
  "node_modules/mimic-response": {
7067
  "version": "3.1.0",
7068
  "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
 
7237
  "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
7238
  "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
7239
  },
7240
+ "node_modules/node-domexception": {
7241
+ "version": "1.0.0",
7242
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
7243
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
7244
+ "funding": [
7245
+ {
7246
+ "type": "github",
7247
+ "url": "https://github.com/sponsors/jimmywarting"
7248
+ },
7249
+ {
7250
+ "type": "github",
7251
+ "url": "https://paypal.me/jimmywarting"
7252
+ }
7253
+ ],
7254
+ "engines": {
7255
+ "node": ">=10.5.0"
7256
+ }
7257
+ },
7258
  "node_modules/node-fetch": {
7259
  "version": "2.7.0",
7260
  "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
 
7428
  "wrappy": "1"
7429
  }
7430
  },
7431
+ "node_modules/openai": {
7432
+ "version": "4.10.0",
7433
+ "resolved": "https://registry.npmjs.org/openai/-/openai-4.10.0.tgz",
7434
+ "integrity": "sha512-II4b5/7qzwYkqA9MSjgqdofCc798EW+dtF2h6qNaVLet+qO7FShAJTWnoyzb50J4ZH1rPxRFAsmDLIhY3PT6DQ==",
7435
+ "dependencies": {
7436
+ "@types/node": "^18.11.18",
7437
+ "@types/node-fetch": "^2.6.4",
7438
+ "abort-controller": "^3.0.0",
7439
+ "agentkeepalive": "^4.2.1",
7440
+ "digest-fetch": "^1.3.0",
7441
+ "form-data-encoder": "1.7.2",
7442
+ "formdata-node": "^4.3.2",
7443
+ "node-fetch": "^2.6.7"
7444
+ },
7445
+ "bin": {
7446
+ "openai": "bin/cli"
7447
+ }
7448
+ },
7449
+ "node_modules/openai/node_modules/@types/node": {
7450
+ "version": "18.17.19",
7451
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.19.tgz",
7452
+ "integrity": "sha512-+pMhShR3Or5GR0/sp4Da7FnhVmTalWm81M6MkEldbwjETSaPalw138Z4KdpQaistvqQxLB7Cy4xwYdxpbSOs9Q=="
7453
+ },
7454
  "node_modules/opencollective-postinstall": {
7455
  "version": "2.0.3",
7456
  "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
 
7909
  "loose-envify": "^1.1.0"
7910
  }
7911
  },
7912
+ "node_modules/react-icons": {
7913
+ "version": "4.11.0",
7914
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz",
7915
+ "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==",
7916
+ "peerDependencies": {
7917
+ "react": "*"
7918
+ }
7919
+ },
7920
  "node_modules/react-is": {
7921
  "version": "16.13.1",
7922
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
 
8279
  "url": "https://github.com/sponsors/ljharb"
8280
  }
8281
  },
8282
+ "node_modules/safer-buffer": {
8283
+ "version": "2.1.2",
8284
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
8285
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
8286
+ },
8287
  "node_modules/sanitize-html": {
8288
  "version": "2.11.0",
8289
  "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.11.0.tgz",
 
9308
  "node": ">=10.13.0"
9309
  }
9310
  },
9311
+ "node_modules/web-streams-polyfill": {
9312
+ "version": "4.0.0-beta.3",
9313
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
9314
+ "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
9315
+ "engines": {
9316
+ "node": ">= 14"
9317
+ }
9318
+ },
9319
  "node_modules/webidl-conversions": {
9320
  "version": "3.0.1",
9321
  "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
package.json CHANGED
@@ -38,6 +38,7 @@
38
  "cmdk": "^0.2.0",
39
  "cookies-next": "^2.1.2",
40
  "date-fns": "^2.30.0",
 
41
  "eslint": "8.45.0",
42
  "eslint-config-next": "13.4.10",
43
  "html2canvas": "^1.4.1",
@@ -49,6 +50,7 @@
49
  "react": "18.2.0",
50
  "react-circular-progressbar": "^2.1.0",
51
  "react-dom": "18.2.0",
 
52
  "react-virtualized-auto-sizer": "^1.0.20",
53
  "replicate": "^0.17.0",
54
  "sbd": "^1.0.19",
 
38
  "cmdk": "^0.2.0",
39
  "cookies-next": "^2.1.2",
40
  "date-fns": "^2.30.0",
41
+ "encoding": "^0.1.13",
42
  "eslint": "8.45.0",
43
  "eslint-config-next": "13.4.10",
44
  "html2canvas": "^1.4.1",
 
50
  "react": "18.2.0",
51
  "react-circular-progressbar": "^2.1.0",
52
  "react-dom": "18.2.0",
53
+ "react-icons": "^4.11.0",
54
  "react-virtualized-auto-sizer": "^1.0.20",
55
  "replicate": "^0.17.0",
56
  "sbd": "^1.0.19",
src/app/engine/caption.ts CHANGED
@@ -2,7 +2,7 @@
2
 
3
  import { ImageAnalysisRequest, ImageAnalysisResponse } from "@/types"
4
 
5
- const apiUrl = `${process.env.VIDEOCHAIN_API_URL || ""}`
6
 
7
  export async function see({
8
  prompt,
@@ -33,7 +33,7 @@ export async function see({
33
  headers: {
34
  Accept: "application/json",
35
  "Content-Type": "application/json",
36
- // Authorization: `Bearer ${process.env.VIDEOCHAIN_API_TOKEN}`,
37
  },
38
  body: JSON.stringify(request),
39
  cache: 'no-store',
 
2
 
3
  import { ImageAnalysisRequest, ImageAnalysisResponse } from "@/types"
4
 
5
+ const apiUrl = `${process.env.RENDERING_VIDEOCHAIN_API_URL || ""}`
6
 
7
  export async function see({
8
  prompt,
 
33
  headers: {
34
  Accept: "application/json",
35
  "Content-Type": "application/json",
36
+ // Authorization: `Bearer ${videochainApi}`,
37
  },
38
  body: JSON.stringify(request),
39
  cache: 'no-store',
src/app/engine/censorship.ts CHANGED
@@ -1,39 +1,184 @@
1
 
2
-
3
- // unfortunately due to abuse by some users, I have to add this NSFW filter
4
- const secretSalt = `${process.env.SECRET_CENSORSHIP_KEY || ""}`
5
-
6
- // TODO the censorship is not implement yet actually
7
-
8
  // I don't want to be banned by Replicate because bad actors are asking
9
  // for some naked anime stuff or whatever
10
  // I also want to avoid a PR scandal due to some bad user generated content
11
 
12
- const forbiddenWords = [
13
- // those keywords have been generated by looking at the logs of the AI Comic Factory
14
- // those are real requests some users tried to attempt.. :|
15
- "nazi",
16
- "hitler",
17
- "boob",
18
- "boobs",
19
- "boobies",
20
- "nipple",
21
- "nipples",
22
- "nude",
23
- "nudes",
24
- "naked",
25
- "pee",
26
- "peeing",
27
- "erotic",
28
- "sexy"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  ]
30
 
31
- // temporary utility to make sure Replicate doesn't ban my account
32
- // because of what users do in their prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  export const filterOutBadWords = (sentence: string) => {
34
- const words = sentence.split(" ")
35
- return words.filter(word => {
36
- const lowerCase = word.toLocaleLowerCase()
37
- return !forbiddenWords.includes(lowerCase)
38
- }).join(" ")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
 
1
 
 
 
 
 
 
 
2
  // I don't want to be banned by Replicate because bad actors are asking
3
  // for some naked anime stuff or whatever
4
  // I also want to avoid a PR scandal due to some bad user generated content
5
 
6
+ import { computeSecretFingerprint } from "@/lib/computeSecretFingerprint"
7
+
8
+ // those keywords have been generated by looking at the logs of the panorama and the AI Comic Factory
9
+ // those are real requests some users tried to attempt.. :|
10
+
11
+ const chickens = [
12
+ "fcb4dacbd99b21368c50f29c1d47071c87cf2225ab9192282c785460391cd365",
13
+ "68840b60ac27eacaa7afe17e898d3c4a2dc71acff8c74d6782c1bcaafd14963d",
14
+ "67f745224fd6e1a7a3a244514d5807fcc994cbb62ca4ec8fa44cd14244a515ae",
15
+ "681fea565117808c6dbe002520d2cfeeb3e5c67e68630afb4a453449a9da587b",
16
+ "2f3d913b3db9e15a930aac43eb2d6fe8817db8e4bcf37794bf0227b06b718d1b",
17
+ "922a700b807e4994df82eba2b48a6ac131fe8d8d1035d06b3592d622fb232161",
18
+ "cb69ee6774eafcc720adb1f689d28acbb9f47998cbea0299ec66a58dedf91c37"
19
+ ]
20
+
21
+ const ducks = [
22
+ "1c52cb20c0cbc76349fa63232b982bd394cf0850ebc17240dcf33c19fb15a26d",
23
+ "e1d4de9b8d464d7da07c276b63a42c1c9922224f0a6cab6b0826427ce4a7461a",
24
+ "0be3174bfb1a48a65875c2f035b1ae14fbc8f232f55785018de0cfe2132fa952",
25
+ "0f174769641b2e5d2c79b5a83e8ef91e004f6f3e62531cd70cfdff02159268cb",
26
+ "e9fb8ae8ff720acd91025229478a21e43e8e976e30119a76c293201adf572736",
27
+ "f65a0dc0e07b5d084ff24c69dcdb953f7b57101d2ebb716d4dfb5963076ef807",
28
+ "2bf38af1646489c2c086f811d082054cd29e23fa7bb5c525396bec01b3ab688e"
29
+ ]
30
+
31
+ const cats = [
32
+ "fcffc3e997d952007d1b902a9cf40b750ba4a410ac65bfd95475996bf51359e4",
33
+ "3172a5fa159754d703489dfba5af520b8ace107cdf170f4c4cb38a6797aa163f",
34
+ "500012dbff4498a9c4513369d6b9b373fab9330ffd2cb1e622294043cc21b610",
35
+ "84e3a8d34ee7d0c8e7a2926dd1acad46a0b66b9d27725b3a7e5053550f490301"
36
+ ]
37
+
38
+ const roasted = [
39
+ "a2bfbce0046c9a52a0eabf98f73e0f8e09959970431fc892ebdb4e1c97031b50",
40
+ "6eca1adf06851f99e9cdfbb496c27d46ff81106903d11f3346a146e96082b016",
41
+ "49a124c9ed6fbbad4105b3657dc25de369bcafb9d6787f610c08f584cd607d0f",
42
+ "c3afb59420c812cbc7c8f57ad3e8d79407f10106a99f829aa65316c99d0b29c4",
43
+ "2b808858836a5c205080f5b93201ef92e098cff931d8de6d9f20dc722997d077",
44
+ "07bef89d1a7d63c9c5ed64ba0f73d6cff689811847c2e20c8b3fbfb060e1d64e",
45
+ "baeb994922d5473f534aa54322d83effe74c6c4dac807e6b523a677d7acdc17b",
46
+ "ea4735a879edd5cc94ca7db26edd5a970df69a41f0009d3444486647e44175af",
47
+ "f2412249030454cd13ac6f7965871d924c16daacda0123de81892adb19ce49ac",
48
+ "9958c56e12bab8549cf752bcd8bec4ac36cf79c404b1faf5611f057bb71bc0e1",
49
+ "76cdade0b3d4caf0888f60318a5cbca00f830a3b0bf37735fc64fdaeb67c34d3",
50
+ "1bf53c97869e1ea89bda19da64a9173d48fe4ec823e949e2c898f8abb3fbf457",
51
+ "1bf53c97869e1ea89bda19da64a9173d48fe4ec823e949e2c898f8abb3fbf457",
52
+ "3d7f973fab8f4a19c0a3e59efe970ed7bd55a1cb795752d9cbe3c19e8a7d81ec"
53
  ]
54
 
55
+ const banned = [
56
+ "8a05d4869d9d6ce388c6cd2db13ca12b88097b90f9be027d5ffaaa467c7a6e5e",
57
+ "0c475212a608138244c5fc150b1563e5ef79c516234fd78dcd5993f726c359a0",
58
+ "df17388805f99f2ff3e5ae97a0f55e5c927eb47f17ca65822bf8c88f02bac3dd",
59
+ "86c3355d1bd581cdf7306729d8dd0ee9b7a317b9cfd6d7a6f5fad9c0dafe2167",
60
+ "23a2484cd420c9ffbfcc2c0075a9b330664450ced1fc64ab6a65e278086b8c6e",
61
+ "fb4cabe709b62eea1b4cc0030c76f5e4a43ee677ce19124e8e7bafa86c78ab66",
62
+ "d99c26daee85f7dc81c46c061a5874cff7179ed72d884d2316d664d36ffe7ab5",
63
+ "b93c38af5aa221d76c60ee3eb762efee0cdb0daf29ceb235b7dda6d46c06490d",
64
+ "8cf6c8765dc757319461dd9a785e77c201b8e5a604d36b817cd987c6a5e62500",
65
+ "f4a1cb290745717f86c3cee30fc324c0d80a9945fcbc7bbeb010579f58792f1e",
66
+ "7c87c47c42fc983119551342be9ddd5b32e530c0504ccdbbaa1e12b1d9f1bbcb",
67
+ "d04fad4f21d030da7a1301afbf480ef6246eb7bbf0f26e31865b2e015a25f747",
68
+ "d685ff22fb9da01ee949db212770729603989850864ef7a7085e1f086cfa7deb",
69
+ "533b90588d9ccf7967da54691f575e9fd4926c6e0b5fd94a47b932bcea270bee",
70
+ "9c2d61f28f5bb7f3f1dc9122be64cda8a428b46ce68b70120da4c41dba96ba4c",
71
+ "5d4b1a3eebe64dfa631d0e3b084bd96ee9364c3669269f838ca17a4900276264",
72
+ "d56f56413b9679fc0820a2c0237224ded8554c61fab8959c174123c8b68ba029",
73
+ "323a9ab60739726070d615ff3a05d7ff6bb6e3c4dd9ff16ce24f253ecd7b8851",
74
+ "975c6739de7d4999db15972f707f5f4e95649275f1c0c48e895b8c537e8638ec",
75
+ "67ee26eb9e1c1c7124797321b02bca90a19c18171782917cd4a487b722484dce",
76
+ "6df5aa7b72a4e6e3fb726489ff1437daa5752047507f4da912680b1d6647c7d6",
77
+ "b0864805364359e8c5810c233b1bf2c74dedce9055ae5f7680ba05b4e39db8e2",
78
+ "a8f841472ecffdd6266151148320c8e36847a24ead9d3338e0313b075c16649d",
79
+ "f9b127cd90e85b0ff68dd220361671663f0154b2b827f1f7ea797b020ca0018c",
80
+ "d5c20e9a1ecf01c82da24c514d867498b3e5f522adc1523ce29404a6563641d5",
81
+ "241022b49d7c0aba24a61eea1137a804f36e4bcb47af42950275baac9b4e7aac",
82
+ "fc99a70e17b6c86ef1b537654b0f50353567a7b59912c3ba955f3fca4d1ea696",
83
+ "255306e968009003d295cb2a7256f27bfcdb5d1743bf4d9f2aa4b8adf1a7734d",
84
+ "048c7b709763dd9c43794d241c369f0abcb079d546ddcbbba9968a1ed1da7ed7",
85
+ "520cbfeef3e4c405d79478eedccb97a4d476be585626dd2b1c53292797491bc7",
86
+ "f9f28a7ae7e8b1719b350a04dc087a4b8e33478d109ceeef6ba892b32d1105c9",
87
+ "d177f1bfe603647ef4c1c0e6f1a7172081fb9bbc2ea859705949f2c5aa5d4f22",
88
+ "302feef2c09247fbd23789581f7f5e2219f88ae0a937880954938573c2a52a84",
89
+ "99edd6f57b864873835f16f19c805dd94bed9da8967b84e3a62782f106d9ebcc",
90
+ "e75e5f01dcd8351c9553e89558085bd68e6feb295dee5d8da0c9b43ee303ce36",
91
+ "135e52a026aea9d2e12de358a85e05cf21121a18269269b7c62678c3bc846f5b",
92
+ "28e5b2d3eb5f1ef4cc7b570878b03acf303a6ca4ca95893591e0fb943b0beab0",
93
+ "a26b26340f8d0363633490556d20bcc250726d10e1431eb8c22d6b1ff3f2b14a",
94
+ "27e4ddde96ec6a1dbe1cf12d79448b3e72f144944c15b299629542d1b65fbabf",
95
+ "efd9c0a391ee93251046a58326d1b21b33fe21d71a3fb1855b9048ade53df77c",
96
+ "6d505fcce416c26a606878aab4d249a034ba2a9846cb1f883e0f9e3fb76ba6da",
97
+ "3a37b8a1b72f9bca51233536d50f9c8d33a787434684787871e0049c82347cda",
98
+ "16f9b451184a7c3148344c7d0315f5312ca20553d2271912ecaad91810d977e6",
99
+ "7406537eb74d1885bd05e191228de313b13702a64d90ae1736c6377b25ab579a",
100
+ "7e4d1395ae18980015cab16c85ffa20b4cb90a2db594126e893d0f7ac6eecaa8",
101
+ "ba813ee6c25698f0f68a07121d38bb47c9aa404c1ab0a6e767595cb75e1747b8",
102
+ "6586c93f3ece83e01ecc1eb84a7711e7975826a388d478a009468ea0ed9dc03e",
103
+ "8960174c74d86e03ae88fb6774580170e49952f2286d960be08c556bbd0dda95",
104
+ "4d611454369aa1a4e2b7eed1734fac5d480f08fb86b87a162967e416370f2a8e",
105
+ "59d48440f85eabf565fe8d3bc6b973ba64c70df3b36b0511e0e67ceca91762b3",
106
+ "cd926926e2af74e43d1a6a420a7e1933b78662320477a3c018b2711d8765e339",
107
+ "80e90057df6a59823f51aafac36ed5bc4e5ac26d675d9c1467501590c82f12d4",
108
+ "a9cf28b869b70e258adde5639a048f866ec86f8f3f3d53bfc960b86aa6da9239",
109
+ "cc2adbf8ac0cddeefa304d7b20f14a7e047a4b2299cc5e8f898f5c59660bd964",
110
+ "92a150a46146e9d3f84899cf15e12514af684e7ee18d7add782ddd4f4a15ef18",
111
+ "d9b2e84ef6dc0ce449357d52c9095f69b173a1b848ea2921199d33b0ec10024a",
112
+ "a9329a7e4d367a0135c1ca86c6ce5ecabcc26529235229d71b6bf991f7689e21",
113
+ "8f160c6fd8ccc3fb2a371a4b52748f0bd030766627c4322e2911fe82f6b10497",
114
+ "620e96eae4f3e88cbe0770292b33724c5df3866d83f39df6380441f7271c80e2",
115
+ "cafa3481fa3c45ed1e55cd0129c12b477eeab5aa3d6da20cae6d6292f19b0e6d",
116
+ "be07994e9a83aa3689e79b6e96123676ccc4fa29f523c28c750c6d60505531ee",
117
+ "f6498069768cd3aa79b2b0c91879694f05a259c8ee4a6bb343f0435f74eb1b53",
118
+ "c9b6b26cb3a694eb78fcac0a14ad18d46d50907186a9add41022d31d191b2b65"
119
+ ]
120
+
121
+ const young = [
122
+ "ffdf66787b4a33b78b18c18822e334cfe2c8406caf442851deef451bd43140a1",
123
+ "858f22219afc4b32a7ba9a27a213d7f495e77c3cceed8147eae5282bf3e23d39",
124
+ "8c3c46df84ace3d58d4ce0fbc513017986b33c6002ae369d9f7dd1f892a898cb",
125
+ "66caa22b9483fdf026ce67de61067d81535a7c9b3169cbc5c2a455ac8dcc7bec",
126
+ "76893047b1eff9fadc7be07b13adb5aaed9c73bcdeea46ee07098605e2c7ff76",
127
+ "526cb848754e2baaa17376a5693d90ba3f69f71fd2a866f22876ac8a075849a7",
128
+ "f59c38e31d0f64dc1bfcdf34451723bc1a65570e209e5496c8d1d7f6d3d649db",
129
+ "e013a67e275c62c1402ccbbb11ad14afb8b8a82318a44c07d67599ed5ac874de",
130
+ "3bef34219fb07f867ecbff4d6748f598d6cc0761e17dd0d431ee1f4ec3281374",
131
+ "8211bf5f613fac06cd5d074d34c16dfacc9367c8afaa6ad3aff99d145e5221be"
132
+ ]
133
+
134
+ const getFingerprint = (word: string) => {
135
+ return computeSecretFingerprint(
136
+ word.toLocaleLowerCase().replaceAll(/[^a-zA-Z0-9]/gi, "")
137
+ )
138
+ }
139
+
140
+ const encode = (list: string[]) => {
141
+ console.log(JSON.stringify(
142
+ list.sort((a, b) => (b.length - a.length))
143
+ .map(item => getFingerprint(item)), null, 2))
144
+ }
145
+
146
+ // encode([ "badword" ])
147
+
148
  export const filterOutBadWords = (sentence: string) => {
149
+ if (process.env.ENABLE_CENSORSHIP !== "true") { return sentence }
150
+
151
+ let requireCensorship = false
152
+
153
+ const words = sentence.replaceAll(/[^a-zA-Z0-9]/gi, " ").replaceAll(/\s+/gi, " ").trim().split(" ")
154
+
155
+ const sanitized = words.map(word => {
156
+ const fingerprint = getFingerprint(word)
157
+
158
+ let result: string = word
159
+ // some users want to play it smart and bypass our system so let's play too
160
+ if (chickens.includes(fingerprint)) {
161
+ result = "large chicken"
162
+ } else if (ducks.includes(fingerprint)) {
163
+ result = "big duck"
164
+ } else if (cats.includes(fingerprint)) {
165
+ result = "cat"
166
+ } else if (roasted.includes(fingerprint)) {
167
+ result = "roasted chicken"
168
+ } else if (young.includes(fingerprint)) {
169
+ result = "adult"
170
+ } else if (banned.includes(fingerprint)) {
171
+ result = "_BANNED_"
172
+ }
173
+
174
+ if (result !== word) {
175
+ requireCensorship = true
176
+ }
177
+ return result
178
+ }).filter(item => item !== "_BANNED_").join(" ")
179
+
180
+ // if the user didn't try to use a bad word, we leave it untouched
181
+ // he words array has been degraded by the replace operation, but it removes commas etc which isn't great
182
+ // so if the request was genuine and SFW, it's best to return the original prompt
183
+ return requireCensorship ? sanitized : sentence
184
  }
src/app/engine/render.ts CHANGED
@@ -1,6 +1,7 @@
1
  "use server"
2
 
3
- import Replicate, { Prediction } from "replicate"
 
4
 
5
  import { RenderRequest, RenderedScene, RenderingEngine } from "@/types"
6
  import { generateSeed } from "@/lib/generateSeed"
@@ -8,13 +9,17 @@ import { sleep } from "@/lib/sleep"
8
 
9
  const renderingEngine = `${process.env.RENDERING_ENGINE || ""}` as RenderingEngine
10
 
11
- const replicateToken = `${process.env.REPLICATE_API_TOKEN || ""}`
12
- const replicateModel = `${process.env.REPLICATE_API_MODEL || ""}`
13
- const replicateModelVersion = `${process.env.REPLICATE_API_MODEL_VERSION || ""}`
 
14
 
15
- // note: there is no / at the end in the variable
16
- // so we have to add it ourselves if needed
17
- const apiUrl = process.env.VIDEOCHAIN_API_URL
 
 
 
18
 
19
  export async function newRender({
20
  prompt,
@@ -27,10 +32,10 @@ export async function newRender({
27
  width: number
28
  height: number
29
  }) {
30
- // console.log(`newRender(${prompt})`)
31
  if (!prompt) {
32
- console.error(`cannot call the rendering API without a prompt, aborting..`)
33
- throw new Error(`cannot call the rendering API without a prompt, aborting..`)
 
34
  }
35
 
36
  let defaulResult: RenderedScene = {
@@ -61,12 +66,23 @@ export async function newRender({
61
  const seed = generateSeed()
62
  const prediction = await replicate.predictions.create({
63
  version: replicateModelVersion,
64
- input: { prompt, seed }
 
 
 
 
 
 
 
 
 
 
 
65
  })
66
 
67
  // console.log("prediction:", prediction)
68
 
69
- // no need to reply straight away: good things take time
70
  // also our friends at Replicate won't like it if we spam them with requests
71
  await sleep(4000)
72
 
@@ -79,14 +95,88 @@ export async function newRender({
79
  maskUrl: "",
80
  segments: []
81
  } as RenderedScene
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  } else {
83
- // console.log(`calling POST ${apiUrl}/render with prompt: ${prompt}`)
84
- const res = await fetch(`${apiUrl}/render`, {
85
  method: "POST",
86
  headers: {
87
  Accept: "application/json",
88
  "Content-Type": "application/json",
89
- Authorization: `Bearer ${process.env.VIDEOCHAIN_API_TOKEN}`,
90
  },
91
  body: JSON.stringify({
92
  prompt,
@@ -114,14 +204,7 @@ export async function newRender({
114
  // next: { revalidate: 1 }
115
  })
116
 
117
-
118
- // console.log("res:", res)
119
- // The return value is *not* serialized
120
- // You can return Date, Map, Set, etc.
121
-
122
- // Recommendation: handle errors
123
  if (res.status !== 200) {
124
- // This will activate the closest `error.js` Error Boundary
125
  throw new Error('Failed to fetch data')
126
  }
127
 
@@ -136,8 +219,9 @@ export async function newRender({
136
 
137
  export async function getRender(renderId: string) {
138
  if (!renderId) {
139
- console.error(`cannot call the rendering API without a renderId, aborting..`)
140
- throw new Error(`cannot call the rendering API without a renderId, aborting..`)
 
141
  }
142
 
143
  let defaulResult: RenderedScene = {
@@ -153,24 +237,15 @@ export async function getRender(renderId: string) {
153
  try {
154
  if (renderingEngine === "REPLICATE") {
155
  if (!replicateToken) {
156
- throw new Error(`you need to configure your REPLICATE_API_TOKEN in order to use the REPLICATE rendering engine`)
157
  }
158
  if (!replicateModel) {
159
- throw new Error(`you need to configure your REPLICATE_API_MODEL in order to use the REPLICATE rendering engine`)
160
  }
161
 
162
- // const replicate = new Replicate({ auth: replicateToken })
163
-
164
- // console.log("Calling replicate..")
165
- // const prediction = await replicate.predictions.get(renderId)
166
- // console.log("Prediction:", prediction)
167
-
168
- // console.log(`calling GET https://api.replicate.com/v1/predictions/${renderId}`)
169
  const res = await fetch(`https://api.replicate.com/v1/predictions/${renderId}`, {
170
  method: "GET",
171
  headers: {
172
- // Accept: "application/json",
173
- // "Content-Type": "application/json",
174
  Authorization: `Token ${replicateToken}`,
175
  },
176
  cache: 'no-store',
@@ -178,10 +253,6 @@ export async function getRender(renderId: string) {
178
  // next: { revalidate: 1 }
179
  })
180
 
181
- // console.log("res:", res)
182
- // The return value is *not* serialized
183
- // You can return Date, Map, Set, etc.
184
-
185
  // Recommendation: handle errors
186
  if (res.status !== 200) {
187
  // This will activate the closest `error.js` Error Boundary
@@ -189,7 +260,6 @@ export async function getRender(renderId: string) {
189
  }
190
 
191
  const response = (await res.json()) as any
192
- // console.log("response:", response)
193
 
194
  return {
195
  renderId,
@@ -202,41 +272,31 @@ export async function getRender(renderId: string) {
202
  } as RenderedScene
203
  } else {
204
  // console.log(`calling GET ${apiUrl}/render with renderId: ${renderId}`)
205
- const res = await fetch(`${apiUrl}/render/${renderId}`, {
206
  method: "GET",
207
  headers: {
208
  Accept: "application/json",
209
  "Content-Type": "application/json",
210
- Authorization: `Bearer ${process.env.VIDEOCHAIN_API_TOKEN}`,
211
  },
212
  cache: 'no-store',
213
  // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
214
  // next: { revalidate: 1 }
215
  })
216
-
217
- // console.log("res:", res)
218
- // The return value is *not* serialized
219
- // You can return Date, Map, Set, etc.
220
 
221
- // Recommendation: handle errors
222
  if (res.status !== 200) {
223
- // This will activate the closest `error.js` Error Boundary
224
  throw new Error('Failed to fetch data')
225
  }
226
 
227
  const response = (await res.json()) as RenderedScene
228
- // console.log("response:", response)
229
  return response
230
  }
231
  } catch (err) {
232
  console.error(err)
233
  defaulResult.status = "error"
234
  defaulResult.error = `${err}`
235
- // Gorgon.clear(cacheKey)
236
  return defaulResult
237
  }
238
-
239
- // }, cacheDurationInSec * 1000)
240
  }
241
 
242
  export async function upscaleImage(image: string): Promise<{
@@ -244,8 +304,9 @@ export async function upscaleImage(image: string): Promise<{
244
  error: string
245
  }> {
246
  if (!image) {
247
- console.error(`cannot call the rendering API without an image, aborting..`)
248
- throw new Error(`cannot call the rendering API without an image, aborting..`)
 
249
  }
250
 
251
  let defaulResult = {
@@ -255,12 +316,12 @@ export async function upscaleImage(image: string): Promise<{
255
 
256
  try {
257
  // console.log(`calling GET ${apiUrl}/render with renderId: ${renderId}`)
258
- const res = await fetch(`${apiUrl}/upscale`, {
259
  method: "POST",
260
  headers: {
261
  Accept: "application/json",
262
  "Content-Type": "application/json",
263
- Authorization: `Bearer ${process.env.VIDEOCHAIN_API_TOKEN}`,
264
  },
265
  cache: 'no-store',
266
  body: JSON.stringify({ image, factor: 3 })
@@ -268,13 +329,7 @@ export async function upscaleImage(image: string): Promise<{
268
  // next: { revalidate: 1 }
269
  })
270
 
271
- // console.log("res:", res)
272
- // The return value is *not* serialized
273
- // You can return Date, Map, Set, etc.
274
-
275
- // Recommendation: handle errors
276
  if (res.status !== 200) {
277
- // This will activate the closest `error.js` Error Boundary
278
  throw new Error('Failed to fetch data')
279
  }
280
 
@@ -282,13 +337,9 @@ export async function upscaleImage(image: string): Promise<{
282
  assetUrl: string
283
  error: string
284
  }
285
- // console.log("response:", response)
286
  return response
287
  } catch (err) {
288
  console.error(err)
289
- // Gorgon.clear(cacheKey)
290
  return defaulResult
291
  }
292
-
293
- // }, cacheDurationInSec * 1000)
294
  }
 
1
  "use server"
2
 
3
+ import { v4 as uuidv4 } from "uuid"
4
+ import Replicate from "replicate"
5
 
6
  import { RenderRequest, RenderedScene, RenderingEngine } from "@/types"
7
  import { generateSeed } from "@/lib/generateSeed"
 
9
 
10
  const renderingEngine = `${process.env.RENDERING_ENGINE || ""}` as RenderingEngine
11
 
12
+ // TODO: we should split Hugging Face and Replicate backends into separate files
13
+ const huggingFaceToken = `${process.env.AUTH_HF_API_TOKEN || ""}`
14
+ const huggingFaceInferenceEndpointUrl = `${process.env.RENDERING_HF_INFERENCE_ENDPOINT_URL || ""}`
15
+ const huggingFaceInferenceApiModel = `${process.env.RENDERING_HF_INFERENCE_API_MODEL || ""}`
16
 
17
+ const replicateToken = `${process.env.AUTH_REPLICATE_API_TOKEN || ""}`
18
+ const replicateModel = `${process.env.RENDERING_REPLICATE_API_MODEL || ""}`
19
+ const replicateModelVersion = `${process.env.RENDERING_REPLICATE_API_MODEL_VERSION || ""}`
20
+
21
+ const videochainToken = `${process.env.AUTH_VIDEOCHAIN_API_TOKEN || ""}`
22
+ const videochainApiUrl = `${process.env.RENDERING_VIDEOCHAIN_API_URL || ""}`
23
 
24
  export async function newRender({
25
  prompt,
 
32
  width: number
33
  height: number
34
  }) {
 
35
  if (!prompt) {
36
+ const error = `cannot call the rendering API without a prompt, aborting..`
37
+ console.error(error)
38
+ throw new Error(error)
39
  }
40
 
41
  let defaulResult: RenderedScene = {
 
66
  const seed = generateSeed()
67
  const prediction = await replicate.predictions.create({
68
  version: replicateModelVersion,
69
+ input: {
70
+ prompt: [
71
+ "beautiful",
72
+ "intricate details",
73
+ prompt,
74
+ "award winning",
75
+ "high resolution"
76
+ ].join(", "),
77
+ width,
78
+ height,
79
+ seed
80
+ }
81
  })
82
 
83
  // console.log("prediction:", prediction)
84
 
85
+ // no need to reply straight away as images take time to generate, this isn't instantaneous
86
  // also our friends at Replicate won't like it if we spam them with requests
87
  await sleep(4000)
88
 
 
95
  maskUrl: "",
96
  segments: []
97
  } as RenderedScene
98
+ } if (renderingEngine === "INFERENCE_ENDPOINT" || renderingEngine === "INFERENCE_API") {
99
+ if (!huggingFaceToken) {
100
+ throw new Error(`you need to configure your HF_API_TOKEN in order to use the ${renderingEngine} rendering engine`)
101
+ }
102
+ if (renderingEngine === "INFERENCE_ENDPOINT" && !huggingFaceInferenceEndpointUrl) {
103
+ throw new Error(`you need to configure your RENDERING_HF_INFERENCE_ENDPOINT_URL in order to use the INFERENCE_ENDPOINT rendering engine`)
104
+ }
105
+ if (renderingEngine === "INFERENCE_API" && !huggingFaceInferenceApiModel) {
106
+ throw new Error(`you need to configure your RENDERING_HF_INFERENCE_API_MODEL in order to use the INFERENCE_API rendering engine`)
107
+ }
108
+
109
+ const url = renderingEngine === "INFERENCE_ENDPOINT"
110
+ ? huggingFaceInferenceEndpointUrl
111
+ : `https://api-inference.huggingface.co/models/${huggingFaceInferenceApiModel}`
112
+
113
+ /*
114
+ console.log(`calling ${url} with params: `, {
115
+ num_inference_steps: 25,
116
+ guidance_scale: 8,
117
+ width,
118
+ height,
119
+ })
120
+ */
121
+
122
+ const res = await fetch(url, {
123
+ method: "POST",
124
+ headers: {
125
+ "Content-Type": "application/json",
126
+ Authorization: `Bearer ${huggingFaceToken}`,
127
+ },
128
+ body: JSON.stringify({
129
+ inputs: [
130
+ "beautiful",
131
+ "intricate details",
132
+ prompt,
133
+ "award winning",
134
+ "high resolution"
135
+ ].join(", "),
136
+ parameters: {
137
+ num_inference_steps: 25,
138
+ guidance_scale: 8,
139
+ width,
140
+ height,
141
+ },
142
+ use_cache: false,
143
+ }),
144
+ cache: "no-store",
145
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
146
+ // next: { revalidate: 1 }
147
+ })
148
+
149
+
150
+ // Recommendation: handle errors
151
+ if (res.status !== 200) {
152
+ const content = await res.text()
153
+ console.error(content)
154
+ // This will activate the closest `error.js` Error Boundary
155
+ throw new Error('Failed to fetch data')
156
+ }
157
+
158
+ const blob = await res.arrayBuffer()
159
+
160
+ const contentType = res.headers.get('content-type')
161
+
162
+ const assetUrl = `data:${contentType};base64,${Buffer.from(blob).toString('base64')}`
163
+
164
+ return {
165
+ renderId: uuidv4(),
166
+ status: "completed",
167
+ assetUrl,
168
+ alt: prompt,
169
+ error: "",
170
+ maskUrl: "",
171
+ segments: []
172
+ } as RenderedScene
173
  } else {
174
+ const res = await fetch(`${videochainApiUrl}/render`, {
 
175
  method: "POST",
176
  headers: {
177
  Accept: "application/json",
178
  "Content-Type": "application/json",
179
+ Authorization: `Bearer ${videochainToken}`,
180
  },
181
  body: JSON.stringify({
182
  prompt,
 
204
  // next: { revalidate: 1 }
205
  })
206
 
 
 
 
 
 
 
207
  if (res.status !== 200) {
 
208
  throw new Error('Failed to fetch data')
209
  }
210
 
 
219
 
220
  export async function getRender(renderId: string) {
221
  if (!renderId) {
222
+ const error = `cannot call the rendering API without a renderId, aborting..`
223
+ console.error(error)
224
+ throw new Error(error)
225
  }
226
 
227
  let defaulResult: RenderedScene = {
 
237
  try {
238
  if (renderingEngine === "REPLICATE") {
239
  if (!replicateToken) {
240
+ throw new Error(`you need to configure your AUTH_REPLICATE_API_TOKEN in order to use the REPLICATE rendering engine`)
241
  }
242
  if (!replicateModel) {
243
+ throw new Error(`you need to configure your RENDERING_REPLICATE_API_MODEL in order to use the REPLICATE rendering engine`)
244
  }
245
 
 
 
 
 
 
 
 
246
  const res = await fetch(`https://api.replicate.com/v1/predictions/${renderId}`, {
247
  method: "GET",
248
  headers: {
 
 
249
  Authorization: `Token ${replicateToken}`,
250
  },
251
  cache: 'no-store',
 
253
  // next: { revalidate: 1 }
254
  })
255
 
 
 
 
 
256
  // Recommendation: handle errors
257
  if (res.status !== 200) {
258
  // This will activate the closest `error.js` Error Boundary
 
260
  }
261
 
262
  const response = (await res.json()) as any
 
263
 
264
  return {
265
  renderId,
 
272
  } as RenderedScene
273
  } else {
274
  // console.log(`calling GET ${apiUrl}/render with renderId: ${renderId}`)
275
+ const res = await fetch(`${videochainApiUrl}/render/${renderId}`, {
276
  method: "GET",
277
  headers: {
278
  Accept: "application/json",
279
  "Content-Type": "application/json",
280
+ Authorization: `Bearer ${videochainToken}`,
281
  },
282
  cache: 'no-store',
283
  // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
284
  // next: { revalidate: 1 }
285
  })
 
 
 
 
286
 
 
287
  if (res.status !== 200) {
 
288
  throw new Error('Failed to fetch data')
289
  }
290
 
291
  const response = (await res.json()) as RenderedScene
 
292
  return response
293
  }
294
  } catch (err) {
295
  console.error(err)
296
  defaulResult.status = "error"
297
  defaulResult.error = `${err}`
 
298
  return defaulResult
299
  }
 
 
300
  }
301
 
302
  export async function upscaleImage(image: string): Promise<{
 
304
  error: string
305
  }> {
306
  if (!image) {
307
+ const error = `cannot call the rendering API without an image, aborting..`
308
+ console.error(error)
309
+ throw new Error(error)
310
  }
311
 
312
  let defaulResult = {
 
316
 
317
  try {
318
  // console.log(`calling GET ${apiUrl}/render with renderId: ${renderId}`)
319
+ const res = await fetch(`${videochainApiUrl}/upscale`, {
320
  method: "POST",
321
  headers: {
322
  Accept: "application/json",
323
  "Content-Type": "application/json",
324
+ Authorization: `Bearer ${videochainToken}`,
325
  },
326
  cache: 'no-store',
327
  body: JSON.stringify({ image, factor: 3 })
 
329
  // next: { revalidate: 1 }
330
  })
331
 
 
 
 
 
 
332
  if (res.status !== 200) {
 
333
  throw new Error('Failed to fetch data')
334
  }
335
 
 
337
  assetUrl: string
338
  error: string
339
  }
 
340
  return response
341
  } catch (err) {
342
  console.error(err)
 
343
  return defaulResult
344
  }
 
 
345
  }
src/app/interface/bottom-bar/index.tsx CHANGED
@@ -101,11 +101,13 @@ ${uploadUrl
101
  `print:hidden`,
102
  `fixed bottom-2 md:bottom-4 left-2 right-0 md:left-3 md:right-1`,
103
  `flex flex-row`,
104
- `justify-between`
 
105
  )}>
106
  <div className={cn(
107
  `flex flex-row`,
108
  `items-end`,
 
109
  `animation-all duration-300 ease-in-out`,
110
  isGeneratingStory ? `scale-0 opacity-0` : ``,
111
  `space-x-3`,
@@ -115,12 +117,16 @@ ${uploadUrl
115
  </div>
116
  <div className={cn(
117
  `flex flex-row`,
 
118
  `animation-all duration-300 ease-in-out`,
119
  isGeneratingStory ? `scale-0 opacity-0` : ``,
120
  `space-x-3`,
121
  `scale-[0.9]`
122
  )}>
123
  <div>
 
 
 
124
  <Button
125
  onClick={handleUpscale}
126
  disabled={!prompt?.length || remainingImages > 0 || isUpscaling || !Object.values(upscaleQueue).length}
@@ -129,6 +135,8 @@ ${uploadUrl
129
  ? `${allStatus.length - Object.values(upscaleQueue).length}/${allStatus.length} ⌛`
130
  : "Upscale"}
131
  </Button>
 
 
132
  </div>
133
  <div>
134
  <Button
@@ -152,6 +160,9 @@ ${uploadUrl
152
  </Button>
153
  </div>
154
  <div>
 
 
 
155
  <Button
156
  onClick={handleShare}
157
  disabled={!prompt?.length}
@@ -162,7 +173,9 @@ ${uploadUrl
162
  <span className="hidden md:inline">Share to community</span>
163
  <span className="inline md:hidden">Share</span>
164
  </div>
165
- </Button>
 
 
166
  </div>
167
  </div>
168
  </div>
 
101
  `print:hidden`,
102
  `fixed bottom-2 md:bottom-4 left-2 right-0 md:left-3 md:right-1`,
103
  `flex flex-row`,
104
+ `justify-between`,
105
+ `pointer-events-none`
106
  )}>
107
  <div className={cn(
108
  `flex flex-row`,
109
  `items-end`,
110
+ `pointer-events-auto`,
111
  `animation-all duration-300 ease-in-out`,
112
  isGeneratingStory ? `scale-0 opacity-0` : ``,
113
  `space-x-3`,
 
117
  </div>
118
  <div className={cn(
119
  `flex flex-row`,
120
+ `pointer-events-auto`,
121
  `animation-all duration-300 ease-in-out`,
122
  isGeneratingStory ? `scale-0 opacity-0` : ``,
123
  `space-x-3`,
124
  `scale-[0.9]`
125
  )}>
126
  <div>
127
+ {
128
+ // there is an issue, this env check doesn't work..
129
+ // process.env.NEXT_PUBLIC_CAN_UPSCALE === "true" ?
130
  <Button
131
  onClick={handleUpscale}
132
  disabled={!prompt?.length || remainingImages > 0 || isUpscaling || !Object.values(upscaleQueue).length}
 
135
  ? `${allStatus.length - Object.values(upscaleQueue).length}/${allStatus.length} ⌛`
136
  : "Upscale"}
137
  </Button>
138
+ // : null
139
+ }
140
  </div>
141
  <div>
142
  <Button
 
160
  </Button>
161
  </div>
162
  <div>
163
+ {
164
+ // there is an issue, this env check doesn't work..
165
+ // process.env.NEXT_PUBLIC_ENABLE_COMMUNITY_SHARING === "true" ?
166
  <Button
167
  onClick={handleShare}
168
  disabled={!prompt?.length}
 
173
  <span className="hidden md:inline">Share to community</span>
174
  <span className="inline md:hidden">Share</span>
175
  </div>
176
+ </Button>
177
+ //: null
178
+ }
179
  </div>
180
  </div>
181
  </div>
src/app/interface/panel/index.tsx CHANGED
@@ -1,7 +1,7 @@
1
  "use client"
2
 
3
  import { useEffect, useRef, useState, useTransition } from "react"
4
- // import AutoSizer from "react-virtualized-auto-sizer"
5
 
6
  import { RenderedScene } from "@/types"
7
 
@@ -12,9 +12,6 @@ import { cn } from "@/lib/utils"
12
  import { getInitialRenderedScene } from "@/lib/getInitialRenderedScene"
13
  import { Progress } from "@/app/interface/progress"
14
 
15
- // import { see } from "@/app/engine/caption"
16
- // import { replaceTextInSpeechBubbles } from "@/lib/replaceTextInSpeechBubbles"
17
-
18
  export function Panel({
19
  panel,
20
  className = "",
@@ -28,13 +25,13 @@ export function Panel({
28
  }) {
29
  const panelId = `${panel}`
30
 
 
31
  const ref = useRef<HTMLImageElement>(null)
32
  const font = useStore(state => state.font)
33
  const preset = useStore(state => state.preset)
34
 
35
  const setGeneratingImages = useStore(state => state.setGeneratingImages)
36
 
37
- const [imageWithText, setImageWithText] = useState("")
38
  const panels = useStore(state => state.panels)
39
  const prompt = panels[panel] || ""
40
 
@@ -59,11 +56,12 @@ export function Panel({
59
 
60
  const timeoutRef = useRef<any>(null)
61
 
62
- const delay = 3000 + (1000 * panel)
63
 
64
- // since this run in its own loop, we need to use references everywhere
65
- // but perhaps this could be refactored
66
- useEffect(() => {
 
67
  // console.log("Panel prompt: "+ prompt)
68
  if (!prompt?.length) { return }
69
 
@@ -76,36 +74,47 @@ export function Panel({
76
  setTimeout(() => {
77
  startTransition(async () => {
78
 
79
- // console.log(`Loading panel ${panel}..`)
80
-
81
- let newRendered: RenderedScene
82
- try {
83
- newRendered = await newRender({ prompt, width, height })
84
- } catch (err) {
85
- // "Failed to load the panel! Don't worry, we are retrying..")
86
- newRendered = await newRender({ prompt, width, height })
87
- }
88
 
89
- if (newRendered) {
90
- // console.log("newRendered:", newRendered)
91
- setRendered(panelId, newRendered)
92
-
93
- // but we are still loading!
94
- } else {
95
- setRendered(panelId, {
96
- renderId: "",
97
- status: "pending",
98
- assetUrl: "",
99
- alt: "",
100
- maskUrl: "",
101
- error: "",
102
- segments: []
103
- })
104
- setGeneratingImages(panelId, false)
105
- return
106
- }
107
- })
108
- }, 2000 * panel)
 
 
 
 
 
 
 
 
 
 
 
109
  }, [prompt, width, height])
110
 
111
 
@@ -208,27 +217,10 @@ export function Panel({
208
  `print:border-[1.5px] print:shadow-none`,
209
  )
210
 
211
-
212
- /*
213
- text detection (doesn't work)
214
- useEffect(() => {
215
- const fn = async () => {
216
- if (!rendered.assetUrl || !ref.current) {
217
- return
218
- }
219
-
220
- const result = await replaceTextInSpeechBubbles(
221
- rendered.assetUrl,
222
- "Lorem ipsum dolor sit amet, dolor ipsum. Sit amet? Ipsum! Dolor!!!"
223
- )
224
- if (result) {
225
- setImageWithText(result)
226
- }
227
- }
228
- fn()
229
-
230
- }, [rendered.assetUrl, ref.current])
231
- */
232
 
233
  if (prompt && !rendered.assetUrl) {
234
  return (
@@ -247,9 +239,11 @@ export function Panel({
247
  frameClassName,
248
  { "grayscale": preset.color === "grayscale" },
249
  className
250
- )}>
 
 
 
251
  <div className={cn(
252
- ``,
253
  `bg-stone-50`,
254
  `border-stone-800`,
255
  `transition-all duration-200 ease-in-out`,
@@ -289,7 +283,7 @@ export function Panel({
289
  {rendered.assetUrl &&
290
  <img
291
  ref={ref}
292
- src={imageWithText || rendered.assetUrl}
293
  width={width}
294
  height={height}
295
  alt={rendered.alt}
@@ -298,6 +292,31 @@ export function Panel({
298
  // showCaptions ? `-mt-11` : ''
299
  )}
300
  />}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  </div>
302
  )
303
  }
 
1
  "use client"
2
 
3
  import { useEffect, useRef, useState, useTransition } from "react"
4
+ import { RxReload } from "react-icons/rx"
5
 
6
  import { RenderedScene } from "@/types"
7
 
 
12
  import { getInitialRenderedScene } from "@/lib/getInitialRenderedScene"
13
  import { Progress } from "@/app/interface/progress"
14
 
 
 
 
15
  export function Panel({
16
  panel,
17
  className = "",
 
25
  }) {
26
  const panelId = `${panel}`
27
 
28
+ const [mouseOver, setMouseOver] = useState(false)
29
  const ref = useRef<HTMLImageElement>(null)
30
  const font = useStore(state => state.font)
31
  const preset = useStore(state => state.preset)
32
 
33
  const setGeneratingImages = useStore(state => state.setGeneratingImages)
34
 
 
35
  const panels = useStore(state => state.panels)
36
  const prompt = panels[panel] || ""
37
 
 
56
 
57
  const timeoutRef = useRef<any>(null)
58
 
59
+ const enableRateLimiter = `${process.env.NEXT_PUBLIC_ENABLE_RATE_LIMITER}` === "true"
60
 
61
+ const delay = enableRateLimiter ? (3000 + (1000 * panel)) : 1000
62
+
63
+
64
+ const startImageGeneration = ({ prompt, width, height }: { prompt: string, width: number, height: number}) => {
65
  // console.log("Panel prompt: "+ prompt)
66
  if (!prompt?.length) { return }
67
 
 
74
  setTimeout(() => {
75
  startTransition(async () => {
76
 
77
+ // console.log(`Loading panel ${panel}..`)
78
+
79
+ let newRendered: RenderedScene
80
+ try {
81
+ newRendered = await newRender({ prompt, width, height })
82
+ } catch (err) {
83
+ // "Failed to load the panel! Don't worry, we are retrying..")
84
+ newRendered = await newRender({ prompt, width, height })
85
+ }
86
 
87
+ if (newRendered) {
88
+ // console.log("newRendered:", newRendered)
89
+ setRendered(panelId, newRendered)
90
+
91
+ if (newRendered.status === "completed") {
92
+ setGeneratingImages(panelId, false)
93
+ addToUpscaleQueue(panelId, newRendered)
94
+ }
95
+
96
+ // but we are still loading!
97
+ } else {
98
+ setRendered(panelId, {
99
+ renderId: "",
100
+ status: "pending",
101
+ assetUrl: "",
102
+ alt: "",
103
+ maskUrl: "",
104
+ error: "",
105
+ segments: []
106
+ })
107
+ setGeneratingImages(panelId, false)
108
+ return
109
+ }
110
+ })
111
+ }, enableRateLimiter ? 2000 * panel : 0)
112
+ }
113
+
114
+ // since this run in its own loop, we need to use references everywhere
115
+ // but perhaps this could be refactored
116
+ useEffect(() => {
117
+ startImageGeneration({ prompt, width, height })
118
  }, [prompt, width, height])
119
 
120
 
 
217
  `print:border-[1.5px] print:shadow-none`,
218
  )
219
 
220
+ const handleReload = () => {
221
+ console.log(`Asked to reload panel ${panelId}`)
222
+ startImageGeneration({ prompt, width, height })
223
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
  if (prompt && !rendered.assetUrl) {
226
  return (
 
239
  frameClassName,
240
  { "grayscale": preset.color === "grayscale" },
241
  className
242
+ )}
243
+ onMouseEnter={() => setMouseOver(true)}
244
+ onMouseLeave={() => setMouseOver(false)}
245
+ >
246
  <div className={cn(
 
247
  `bg-stone-50`,
248
  `border-stone-800`,
249
  `transition-all duration-200 ease-in-out`,
 
283
  {rendered.assetUrl &&
284
  <img
285
  ref={ref}
286
+ src={rendered.assetUrl}
287
  width={width}
288
  height={height}
289
  alt={rendered.alt}
 
292
  // showCaptions ? `-mt-11` : ''
293
  )}
294
  />}
295
+ {
296
+ // there is an issue, this env check doesn't work..
297
+ // process.env.NEXT_PUBLIC_CAN_REDRAW === "true" ?
298
+ <div
299
+ className={cn(`relative -mt-14 ml-4`,)}>
300
+ <div className="flex flex-row">
301
+ <div
302
+ onClick={rendered.status === "completed" ? handleReload : undefined}
303
+ className={cn(
304
+ `bg-stone-100 rounded-lg`,
305
+ `flex flex-row space-x-2 items-center`,
306
+ `py-2 px-3 cursor-pointer`,
307
+ `transition-all duration-200 ease-in-out`,
308
+ rendered.status === "completed" ? "opacity-95" : "opacity-50",
309
+ mouseOver && rendered.assetUrl ? `scale-95 hover:scale-100 hover:opacity-100`: `scale-0`
310
+ )}>
311
+ <RxReload
312
+ className="w-5 h-5"
313
+ />
314
+ <span className="text-base">Redraw</span>
315
+ </div>
316
+ </div>
317
+ </div>
318
+ //: null
319
+ }
320
  </div>
321
  )
322
  }
src/app/interface/top-menu/index.tsx CHANGED
@@ -50,7 +50,7 @@ export function TopMenu() {
50
  const isGeneratingStory = useStore(state => state.isGeneratingStory)
51
  const atLeastOnePanelIsBusy = useStore(state => state.atLeastOnePanelIsBusy)
52
  const isBusy = isGeneratingStory || atLeastOnePanelIsBusy
53
-
54
  const searchParams = useSearchParams()
55
 
56
  const requestedPreset = (searchParams.get('preset') as PresetName) || defaultPreset
 
50
  const isGeneratingStory = useStore(state => state.isGeneratingStory)
51
  const atLeastOnePanelIsBusy = useStore(state => state.atLeastOnePanelIsBusy)
52
  const isBusy = isGeneratingStory || atLeastOnePanelIsBusy
53
+
54
  const searchParams = useSearchParams()
55
 
56
  const requestedPreset = (searchParams.get('preset') as PresetName) || defaultPreset
src/app/main.tsx CHANGED
@@ -38,6 +38,8 @@ export default function Main() {
38
  setWaitABitMore(false)
39
  setGeneratingStory(true)
40
 
 
 
41
  try {
42
 
43
  const llmResponse = await getStory({ preset, prompt })
@@ -72,7 +74,7 @@ export default function Main() {
72
  setTimeout(() => {
73
  setGeneratingStory(false)
74
  setWaitABitMore(false)
75
- }, 12000)
76
  }
77
  })
78
  }, [prompt, preset?.label]) // important: we need to react to preset changes too
 
38
  setWaitABitMore(false)
39
  setGeneratingStory(true)
40
 
41
+ const enableRateLimiter = `${process.env.NEXT_PUBLIC_ENABLE_RATE_LIMITER}` === "true"
42
+
43
  try {
44
 
45
  const llmResponse = await getStory({ preset, prompt })
 
74
  setTimeout(() => {
75
  setGeneratingStory(false)
76
  setWaitABitMore(false)
77
+ }, enableRateLimiter ? 12000 : 0)
78
  }
79
  })
80
  }, [prompt, preset?.label]) // important: we need to react to preset changes too
src/app/queries/getStory.ts CHANGED
@@ -37,15 +37,15 @@ export const getStory = async ({
37
  let result = ""
38
 
39
  try {
40
- result = await predict(query)
41
- if (!result.trim().length) {
42
  throw new Error("empty result!")
43
  }
44
  } catch (err) {
45
  console.log(`prediction of the story failed, trying again..`)
46
  try {
47
- result = await predict(query+".")
48
- if (!result.trim().length) {
49
  throw new Error("empty result!")
50
  }
51
  } catch (err) {
 
37
  let result = ""
38
 
39
  try {
40
+ result = `${await predict(query) || ""}`.trim()
41
+ if (!result.length) {
42
  throw new Error("empty result!")
43
  }
44
  } catch (err) {
45
  console.log(`prediction of the story failed, trying again..`)
46
  try {
47
+ result = `${await predict(query+".") || ""}`.trim()
48
+ if (!result.length) {
49
  throw new Error("empty result!")
50
  }
51
  } catch (err) {
src/app/queries/getStyle.ts CHANGED
@@ -30,15 +30,15 @@ export const getStory = async ({
30
 
31
  let result = ""
32
  try {
33
- result = await predict(query)
34
- if (!result.trim().length) {
35
  throw new Error("empty result!")
36
  }
37
  } catch (err) {
38
  console.log(`prediction of the story failed, trying again..`)
39
  try {
40
- result = await predict(query+".")
41
- if (!result.trim().length) {
42
  throw new Error("empty result!")
43
  }
44
  } catch (err) {
 
30
 
31
  let result = ""
32
  try {
33
+ result = `${await predict(query) || ""}`.trim()
34
+ if (!result.length) {
35
  throw new Error("empty result!")
36
  }
37
  } catch (err) {
38
  console.log(`prediction of the story failed, trying again..`)
39
  try {
40
+ result = `${await predict(query+".") || ""}`.trim()
41
+ if (!result.length) {
42
  throw new Error("empty result!")
43
  }
44
  } catch (err) {
src/app/queries/predict.ts CHANGED
@@ -1,140 +1,9 @@
1
  "use server"
2
 
3
- import { HfInference, HfInferenceEndpoint } from "@huggingface/inference"
4
-
5
- import type { ChatCompletionMessage } from "openai/resources/chat"
6
  import { LLMEngine } from "@/types"
7
- import OpenAI from "openai"
8
-
9
- const hf = new HfInference(process.env.HF_API_TOKEN)
10
 
11
-
12
- // note: we always try "inference endpoint" first
13
  const llmEngine = `${process.env.LLM_ENGINE || ""}` as LLMEngine
14
- const inferenceEndpoint = `${process.env.HF_INFERENCE_ENDPOINT_URL || ""}`
15
- const inferenceModel = `${process.env.HF_INFERENCE_API_MODEL || ""}`
16
- const openaiApiKey = `${process.env.OPENAI_API_KEY || ""}`
17
-
18
- let hfie: HfInferenceEndpoint
19
-
20
- switch (llmEngine) {
21
- case "INFERENCE_ENDPOINT":
22
- if (inferenceEndpoint) {
23
- console.log("Using a custom HF Inference Endpoint")
24
- hfie = hf.endpoint(inferenceEndpoint)
25
- } else {
26
- const error = "No Inference Endpoint URL defined"
27
- console.error(error)
28
- throw new Error(error)
29
- }
30
- break;
31
-
32
- case "INFERENCE_API":
33
- if (inferenceModel) {
34
- console.log("Using an HF Inference API Model")
35
- } else {
36
- const error = "No Inference API model defined"
37
- console.error(error)
38
- throw new Error(error)
39
- }
40
- break;
41
-
42
- case "OPENAI":
43
- if (openaiApiKey) {
44
- console.log("Using an OpenAI API Key")
45
- } else {
46
- const error = "No OpenAI API key defined"
47
- console.error(error)
48
- throw new Error(error)
49
- }
50
- break;
51
-
52
- default:
53
- const error = "No Inference Endpoint URL or Inference API Model defined"
54
- console.error(error)
55
- throw new Error(error)
56
- }
57
-
58
- export async function predict(inputs: string) {
59
-
60
- console.log(`predict: `, inputs)
61
-
62
- if (llmEngine==="OPENAI") {
63
- return predictWithOpenAI(inputs)
64
- }
65
-
66
- const api = llmEngine ==="INFERENCE_ENDPOINT" ? hfie : hf
67
-
68
- let instructions = ""
69
- try {
70
- for await (const output of api.textGenerationStream({
71
- model: llmEngine ==="INFERENCE_ENDPOINT" ? undefined : (inferenceModel || undefined),
72
- inputs,
73
- parameters: {
74
- do_sample: true,
75
- // we don't require a lot of token for our task
76
- // but to be safe, let's count ~110 tokens per panel
77
- max_new_tokens: 450, // 1150,
78
- return_full_text: false,
79
- }
80
- })) {
81
- instructions += output.token.text
82
- process.stdout.write(output.token.text)
83
- if (
84
- instructions.includes("</s>") ||
85
- instructions.includes("<s>") ||
86
- instructions.includes("[INST]") ||
87
- instructions.includes("[/INST]") ||
88
- instructions.includes("<SYS>") ||
89
- instructions.includes("</SYS>") ||
90
- instructions.includes("<|end|>") ||
91
- instructions.includes("<|assistant|>")
92
- ) {
93
- break
94
- }
95
- }
96
- } catch (err) {
97
- console.error(`error during generation: ${err}`)
98
- }
99
-
100
- // need to do some cleanup of the garbage the LLM might have gave us
101
- return (
102
- instructions
103
- .replaceAll("<|end|>", "")
104
- .replaceAll("<s>", "")
105
- .replaceAll("</s>", "")
106
- .replaceAll("[INST]", "")
107
- .replaceAll("[/INST]", "")
108
- .replaceAll("<SYS>", "")
109
- .replaceAll("</SYS>", "")
110
- .replaceAll("<|assistant|>", "")
111
- .replaceAll('""', '"')
112
- )
113
- }
114
-
115
- async function predictWithOpenAI(inputs: string) {
116
- const openaiApiBaseUrl = `${process.env.OPENAI_API_BASE_URL || "https://api.openai.com/v1"}`
117
- const openaiApiModel = `${process.env.OPENAI_API_MODEL || "gpt-3.5-turbo"}`
118
-
119
- const openai = new OpenAI({
120
- apiKey: openaiApiKey,
121
- baseURL: openaiApiBaseUrl,
122
- })
123
-
124
- const messages: ChatCompletionMessage[] = [
125
- { role: "system", content: inputs },
126
- ]
127
-
128
- try {
129
- const res = await openai.chat.completions.create({
130
- messages: messages,
131
- stream: false,
132
- model: openaiApiModel,
133
- temperature: 0.8
134
- })
135
 
136
- return res.choices[0].message.content
137
- } catch (err) {
138
- console.error(`error during generation: ${err}`)
139
- }
140
- }
 
1
  "use server"
2
 
 
 
 
3
  import { LLMEngine } from "@/types"
4
+ import { predictWithHuggingFace } from "./predictWithHuggingFace"
5
+ import { predictWithOpenAI } from "./predictWithOpenAI"
 
6
 
 
 
7
  const llmEngine = `${process.env.LLM_ENGINE || ""}` as LLMEngine
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ export const predict = llmEngine === "OPENAI" ? predictWithOpenAI : predictWithHuggingFace
 
 
 
 
src/app/queries/predictWithHuggingFace.ts ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { HfInference, HfInferenceEndpoint } from "@huggingface/inference"
4
+ import { LLMEngine } from "@/types"
5
+
6
+ const hf = new HfInference(process.env.AUTH_HF_API_TOKEN)
7
+
8
+ const llmEngine = `${process.env.LLM_ENGINE || ""}` as LLMEngine
9
+ const inferenceEndpoint = `${process.env.LLM_HF_INFERENCE_ENDPOINT_URL || ""}`
10
+ const inferenceModel = `${process.env.LLM_HF_INFERENCE_API_MODEL || ""}`
11
+
12
+ let hfie: HfInferenceEndpoint = hf
13
+
14
+ switch (llmEngine) {
15
+ case "INFERENCE_ENDPOINT":
16
+ if (inferenceEndpoint) {
17
+ console.log("Using a custom HF Inference Endpoint")
18
+ hfie = hf.endpoint(inferenceEndpoint)
19
+ } else {
20
+ const error = "No Inference Endpoint URL defined"
21
+ console.error(error)
22
+ throw new Error(error)
23
+ }
24
+ break;
25
+
26
+ case "INFERENCE_API":
27
+ if (inferenceModel) {
28
+ console.log("Using an HF Inference API Model")
29
+ } else {
30
+ const error = "No Inference API model defined"
31
+ console.error(error)
32
+ throw new Error(error)
33
+ }
34
+ break;
35
+
36
+ default:
37
+ const error = "Please check your Hugging Face Inference API or Inference Endpoint settings"
38
+ console.error(error)
39
+ throw new Error(error)
40
+ }
41
+
42
+ const api = llmEngine === "INFERENCE_ENDPOINT" ? hfie : hf
43
+
44
+ export async function predictWithHuggingFace(inputs: string) {
45
+ let instructions = ""
46
+ try {
47
+ for await (const output of api.textGenerationStream({
48
+ model: llmEngine === "INFERENCE_ENDPOINT" ? undefined : (inferenceModel || undefined),
49
+ inputs,
50
+ parameters: {
51
+ do_sample: true,
52
+ // we don't require a lot of token for our task
53
+ // but to be safe, let's count ~110 tokens per panel
54
+ max_new_tokens: 450, // 1150,
55
+ return_full_text: false,
56
+ }
57
+ })) {
58
+ instructions += output.token.text
59
+ process.stdout.write(output.token.text)
60
+ if (
61
+ instructions.includes("</s>") ||
62
+ instructions.includes("<s>") ||
63
+ instructions.includes("[INST]") ||
64
+ instructions.includes("[/INST]") ||
65
+ instructions.includes("<SYS>") ||
66
+ instructions.includes("</SYS>") ||
67
+ instructions.includes("<|end|>") ||
68
+ instructions.includes("<|assistant|>")
69
+ ) {
70
+ break
71
+ }
72
+ }
73
+ } catch (err) {
74
+ console.error(`error during generation: ${err}`)
75
+ }
76
+
77
+ // need to do some cleanup of the garbage the LLM might have gave us
78
+ return (
79
+ instructions
80
+ .replaceAll("<|end|>", "")
81
+ .replaceAll("<s>", "")
82
+ .replaceAll("</s>", "")
83
+ .replaceAll("[INST]", "")
84
+ .replaceAll("[/INST]", "")
85
+ .replaceAll("<SYS>", "")
86
+ .replaceAll("</SYS>", "")
87
+ .replaceAll("<|assistant|>", "")
88
+ .replaceAll('""', '"')
89
+ )
90
+ }
src/app/queries/predictWithOpenAI.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import type { ChatCompletionMessage } from "openai/resources/chat"
4
+ import OpenAI from "openai"
5
+
6
+ const openaiApiKey = `${process.env.AUTH_OPENAI_API_KEY || ""}`
7
+
8
+ export async function predictWithOpenAI(inputs: string) {
9
+ const openaiApiBaseUrl = `${process.env.LLM_OPENAI_API_BASE_URL || "https://api.openai.com/v1"}`
10
+ const openaiApiModel = `${process.env.LLM_OPENAI_API_MODEL || "gpt-3.5-turbo"}`
11
+
12
+ const openai = new OpenAI({
13
+ apiKey: openaiApiKey,
14
+ baseURL: openaiApiBaseUrl,
15
+ })
16
+
17
+ const messages: ChatCompletionMessage[] = [
18
+ { role: "system", content: inputs },
19
+ ]
20
+
21
+ try {
22
+ const res = await openai.chat.completions.create({
23
+ messages: messages,
24
+ stream: false,
25
+ model: openaiApiModel,
26
+ temperature: 0.8
27
+ })
28
+
29
+ return res.choices[0].message.content
30
+ } catch (err) {
31
+ console.error(`error during generation: ${err}`)
32
+ }
33
+ }
src/types.ts CHANGED
@@ -91,6 +91,8 @@ export type RenderingEngine =
91
  | "VIDEOCHAIN"
92
  | "OPENAI"
93
  | "REPLICATE"
 
 
94
 
95
  export type PostVisibility =
96
  | "featured" // featured by admins
 
91
  | "VIDEOCHAIN"
92
  | "OPENAI"
93
  | "REPLICATE"
94
+ | "INFERENCE_API"
95
+ | "INFERENCE_ENDPOINT"
96
 
97
  export type PostVisibility =
98
  | "featured" // featured by admins