library-tizing synopsis_generator

This commit is contained in:
2026-09-14 18:07:27 -06:00
parent 8708c896aa
commit 707776c12d
3 changed files with 91 additions and 93 deletions
+44
View File
@@ -0,0 +1,44 @@
local prompts = {}
prompts.synopsis_prompt = [[
Generate a lengthy novel synopsis from the following:
]]
prompts.scoring_prompt = [[
You are evaluating novel synopses for development priority. The goal is NOT to judge writing quality, grammar, or polish. The synopsis is only a rough idea. Assign an integer score from 1–100 for each category. Be extremely harsh with your scoring.
1. Hook
2. Originality
3. Memorability
4. Expansion Potential
5. Conflict Potential
6. Character Potential
7. Worldbuilding Potential
8. Emotional Potential
9. Curiosity
10. Overall Promise
Guidelines:
- Avoid clustering scores near the middle. Use the full 1–100 range.
- A score around 50 represents an average publishable premise.
- Scores above 85 should be rare and reserved for genuinely exceptional ideas.
- Scores below 25 should represent ideas with major conceptual weaknesses.
- Return only valid JSON.
Output format:
{
"hook": 0,
"originality": 0,
"memorability": 0,
"expansion_potential": 0,
"conflict_potential": 0,
"character_potential": 0,
"worldbuilding_potential": 0,
"emotional_potential": 0,
"curiosity": 0,
"overall_promise": 0
}
]]
return prompts
+31 -1
View File
@@ -32,7 +32,7 @@ else
} }
end end
utility.version = "1.6.0" utility.version = "1.6.0-modified"
-- WARNING: This will return "./" if the original script is called locally instead of with an absolute path! -- WARNING: This will return "./" if the original script is called locally instead of with an absolute path!
if arg[0] ~= nil then if arg[0] ~= nil then
utility.path = (arg[0]:match("@?(.*/)") or arg[0]:match("@?(.*\\)")) -- inspired by discussion in https://stackoverflow.com/q/6380820 utility.path = (arg[0]:match("@?(.*/)") or arg[0]:match("@?(.*\\)")) -- inspired by discussion in https://stackoverflow.com/q/6380820
@@ -235,6 +235,36 @@ utility.ls = function(...)
return utility.list(...) return utility.list(...)
end end
utility.tree = function(path, options, fn)
if type(options) == "function" then
fn = options
options = {}
end
utility.list(path or ".", function(path_name)
if options.blacklist and options.blacklist[path_name] then return end
if options.whitelist and (not options.whitelist[path_name]) then return end
if utility.is_file(path_name) then
fn(path_name)
else
utility.tree(path .. utility.path_separator .. path_name, options, fn)
end
end)
end
utility.read_file = function(file_name)
return utility.open(file_name, "r", function(file)
return file:read("*all")
end)
end
utility.write_file = function(file_name, text)
return utility.open(file_name, "w", function(file)
file:write(text)
file:write("\n")
end)
end
utility.path_exists = function(file_name) utility.path_exists = function(file_name)
local file = io.open(file_name, "r") local file = io.open(file_name, "r")
if file then file:close() return true else return false end if file then file:close() return true else return false end
+16 -92
View File
@@ -2,68 +2,17 @@
package.path = (arg[0]:match("@?(.*/)") or arg[0]:match("@?(.*\\)")) .. "lib" .. package.config:sub(1, 1) .. "?.lua;" .. package.path package.path = (arg[0]:match("@?(.*/)") or arg[0]:match("@?(.*\\)")) .. "lib" .. package.config:sub(1, 1) .. "?.lua;" .. package.path
local utility = require "utility" local utility = require "utility"
local json = utility.require("dkjson") local json = utility.require("dkjson")
local prompts = utility.require("prompts")
local default_model = "gemma4:12b-mlx" local default_model = "gemma4:12b-mlx"
local minimum_bytes = 1000 local minimum_bytes = 1000
local maximum_bytes = 40000 local maximum_bytes = 40000
local synopsis_prompt = [[
Generate a lengthy novel synopsis from the following:
]]
local scoring_prompt = [[
You are evaluating novel synopses for development priority. The goal is NOT to judge writing quality, grammar, or polish. The synopsis is only a rough idea. Assign an integer score from 1–100 for each category. Be extremely harsh with your scoring.
1. Hook
2. Originality
3. Memorability
4. Expansion Potential
5. Conflict Potential
6. Character Potential
7. Worldbuilding Potential
8. Emotional Potential
9. Curiosity
10. Overall Promise
Guidelines:
- Avoid clustering scores near the middle. Use the full 1–100 range.
- A score around 50 represents an average publishable premise.
- Scores above 85 should be rare and reserved for genuinely exceptional ideas.
- Scores below 25 should represent ideas with major conceptual weaknesses.
- Return only valid JSON.
Output format:
{
"hook": 0,
"originality": 0,
"memorability": 0,
"expansion_potential": 0,
"conflict_potential": 0,
"character_potential": 0,
"worldbuilding_potential": 0,
"emotional_potential": 0,
"curiosity": 0,
"overall_promise": 0
}
]]
local read_all = function(file_name)
return utility.open(file_name, "r", function(file)
return file:read("*all")
end)
end
local write_all = function(file_name, text)
return utility.open(file_name, "w", function(file)
file:write(text)
file:write("\n")
end)
end
local files local files
if utility.path_exists("PRIVATE_DATA/file_list.json") then if utility.path_exists("PRIVATE_DATA/file_list.json") then
files = json.decode(read_all("PRIVATE_DATA/file_list.json")) files = utility.load_data("PRIVATE_DATA/file_list.json")
else else
files = {} files = {}
end end
@@ -140,55 +89,30 @@ local send_prompt = function(text, model)
return output, thinking return output, thinking
end end
local tree
tree = function(path, fn)
utility.list(path or ".", function(path_name)
local blacklist = {
[".git"] = true,
[".gitkeep"] = true,
[".gitignore"] = true,
[".DS_Store"] = true,
}
if blacklist[path_name] then return end
if utility.is_file(path_name) then
fn(path_name)
else
tree(path .. utility.path_separator .. path_name, fn)
end
end)
end
local get_file_size = function(file_name)
local file = io.open(file_name, "rb")
if file then
local size = file:seek("end")
file:close()
return size
end
end
local refresh_file_list = function() local refresh_file_list = function()
local blacklist = { -- I'm only blacklisting binary formats because funny results happen with really invalid texts local blacklist = { -- I'm only blacklisting binary formats because funny results happen with really invalid texts
"jpg", "mp4", "pdf", "png", "webp", "jpeg", "gif", "jpg", "mp4", "pdf", "png", "webp", "jpeg", "gif",
} for _, name in ipairs(blacklist) do blacklist[name] = true end } for _, name in ipairs(blacklist) do blacklist[name] = true end
local new_files_list = {} local new_files_list = {}
tree("PRIVATE_DATA/notebook", function(file_name) utility.tree("PRIVATE_DATA/notebook", {
blacklist = utility.enumerate{".git", ".gitignore", ".gitkeep", ".DS_Store"}
}, function(file_name)
local _, _, extension = utility.split_path_components(file_name) local _, _, extension = utility.split_path_components(file_name)
if not blacklist[extension] then if not blacklist[extension] then
new_files_list[#new_files_list + 1] = file_name new_files_list[#new_files_list + 1] = file_name
end end
end) end)
files = new_files_list files = new_files_list
write_all("PRIVATE_DATA/file_list.json", json.encode(new_files_list, { indent = true })) utility.save_data(new_files_list, "PRIVATE_DATA/file_list.json")
end end
local generate_and_score = function(file_name, text) local generate_and_score = function(file_name, text)
print("Writing synopsis...") print("Writing synopsis...")
local synopsis = send_prompt(synopsis_prompt .. text) local synopsis = send_prompt(prompts.synopsis_prompt .. text)
print(synopsis) print(synopsis)
print("Scoring synopsis...") print("Scoring synopsis...")
local scoring, thinking = send_prompt(scoring_prompt .. synopsis) local scoring, thinking = send_prompt(prompts.scoring_prompt .. synopsis)
print(scoring) print(scoring)
local scoring_decoded = json.decode(scoring) -- likely will not work because it consistently returns Markdown instead of JSON local scoring_decoded = json.decode(scoring) -- likely will not work because it consistently returns Markdown instead of JSON
if not scoring_decoded then if not scoring_decoded then
@@ -201,7 +125,7 @@ local generate_and_score = function(file_name, text)
thinking = thinking, thinking = thinking,
} }
write_all("PRIVATE_DATA/synopses/" .. utility.uuid() .. ".json", json.encode(object, { indent = true })) utility.save_data(object, "PRIVATE_DATA/synopses/" .. utility.uuid() .. ".json")
end end
@@ -218,12 +142,12 @@ if arg[1] == "repair_synopsis_exports" then
utility.list(path, function(path_name) utility.list(path, function(path_name)
path_name = path .. utility.path_separator .. path_name path_name = path .. utility.path_separator .. path_name
if path_name:find("%.json") then if path_name:find("%.json") then
local object = json.decode(read_all(path_name)) local object = utility.load_data(path_name)
if type(object.scoring) == "table" then return end -- don't fuck with working pieces if type(object.scoring) == "table" then return end -- don't fuck with working pieces
local decoded = json.decode(strip_markdown_codeblock(object.scoring)) local decoded = json.decode(strip_markdown_codeblock(object.scoring))
if decoded then if decoded then
object.scoring = decoded object.scoring = decoded
write_all(path_name, json.encode(object, { indent = true, })) utility.save_data(object, path_name)
end end
end end
end) end)
@@ -237,7 +161,7 @@ if arg[1] == "export_ordered_list_of_prompts" then
utility.list(path, function(path_name) utility.list(path, function(path_name)
local full_path = path .. utility.path_separator .. path_name local full_path = path .. utility.path_separator .. path_name
if path_name:find("%.json") then if path_name:find("%.json") then
local object = json.decode(read_all(full_path)) local object = utility.load_data(full_path)
items[path_name] = object items[path_name] = object
if type(object.scoring) == "table" then if type(object.scoring) == "table" then
local s = object.scoring local s = object.scoring
@@ -269,7 +193,7 @@ if arg[1] == "export_ordered_list_of_prompts" then
output[#output + 1] = "# " .. v.path_name .. " (" .. v.total_score .. ")\n\n" .. table.concat(tab, "\n") .. "\n" output[#output + 1] = "# " .. v.path_name .. " (" .. v.total_score .. ")\n\n" .. table.concat(tab, "\n") .. "\n"
output[#output + 1] = "## Scoring\n\n```json\n" .. json.encode(item.scoring, { indent = true, }) .. "\n```\n" output[#output + 1] = "## Scoring\n\n```json\n" .. json.encode(item.scoring, { indent = true, }) .. "\n```\n"
end end
write_all("PRIVATE_DATA/Ordered Synopses.md", table.concat(output, "\n")) utility.write_file("PRIVATE_DATA/Ordered Synopses.md", table.concat(output, "\n"))
os.execute("pandoc \"PRIVATE_DATA/Ordered Synopses.md\" -o \"PRIVATE_DATA/Ordered Synopses.epub\"") os.execute("pandoc \"PRIVATE_DATA/Ordered Synopses.md\" -o \"PRIVATE_DATA/Ordered Synopses.epub\"")
os.exit(0) os.exit(0)
end end
@@ -283,9 +207,9 @@ end
while true do while true do
print("Selecting a file...") print("Selecting a file...")
local file_name = files[math.random(1, #files)] local file_name = files[math.random(1, #files)]
local file_size = get_file_size(file_name) local file_size = utility.file_size(file_name)
if file_size > minimum_bytes and file_size <= maximum_bytes then if file_size > minimum_bytes and file_size <= maximum_bytes then
local text = read_all(file_name) local text = utility.read_file(file_name)
text = strip_frontmatter(text) text = strip_frontmatter(text)
if #text > minimum_bytes and #text <= maximum_bytes then if #text > minimum_bytes and #text <= maximum_bytes then
print(file_name .. " chosen.") print(file_name .. " chosen.")