Compare commits

..

13 Commits

Author SHA1 Message Date
49c8d9b606 fix summary parsing
Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-23 10:19:36 -04:00
de0737f641 fix summary
Closes #1

Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-23 10:04:30 -04:00
50410c7910 fix ci?
Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-23 09:28:59 -04:00
386823ef0e no
Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-23 08:07:18 -04:00
9175904b45 make CI happy
Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-23 08:06:43 -04:00
f4f9109519 use gitea npm package for plottoriffic
Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-23 07:57:42 -04:00
495b044fc2
Update node.js.yml 2023-04-23 07:57:09 -04:00
b1bc889a3b
Create node.js.yml 2023-04-23 07:55:26 -04:00
0b40fd2c12
Create CODE_OF_CONDUCT.md 2023-04-18 12:04:31 -04:00
88951b1646 paper
Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-15 16:21:39 -04:00
9cd45747e1 index: command to rewrite an entire chapter
Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-15 07:56:21 -04:00
b264b0b467 index: generate HTML, discern wordcount
Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-15 07:56:00 -04:00
508ab9cb2c book: 6 scenes per chapter
Signed-off-by: Xe Iaso <me@xeiaso.net>
2023-04-15 07:55:29 -04:00
15 changed files with 467 additions and 33 deletions

25
.github/workflows/node.js.yml vendored Normal file
View File

@ -0,0 +1,25 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'npm'
- run: npm config set -- '//tulpa.dev/api/packages/ebooks/npm/:_authToken' "${{ secrets.GITEA_TOKEN }}"
- run: npm ci
- run: npm run build --if-present
- run: npm test

1
.npmrc Normal file
View File

@ -0,0 +1 @@
@ebooks:=https://tulpa.dev/api/packages/ebooks/npm/

128
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
me@xeiaso.net.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@ -46,7 +46,7 @@
'';
};
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [ nodejs-18_x pandoc typstWithIosevka ];
buildInputs = with pkgs; [ nodejs-18_x pandoc typstWithIosevka calibre ];
shellHook = ''
export PATH="$PATH":$(pwd)/node_modules/.bin
'';

26
package-lock.json generated
View File

@ -1,16 +1,16 @@
{
"name": "@xeserv/automuse",
"name": "@ebooks/automuse",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@xeserv/automuse",
"name": "@ebooks/automuse",
"version": "0.0.1",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@ebooks/plottoriffic": "^2.2.0",
"@kotofurumiya/th-namegen": "^1.1.3",
"@xeserv/plottoriffic": "^2.2.0",
"commander": "^10.0.0",
"dotenv": "^16.0.3",
"execa": "^7.1.1",
@ -122,6 +122,15 @@
"node": ">=4"
}
},
"node_modules/@ebooks/plottoriffic": {
"version": "2.2.0",
"resolved": "https://tulpa.dev/api/packages/ebooks/npm/%40ebooks%2Fplottoriffic/-/2.2.0/plottoriffic-2.2.0.tgz",
"integrity": "sha512-33vOQX+QZL3Nk6SHYpn+k21oWbY1DvGin4e+NXHumt7vxJojGIpgNYSawDxX6gk8A7bfOTi8LXPXYTdJejGREg==",
"license": "GPL-3.0",
"dependencies": {
"seedrandom": "^2.4.3"
}
},
"node_modules/@kotofurumiya/th-namegen": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@kotofurumiya/th-namegen/-/th-namegen-1.1.3.tgz",
@ -138,17 +147,6 @@
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz",
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw=="
},
"node_modules/@xeserv/plottoriffic": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@xeserv/plottoriffic/-/plottoriffic-2.2.0.tgz",
"integrity": "sha512-/T9IHZVYsdEwQx4m2RJbf1vgnO8N9wGFJ43MQRizfBO0YcUd2gkySVxfJ/mCmZkagdDCfhfXYfQTvJCaBfTwgA==",
"dependencies": {
"seedrandom": "^2.4.3"
},
"engines": {
"node": ">=6.9.5"
}
},
"node_modules/aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",

View File

@ -1,9 +1,10 @@
{
"name": "@xeserv/automuse",
"name": "@ebooks/automuse",
"version": "0.0.1",
"description": "Xe's automatic novel generation muse using Plotto and ChatGPT",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "tsc && node dist/index.js",
"test": "mocha",
"prepare": "husky install"
@ -28,8 +29,8 @@
"typescript": "^5.0.4"
},
"dependencies": {
"@ebooks/plottoriffic": "^2.2.0",
"@kotofurumiya/th-namegen": "^1.1.3",
"@xeserv/plottoriffic": "^2.2.0",
"commander": "^10.0.0",
"dotenv": "^16.0.3",
"execa": "^7.1.1",

1
paper/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.pdf

View File

@ -15,6 +15,33 @@
year = {2016},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/justinoverton/plottoriffic}},
howpublished = {https://github.com/justinoverton/plottoriffic},
commit = {3f23fabae9dafef1bb3c1733f478f980e660362c}
}
@misc{Coetzee2023GPT4,
title = {Generating a full-length work of fiction with GPT-4},
author = {Coetzee, Chiara},
howpublished = {https://medium.com/@chiaracoetzee/generating-a-full-length-work-of-fiction-with-gpt-4-4052cfeddef3},
note = {Accessed: 2023-04-15}
}
@misc{NaNoWriMoRules,
title = {Why 50,000 words? And how do you define “novel”?},
author = {National Novel Writing Month},
note = {Accessed: 2024-04-15}
}
@misc{chatgpt,
author = {ChatGPT},
year = {2023},
note = {OpenAI. Accessed on April 15, 2023. chat.openai.com/chat}
}
@misc{Olson2022,
title={Contrepreneurs: The mikkelsen twins},
url={https://youtu.be/biYciU1uiUw},
journal={YouTube},
year={2022},
month={Sep}
}

158
paper/main.typ Normal file
View File

@ -0,0 +1,158 @@
#import "template.typ": conf
#show: doc => conf(
title: [
Automuse: A System for Generating Pulp Novels
],
authors: (
(
name: "Cadey A. Ratio",
affiliation: "xn--g28h",
email: "cadey@xeserv.us",
),
(
name: "Nicole Brennan",
affiliation: "xn--g28h",
email: "twi@xeserv.us",
),
(
name: "Jessica Williams",
affiliation: "xn--g28h",
email: "jess@xeserv.us",
),
(
name: "Ashley Kaplan",
affiliation: "xn--g28h",
email: "ashe@xeserv.us",
),
(
name: "Stephanie Williams",
affiliation: "xn--g28h",
email: "phi@xeserv.us",
),
(
name: "Ma Insa",
affiliation: "xn--g28h",
email: "mai@xeserv.us",
),
),
abstract: [
A novel approach to generating fiction novels using a combination of Plotto, a system of plot formulas, and GPT-4, a state-of-the-art language model is presented. An eBook publication pipeline that automates the process of creating and formatting eBooks from the generated text is also described. The aim is to explore the potential and limitations of using artificial intelligence for creative writing, as well as to provide a tool for amusement and experimentation.
],
doc,
)
= Introduction
Modern advancements in large language models such as GPT-4#cite("openai2023gpt4") present many opportunities when used creatively. There have been a few attempts at doing this such as *Echoes of Atlantis*#cite("Coetzee2023GPT4") which used the ChatGPT#cite("chatgpt") web UI to synthesize prose to fill a novel, but none of these options were sufficiently automated for the author's tastes.
The goal of Automuse is to be able to fabricate these novels in minutes with little or no human intervention. As such, existing processes and published prior works were insufficient, requiring Automuse to be created.
Automuse is distributed as a GitHub repository at #link("https://github.com/Xe/automuse") for anyone to download or
attempt to use. Automuse wraps the following tools:
- Plottoriffic to generate the overall premise of a story and to name the main dramatis personae.
- The ChatGPT API to generate novel summary information, chapter summary information, and the prose of the novel.
- Stable Diffusion to generate cover art for publication.
- Pandoc to take generated prose and stitch it together into an eBook.
== Motivation
The authors discovered Plotto#cite("cook_1928"), a kind of algebra for generating the overall plot structure of pulp novels. This was written by William Cook, a man affectionately known as "the man who deforested Canada", who had an impressive publishing record at up to one entire novel written every week.
The authors wanted to find out if such a publishing pace could be met using the ChatGPT API. After experimentation and repair of plottoriffic#cite("Overton2016"), a Node.js package to implement Plotto's rule evaluation engine, Automuse was created.
== Results
According to the National November Writing Month rules#cite("NaNoWriMoRules"), the works of Automuse count as "novels". In testing, the program has been able to produce works of over 50,000 words (usually by a margin of 5-10 percent). The outputs of the program have been described as "hilarious", "partially nonsensical", and overall they have left readers wanting more somehow.
The authors of this paper consider this to be a success, though they note that future research is required to ascertain as to why readers have an affinity towards the AI generated content.
= Methodology
When writing novels, generally a human author starts by creating the premise of a novel, the major actors and their functions, the overall motivations, and the end result of the story. Plotto is a system that helps you do all of this by following a series of rules to pick a core conflict and then flesh things out with details. As an example, here is the core plot summary that Potto created for Network Stranded:
> Enterprise / Misfortune: Meeting with Misfortune and Being Cast Away in a Primitive, Isolated, and Savage Environment \
> \
> A Lawless Person \
> \
> Ismael takes a sea voyage in the hope of recovering aer health Ismael, taking a sea voyage, is shipwrecked and cast away on a desert island \
> \
> Ismael, of gentle birth and breeding, is isolated in a primitive, uninhabited wilderness, and compelled to battle with Nature for aer very existence \
> \
> Ismael, without food or water, is adrift in a small boat at sea \
> \
> Comes finally to the blank wall of enigma.
It's worth noting that Plotto is very much a product of its time. Plotto was written in the late 1920's and as such the information it generates is very dated and can sometimes generate things that are seen as problematic in modern sensibilities. Luckily, ChatGPT seems to sand away this roughness and is able to fabricate a better premise. All the pronouns are replaced with ae/aer because that is a decision that the creator of Plottoriffic made at some point.
== Summary Generation
From this description, ChatGPT is used to create a plot summary for the novel. These summaries look like this:
> After a disastrous turn of events, software engineer, Mia, finds herself stranded on a deserted island with no communication to the outside world. Mia uses her knowledge of peer to peer networks to create a makeshift communication system with other stranded individuals around the world, all connected by the same network. Together, they navigate survival and search for a way back to civilization while facing challenges posed by the island.
Normally GPT "hallucinations" (or when the model generates grammatically valid nonsense) are seen as an impediment or something to avoid. In this case, hallucinations are actually useful, as the summaries from Plotto are rarely enough information to fabricate believable stories. It was intended as an aid to the writing process, not a replacement for it.
In the same prompt, ChatGPT also creates a list of chapters for the novel with a high level summary of the events that happen in them. Here is the chapter list for Network Stranded:
- Disaster Strikes - Mia's company experiences a catastrophic network failure leading to her being stranded on an island.
- Stranded - Mia wakes up on a deserted island with limited supplies and no way to communicate.
- Building Connections - Mia develops a peer to peer network to connect with other people stranded around the world.
- Challenges of Survival - Mia and the other stranded individuals must navigate the hardships of surviving on the island.
- Exploration - Mia and a small group of stranded people head out to explore the island.
- Uncovering Secrets - During their exploration, Mia and the group discover hidden secrets about the island.
- Frayed Relationships - As resources begin to dwindle, tensions rise among the stranded survivors.
- Hopeful Discoveries - Mia receives a signal on her makeshift communication system, offering hope for rescue.
- Setbacks - Mia experiences a crushing setback in her plans for rescue.
- Moving Forward - Mia refuses to give up and formulates a new plan for rescue.
- Unexpected Allies - Mia and the other stranded survivors meet another group of people on the island who agree to help with their rescue.
- Facing Obstacles - Mia and the combined group must face obstacles and dangers as they try to implement their rescue plan.
- Breaking Through - After a grueling journey and setbacks, the survivors finally make a breakthrough in their rescue efforts.
- Homecoming - Mia and the other survivors return to civilization and adjust to life back in society.
- The Aftermath - Mia reflects on her experiences and the impact of the peer to peer network on their survival and rescue.
These chapter names and descriptions are fed into ChatGPT with the novel summary to create a list of scenes with major events in them. Here is the list of scenes for Chapter 1: "Disaster Strikes":
- Mia frantically tries to contact someone for help, but her phone and computer are dead. She decides to go outside to search for a signal, but realizes she's on a deserted island.
- As Mia tries to collect herself, she meets Ismael, who is also stranded on the island. They introduce themselves and discuss possible ways to survive.
- Mia remembers her knowledge about peer to peer networks and brainstorms a plan to create a makeshift communication system with other stranded individuals around the world, all connected by the same network.
- Mia and Ismael team up to scavenge for resources and build the communication system. They search for anything that could be used to amplify the signal, such as metal objects and wires.
- They encounter a danger while searching for materials: venomous snakes. Mia and Ismael must use their survival skills to avoid getting bitten.
- As the sun sets, Mia and Ismael finalize the communication system and connect with other stranded individuals on the network. They share their stories and discuss possible ways to get back home.
These scene descriptions are fed into ChatGPT to generate plausible prose to describe the novel. The main innovation of this part is that ChatGPT is few-shot primed to continue each scene after initial writing. If ChatGPT emitted a scene such as:
> As the helicopter landed, Ismael saw the first human beings he had seen in days. They jumped down from the helicopter, and looked at him with a mix of pity and relief. Ismael couldn't believe it he was finally going home. \
> \
> As the helicopter took off, Ismael looked back at the deserted island, knowing that he had survived against all odds, but also knowing that he would never forget the terror and hopelessness that had left its mark on him forever.
The next prompt would be primed with the last paragraph, allowing ChatGPT to continue writing the story in a plausible manner. This does not maintain context or event contiunity, however the authors consider this to be a feature. When the authors have access to the variant of GPT-4 with an expanded context window, they plan to use this to generate more detailed scenes.
= Known Issues
Automuse is known to have a number of implementation problems that may hinder efforts to use it in a productive manner. These include, but are not limited to the following:
- Automuse uses GPT-3.5 to generate text. This has a number of problems and is overall unsuitable for making text that humans find aesthetically pleasing.
- Automuse uses Plotto as a source of plot generation. Plotto was created in 1928 and reflects many stereotypes of its time. Careful filtering of Plotto summaries is required to avoid repeating harmful cultural and social biases.
- Automuse does not maintain a context window for major events that occur during prose generation. This can create situations where events happen and then un-happen. This can be confusing for readers.
== Potential Industry Effects
According to Dan Olson's documentary about the predatory ghostwriting industry named Contrepreneurs: The Mikkensen Twins#cite("Olson2022"), the average pay rate for a ghostwriter for The Urban Writers can get as low as USD\$0.005 per word. Given that Automuse spends about USD\$0.20 to write about 50,000 words using GPT-3.5, this makes Automuse a significant cost reduction in the process for creating pulp novels, or about 1,250 times cheaper than hiring a human to perform the same job.
This would make Automuse an incredibly cost-effective solution for churning out novels at an industrial scale. With a total unit development cost of USD\$0.35 (including additional costs for cover design with Stable Diffusion, etc.), this could displace the lower end of the human-authored creative writing profession by a significant margin.
However, the quality of novels generated by Automuse is questionable at best. It falters and stubles with complicated contexts that haven't been written before. At one point in Network Stranded, the protagonist shares a secret to another character and that secret is never revealed to the reader.
It is worth noting that the conditions that writers for groups like The Urban Writers are absolutely miserable. If this technology manages to displace them, this may be a blessing in disguise. The conditions put upon writers to meet quotas and deadlines are unimaginably strict. This technology could act as a means of liberation for people forced to endure these harsh conditions, allowing them to pursue other ventures that may be better uses of their time and skills.
But, this would potentially funnel income away from them. In our capitalist society, income is required in order to afford basic necessities such as food, lodging, and clothing. This presents an ethical challenge that is beyond the scope of Automuse to fix.
If an Automuse novel manages to generate more than USD\$1 of income, this will represent a net profit. More sales means that there is more profit potential, as novel generation costs are fixed upon synthesis of the prose. This program is known to use a very small amount of resources, it is concievable that a system could be set up on a very cheap (USD\$50 or less) development board running a freely available Linux distribution and then automatically create novels on a weekly cadence for less than a total cost of USD\$5 per month.
= Conclusion
Automuse is a promising solution for people looking to experiment with the use of large language models such as GPT-3.5 or GPT-4 to generate fiction prose. Automuse's source code and selected outputs are made freely available to the public for inspection and inspiration of future downstream projects. The authors of this paper hope that Automuse is entertaining and encourage readers to engage with the novel #link("https://xena.greedo.xeserv.us/books/network-stranded.html")[Network Stranded] as an example of Automuse's capabilities. There is a promising future ahead.
#bibliography("citations.bib", style: "ieee")

42
paper/template.typ Normal file
View File

@ -0,0 +1,42 @@
#let conf(
title: none,
authors: (),
abstract: [],
doc,
) = {
set page(
paper: "us-letter",
header: align(
right + horizon,
text(font: "Iosevka Etoile Iaso", size: 9pt)[#title]
),
)
set par(justify: true)
set text(
font: "Iosevka Aile Iaso",
size: 9.8pt,
)
set align(center)
text(font: "Iosevka Etoile Iaso", size: 17pt)[#title]
let count = authors.len()
let ncols = calc.min(count, 6)
grid(
columns: (1fr,) * ncols,
row-gutter: 24pt,
..authors.map(author => text(font: "Iosevka Aile Iaso", size: 9pt)[
#author.name \
#author.affiliation \
#link("mailto:" + author.email)
]),
)
par(justify: false)[
#text(font: "Iosevka Etoile Iaso", size: 11pt)[*Abstract*] \
#abstract
]
set align(left)
columns(2, doc)
}

View File

@ -1,6 +1,6 @@
import * as fs from "node:fs/promises";
import { ChatCompletionRequestMessage, OpenAIApi } from "openai";
import PlotGenerator, { Plot } from "@xeserv/plottoriffic";
import PlotGenerator, { Plot } from "@ebooks/plottoriffic";
export const authorBio = `Yasomi Midori is a science fiction author who explores the themes of identity, memory, and technology in her novels. Her debut novel “The Memory Thief” was a critically acclaimed bestseller that captivated readers with its thrilling plot and complex characters. Yasomi also contributes to the Xe Iaso blog as the character Mimi, a hacker and activist who exposes the secrets of the powerful corporations that control the world. Yasomi was born and raised in Tokyo, Japan, where she developed a passion for reading and writing at an early age. She studied computer science and literature at the University of Tokyo, and worked as a software engineer before becoming a full-time writer. She lives in Kyoto with her husband and two cats.`;
@ -42,13 +42,14 @@ export const createAndParseSummary = async (
plot: Plot
): Promise<Summary> => {
console.log("generating plot summary");
const promptBase = [
"You are a Novel generation AI, write plot summaries for novels given a specified format.",
'- A two two five word novel title in the format of "Title: " followed by two newlines. (Examples: "Fresh Beginnings", "Jared\'s Adventure")',
'- A two to five word novel title in the format of "Title: " followed by two newlines. (Examples: "Fresh Beginnings", "Jared\'s Adventure")',
'- A detailed plot summary in the format of "Plot Summary: " followed by two newlines. The story MUST relate to peer to peer networks.',
'- A section titled "Chapter Summaries:" followed by two newlines which will follow a specified structure.',
"\t- A markdown style list of very detailed summaries of each chapter. Each must be 3 sentences and have a title for each chapter. There MUST be 15 chapters. Each chapter must tie in to the overaching plot summary. They should follow a specified format.",
'\t\t- Each chapter section should be in the format of "Chapter Name: Chapter summary." followed with a single newline.',
'\t\t- Each chapter section should be in the format of "Chapter Name: Chapter summary." followed with a single newline. DO NOT include the word "Chapter" or the chapter number. You MUST put them on the same line. DO NOT put them on separate lines.',
];
/* Create the messages array */
@ -65,7 +66,6 @@ export const createAndParseSummary = async (
role: "system",
content: `${message}`,
};
messages.push(systemObject);
});
@ -73,7 +73,7 @@ export const createAndParseSummary = async (
const summary = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: messages,
messages,
});
if (!!summary.data.usage) {
@ -84,12 +84,13 @@ export const createAndParseSummary = async (
}
const summaryText = summary.data.choices[0].message?.content;
console.log(summaryText);
await fs.writeFile(`${dirName}/summary.txt`, summaryText as string);
const titleRegex = /^Title: (.+)$/gm;
const plotSummaryRegex = /^Plot Summary: (.+)$/gm;
const chapterSummaryRegex = /^- (.+) ?- (.+)$/gm;
const chapterSummaryRegex = /^- (.+): (.+)$/gm;
let title = summaryText?.split("\n", 2)[0].split(titleRegex)[1] as string;
@ -109,7 +110,7 @@ export const createAndParseSummary = async (
ch.shift();
ch.pop();
return {
title: ch[0].slice(1, -1) as string,
title: ch[0] as string,
summary: ch[1] as string,
} as ChapterListItem;
}) as ChapterListItem[];
@ -149,7 +150,7 @@ export const createChapterScenes = async (
console.log(`creating chapter scene information for chapter ${ch.title}`);
const prompt =
`Given the following plot summary, character information, and chapter information, write descriptions of scenes that would happen in that chapter. End each description with two newlines. Write at least 4 scenes. DO NOT only write one scene. Use detail and be creative. DO NOT include the chapter title in your output. ONLY output the scenes separated by newlines like this.
`Given the following plot summary, character information, and chapter information, write descriptions of scenes that would happen in that chapter. End each description with two newlines. Write at least six to eight scenes. DO NOT only write one scene. Use detail and be creative. DO NOT include the chapter title in your output. ONLY output the scenes separated by newlines like this.
What happens first.
@ -202,7 +203,7 @@ export const writeChapterScene = async (
scene: string
): Promise<string> => {
const prompt =
`Given the following information, write the scene of the novel. Be detailed about the setting and character descriptions. End each paragraph with two newlines. Write many sentences. ONLY return the text of the novel.
`Given the following information, write the scene of the novel. Be detailed about the setting and character descriptions. End each paragraph with two newlines. Write many sentences. Write like Steven King or Margaret Mitchell. ONLY return the text of the novel.
` +
summary.characters.map((char) => `- ${char.name}: ${char.role}`).join("\n") +
`

View File

@ -1,7 +1,7 @@
import { Command } from "commander";
import * as dotenv from "dotenv";
import { Configuration, OpenAIApi } from "openai";
import { Plot } from "@xeserv/plottoriffic";
import { Plot } from "@ebooks/plottoriffic";
import { generateName } from "@kotofurumiya/th-namegen";
import * as fs from "node:fs/promises";
import { existsSync as fileExists } from "fs";
@ -207,17 +207,60 @@ title: "${summary.title}"
author: Midori Yasomi
rights: All rights reserved
language: en-US
cover-image: ${dir}/cover.jpg
---
`
);
await fs.writeFile(`${dir}/src/aboutAuthor.txt`, "---\n\n" + book.authorBio);
let args = ["-o", `${dir}/ebook.epub`, "--to", "epub", `${dir}/src/title.txt`];
args = args.concat(fnames.map((fname) => `${dir}/src/${fname}`));
args = args.concat([`${dir}/src/aboutAuthor.txt`]);
let files = [`${dir}/src/title.txt`];
files = files.concat(fnames.map((fname) => `${dir}/src/${fname}`));
files = files.concat([`${dir}/src/aboutAuthor.txt`]);
console.log(await execa("pandoc", args));
let args = ["-o", `${dir}/ebook.epub`, "--to", "epub"];
args = args.concat(files);
await execa("pandoc", args);
args = ["-o", `${dir}/ebook.html`, "--to", "html"];
args = args.concat(files);
await execa("pandoc", args);
args = ["--lua-filter", "./wordcount.lua"];
args = args.concat(files);
const { stdout } = await execa("pandoc", args);
console.log(stdout);
});
program
.command("rewriteChapter <dir> <chapter>")
.description("completely rewrite a single chapter, useful for fixing the generation process")
.action(async (dir, chapterStr) => {
await fs.mkdir(`${dir}/src`, { recursive: true });
const chapter = parseInt(chapterStr);
console.log({ dir, chapter });
const summary: book.Summary = JSON.parse(await fs.readFile(`${dir}/summary.json`, "utf8"));
const chapters: book.Chapter[] = JSON.parse(
await fs.readFile(`${dir}/chapterScenes.json`, "utf8")
);
const ch = await book.createChapterScenes(
dir,
openai,
summary,
summary.chapterList[chapter - 1]
);
chapters[chapter - 1] = ch;
await fs.writeFile(`${dir}/chapterScenes.json`, JSON.stringify(chapters));
for (let [sceneNum, scene] of ch.sceneDescriptions.entries()) {
sceneNum = sceneNum + 1;
console.log(await book.writeChapterScene(dir, openai, summary, ch, chapter, sceneNum, scene));
}
});
program

8
test/test.js Normal file
View File

@ -0,0 +1,8 @@
import * as assert from "assert";
describe("Array", function () {
describe("#indexOf()", function () {
it("should return -1 when the value is not present", function () {
assert.equal([1, 2, 3].indexOf(4), -1);
});
});
});

View File

@ -12,5 +12,6 @@
"types": ["node"],
"outDir": "dist"
},
"include": ["src/**/*", "tests/**/*", "types/**/*"],
"exclude": ["node_modules"]
}

View File

@ -1,4 +1,4 @@
declare module "@xeserv/plottoriffic" {
declare module "@ebooks/plottoriffic" {
export default PlotGenerator;
export type Character = {