ShiJiaoMing commited on
Commit ·
526a287
1
Parent(s): 893e50c
init
Browse files- .env +11 -0
- .env.example +11 -0
- .gitignore +93 -0
- LICENSE +201 -0
- README.md +87 -13
- app.py +76 -0
- author.py +145 -0
- config.py +53 -0
- cover/folder +0 -0
- epub/folder +0 -0
- flagged/log.csv +2 -0
- keyTest.py +32 -0
- requirements.txt +6 -0
- simpleTest.py +42 -0
- story/0e5d9be3d7f84ac28961947d5f3f942f/chapter_1/Chapter 1 - The Betrayal.txt +25 -0
- story/0e5d9be3d7f84ac28961947d5f3f942f/chapter_2/Chapter 2 - The Rise.txt +9 -0
- story/13927aa500284de3a924b54fbb3cd97c/chapter_1/第一章 - 幻影的脱困.txt +23 -0
- write_story.py +385 -0
.env
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenAI 接口代理地址,(可选配置)
|
| 2 |
+
OPENAI_API_BASE="https://api.openai-hk.com/v1"
|
| 3 |
+
|
| 4 |
+
# * 配置你的 OpenAI key(建议使用GPT-4正式账号,临时账号有每分钟3次请求限制)
|
| 5 |
+
OPENAI_API_KEY="hk-218wghmq4zcf4x42crjof0zlrpl4p22r96d44ola4wmyw92o"
|
| 6 |
+
|
| 7 |
+
# * 生成小说封面,具体可访问 https://platform.stability.ai/ 地址申请key
|
| 8 |
+
STABILITY_API_KEY="sk-iWYWb4PNfkIAv193eRuAoHBkk22FzkWzFSap62YydnUFKEcl"
|
| 9 |
+
|
| 10 |
+
# 配置你的 Claude2 API Key(可选配置)
|
| 11 |
+
ANTHROPIC_API_KEY="your-anthropic-api-key"
|
.env.example
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenAI 接口代理地址,(可选配置)
|
| 2 |
+
OPENAI_API_BASE="your-openai-proxy-url"
|
| 3 |
+
|
| 4 |
+
# * 配置你的 OpenAI key(建议使用GPT-4正式账号,临时账号有每分钟3次请求限制)
|
| 5 |
+
OPENAI_API_KEY="your-openai-api-key"
|
| 6 |
+
|
| 7 |
+
# * 生成小说封面,具体可访问 https://platform.stability.ai/ 地址申请key
|
| 8 |
+
STABILITY_API_KEY="your-stablity-api-key"
|
| 9 |
+
|
| 10 |
+
# 配置你的 Claude2 API Key(可选配置)
|
| 11 |
+
ANTHROPIC_API_KEY="your-anthropic-api-key"
|
.gitignore
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.DS_Store
|
| 2 |
+
git.properties
|
| 3 |
+
*.userlibraries
|
| 4 |
+
*.log.20*
|
| 5 |
+
*.log
|
| 6 |
+
log.*
|
| 7 |
+
### Intellij ###
|
| 8 |
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
|
| 9 |
+
|
| 10 |
+
*.iml
|
| 11 |
+
*.eml
|
| 12 |
+
|
| 13 |
+
## Directory-based project format:
|
| 14 |
+
.idea/
|
| 15 |
+
|
| 16 |
+
## File-based project format:
|
| 17 |
+
*.ipr
|
| 18 |
+
*.iws
|
| 19 |
+
|
| 20 |
+
## Plugin-specific files:
|
| 21 |
+
|
| 22 |
+
# IntelliJ
|
| 23 |
+
/out/
|
| 24 |
+
|
| 25 |
+
# mpeltonen/sbt-idea plugin
|
| 26 |
+
.idea_modules/
|
| 27 |
+
|
| 28 |
+
# JIRA plugin
|
| 29 |
+
atlassian-ide-plugin.xml
|
| 30 |
+
|
| 31 |
+
# Python
|
| 32 |
+
__pycache__
|
| 33 |
+
*.pyc
|
| 34 |
+
|
| 35 |
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
| 36 |
+
com_crashlytics_export_strings.xml
|
| 37 |
+
crashlytics.properties
|
| 38 |
+
crashlytics-build.properties
|
| 39 |
+
|
| 40 |
+
# java build files
|
| 41 |
+
target
|
| 42 |
+
node_modules
|
| 43 |
+
gulpfile.js
|
| 44 |
+
|
| 45 |
+
# debug scripts.
|
| 46 |
+
deploy.sh
|
| 47 |
+
ftpsync.settings
|
| 48 |
+
|
| 49 |
+
*.swp
|
| 50 |
+
.DS_Store
|
| 51 |
+
|
| 52 |
+
# Xcode
|
| 53 |
+
build/*
|
| 54 |
+
*.pbxuser
|
| 55 |
+
!default.pbxuser
|
| 56 |
+
*.mode1v3
|
| 57 |
+
!default.mode1v3
|
| 58 |
+
*.mode2v3
|
| 59 |
+
!default.mode2v3
|
| 60 |
+
*.perspectivev3
|
| 61 |
+
!default.perspectivev3
|
| 62 |
+
*.xcworkspace
|
| 63 |
+
!default.xcworkspace
|
| 64 |
+
xcuserdata
|
| 65 |
+
profile
|
| 66 |
+
*.moved-aside
|
| 67 |
+
*.cer
|
| 68 |
+
*.p12
|
| 69 |
+
*.mobileprovision
|
| 70 |
+
|
| 71 |
+
# VSCode
|
| 72 |
+
.vscode
|
| 73 |
+
# AppCode
|
| 74 |
+
.idea
|
| 75 |
+
|
| 76 |
+
# Tern plugin
|
| 77 |
+
**/.tern-project
|
| 78 |
+
|
| 79 |
+
#emacs temp file
|
| 80 |
+
*~
|
| 81 |
+
|
| 82 |
+
# log files
|
| 83 |
+
batch_loader/hadoop_mapred*
|
| 84 |
+
batch_loader/kafka_consumer*
|
| 85 |
+
|
| 86 |
+
# Maven Repository
|
| 87 |
+
.repository
|
| 88 |
+
|
| 89 |
+
# UT generated files
|
| 90 |
+
swagger.json
|
| 91 |
+
swagger-model.json
|
| 92 |
+
|
| 93 |
+
/dependencies/
|
LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
README.md
CHANGED
|
@@ -1,13 +1,87 @@
|
|
| 1 |
-
--
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<h1 align="center">● gpt-story-genius</h1>
|
| 2 |
+
|
| 3 |
+
<p align="center">
|
| 4 |
+
<br>
|
| 5 |
+
<b>StoryGenius:一款AI自动创作小说工具。</b><br><br>
|
| 6 |
+
根据小说的提示词、写作风格和章节数量几分钟即可快速生成奇幻小说。并自动打包为电子书格式。<br>
|
| 7 |
+
</p>
|
| 8 |
+
<br>
|
| 9 |
+
|
| 10 |
+

|
| 11 |
+
<br>
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
**gpt-story-genius** 是一个自动创作小说的AI,它可以在几分钟内根据用户提供的初始提示和章节数生成一整本奇幻小说,并自动打包为电子书格式。
|
| 15 |
+
该项目利用 **GPT-4**、**Stable Diffusion API** 和 **Anthropic API** 等一系列大模型调用组成的链来生成原创奇幻小说。<br>
|
| 16 |
+
|
| 17 |
+
此外,它还可以根据这本书创建一个原创封面,并将整本作品一次性转换为PDF或电子书格式,并且`制作成本低廉,制作一本15章的小说仅需4美元成本`,并且该工具是开源的,可以免费使用。
|
| 18 |
+
<br>
|
| 19 |
+
|
| 20 |
+
## 快速使用
|
| 21 |
+
|
| 22 |
+
在 Google Colab 中,只需打开笔记本,添加 API 密钥,然后按顺序运行单元即可。 </br></br>
|
| 23 |
+
[](https://colab.research.google.com/github/Crossme0809/frenzyTechAI/blob/main/gpt-author/gpt_author_v2.ipynb)
|
| 24 |
+
|
| 25 |
+
在笔记本的最后一个单元格中,您可以自定义小说的提示和章节数。例如:
|
| 26 |
+
|
| 27 |
+
```python
|
| 28 |
+
prompt = "一个被遗忘的小岛,上面有一座古老的灯塔。当灯塔亮起时,岛上的生物就会发生奇异的变化。"
|
| 29 |
+
num_chapters = 10
|
| 30 |
+
writing_style = "紧张刺激,类似于青少年恐怖小说。有很多对话和内心独白"
|
| 31 |
+
novel, title, chapters, chapter_titles = write_fantasy_novel(prompt, num_chapters, writing_style)
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
这将根据给定的提示生成一本 10 章的小说。**注意——少于 7 章的提示往往会导致问题。**
|
| 35 |
+
|
| 36 |
+
## 本地部署
|
| 37 |
+
|
| 38 |
+
- 下载项目代码
|
| 39 |
+
```bash
|
| 40 |
+
git clone https://github.com/Crossme0809/gpt-story-genius.git
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
- 复制项目配置文件
|
| 44 |
+
```bash
|
| 45 |
+
cp .env.example .env
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
- 配置GPT Key
|
| 49 |
+
```bash
|
| 50 |
+
# OpenAI 接口代理地址,(可选配置)
|
| 51 |
+
OPENAI_API_BASE="your-openai-proxy-url"
|
| 52 |
+
|
| 53 |
+
# * 配置你的 OpenAI key(建议使用GPT-4正式账号,临时账号有每分钟3次请求限制)
|
| 54 |
+
OPENAI_API_KEY="your-openai-api-key"
|
| 55 |
+
|
| 56 |
+
# * 生成小说封面,具体可访问 https://platform.stability.ai/ 地址申请key
|
| 57 |
+
STABILITY_API_KEY="your-stablity-api-key"
|
| 58 |
+
sk-iWYWb4PNfkIAv193eRuAoHBkk22FzkWzFSap62YydnUFKEcl
|
| 59 |
+
# 配置你的 Claude2 API Key(可选配置)
|
| 60 |
+
ANTHROPIC_API_KEY="your-anthropic-api-key"
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
- 安装项目依赖
|
| 64 |
+
```bash
|
| 65 |
+
pip install -r requirements.txt
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
- 启动项目
|
| 69 |
+
```bash
|
| 70 |
+
gradio app.py
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
- 后台进程启动
|
| 74 |
+
```bash
|
| 75 |
+
nohup gradio app.py > log.txt 2>&1 &
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
启动成功后,访问8000端口即可打开 **StoryGenius** 项目主页,如需要修改端口,只需要编辑 `run.py` 最后一行中的 `server_port` 即可。
|
| 79 |
+
<br><br>
|
| 80 |
+
<p>生成完的小说Epub文可以下载其文件并在 https://www.fviewer.com/view-epub 上查看,或将其安装在 Kindle 等上。(Mac上直接预览)</p>
|
| 81 |
+
|
| 82 |
+

|
| 83 |
+
|
| 84 |
+
## 特别说明
|
| 85 |
+
|
| 86 |
+
本项目基于[gpt-author](https://github.com/mshumer/gpt-author)开发,由[@mattshumer_](https://twitter.com/mattshumer_)创建。
|
| 87 |
+
|
app.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding: utf8
|
| 2 |
+
import gradio as gr
|
| 3 |
+
from write_story import write_fantasy_novel
|
| 4 |
+
from author import create_cover_image
|
| 5 |
+
from author import create_epub
|
| 6 |
+
from config import anthropic_api_key
|
| 7 |
+
|
| 8 |
+
if anthropic_api_key and anthropic_api_key != "your-anthropic-api-key":
|
| 9 |
+
print(">>>>>> ========= use anthropic ========")
|
| 10 |
+
claude_true = True
|
| 11 |
+
else:
|
| 12 |
+
print(">>>>>> ========= claude_true = False ========")
|
| 13 |
+
claude_true = False
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def generate_novel(prompt, num_chapters, writing_style, model_name):
|
| 17 |
+
# 调用GPT和Claude API,生成小说结果
|
| 18 |
+
# prompt = "A kingdom hidden deep in the forest, where every tree is a portal to another world."
|
| 19 |
+
# num_chapters = 2
|
| 20 |
+
# writing_style = "Clear and easily understandable, similar to a young adult novel. Lots of dialogue."
|
| 21 |
+
# model_name = "gpt-3.5-turbo-16k"
|
| 22 |
+
if not prompt or not writing_style:
|
| 23 |
+
raise gr.Error("提示词和写作风格是必填项")
|
| 24 |
+
if num_chapters < 1:
|
| 25 |
+
raise gr.Error("章节数必须大于等于1")
|
| 26 |
+
|
| 27 |
+
num_chapters = int(num_chapters)
|
| 28 |
+
novel, title, chapters, chapter_titles = write_fantasy_novel(prompt,
|
| 29 |
+
num_chapters, writing_style, claude_true, model_name)
|
| 30 |
+
|
| 31 |
+
# 用chapter_titles中的正文取代章节说明
|
| 32 |
+
for i, chapter in enumerate(chapters):
|
| 33 |
+
chapter_number_and_title = list(chapter_titles[i].keys())[0]
|
| 34 |
+
chapter_titles[i] = {chapter_number_and_title: chapter}
|
| 35 |
+
|
| 36 |
+
# 生成小说的封面
|
| 37 |
+
image_url = create_cover_image(str(chapter_titles))
|
| 38 |
+
print(f"Image URL: {image_url}")
|
| 39 |
+
|
| 40 |
+
# 生成小说 EPUB 文件
|
| 41 |
+
file_url = create_epub(title, 'AI', chapter_titles, image_url)
|
| 42 |
+
print(f"Novel URL: {file_url}")
|
| 43 |
+
|
| 44 |
+
# novel, file_path = write_fantasy_novel(prompt, num_chapters, writing_style)
|
| 45 |
+
return { "image_url": image_url, "file_url": file_url }
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def generate_output(prompt, num_chapters, writing_style, model_name):
|
| 49 |
+
try:
|
| 50 |
+
output = generate_novel(prompt, num_chapters, writing_style, model_name)
|
| 51 |
+
print(output)
|
| 52 |
+
return (output["image_url"], output["file_url"])
|
| 53 |
+
except Exception as e:
|
| 54 |
+
raise gr.Error({str(e)})
|
| 55 |
+
return f"An error occurred: {str(e)}"
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
inputs = [
|
| 59 |
+
gr.Textbox(value="一个被遗忘的小岛,上面有一座古老的灯塔。当灯塔亮起时,岛上的生物就会发生奇异的变化。", lines=2, placeholder="Usage:一个被遗忘的小岛,上面有一座古老的灯塔。当灯塔亮起时,岛上的生物就会发生奇异的变化。", label="小说提示词"),
|
| 60 |
+
gr.Number(value=1, label="小说章节数"),
|
| 61 |
+
gr.Textbox(value="紧张刺激,类似于青少年恐怖小说。有很多对话和内心独白", lines=2, placeholder="Usage:紧张刺激,类似于青少年恐怖小说。有很多对话和内心独白", label="AI写作风格"),
|
| 62 |
+
gr.Dropdown(["gpt-3.5-turbo-16k", "gpt-3.5-turbo", "gpt-4", "gpt-4-32k"], label="选择GPT模型", value="gpt-3.5-turbo")
|
| 63 |
+
]
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
outputs = [
|
| 67 |
+
gr.Image(label="封面图片", width=1028, height=300),
|
| 68 |
+
gr.File(label="EPUB文件")
|
| 69 |
+
]
|
| 70 |
+
|
| 71 |
+
title = "StoryGenius:一款AI自动创作小说工具"
|
| 72 |
+
description = "根据小说的提示词、写作风格和章节数量几分钟即可快速生成奇幻小说。并自动打包为电子书格式。"
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
iface = gr.Interface(fn=generate_output, inputs=inputs, outputs=outputs, title=title, description=description)
|
| 76 |
+
iface.launch(server_name="127.0.0.1", server_port=8000)
|
author.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding: utf8
|
| 2 |
+
from ebooklib import epub
|
| 3 |
+
import base64
|
| 4 |
+
import os
|
| 5 |
+
import requests
|
| 6 |
+
from config import completion_with_backoff
|
| 7 |
+
from config import stability_api_key
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def generate_cover_prompt(plot):
|
| 11 |
+
response = completion_with_backoff(
|
| 12 |
+
model="gpt-3.5-turbo-16k",
|
| 13 |
+
messages=[
|
| 14 |
+
{"role": "system",
|
| 15 |
+
"content": "You are a creative assistant that writes a spec for the cover art of a book, based on the book's plot."},
|
| 16 |
+
{"role": "user",
|
| 17 |
+
"content": f"Plot: {plot}\n\n--\n\nDescribe the cover we should create, based on the plot. This should be two sentences long, maximum."}
|
| 18 |
+
]
|
| 19 |
+
)
|
| 20 |
+
return response['choices'][0]['message']['content']
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def create_cover_image(plot):
|
| 24 |
+
plot = str(generate_cover_prompt(plot))
|
| 25 |
+
|
| 26 |
+
engine_id = "stable-diffusion-xl-beta-v2-2-2"
|
| 27 |
+
api_host = os.getenv('API_HOST', 'https://api.stability.ai')
|
| 28 |
+
api_key = stability_api_key
|
| 29 |
+
|
| 30 |
+
if api_key is None:
|
| 31 |
+
raise Exception("Missing Stability API key.")
|
| 32 |
+
|
| 33 |
+
response = requests.post(
|
| 34 |
+
f"{api_host}/v1/generation/{engine_id}/text-to-image",
|
| 35 |
+
headers={
|
| 36 |
+
"Content-Type": "application/json",
|
| 37 |
+
"Accept": "application/json",
|
| 38 |
+
"Authorization": f"Bearer {api_key}"
|
| 39 |
+
},
|
| 40 |
+
json={
|
| 41 |
+
"text_prompts": [
|
| 42 |
+
{
|
| 43 |
+
"text": plot
|
| 44 |
+
}
|
| 45 |
+
],
|
| 46 |
+
"cfg_scale": 7,
|
| 47 |
+
"clip_guidance_preset": "FAST_BLUE",
|
| 48 |
+
"height": 768,
|
| 49 |
+
"width": 512,
|
| 50 |
+
"samples": 1,
|
| 51 |
+
"steps": 30,
|
| 52 |
+
},
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
if response.status_code != 200:
|
| 56 |
+
raise Exception("Non-200 response: " + str(response.text))
|
| 57 |
+
|
| 58 |
+
data = response.json()
|
| 59 |
+
|
| 60 |
+
# 检查目录是否存在,如果不存在则创建
|
| 61 |
+
directory = "./cover/"
|
| 62 |
+
if not os.path.exists(directory):
|
| 63 |
+
os.makedirs(directory)
|
| 64 |
+
|
| 65 |
+
for i, image in enumerate(data["artifacts"]):
|
| 66 |
+
image_bytes = base64.b64decode(image["base64"])
|
| 67 |
+
file_path = f"./cover/cover_{i}.png" # 修改为您想要的文件路径
|
| 68 |
+
with open(file_path, "wb") as f:
|
| 69 |
+
f.write(image_bytes)
|
| 70 |
+
|
| 71 |
+
return file_path
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def create_epub(title, author, chapters, cover_image_path='cover.png'):
|
| 75 |
+
book = epub.EpubBook()
|
| 76 |
+
|
| 77 |
+
# Set metadata
|
| 78 |
+
book.set_identifier('id123456')
|
| 79 |
+
book.set_title(title)
|
| 80 |
+
book.set_language('zh-cn')
|
| 81 |
+
book.add_author(author)
|
| 82 |
+
|
| 83 |
+
# Add cover image
|
| 84 |
+
with open(cover_image_path, 'rb') as cover_file:
|
| 85 |
+
cover_image = cover_file.read()
|
| 86 |
+
book.set_cover('cover.png', cover_image)
|
| 87 |
+
|
| 88 |
+
# Create chapters and add them to the book
|
| 89 |
+
epub_chapters = []
|
| 90 |
+
for i, chapter_dict in enumerate(chapters):
|
| 91 |
+
full_chapter_title = list(chapter_dict.keys())[0]
|
| 92 |
+
chapter_content = list(chapter_dict.values())[0]
|
| 93 |
+
if ' - ' in full_chapter_title:
|
| 94 |
+
chapter_title = full_chapter_title.split(' - ')[1]
|
| 95 |
+
else:
|
| 96 |
+
chapter_title = full_chapter_title
|
| 97 |
+
|
| 98 |
+
chapter_file_name = f'chapter_{i + 1}.xhtml'
|
| 99 |
+
epub_chapter = epub.EpubHtml(title=chapter_title, file_name=chapter_file_name, lang='zh-cn')
|
| 100 |
+
|
| 101 |
+
# Add paragraph breaks
|
| 102 |
+
formatted_content = ''.join(
|
| 103 |
+
f'<p>{paragraph.strip()}</p>' for paragraph in chapter_content.split('\n') if paragraph.strip())
|
| 104 |
+
|
| 105 |
+
epub_chapter.content = f'<h1>{chapter_title}</h1>{formatted_content}'
|
| 106 |
+
book.add_item(epub_chapter)
|
| 107 |
+
epub_chapters.append(epub_chapter)
|
| 108 |
+
|
| 109 |
+
# Define Table of Contents
|
| 110 |
+
book.toc = (epub_chapters)
|
| 111 |
+
|
| 112 |
+
# Add default NCX and Nav files
|
| 113 |
+
book.add_item(epub.EpubNcx())
|
| 114 |
+
book.add_item(epub.EpubNav())
|
| 115 |
+
|
| 116 |
+
# Define CSS style
|
| 117 |
+
style = '''
|
| 118 |
+
@namespace epub "http://www.idpf.org/2007/ops";
|
| 119 |
+
body {
|
| 120 |
+
font-family: Cambria, Liberation Serif, serif;
|
| 121 |
+
}
|
| 122 |
+
h1 {
|
| 123 |
+
text-align: left;
|
| 124 |
+
text-transform: uppercase;
|
| 125 |
+
font-weight: 200;
|
| 126 |
+
}
|
| 127 |
+
'''
|
| 128 |
+
|
| 129 |
+
# Add CSS file
|
| 130 |
+
nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style)
|
| 131 |
+
book.add_item(nav_css)
|
| 132 |
+
|
| 133 |
+
# Create spine
|
| 134 |
+
book.spine = ['nav'] + epub_chapters
|
| 135 |
+
|
| 136 |
+
# 检查目录是否存在,如果不存在则创建
|
| 137 |
+
directory = "./epub/"
|
| 138 |
+
if not os.path.exists(directory):
|
| 139 |
+
os.makedirs(directory)
|
| 140 |
+
|
| 141 |
+
# 保存 EPUB 文件
|
| 142 |
+
file_path = f"./epub/{title}.epub" # 修改为您想要的文件路径
|
| 143 |
+
epub.write_epub(file_path, book)
|
| 144 |
+
|
| 145 |
+
return file_path
|
config.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import openai
|
| 2 |
+
import os
|
| 3 |
+
import uuid
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
from tenacity import (
|
| 7 |
+
retry,
|
| 8 |
+
stop_after_attempt,
|
| 9 |
+
wait_random_exponential,
|
| 10 |
+
) # for exponential backoff
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# 加载.env文件中的环境变量
|
| 14 |
+
load_dotenv()
|
| 15 |
+
|
| 16 |
+
llm_model_name = "gpt-3.5-turbo-16k"
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
openai.api_key = os.getenv("OPENAI_API_KEY") # get it at https://platform.openai.com/
|
| 20 |
+
if os.getenv("OPENAI_API_BASE"):
|
| 21 |
+
openai.api_base = os.getenv("OPENAI_API_BASE")
|
| 22 |
+
stability_api_key = os.getenv("STABILITY_API_KEY") # get it at https://beta.dreamstudio.ai/
|
| 23 |
+
anthropic_api_key = os.getenv("ANTHROPIC_API_KEY") # optional, if you don't add it, keep it as "YOUR ANTHROPIC API KEY"
|
| 24 |
+
|
| 25 |
+
print("============ config info ===============\n")
|
| 26 |
+
print("OPENAI_API_KEY:" + openai.api_key +"\n")
|
| 27 |
+
print("OPENAI_API_BASE:" + openai.api_base +"\n")
|
| 28 |
+
print("STABILITY_API_KEY:" + str(stability_api_key) +"\n")
|
| 29 |
+
print("ANTHROPIC_API_KEY:" + str(anthropic_api_key) +"\n")
|
| 30 |
+
|
| 31 |
+
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
|
| 32 |
+
def completion_with_backoff(**kwargs):
|
| 33 |
+
return openai.ChatCompletion.create(**kwargs)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# 生成32位唯一的uuid
|
| 37 |
+
def generate_uuid():
|
| 38 |
+
# 生成UUID
|
| 39 |
+
id = uuid.uuid4().hex
|
| 40 |
+
return id
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# 保存小说每章节的内容
|
| 44 |
+
def save_novel_chapter(novel_id, chapter_index, file_name, file_content):
|
| 45 |
+
# 创建章节文件目录
|
| 46 |
+
chapter_folder = os.path.join(os.getcwd(), f"story/{novel_id}/chapter_{chapter_index + 1}")
|
| 47 |
+
if not os.path.exists(chapter_folder):
|
| 48 |
+
os.makedirs(chapter_folder)
|
| 49 |
+
|
| 50 |
+
# 写入章节内容到文件
|
| 51 |
+
file_path = os.path.join(chapter_folder, f"{file_name}.txt")
|
| 52 |
+
with open(file_path, "w") as file:
|
| 53 |
+
file.write(file_content)
|
cover/folder
ADDED
|
File without changes
|
epub/folder
ADDED
|
File without changes
|
flagged/log.csv
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
小说提示词,小说章节数,AI写作风格,选择GPT模型,封面图片,EPUB文件,flag,username,timestamp
|
| 2 |
+
萍儿是一个职场新人,她和自己的好闺蜜佩佩是同事,她们之间无话不谈,但在一次职位竞选中被自己的闺蜜陷害,萍儿厌恶了职场的勾心斗角,有一天从梦中醒来发现自己从现代穿越到了唐朝,凭借自己的能力从庶女一路逆袭成为太后,在逆袭的过程中受尽了各种暗算,有一次遭人陷害,偶然遇到了一个大明王爷,大明王爷使出计谋出手相救,最终萍儿成为皇后,大明成为皇上。梦醒了,但是萍儿从大明身上学到很多谋略,最终在现实的职场中成为一名精英,叱诧风云。,7,紧张刺激,小说主要讲述现代的一名职场新人穿越到古代变成庶女后的故事,类似《知否知否应是绿肥红瘦》小说。有很多对话和内心独白,描述主人公萍儿坎坷的逆袭之路。,gpt-4,,,,,2023-10-31 16:36:04.485217
|
keyTest.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
url = "https://api.openai-hk.com/v1/chat/completions"
|
| 5 |
+
|
| 6 |
+
headers = {
|
| 7 |
+
"Content-Type": "application/json",
|
| 8 |
+
"Authorization": "Bearer hk-218wghmq4zcf4x42crjof0zlrpl4p22r96d44ola4wmyw92o"
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
data = {
|
| 12 |
+
"max_tokens": 1200,
|
| 13 |
+
"model": "gpt-3.5-turbo",
|
| 14 |
+
"temperature": 0.8,
|
| 15 |
+
"top_p": 1,
|
| 16 |
+
"presence_penalty": 1,
|
| 17 |
+
"messages": [
|
| 18 |
+
{
|
| 19 |
+
"role": "system",
|
| 20 |
+
"content": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible."
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"role": "user",
|
| 24 |
+
"content": "你是chatGPT多少?"
|
| 25 |
+
}
|
| 26 |
+
]
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
response = requests.post(url, headers=headers, data=json.dumps(data).encode('utf-8') )
|
| 30 |
+
result = response.content.decode("utf-8")
|
| 31 |
+
|
| 32 |
+
print(result)
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openai==0.28.0
|
| 2 |
+
EbookLib==0.18
|
| 3 |
+
anthropic==0.3.11
|
| 4 |
+
python-dotenv==1.0.0
|
| 5 |
+
gradio==3.42.0
|
| 6 |
+
tenacity==8.2.3
|
simpleTest.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding: utf8
|
| 2 |
+
import ast
|
| 3 |
+
import unicodedata
|
| 4 |
+
|
| 5 |
+
def fullwidth_to_halfwidth(text):
|
| 6 |
+
result = ''
|
| 7 |
+
for char in text:
|
| 8 |
+
char_width = unicodedata.east_asian_width(char)
|
| 9 |
+
if char_width == 'F' or char_width == 'W':
|
| 10 |
+
# 全角字符的宽度标记为 'F' 或 'W'
|
| 11 |
+
halfwidth_char = unicodedata.normalize('NFKC', char)
|
| 12 |
+
# 使用 NFKC 形式将全角字符转换为半角字符
|
| 13 |
+
result += halfwidth_char
|
| 14 |
+
else:
|
| 15 |
+
result += char
|
| 16 |
+
return result
|
| 17 |
+
|
| 18 |
+
def strQ2B(ustring):
|
| 19 |
+
"""把字符串全角转半角"""
|
| 20 |
+
ss = []
|
| 21 |
+
for s in ustring:
|
| 22 |
+
rstring = ""
|
| 23 |
+
for uchar in s:
|
| 24 |
+
inside_code = ord(uchar)
|
| 25 |
+
if inside_code == 12288: # 全角空格直接转换
|
| 26 |
+
inside_code = 32
|
| 27 |
+
elif (inside_code >= 65281 and inside_code <= 65374): # 全角字符(除空格)根据关系转化
|
| 28 |
+
inside_code -= 65248
|
| 29 |
+
rstring += chr(inside_code)
|
| 30 |
+
ss.append(rstring)
|
| 31 |
+
return ''.join(ss)
|
| 32 |
+
|
| 33 |
+
name = "[{\"Chapter 1 - 骗局的蔓延\":\"平儿和佩佩是两名才华横溢但缺乏经验的年轻职员,她们的友谊因为争夺晋升机会而面临考验。佩佩因嫉妒和欲望而背叛了平儿,导致平儿不仅失去了晋升的机会,也失去了对办公室政治的信任。然而,平儿并不是一个轻易被击倒的人。她决心揭开佩佩的阴谋并证明自己的价值。\"},{\"Chapter 2 - 时间之门\":\"平儿被背叛的伤害使她陷入沉睡,当她醒来时,她发现自己来到了一个神秘的花园。花园里有一扇通往不同时空的门,平儿勇敢地穿越了时空。她来到了一个名为“时光之门”的地方,那里是通往古代的大门。平儿在这个充满危险和诱惑的新世界中,发现自己拥有操控时间的特殊能力。\"},{\"Chapter 3 - 宫廷的险棋\":\"平儿发现自己来到了唐朝的宫廷中,成为了一个卑微的妃子。尽管身份低微,但平儿依靠她的聪明才智和前世记忆,巧妙地在宫廷中游刃有余。她与朝臣和其他妃子之间的斗争越来越激烈,而平儿则不断改变和正义的转变引起了皇帝的注意。平儿必须面对宫廷的阴谋和挑战,才能够达到她的目标。\"},{\"Chapter 4 - 诡谲的盟约\":\"当平儿以为自己已经确立了皇后地位时,一个强大的阴谋威胁到了她的统治。在最黑暗的时刻,一个神秘的明朝王子以宫廷学者的身份,显示出他的真正身份。平儿与这位王子结盟,共同对抗阴谋,保护自己的地位。他们的智慧与战略才能,将会决定唐朝的命运和平儿的未来。\"},{\"Chapter 5 - 觉醒与蓄势\":\"当平儿从梦中醒来时,她带着前世的智慧和力量回到了现代世界。她发现自己变成了一位职场精英,凭借她对历史和背叛的了解,她在职业生涯中崭露头角,并留下了持久的遗产。平儿开始运用她在古代所学到的技巧,在现代社会中迎接新的挑战和冲突。\"},{\"Chapter 6 - 背叛与复仇\":\"《时间之门:从背叛到复仇的传奇》是一个充满惊险和冒险的奇幻故事。它探索了友谊、背叛和自我成长的主题。平儿在古代和现代两个世界中的经历,让她成为一个强大且无法被忽视的力量。故事深入探讨了权力、勇气和复仇的概念,塑造了一个令人着迷的女主角与命运抗争的故事。\"},{\"Chapter 7 - 荣耀与遗留\":\"为了让故事更加吸引人,我增加了对平儿和佩佩关系的深入探索。这突出了友谊的力量和背叛的伤害。在唐朝宫廷中,我加入了更多展示平儿成长和发展的情节,强调她在危险的宫廷游戏中的独到能力。我还引入了更多复杂的宫廷角色,为平儿提供更多机遇和挑战,以推动她的角色发展。通过增加阴谋情节和紧张气氛,增加了读者的悬念和猜测。最后,我更加流畅地将古代与现代世界联系起来,以展现平儿在两个环境中的相似与联系。\"}]"
|
| 34 |
+
|
| 35 |
+
name1 = "[{\"Chapter 6 - 背叛与复仇\": \"《时间之门:从背叛到复仇的传奇》是一个充满惊险和冒险的奇幻故事它探索了友谊背叛和自我成长的主题平儿在古代和现代两个世界中的经历,让她成为一个强大且无法被忽视的力量故事深入探讨了权力勇气和复仇的概念,塑造了一个令人着迷的女主角与命运抗争的故事\"},{\"Chapter 7 - 荣耀与遗留\": \"为了让故事更加吸引人,我增加了对平儿和佩佩关系的深入探索。这突出了友谊的力量和背叛的伤害。在唐朝宫廷中,我加入了更多展示平儿成长和发展的情节,强调她在危险的宫廷游戏中的独到能力。我还引入了更多复杂的宫廷角色,为平儿提供更多机遇和挑战,以推动她的角色发展。通过增加阴谋情节和紧张气氛,增加了读者的悬念和猜测。最后,我更加流畅地将古代与现代世界��系起来,以展现平儿在两个环境中的相似与联系。\"}]"
|
| 36 |
+
|
| 37 |
+
print(f'【storyline】: {name}\n\n')
|
| 38 |
+
|
| 39 |
+
chapter_titles = ast.literal_eval(name)
|
| 40 |
+
print(f'【chapter_titles】: {chapter_titles}\n\n')
|
| 41 |
+
|
| 42 |
+
|
story/0e5d9be3d7f84ac28961947d5f3f942f/chapter_1/Chapter 1 - The Betrayal.txt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
第一章 - 背叛
|
| 2 |
+
|
| 3 |
+
年轻的企业精英平儿,才华出众且野心勃勃,卷入了与她信任的朋友佩佩之间的恶意办公室竞争中。在无情商业世界的背叛和失望之中,平儿一觉醒来,发现自己穿越到唐朝成为一个卑微的宫女身体寄生的灵魂。
|
| 4 |
+
|
| 5 |
+
一片黑暗中,平儿缓缓睁开眼睛。疲惫的身体透露出一种淡淡的不安,仿佛来自另一个时空。纷乱的思绪渐渐清晰起来,她发现自己置身于华丽的宫殿之中,房间充满着沉重的气息。她的头脑里回想起了刚才的一幕:佩佩对她的背叛,朋友之间的狼狈一面。
|
| 6 |
+
|
| 7 |
+
"怎么会这样?我的未来本该美好。" 平儿默默想道。她曾以为在商业世界中,能够发展自己的梦想,与佩佩一起开创一片新的天地。然而,现实却残酷无情。平儿被背叛和失望深深地伤害了。这个世界让她感到疲惫,决绝地迫使着她面对不可避免的挫折。
|
| 8 |
+
|
| 9 |
+
无尽的黑暗中,她的脑海里依稀浮现出和佩佩密切相关的画面,商业报表和办公室的暗斗。佩佩的一句顺手牵羊的话让平儿崩溃了。如今,她在这个古代宫廷中,变成了一个庶女的灵魂寄生体,命运嘲弄着她的一切抱负和努力。
|
| 10 |
+
|
| 11 |
+
躺在床上,把手掌紧握成拳头,平儿咬紧牙关。她发誓,无论在哪个时代,她都要找回自己失去的尊严和力量。她深呼吸,舒展凝固的身体,让思绪回到当下。
|
| 12 |
+
|
| 13 |
+
"我是平儿,一个坚定追求目标的女人。这个时代或许不理解我的雄心,但我要利用我的才智,战胜这个陌生的世界,重铸我的命运!" 平儿发誓着。
|
| 14 |
+
|
| 15 |
+
慢慢地,平儿挣脱沉重的惆怅,不再让失去束缚住她的灵魂。她站起身,目光坚毅地扫视宅子的四周。她知道自己需要找到适应这个新世界的方法。
|
| 16 |
+
|
| 17 |
+
唐朝的宫廷毫不留情,她必须敏锐地观察并学会扭转局势。平儿决定利用她无与伦比的智慧和机智,赢得这个挑战。她的目标是晋升,成为这个宫廷中引起有影响力人物注意的存在。
|
| 18 |
+
|
| 19 |
+
未来的道路虽然漫长而充满荆棘,平儿知道她已有了解决困难的内在力量。她要站稳脚跟,融入这个新的世界,同时保护自己免受险恶阴谋的伤害。这意味着她必须学会心机和耐心,提高自己的智慧,以此超越那些企图阻止她崛起的人。
|
| 20 |
+
|
| 21 |
+
平儿看向窗外,安慰自己的决心。一场新的冒险即将展开,她将不再是一个被背叛的受害者,而是一名坚强而富有智慧的女性,为重塑她的命运而奋斗。她知道,只有这样,她才能回到现代世界,并彻底改变自己的未来。
|
| 22 |
+
|
| 23 |
+
在这个陌生的古代天地中,平儿决定成为那个无人忽略的存在,无论付出多大代价。她相信背叛与失望只是人生中的一小部分,她一定能赢得她应得的荣光和自由。
|
| 24 |
+
|
| 25 |
+
平儿开始紧锁眉头,考
|
story/0e5d9be3d7f84ac28961947d5f3f942f/chapter_2/Chapter 2 - The Rise.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
第二章 - 崛起
|
| 2 |
+
|
| 3 |
+
为了改变自己的命运,平儿运用她非凡的智慧和机智,逐渐适应了险恶的宫廷环境。她坚定决心地晋升,凭借着她的策略头脑吸引起朝廷中有影响力的人物的注意。
|
| 4 |
+
|
| 5 |
+
平儿逐渐适应了这个陌生而危险的宫廷世界。她深知在这里晋升是一条漫长而需要精明策略的道路。她要抓住每个机会,争取每一次相见的机会。她不断观察并分析各个人物之间的互动,找到他们的弱点和喜好。
|
| 6 |
+
|
| 7 |
+
平儿用她的聪慧和洞察力,悄悄地积累起宫廷中的力量。她学会了如何在政治斗争的泥潭中游刃有余地舞动。她发现自己的每一步都引起了朝廷中有影响力的人物的注意。
|
| 8 |
+
|
| 9 |
+
她以巧妙的方式与那些有势力的人建立起联系,并展示出她的价值。她通过认真执着的工作,成为皇帝赏识的宫
|
story/13927aa500284de3a924b54fbb3cd97c/chapter_1/第一章 - 幻影的脱困.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
第一章 - 幻影的脱困
|
| 2 |
+
|
| 3 |
+
珍妮佩恩是一位才智过人的年轻女士,在一个充满压力和竞争的企业中辛勤工作。每天,她穿过一栋巨大而冷酷的办公楼,度过无尽的工作时间,承受着高压和残酷的竞争。然而,在她看似平凡的外表下,隐藏着一颗渴望冒险和探索的梦想家的心。
|
| 4 |
+
|
| 5 |
+
这一天,珍妮佩恩按部就班地完成了一连串繁重的任务,却感到自己又一次沉浸在绝望和无望之中。她渴望一种解脱,渴望逃离现实的枷锁,再次找到那个能给她带来兴奋和激情的地方。
|
| 6 |
+
|
| 7 |
+
她双手颤抖地打开了她钟爱的虚拟现实游戏《幻影》。眼前展现出了一个神奇而又充满魔法的世界。这个世界充满了各种奇异的生物、令人叹为观止的景色和潜藏着无尽秘密的地方。这是一个让珍妮佩恩可以成为真正无所不能的英雄,并与其他游戏玩家建立联系的地方。
|
| 8 |
+
|
| 9 |
+
当她穿越游戏门户进入《幻影》的时刻,她完全沉浸在了奇幻世界的美妙之中。她的外貌转变,她变成了一名崭露头角的战士,凭借卓越的技巧和无尽的勇气赢得了众人的尊敬和仰慕。
|
| 10 |
+
|
| 11 |
+
然而,她并不知道,《幻影》隐藏着一个永久改变她生活的魔法秘密。在她击败怪物、探索未知领域的过程中,一股神秘的力量渗透进她的身体,赋予她非凡的能力。她的眼睛闪烁着星星般的光芒,她的手指蕴含着无尽的魔力。
|
| 12 |
+
|
| 13 |
+
然而,幻影的影响力并不仅限于游戏。当珍妮佩恩回到现实世界时,她开始在日常生活中出现奇怪的现象。她的手指能释放出闪电,她的眼睛能看到隐藏的真相。这让她震惊不已,也让她感到困惑和恐慌。
|
| 14 |
+
|
| 15 |
+
虚拟和现实的界限开始模糊起来,珍妮佩恩陷入了困惑和恐慌之中。她不知道自己身处何处,也不知道如何控制这些力量。于是,她开始探寻《幻影》背后的秘密,希望找到答案,同时追求自己内心的平静与和谐。
|
| 16 |
+
|
| 17 |
+
通过在在线游戏社区的交流和探寻,《幻影》揭示了一个隐藏的游戏玩家社群。这群玩家发现了连接现实世界和虚拟世界的门户。尽管他们来自不同的背景和经历,但他们团结一致,展开任务,保护两个世界免受即将降临的邪恶混乱的威胁。
|
| 18 |
+
|
| 19 |
+
对珍妮佩恩来说,《幻影》不再只是一款游戏,它变成了她的命运。她必须面对自己内心的恐惧和挑战,接受自己身为领导者和英雄的真正潜力。
|
| 20 |
+
|
| 21 |
+
《幻影的脱困》讲述了一个关于勇气、发现真正自我的旅程。在珍妮佩恩的冒险中,她不仅找到了自己,也找到了一群真挚的盟友和隐藏的力量。这个故事将带你进入一个充满魔法、想象力和意想不到的转变的世界,唤醒你内在的勇气和梦想。
|
| 22 |
+
|
| 23 |
+
(注:通过这个章节,预示着珍妮佩恩即将进入她作为主人公的冒险旅程,将她的命运与《幻影》紧密联系在一起。章节的语气紧张刺激,小说主要讲述现代的一名职场新人穿越到古代变成庶女后的故事,类似《知否知否应是绿肥红瘦》小说。有很多对话和内心独白,描述主人公萍儿坎坷的逆袭之路。)
|
write_story.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding: utf8
|
| 2 |
+
import random
|
| 3 |
+
import os
|
| 4 |
+
import requests
|
| 5 |
+
import ast
|
| 6 |
+
import time
|
| 7 |
+
from anthropic import Anthropic
|
| 8 |
+
from config import anthropic_api_key
|
| 9 |
+
from config import llm_model_name
|
| 10 |
+
from config import completion_with_backoff
|
| 11 |
+
from config import save_novel_chapter
|
| 12 |
+
from config import generate_uuid
|
| 13 |
+
|
| 14 |
+
def print_step_costs(response, model):
|
| 15 |
+
input = response['usage']['prompt_tokens']
|
| 16 |
+
output = response['usage']['completion_tokens']
|
| 17 |
+
|
| 18 |
+
if model == "gpt-4" or model == "gpt-4":
|
| 19 |
+
input_per_token = 0.00003
|
| 20 |
+
output_per_token = 0.00006
|
| 21 |
+
if model == "gpt-3.5-turbo-16k":
|
| 22 |
+
input_per_token = 0.000003
|
| 23 |
+
output_per_token = 0.000004
|
| 24 |
+
if model == "gpt-4-32k" or model == "gpt-4-32k":
|
| 25 |
+
input_per_token = 0.00006
|
| 26 |
+
output_per_token = 0.00012
|
| 27 |
+
if model == "gpt-3.5-turbo" or model == "gpt-3.5-turbo":
|
| 28 |
+
input_per_token = 0.0000015
|
| 29 |
+
output_per_token = 0.000002
|
| 30 |
+
if model == "claude-2":
|
| 31 |
+
input_per_token = 0.00001102
|
| 32 |
+
output_per_token = 0.00003268
|
| 33 |
+
|
| 34 |
+
input_cost = int(input) * input_per_token
|
| 35 |
+
output_cost = int(output) * output_per_token
|
| 36 |
+
|
| 37 |
+
total_cost = input_cost + output_cost
|
| 38 |
+
print('Step Cost (OpenAI):', total_cost)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def print_step_costs_anthropic(prompt, response):
|
| 42 |
+
client = Anthropic()
|
| 43 |
+
in_tokens = client.count_tokens(prompt)
|
| 44 |
+
out_tokens = client.count_tokens(response)
|
| 45 |
+
|
| 46 |
+
input_cost = 0.00001102 * in_tokens
|
| 47 |
+
output_cost = 0.00003268 * out_tokens
|
| 48 |
+
|
| 49 |
+
total_cost = input_cost + output_cost
|
| 50 |
+
print('Step Cost (Anthropic):', total_cost)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def generate_plots(prompt):
|
| 54 |
+
response = completion_with_backoff(
|
| 55 |
+
model=llm_model_name,
|
| 56 |
+
messages=[
|
| 57 |
+
{"role": "system", "content": "You are a creative assistant that generates engaging fantasy novel plots."},
|
| 58 |
+
{"role": "user", "content": f"Generate 10 fantasy novel plots based on this prompt: {prompt}"}
|
| 59 |
+
]
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
print_step_costs(response, llm_model_name)
|
| 63 |
+
|
| 64 |
+
return response['choices'][0]['message']['content'].split('\n')
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def select_most_engaging(plots):
|
| 68 |
+
response = completion_with_backoff(
|
| 69 |
+
model=llm_model_name,
|
| 70 |
+
messages=[
|
| 71 |
+
{"role": "system", "content": "You are an expert in writing fantastic fantasy novel plots."},
|
| 72 |
+
{"role": "user",
|
| 73 |
+
"content": f"Here are a number of possible plots for a new novel: "
|
| 74 |
+
f"{plots}\n\n--\n\nNow, write the final plot that we will go with. "
|
| 75 |
+
f"It can be one of these, a mix of the best elements of multiple, "
|
| 76 |
+
f"or something completely new and better. "
|
| 77 |
+
f"The most important thing is the plot should be fantastic, unique, and engaging."}
|
| 78 |
+
]
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
print_step_costs(response, llm_model_name)
|
| 82 |
+
|
| 83 |
+
return response['choices'][0]['message']['content']
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def improve_plot(plot):
|
| 87 |
+
response = completion_with_backoff(
|
| 88 |
+
model=llm_model_name,
|
| 89 |
+
messages=[
|
| 90 |
+
{"role": "system", "content": "You are an expert in improving and refining story plots."},
|
| 91 |
+
{"role": "user", "content": f"Improve this plot: {plot}"}
|
| 92 |
+
]
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
print_step_costs(response, llm_model_name)
|
| 96 |
+
|
| 97 |
+
return response['choices'][0]['message']['content']
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def get_title(plot):
|
| 101 |
+
response = completion_with_backoff(
|
| 102 |
+
model=llm_model_name,
|
| 103 |
+
messages=[
|
| 104 |
+
{"role": "system", "content": "You are an expert writer."},
|
| 105 |
+
{"role": "user",
|
| 106 |
+
"content": f"Here is the plot: {plot}\n\nWhat is the title of this book? "
|
| 107 |
+
f"Just respond with the title, do nothing else. Please respond in Chinese."}
|
| 108 |
+
]
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
print_step_costs(response, llm_model_name)
|
| 112 |
+
|
| 113 |
+
return response['choices'][0]['message']['content']
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def write_first_chapter(plot, first_chapter_title, writing_style, claude=True):
|
| 117 |
+
if claude:
|
| 118 |
+
url = "https://api.anthropic.com/v1/complete"
|
| 119 |
+
|
| 120 |
+
headers = {
|
| 121 |
+
"anthropic-version": "2023-06-01",
|
| 122 |
+
"content-type": "application/json",
|
| 123 |
+
"x-api-key": anthropic_api_key,
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
prompt_one = f"\n\nHuman: You are a world-class fantasy writer. " \
|
| 127 |
+
f"I will give you the title of a novel, a high-level plot to follow, " \
|
| 128 |
+
f"and a desired writing style to use. " \
|
| 129 |
+
f"From the title, plot, and writing style, write the first chapter of the novel. " \
|
| 130 |
+
f"Make it incredibly unique, engaging, and well-written. " \
|
| 131 |
+
f"Start it off with a bang, and include dialogue. " \
|
| 132 |
+
f"Include only the chapter text, and no surrounding explanations or text. " \
|
| 133 |
+
f"Do you understand?\n\nAssistant: Yes, I understand. " \
|
| 134 |
+
f"Please provide the title, plot, and writing style, " \
|
| 135 |
+
f"and I will write a fantastic opening chapter with dialogue that will hook the reader." \
|
| 136 |
+
f"\n\nHuman: Here is the high-level plot to follow: {plot}" \
|
| 137 |
+
f"\n\nThe title of the novel is: `{first_chapter_title}`.\n\n" \
|
| 138 |
+
f"Here is a description of the writing style you should use: `{writing_style}`" \
|
| 139 |
+
f"\n\nWrite the first chapter please!\n\nAssistant: " \
|
| 140 |
+
f"Okay, I've got a really exciting first chapter for you. " \
|
| 141 |
+
f"It's twenty paragraphs long and very well-written. " \
|
| 142 |
+
f"As you can see, the language I use is very understandable — " \
|
| 143 |
+
f"I avoided using overly complex words and phrases. Please respond in Chinese:" \
|
| 144 |
+
f"\n\nTitle: {first_chapter_title}\n\nChapter #1 Text```"
|
| 145 |
+
|
| 146 |
+
data = {
|
| 147 |
+
"model": "claude-2",
|
| 148 |
+
"prompt": prompt_one,
|
| 149 |
+
"max_tokens_to_sample": 5000,
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
response = requests.post(url, headers=headers, json=data)
|
| 153 |
+
print(response)
|
| 154 |
+
initial_first_chapter = response.json()['completion'].strip().split('```')[0].strip()
|
| 155 |
+
|
| 156 |
+
print_step_costs_anthropic(prompt_one, response.json()['completion'])
|
| 157 |
+
|
| 158 |
+
prompt_two = f"\n\nHuman: You are a world-class fantasy writer. " \
|
| 159 |
+
f"Your job is to take your student's rough initial draft of the first chapter of their fantasy novel, " \
|
| 160 |
+
f"and rewrite it to be significantly better, with much more detail. " \
|
| 161 |
+
f"Do you understand?\n\nAssistant: Yes, I understand. " \
|
| 162 |
+
f"Please provide the plot and the student-written chapter, " \
|
| 163 |
+
f"and I will rewrite the chapter in a far superior way.\n\nHuman: " \
|
| 164 |
+
f"Here is the high-level plot you asked your student to follow: " \
|
| 165 |
+
f"{plot}\n\nHere is the first chapter they wrote: {initial_first_chapter}\n\n" \
|
| 166 |
+
f"Now, rewrite the first chapter of this novel, in a way that is far superior to your student's chapter. " \
|
| 167 |
+
f"It should still follow the exact same plot, but it should be far more detailed, much longer, and more engaging. " \
|
| 168 |
+
f"Here is a description of the writing style you should use: `{writing_style}`\n\nAssistant: Okay, I've rewritten the first chapter. " \
|
| 169 |
+
f"I took great care to improve it. While the plot is the same, " \
|
| 170 |
+
f"you can see that my version is noticeably longer, easier to read, " \
|
| 171 |
+
f"and more exciting. Also, the language I used is far more accessible to a broader audience." \
|
| 172 |
+
f"Please respond in Chinese.\n\n```"
|
| 173 |
+
data = {
|
| 174 |
+
"model": "claude-2",
|
| 175 |
+
"prompt": prompt_two,
|
| 176 |
+
"max_tokens_to_sample": 5000,
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
response_improved = requests.post(url, headers=headers, json=data)
|
| 180 |
+
|
| 181 |
+
print_step_costs_anthropic(prompt_two, response_improved.json()['completion'])
|
| 182 |
+
|
| 183 |
+
return response_improved.json()['completion'].strip().split('```')[0].strip()
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
else:
|
| 187 |
+
response = completion_with_backoff(
|
| 188 |
+
model=llm_model_name,
|
| 189 |
+
messages=[
|
| 190 |
+
{"role": "system", "content": "You are a world-class fantasy writer."},
|
| 191 |
+
{"role": "user",
|
| 192 |
+
"content": f"Here is the high-level plot to follow: {plot}\n\nWrite the first chapter of this novel: `{first_chapter_title}`.\n\nMake it incredibly unique, engaging, and well-written.\n\nHere is a description of the writing style you should use: `{writing_style}`\n\nInclude only the chapter text. There is no need to rewrite the chapter name. Please respond in Chinese."}
|
| 193 |
+
]
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
print_step_costs(response, llm_model_name)
|
| 197 |
+
|
| 198 |
+
improved_response = completion_with_backoff(
|
| 199 |
+
model=llm_model_name,
|
| 200 |
+
messages=[
|
| 201 |
+
{"role": "system",
|
| 202 |
+
"content": "You are a world-class fantasy writer. Your job is to take your student's rough initial draft of the first chapter of their fantasy novel, and rewrite it to be significantly better, with much more detail."},
|
| 203 |
+
{"role": "user",
|
| 204 |
+
"content": f"Here is the high-level plot you asked your student to follow: {plot}\n\nHere is the first chapter they wrote: {response['choices'][0]['message']['content']}\n\nNow, rewrite the first chapter of this novel, in a way that is far superior to your student's chapter. It should still follow the exact same plot, but it should be far more detailed, much longer, and more engaging. Here is a description of the writing style you should use: `{writing_style}`.Please respond in Chinese."}
|
| 205 |
+
]
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
print_step_costs(response, llm_model_name)
|
| 209 |
+
|
| 210 |
+
return improved_response['choices'][0]['message']['content']
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def write_chapter(previous_chapters, plot, chapter_title, claude=True):
|
| 214 |
+
if claude:
|
| 215 |
+
url = "https://api.anthropic.com/v1/complete"
|
| 216 |
+
|
| 217 |
+
headers = {
|
| 218 |
+
"anthropic-version": "2023-06-01",
|
| 219 |
+
"content-type": "application/json",
|
| 220 |
+
"x-api-key": anthropic_api_key,
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
prompt = f"\n\nHuman: You are a world-class fantasy writer. I will provide you with the plot of the novel, the previous chapters, and the plan for the next chapter. Your task is to write the next chapter of the novel in Chinese, following the plot and taking in the previous chapters as context. Do you understand?\n\nAssistant: Yes, I understand. You want me to write the next chapter of a novel, using the plot you provide, the previous chapters for context, and a specific plan for the next chapter. I will ensure the chapter is beautifully written and I will not rewrite the chapter name.\n\nHuman: That's correct. Here is the plot: {plot}\n\nHere are the previous chapters: {previous_chapters}\n\nHere is the plan for the next chapter: {chapter_title}\n\nWrite it beautifully. Include only the chapter text. There is no need to rewrite the chapter name.\n\nAssistant: Here is the next chapter. As you can see, it's around the same length as the previous chapters, and contains witty dialogue:\n```Chapter"
|
| 224 |
+
|
| 225 |
+
data = {
|
| 226 |
+
"model": "claude-2",
|
| 227 |
+
"prompt": prompt,
|
| 228 |
+
"max_tokens_to_sample": 5000,
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
response = requests.post(url, headers=headers, json=data)
|
| 232 |
+
|
| 233 |
+
print_step_costs_anthropic(prompt, response.json()['completion'])
|
| 234 |
+
|
| 235 |
+
return 'Chapter ' + response.json()['completion'].strip().split('```')[0].strip()
|
| 236 |
+
else:
|
| 237 |
+
try:
|
| 238 |
+
i = random.randint(1, 2242)
|
| 239 |
+
response = completion_with_backoff(
|
| 240 |
+
model=llm_model_name,
|
| 241 |
+
messages=[
|
| 242 |
+
{"role": "system", "content": "You are a world-class fantasy writer. "},
|
| 243 |
+
{"role": "user",
|
| 244 |
+
"content": f"Plot: {plot}, Previous Chapters: {previous_chapters}\n\n--\n\nWrite the next chapter of this novel in Chinese, following the plot and taking in the previous chapters as context. Here is the plan for this chapter: {chapter_title}\n\nWrite it beautifully. Include only the chapter text. There is no need to rewrite the chapter name."}
|
| 245 |
+
]
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
print_step_costs(response, llm_model_name)
|
| 249 |
+
|
| 250 |
+
return response['choices'][0]['message']['content']
|
| 251 |
+
except:
|
| 252 |
+
response = completion_with_backoff(
|
| 253 |
+
model=llm_model_name,
|
| 254 |
+
messages=[
|
| 255 |
+
{"role": "system", "content": "You are a world-class fantasy writer."},
|
| 256 |
+
{"role": "user",
|
| 257 |
+
"content": f"Plot: {plot}, Previous Chapters: {previous_chapters}\n\n--\n\nWrite the next chapter of this novel in Chinese, following the plot and taking in the previous chapters as context. Here is the plan for this chapter: {chapter_title}\n\nWrite it beautifully. Include only the chapter text. There is no need to rewrite the chapter name."}
|
| 258 |
+
]
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
print_step_costs(response, llm_model_name)
|
| 262 |
+
|
| 263 |
+
return response['choices'][0]['message']['content']
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
def generate_storyline(prompt, num_chapters):
|
| 267 |
+
print("Generating storyline with chapters and high-level details...")
|
| 268 |
+
json_format = """[{"Chapter CHAPTER_NUMBER_HERE - CHAPTER_TITLE_GOES_HERE":
|
| 269 |
+
"CHAPTER_OVERVIEW_AND_DETAILS_GOES_HERE"}, ...]"""
|
| 270 |
+
response = completion_with_backoff(
|
| 271 |
+
model=llm_model_name,
|
| 272 |
+
messages=[
|
| 273 |
+
{"role": "system",
|
| 274 |
+
"content": "You are a world-class fantasy writer. Your job is to write a detailed storyline,"
|
| 275 |
+
" complete with chapters, for a fantasy novel. "
|
| 276 |
+
"Don't be flowery -- you want to get the message across in as few words as possible. "
|
| 277 |
+
"But those words should contain lots of information. Please respond in Chinese"},
|
| 278 |
+
{"role": "user",
|
| 279 |
+
"content": f'Write a fantastic storyline with {num_chapters} chapters and high-level details based on this plot:'
|
| 280 |
+
f' {prompt}.\n\nDo it in this list of dictionaries format {json_format}.'
|
| 281 |
+
f' And Please respond in Chinese. The response content must be in standard JSON format, without any prefixes and special symbols.'}
|
| 282 |
+
]
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
print_step_costs(response, llm_model_name)
|
| 286 |
+
|
| 287 |
+
improved_response = completion_with_backoff(
|
| 288 |
+
model=llm_model_name,
|
| 289 |
+
messages=[
|
| 290 |
+
{"role": "system",
|
| 291 |
+
"content": "You are a world-class fantasy writer. "
|
| 292 |
+
"Your job is to take your student's rough initial draft of the storyline of a fantasy novel in Chinese, "
|
| 293 |
+
"and rewrite it to be significantly better. Please respond in Chinese"},
|
| 294 |
+
{"role": "user",
|
| 295 |
+
"content": f"Here is the draft storyline they wrote: {response['choices'][0]['message']['content']}\n\nNow, "
|
| 296 |
+
f"rewrite the storyline in Chinese, in a way that is far superior to your student's version. "
|
| 297 |
+
f"It should have the same number of chapters, "
|
| 298 |
+
f"but it should be much improved in as many ways as possible. "
|
| 299 |
+
f"Remember to do it in this list of dictionaries format {json_format}. please respond in Chinese"
|
| 300 |
+
f' And please, only return the JSON content without any prefix.'}
|
| 301 |
+
]
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
print_step_costs(improved_response, llm_model_name)
|
| 305 |
+
|
| 306 |
+
return improved_response['choices'][0]['message']['content']
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def write_to_file(prompt, content):
|
| 310 |
+
# Create a directory for the prompts if it doesn't exist
|
| 311 |
+
if not os.path.exists('prompts'):
|
| 312 |
+
os.mkdir('prompts')
|
| 313 |
+
|
| 314 |
+
# Replace invalid characters for filenames
|
| 315 |
+
valid_filename = ''.join(c for c in prompt if c.isalnum() or c in (' ', '.', '_')).rstrip()
|
| 316 |
+
file_path = f'prompts/{valid_filename}.txt'
|
| 317 |
+
|
| 318 |
+
with open(file_path, 'w', encoding='utf-8') as f:
|
| 319 |
+
f.write(content)
|
| 320 |
+
|
| 321 |
+
print(f'Output for prompt "{prompt}" has been written to {file_path}\n')
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def write_fantasy_novel(prompt, num_chapters, writing_style, claude_true=False, model_name="gpt-3.5-turbo-16k"):
|
| 325 |
+
global llm_model_name
|
| 326 |
+
llm_model_name = model_name
|
| 327 |
+
|
| 328 |
+
# 每本小说生成一个唯一的uuid
|
| 329 |
+
novel_id = generate_uuid()
|
| 330 |
+
|
| 331 |
+
plots = generate_plots(prompt)
|
| 332 |
+
print('generated plots')
|
| 333 |
+
print(f'【plots】: {plots}\n\n')
|
| 334 |
+
|
| 335 |
+
best_plot = select_most_engaging(plots)
|
| 336 |
+
print('selected best plot')
|
| 337 |
+
print(f'【best_plot】: {best_plot}\n\n')
|
| 338 |
+
|
| 339 |
+
improved_plot = improve_plot(best_plot)
|
| 340 |
+
print('plot improved')
|
| 341 |
+
print(f'【improved_plot】: {improved_plot}\n\n')
|
| 342 |
+
time.sleep(20)
|
| 343 |
+
|
| 344 |
+
title = get_title(improved_plot)
|
| 345 |
+
print('title generated')
|
| 346 |
+
print(f'【title】: {title}\n\n')
|
| 347 |
+
|
| 348 |
+
storyline = generate_storyline(improved_plot, num_chapters)
|
| 349 |
+
print('storyline generated')
|
| 350 |
+
print(f'【storyline】: {storyline}\n\n')
|
| 351 |
+
|
| 352 |
+
chapter_titles = ast.literal_eval(storyline)
|
| 353 |
+
print(f'【chapter_titles】: {chapter_titles}\n\n')
|
| 354 |
+
|
| 355 |
+
novel = f"Storyline:\n{storyline}\n\n"
|
| 356 |
+
|
| 357 |
+
first_chapter = write_first_chapter(storyline, chapter_titles[0], writing_style.strip(), claude_true)
|
| 358 |
+
print('first chapter written')
|
| 359 |
+
save_novel_chapter(novel_id, 0, list(chapter_titles[0])[0], first_chapter)
|
| 360 |
+
print(f'【first_chapter】: {first_chapter}\n\n')
|
| 361 |
+
time.sleep(20)
|
| 362 |
+
|
| 363 |
+
novel += f"Chapter 1:\n{first_chapter}\n"
|
| 364 |
+
chapters = [first_chapter]
|
| 365 |
+
|
| 366 |
+
for i in range(num_chapters - 1):
|
| 367 |
+
print(f"Writing chapter {i + 2}...") # + 2 because the first chapter was already added
|
| 368 |
+
time.sleep(30)
|
| 369 |
+
|
| 370 |
+
chapter = write_chapter(novel, storyline, chapter_titles[i + 1], claude_true)
|
| 371 |
+
try:
|
| 372 |
+
if len(str(chapter)) < 100:
|
| 373 |
+
time.sleep(30)
|
| 374 |
+
print('Length minimum not hit. Trying again.')
|
| 375 |
+
chapter = write_chapter(novel, storyline, chapter_titles[i + 1], claude_true)
|
| 376 |
+
except:
|
| 377 |
+
time.sleep(20)
|
| 378 |
+
chapter = write_chapter(novel, storyline, chapter_titles[i + 1], claude_true)
|
| 379 |
+
|
| 380 |
+
novel += f"Chapter {i + 2}:\n{chapter}\n"
|
| 381 |
+
chapters.append(chapter)
|
| 382 |
+
print(f'【Chapter_{i + 2}】: {chapter}\n\n')
|
| 383 |
+
save_novel_chapter(novel_id, (i+1), list(chapter_titles[i + 1])[0], chapter)
|
| 384 |
+
|
| 385 |
+
return novel, title, chapters, chapter_titles
|