File size: 9,016 Bytes
217acfe |
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
import time
import importlib
from core.draft_writer import DraftWriter
from core.plot_writer import PlotWriter
from core.outline_writer import OutlineWriter
from core.writer_utils import KeyPointMsg
from core.diff_utils import match_span_by_char
import copy
import types
def load_novel_writer(writer, setting) -> DraftWriter:
current_w_name = writer['current_w']
current_w = writer[current_w_name]
kwargs = dict(
xy_pairs=list(current_w.get('xy_pairs', [['', '']])),
model=setting['model'],
sub_model=setting['sub_model'],
)
kwargs['x_chunk_length'] = current_w['x_chunk_length']
kwargs['y_chunk_length'] = current_w['y_chunk_length']
match current_w_name:
case 'draft_w':
novel_writer = DraftWriter(**kwargs)
case 'outline_w':
novel_writer = OutlineWriter(**kwargs)
case 'chapters_w' | 'plot_w':
novel_writer = PlotWriter(**kwargs)
case _:
raise ValueError(f"unknown writer: {current_w_name}")
return novel_writer
def dump_novel_writer(writer, novel_writer, apply_chunks={}, cost=0, currency_symbol='¥'):
new_writer = copy.deepcopy(writer) # TODO: dump从设计角度上来说,不应该更改原有的writer,但是在此处copy可能更耗时
current_w_name = new_writer['current_w']
current_w = new_writer[current_w_name]
# if current_w_name == 'draft_w':
# assert isinstance(novel_writer, DraftWriter), "draft_w需要传入DraftWriter"
current_w['xy_pairs'] = list(novel_writer.xy_pairs)
current_w['current_cost'] = cost
current_w['currency_symbol'] = currency_symbol
#current_w['total_cost'] += current_w['current_cost']
current_w['apply_chunks'] = apply_chunks
return new_writer
def call_write_long_novel(writer, setting):
writer = copy.deepcopy(writer)
progress = writer['progress']
if not progress or True:
progress = dict(
cur_op_i = progress['cur_op_i'] if progress else 0,
ops = [
{
'before_eval': 'writer["current_w"] = "outline_w"',
'eval': 'call_write(writer, setting, False, "构思全书的大致剧情,并将其以一个故事的形式写下来,只写大致情节。")',
'title': '创作大纲',
'subtitle': '生成大纲'
},
{
'eval': 'call_accept(writer, setting)',
},
{
'eval': 'call_write(writer, setting, True, "对整个情节进行重写,使其更加有故事性。")',
'title': '创作大纲',
'subtitle': '润色大纲',
},
{
'eval': 'call_accept(writer, setting)',
},
# 下面是创作剧情
{
'before_eval': 'init_chapters_w(writer)',
'eval': 'call_write(writer, setting, False, "丰富其中的剧情细节。")',
'title': '创作剧情',
'subtitle': '生成剧情'
},
{
'eval': 'call_accept(writer, setting)',
},
{
'eval': 'call_write(writer, setting, True, "对情节进行重写,使其有更多的剧情细节,同时更加有具有故事性。")',
'title': '创作剧情',
'subtitle': '扩充剧情',
},
{
'eval': 'call_accept(writer, setting)',
},
# 下面是创作正文
{
'before_eval': 'init_draft_w(writer)',
'eval': 'call_write(writer, setting, False, "创作的是正文,而不是剧情,需要像一个小说家那样去描写这个故事。")',
'title': '创作正文',
'subtitle': '生成正文'
},
{
'eval': 'call_accept(writer, setting)',
},
{
'eval': 'call_write(writer, setting, True, "润色正文")',
'title': '创作正文',
'subtitle': '润色正文'
},
{
'eval': 'call_accept(writer, setting)',
}
]
)
# TODO: 考虑在init_plot时就给到上下文,类似rewrite_plot
title, subtitle = '', ''
for op in progress['ops']:
if 'title' not in op:
op['title'], op['subtitle'] = title, subtitle
else:
title, subtitle = op['title'], op['subtitle']
writer['progress'] = progress
yield writer
while progress['cur_op_i'] < len(progress['ops']):
current_op = progress['ops'][progress['cur_op_i']]
if 'before_eval' in current_op:
exec(current_op['before_eval'])
writer = yield from eval(current_op['eval'])
progress = writer['progress']
progress['cur_op_i'] += 1
yield writer # 当cur_op_i有更新时,也就标志着yield的是一个“稳定版本”的writer_state
return writer
def match_quote_text(writer, setting, quote_text):
novel_writer = load_novel_writer(writer, setting)
y_text = novel_writer.y
quote_text_span, match_ratio = match_span_by_char(y_text, quote_text)
if match_ratio > 0.5:
aligned_span, _ = novel_writer.align_span(y_span=quote_text_span)
return aligned_span, y_text[aligned_span[0]:aligned_span[1]]
else:
return None, ''
# 这是后端函数,接受前端writer_state的copy做为输入
# 返回的是修改后的writer_state,注意yield的值一般被用于前端展示执行的过程和进度
# 只有return值才会被前端考虑用于writer_state的更新
def call_write(writer, setting, auto_write=False, suggestion=None):
novel_writer = load_novel_writer(writer, setting)
current_w = writer[writer['current_w']]
current_w['xy_pairs'] = list(novel_writer.xy_pairs)
quote_span = writer['quote_span']
if auto_write:
assert quote_span is None, "auto_write模式下,不能有quote_text"
generator = novel_writer.auto_write()
else:
# TODO: writer.write 应该保证无论什么prompt,都能够同时适应y为空和y有值地情况
# 换句话说,就是虽然可以单列出一个“新建正文”,但用扩写正文也能实现同样的效果。
generator = novel_writer.write(suggestion, y_span=quote_span)
prompt_outputs = []
for kp_msg in generator:
if isinstance(kp_msg, KeyPointMsg):
# 如果要支持关键节点保存,需要计算一个编辑上的更改,然后在这里yield writer
yield kp_msg
continue
else:
chunk_list = kp_msg
current_cost = 0
apply_chunks = []
prompt_outputs.clear()
for output, chunk in chunk_list:
prompt_outputs.append(output)
current_text = ""
current_cost += output['response_msgs'].cost
currency_symbol = output['response_msgs'].currency_symbol
cost_info = f"\n(预计花费:{output['response_msgs'].cost:.4f}{output['response_msgs'].currency_symbol})"
if 'plot2text' in output:
current_text += f"正在建立映射关系..." + cost_info + '\n'
else:
current_text += output['text'] + cost_info + '\n'
apply_chunks.append((chunk, 'y_chunk', current_text))
new_writer = dump_novel_writer(writer, novel_writer, apply_chunks=apply_chunks, cost=current_cost, currency_symbol=currency_symbol)
new_writer['prompt_outputs'] = prompt_outputs
yield new_writer
# 这里是计算出一个编辑上的更改,方便前端显示,后续diff功能将不由writer提供,因为这是为了显示的要求
apply_chunks = []
for chunk, key, value in load_novel_writer(writer, setting).diff_to(novel_writer):
apply_chunks.append((chunk, key, value))
writer[writer['current_w']]['apply_chunks'] = apply_chunks
writer['prompt_outputs'] = prompt_outputs
return writer
def call_accept(writer, setting):
current_w_name = writer['current_w']
current_w = writer[current_w_name]
novel_writer = load_novel_writer(writer, setting)
for chunk, key, text in current_w['apply_chunks']:
novel_writer.apply_chunk(chunk, key, text)
writer = dump_novel_writer(writer, novel_writer)
return writer
|