Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
|
||||||
|
第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章 :新世界的曙光
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
小说设定
|
||||||
|
|
||||||
|
小说名称: 星陨提瓦特
|
||||||
|
|
||||||
|
总字数走向: 约40万字,分为100章,每章约4000字。整体走向为主角经历成长、探索与冲突,逐渐揭开提瓦特大陆的秘密与危机,最终实现拯救与情感的圆满。
|
||||||
|
|
||||||
|
小说类型与基调: 本作为玄幻、穿越与冒险题材,主线围绕丰富的爱恨情仇展展开。基调相对轻松,同时穿插挑战与深思,旨在营造出一种既有趣又引人深思的氛围。
|
||||||
|
|
||||||
|
写作风格: 采用细腻、抒情的写作风格,重点描绘角色内心的探索与情感。叙事视角主要为第三人称全知视角,辅以第一人称的内心独白,以增强角色深度。
|
||||||
|
|
||||||
|
整体世界观: 故事设定在提瓦特大陆,这是一个拥有七个不同国度和独特元素力量的神秘幻想世界。历史与传说丰富,比如“天理之战”及各个神祇的传奇故事。科技与魔法并存,元素魔法高度发达,社会结构以王国与神明为核心,复杂的势力交错使人民生活受其影响。
|
||||||
|
|
||||||
|
核心内容梗概: 故事采用英雄之旅结构,主角星因意外穿越到提瓦特,面临神明失去信仰、元素失衡,国家动荡的空前危机。在寻找回家的过程中,星结识了来自不同国家的伙伴,体验各地人文风情,并逐渐揭示穿越的真相。最终,星与伙伴们合力对抗共同的敌人,重塑世界的平衡,也收获真挚的情感。
|
||||||
|
|
||||||
|
情节安排设想: 主线围绕星的成长与拯救提瓦特的冒险分为三个阶段:第一阶段是星的穿越与初识,以及角色冲突的引入;第二阶段是与伙伴的深入互动,逐步揭露敌人阴谋及情感发展;第三阶段是最终决战与角色命运交织。副线聚焦其他角色的故事,展示他们与星的命运交织,为故事营造丰富的世界感。
|
||||||
|
|
||||||
|
角色关系与主要角色设定:
|
||||||
|
|
||||||
|
1. 星(主角): 一名勇敢、好奇且善良的年轻人,满怀探索未知的欲望,面临对归属感的挣扎。在拯救提瓦特的过程中,她渴望成为真正的英雄,同时也处理与伙伴间复杂的情感关系。
|
||||||
|
|
||||||
|
2. 伊尔(冷静的魔法师): 理智、谨慎,拥有强大师法的他在经历失败后变得冷淡,内心深处隐藏痛苦。希望能通过知识与能力解决危机,努力解脱自己的内心痛楚。与星的理想与现实相悖,易引发冲突。
|
||||||
|
|
||||||
|
3. 莱娜(隐秘的元素使者): 神秘而强大的她深谙元素魔法,在寻找与星的关系中,探索自己的身份之谜。与伊尔之间存在信任危机,随着故事发展,二人将面临信任与情感的考验。
|
||||||
|
|
||||||
|
暗线与伏笔构思:
|
||||||
|
|
||||||
|
1. 星的过往: 星对旧生活的怀念与痛苦逐渐显现,推动她在救助他人和回归原生活之间的抉择,进而引发与伙伴之间的深入讨论。
|
||||||
|
|
||||||
|
2. 伊尔的秘密: 伊尔对过去的隐瞒以及与敌对势力的牵连,在关键时刻暴露,导致团队关系动摇,让星更深刻地理解他的痛苦。
|
||||||
|
|
||||||
|
3. 莱娜的使命: 莱娜的神秘身份及意图逐步显露,最终必须面对身份的真实,仅在关键时刻,有赖于星与伊尔的支持,坚定她内心的归属感。
|
||||||
|
|
||||||
|
4. 星与伊尔的理念冲突: 星与伊尔在道德取向上发生摩擦,在选择重大事件时,两人的冲突达到顶峰,最终通过事件的解决促进彼此之间的理解与成长。
|
||||||
|
|
||||||
|
在这些设定中,角色的动机与暗线及伏笔相互交织,为整个故事增添了复杂性与紧张感,确保每一位角色的成长与情感发展贯穿始终,丰富了提瓦特大陆的冒险传奇。
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
星缓缓睁开眼睛,意识到自己正躺在一片陌生的土地上。四周是葱茏的树木,阳光透过树叶洒下斑驳的光影,微风轻拂,带来一丝清新的气息。她试图站起身来,身体却感到无力,仿佛刚经历了一场翻天覆地的梦境。迷茫与恐惧瞬间涌上心头——她究竟身处何地?
|
||||||
|
|
||||||
|
“别 panicking,星。”她自言自语,努力让自己冷静下来。可无论她怎么深呼吸,心中的慌乱与不安依旧如影随形。刚才她还在自己的家中,思绪被一扇打开的窗户吸引,目光追随着飞过的鸟儿,突然遭遇了一道神秘的光芒,她的意识就在那瞬间被撕扯了开来。而现在,她却身处于这个陌生的世界,这一切都让她感到无比的恐慌。
|
||||||
|
|
||||||
|
尝试坐起身来,星环顾四周,发现这片森林似乎与她的记忆截然相反。树木高耸入云,树干粗壮而苍翠,叶子则在微风中轻轻摇晃,发出沙沙的声响。然而,这些美丽的自然景象并没有为她带来一丝安慰——相反,她觉得一种不安的气息弥漫在空气中,仿佛这片森林隐藏着某种不为人知的秘密。
|
||||||
|
|
||||||
|
星的心中掠过一丝奇异的感觉,似乎这片土地上有着某种她无法理解的力量在流动。她强迫自己站起身,决定探索这个世界。若想办法找到回家的路,先得了解这个地方。她开始慢慢前行,脚下的土地干燥而坚硬,偶尔踩到几根枯枝,发出清脆的声响。
|
||||||
|
|
||||||
|
随着深入森林,她注意到周围的植物似乎散发着微弱的光芒,仿佛在回应着她的存在。星试图触摸那些植物,感受那股能量,却发现它们在她的手指间划过时变成了莹莹的光点,瞬间就消散了。她心中一紧,这显然不是普通的植物,她的直觉告诉她,自己可能身处于一个充满魔法的地方。
|
||||||
|
|
||||||
|
就在这时,她的思绪被一阵低语声打断。星停下脚步,四处张望,试图找到声音的来源。声音似乎从四面八方传来,但却又似乎是从某个深渊中回荡而出,朦胧而模糊。
|
||||||
|
|
||||||
|
“是谁在说话?”星喃喃自语,然而没有人回应。她心中浮现出一丝不安,迫切想要找到声音的源头。正当她准备继续探索时,眼前突然闪过一道亮光。她的心一颤,抬头看去,透过树木的缝隙,她隐约看到一个身影,似乎在远处的空旷地带移动。
|
||||||
|
|
||||||
|
出于好奇,星忍不住向前走去,穿越过一片茂密的灌木丛。终于,她走出林间,来到了一个开阔的空地上。眼前的一幕让她惊讶不已——一座宏伟的法阵正镶嵌于地面,阵法复杂而美丽,闪烁着微光,似乎在向她召唤。
|
||||||
|
|
||||||
|
“这是什么?”星自言自语,心中充满了疑惑与好奇。她走到法阵边缘,感觉到一股温暖的气流从阵法中透出。星的手指微微颤动,不由自主地伸向了那个法阵,触碰到了那些复杂的符文。瞬间,她感到一阵剧烈的震动,法阵光芒大盛,星的身体被卷入其中,意识在瞬间模糊而又清晰。
|
||||||
|
|
||||||
|
“啊!”她惊呼,仿佛被一股无形的力量撕扯着。整个世界都在颠覆,光芒刺眼,四周的景象如同狂风中的飘絮般不断变换,星感到自己像是随风而舞的叶子,无法控制。就在意识即将消散的瞬间,她听到了那个低语声,竟然清晰而响亮:“命运的选择。”
|
||||||
|
|
||||||
|
然后,星失去了意识。
|
||||||
|
|
||||||
|
当她再次睁开眼睛时,发现自己坐在一块石头上,周围是一片开阔的草地,远处是连绵起伏的山脉,天空湛蓝,阳光温暖,仿佛一切都是那么美好。可在她心中,却依然残留着那股恐惧与不安。
|
||||||
|
|
||||||
|
“这是哪里?”她自言自语,四处打量。就在这时,一个阴影笼罩了她,星抬头一看,面前站着一个高大的男子,身穿长袍,手中握着法杖,面容冷峻,眼神中透着智慧与审视。
|
||||||
|
|
||||||
|
“你是谁?”星有些警惕地问道,虽然心中已经有了几分预感。
|
||||||
|
|
||||||
|
“我是伊尔,一个魔法师。”男子的声音低沉而有力,似乎带着某种压迫感,“你应该是从那个法阵中被传送过来的吧?”
|
||||||
|
|
||||||
|
星点点头,虽然内心依旧充满困惑,但她知道自己必须与眼前这个陌生人好好交流。“是的,我…我不知道自己为什么来到这里。”
|
||||||
|
|
||||||
|
伊尔微微皱眉,目光在她身上扫视,似乎在打量她的言语是否可信。“这片土地并不安全,你的到来可不是什么偶然。元素的平衡正在崩溃,神明的意志也逐渐淡去。”
|
||||||
|
|
||||||
|
“神明的意志?”星困惑地问道,心中浮现出关于提瓦特的传说与故事。她从小就听说过这个富饶而神秘的大陆,但从未想过自己会置身其中。
|
||||||
|
|
||||||
|
“没错,”伊尔坚定地点头,“提瓦特大陆上,七位神明各自守护着一个国度,而你在这里的出现,可能与某种更大的命运息息相关。”
|
||||||
|
|
||||||
|
“我只想回家。”星低声说道,心中依然渴望着熟悉的生活,尽管她也感受到这个新的世界是如此吸引人。
|
||||||
|
|
||||||
|
“回家并不是那么简单。”伊尔的声音中透着冷静,他的眼神中闪烁着某种复杂的情感,“在这片土地上,你必须要面对许多你无法想象的挑战。”
|
||||||
|
|
||||||
|
星感到了一丝绝望,她并不想成为一名英雄,自己不过是一名普通的年轻人。“我……我能做些什么?”
|
||||||
|
|
||||||
|
“或许我们可以一起探索这个世界。”伊尔的语气缓和了一些,似乎对她产生了一丝信任,“我对魔法的研究或许能帮助你找到线索。”
|
||||||
|
|
||||||
|
“谢谢你,伊尔。”星感激地点头,虽然她对眼前的陌生人依然有些顾虑,但她知道自己必须要有人陪伴,才能在这个陌生的世界中生存下去。
|
||||||
|
|
||||||
|
就在这时,草地的另一侧,一道神秘的光芒闪过,随即出现了另一个身影。那是一位少女,长发披肩,身穿轻盈的衣袍,眼神深邃而神秘,她似乎在默默地注视着星与伊尔。
|
||||||
|
|
||||||
|
“你是……”星愣了一下,有些惊讶。
|
||||||
|
|
||||||
|
“我叫莱娜。”少女轻声说道,她的声音如同空灵的风铃,给人一种奇特的亲切感,“我感觉到了你的到来,希望能和你们一起探索这个世界。”
|
||||||
|
|
||||||
|
“你也感知到了?”伊尔微微皱眉,似乎对莱娜的出现有些意外,却又没有质疑的意思。
|
||||||
|
|
||||||
|
“是的。”莱娜的眼神透着某种不易察觉的情感,“我对自然元素有着强烈的感应,能够感受到世界的变化。”
|
||||||
|
|
||||||
|
星心中一震,莱娜的出现让她感到了一丝安慰,也增添了对这个世界的探索欲望。“或许我们可以一起合作,找出这个世界的秘密。”
|
||||||
|
|
||||||
|
莱娜微微一笑,仿佛认可了星的提议。“我相信,我们的命运将会交织在一起。”
|
||||||
|
|
||||||
|
三人之间逐渐形成了一种微妙的默契。尽管星对这个新世界的未来仍感到模糊不清,内心的惶恐也未完全消散,但在伊尔与莱娜的陪伴下,她逐渐意识到自己不再孤单。接下来,等待她的将是未知的冒险,她必须做好面对的准备。
|
||||||
|
|
||||||
|
正当星准备问伊尔关于这个世界的更多事情时,突然,一股强烈的能量波动猛然袭来,她耳边仿佛听到了那道低语的声音再度浮现,清晰而坚定:“命运的选择,终于来临!”
|
||||||
|
|
||||||
|
星的心脏骤然加速,四周的空气似乎在那一瞬间凝固。她抬头,看到远处的天空中,一片阴云迅速聚集而来,预示着某种危险的逼近。一个模糊的影像在天际闪现,似乎正暗示着未来的冲突与挑战,而她所要面对的,绝不仅仅是对自我归属的探索。
|
||||||
|
|
||||||
|
“我们要走!”伊尔的声音打断了星的思绪,他的目光变得凝重,“不可忽视的危机即将降临,我们必须立刻找到安全的地方!”
|
||||||
|
|
||||||
|
星与莱娜对视一眼,心中都涌起一股紧迫感。对即将来临的挑战,三人并肩而立,准备迎接未知的未来。此刻,她们的命运已经交织在了一起,故事的篇章才刚刚开启,而未来的旅程充满了未知与悬念。
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
星缓缓睁开眼睛,意识到自己正躺在一片陌生的土地上。四周是葱茏的树木,阳光透过树叶洒下斑驳的光影,微风轻拂,带来一丝清新的气息。她试图站起身来,身体却感到无力,仿佛刚经历了一场翻天覆地的梦境。迷茫与恐惧瞬间涌上心头——她究竟身处何地?
|
||||||
|
|
||||||
|
“别 panicking,星。”她自言自语,努力让自己冷静下来。可无论她怎么深呼吸,心中的慌乱与不安依旧如影随形。刚才她还在自己的家中,思绪被一扇打开的窗户吸引,目光追随着飞过的鸟儿,突然遭遇了一道神秘的光芒,她的意识就在那瞬间被撕扯了开来。而现在,她却身处于这个陌生的世界,这一切都让她感到无比的恐慌。
|
||||||
|
|
||||||
|
尝试坐起身来,星环顾四周,发现这片森林似乎与她的记忆截然相反。树木高耸入云,树干粗壮而苍翠,叶子则在微风中轻轻摇晃,发出沙沙的声响。然而,这些美丽的自然景象并没有为她带来一丝安慰——相反,她觉得一种不安的气息弥漫在空气中,仿佛这片森林隐藏着某种不为人知的秘密。
|
||||||
|
|
||||||
|
星的心中掠过一丝奇异的感觉,似乎这片土地上有着某种她无法理解的力量在流动。她强迫自己站起身,决定探索这个世界。若想办法找到回家的路,先得了解这个地方。她开始慢慢前行,脚下的土地干燥而坚硬,偶尔踩到几根枯枝,发出清脆的声响。
|
||||||
|
|
||||||
|
随着深入森林,她注意到周围的植物似乎散发着微弱的光芒,仿佛在回应着她的存在。星试图触摸那些植物,感受那股能量,却发现它们在她的手指间划过时变成了莹莹的光点,瞬间就消散了。她心中一紧,这显然不是普通的植物,她的直觉告诉她,自己可能身处于一个充满魔法的地方。
|
||||||
|
|
||||||
|
就在这时,她的思绪被一阵低语声打断。星停下脚步,四处张望,试图找到声音的来源。声音似乎从四面八方传来,但却又似乎是从某个深渊中回荡而出,朦胧而模糊。
|
||||||
|
|
||||||
|
“是谁在说话?”星喃喃自语,然而没有人回应。她心中浮现出一丝不安,迫切想要找到声音的源头。正当她准备继续探索时,眼前突然闪过一道亮光。她的心一颤,抬头看去,透过树木的缝隙,她隐约看到一个身影,似乎在远处的空旷地带移动。
|
||||||
|
|
||||||
|
出于好奇,星忍不住向前走去,穿越过一片茂密的灌木丛。终于,她走出林间,来到了一个开阔的空地上。眼前的一幕让她惊讶不已——一座宏伟的法阵正镶嵌于地面,阵法复杂而美丽,闪烁着微光,似乎在向她召唤。
|
||||||
|
|
||||||
|
“这是什么?”星自言自语,心中充满了疑惑与好奇。她走到法阵边缘,感觉到一股温暖的气流从阵法中透出。星的手指微微颤动,不由自主地伸向了那个法阵,触碰到了那些复杂的符文。瞬间,她感到一阵剧烈的震动,法阵光芒大盛,星的身体被卷入其中,意识在瞬间模糊而又清晰。
|
||||||
|
|
||||||
|
“啊!”她惊呼,仿佛被一股无形的力量撕扯着。整个世界都在颠覆,光芒刺眼,四周的景象如同狂风中的飘絮般不断变换,星感到自己像是随风而舞的叶子,无法控制。就在意识即将消散的瞬间,她听到了那个低语声,竟然清晰而响亮:“命运的选择。”
|
||||||
|
|
||||||
|
然后,星失去了意识。
|
||||||
|
|
||||||
|
当她再次睁开眼睛时,发现自己坐在一块石头上,周围是一片开阔的草地,远处是连绵起伏的山脉,天空湛蓝,阳光温暖,仿佛一切都是那么美好。可在她心中,却依然残留着那股恐惧与不安。
|
||||||
|
|
||||||
|
“这是哪里?”她自言自语,四处打量。就在这时,一个阴影笼罩了她,星抬头一看,面前站着一个高大的男子,身穿长袍,手中握着法杖,面容冷峻,眼神中透着智慧与审视。
|
||||||
|
|
||||||
|
“你是谁?”星有些警惕地问道,虽然心中已经有了几分预感。
|
||||||
|
|
||||||
|
“我是伊尔,一个魔法师。”男子的声音低沉而有力,似乎带着某种压迫感,“你应该是从那个法阵中被传送过来的吧?”
|
||||||
|
|
||||||
|
星点点头,虽然内心依旧充满困惑,但她知道自己必须与眼前这个陌生人好好交流。“是的,我…我不知道自己为什么来到这里。”
|
||||||
|
|
||||||
|
伊尔微微皱眉,目光在她身上扫视,似乎在打量她的言语是否可信。“这片土地并不安全,你的到来可不是什么偶然。元素的平衡正在崩溃,神明的意志也逐渐淡去。”
|
||||||
|
|
||||||
|
“神明的意志?”星困惑地问道,心中浮现出关于提瓦特的传说与故事。她从小就听说过这个富饶而神秘的大陆,但从未想过自己会置身其中。
|
||||||
|
|
||||||
|
“没错,”伊尔坚定地点头,“提瓦特大陆上,七位神明各自守护着一个国度,而你在这里的出现,可能与某种更大的命运息息相关。”
|
||||||
|
|
||||||
|
“我只想回家。”星低声说道,心中依然渴望着熟悉的生活,尽管她也感受到这个新的世界是如此吸引人。
|
||||||
|
|
||||||
|
“回家并不是那么简单。”伊尔的声音中透着冷静,他的眼神中闪烁着某种复杂的情感,“在这片土地上,你必须要面对许多你无法想象的挑战。”
|
||||||
|
|
||||||
|
星感到了一丝绝望,她并不想成为一名英雄,自己不过是一名普通的年轻人。“我……我能做些什么?”
|
||||||
|
|
||||||
|
“或许我们可以一起探索这个世界。”伊尔的语气缓和了一些,似乎对她产生了一丝信任,“我对魔法的研究或许能帮助你找到线索。”
|
||||||
|
|
||||||
|
“谢谢你,伊尔。”星感激地点头,虽然她对眼前的陌生人依然有些顾虑,但她知道自己必须要有人陪伴,才能在这个陌生的世界中生存下去。
|
||||||
|
|
||||||
|
就在这时,草地的另一侧,一道神秘的光芒闪过,随即出现了另一个身影。那是一位少女,长发披肩,身穿轻盈的衣袍,眼神深邃而神秘,她似乎在默默地注视着星与伊尔。
|
||||||
|
|
||||||
|
“你是……”星愣了一下,有些惊讶。
|
||||||
|
|
||||||
|
“我叫莱娜。”少女轻声说道,她的声音如同空灵的风铃,给人一种奇特的亲切感,“我感觉到了你的到来,希望能和你们一起探索这个世界。”
|
||||||
|
|
||||||
|
“你也感知到了?”伊尔微微皱眉,似乎对莱娜的出现有些意外,却又没有质疑的意思。
|
||||||
|
|
||||||
|
“是的。”莱娜的眼神透着某种不易察觉的情感,“我对自然元素有着强烈的感应,能够感受到世界的变化。”
|
||||||
|
|
||||||
|
星心中一震,莱娜的出现让她感到了一丝安慰,也增添了对这个世界的探索欲望。“或许我们可以一起合作,找出这个世界的秘密。”
|
||||||
|
|
||||||
|
莱娜微微一笑,仿佛认可了星的提议。“我相信,我们的命运将会交织在一起。”
|
||||||
|
|
||||||
|
三人之间逐渐形成了一种微妙的默契。尽管星对这个新世界的未来仍感到模糊不清,内心的惶恐也未完全消散,但在伊尔与莱娜的陪伴下,她逐渐意识到自己不再孤单。接下来,等待她的将是未知的冒险,她必须做好面对的准备。
|
||||||
|
|
||||||
|
正当星准备问伊尔关于这个世界的更多事情时,突然,一股强烈的能量波动猛然袭来,她耳边仿佛听到了那道低语的声音再度浮现,清晰而坚定:“命运的选择,终于来临!”
|
||||||
|
|
||||||
|
星的心脏骤然加速,四周的空气似乎在那一瞬间凝固。她抬头,看到远处的天空中,一片阴云迅速聚集而来,预示着某种危险的逼近。一个模糊的影像在天际闪现,似乎正暗示着未来的冲突与挑战,而她所要面对的,绝不仅仅是对自我归属的探索。
|
||||||
|
|
||||||
|
“我们要走!”伊尔的声音打断了星的思绪,他的目光变得凝重,“不可忽视的危机即将降临,我们必须立刻找到安全的地方!”
|
||||||
|
|
||||||
|
星与莱娜对视一眼,心中都涌起一股紧迫感。对即将来临的挑战,三人并肩而立,准备迎接未知的未来。此刻,她们的命运已经交织在了一起,故事的篇章才刚刚开启,而未来的旅程充满了未知与悬念。
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"api_key": "sk-TxjhfBwxVJ6jA7Px3reCDR2x3Ua6vVghoraX8LtwUBFntD4I",
|
||||||
|
"base_url": "https://api.agicto.cn/v1",
|
||||||
|
"model_name": "gpt-4o-mini",
|
||||||
|
"topic": "星穹铁道主角星穿越到原神提瓦特大陆,拯救提瓦特大陆,并与其中的角色展开爱恨情仇的小说设定",
|
||||||
|
"genre": "玄幻",
|
||||||
|
"num_chapters": 100,
|
||||||
|
"word_number": 4000,
|
||||||
|
"filepath": "C:/Users/xieli/Desktop/AI_NovelGenerator/AI_novel"
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
def load_config(config_file: str) -> dict:
|
||||||
|
"""从指定的 config_file 加载配置,若不存在则返回空字典。"""
|
||||||
|
if os.path.exists(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_config(config_data: dict, config_file: str) -> bool:
|
||||||
|
"""将 config_data 保存到 config_file 中,返回 True/False 表示是否成功。"""
|
||||||
|
try:
|
||||||
|
with open(config_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(config_data, f, ensure_ascii=False, indent=4)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
演示多Agent思路中的“审校Agent”,对最新章节进行简单的一致性或逻辑冲突检查。
|
||||||
|
可根据需要进行扩展。
|
||||||
|
"""
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
|
||||||
|
CONSISTENCY_PROMPT = """\
|
||||||
|
请检查下面的小说设定与最新章节是否存在明显冲突或不一致之处,如有请列出:
|
||||||
|
- 小说设定:
|
||||||
|
{novel_setting}
|
||||||
|
|
||||||
|
- 角色状态(可能包含重要信息):
|
||||||
|
{character_state}
|
||||||
|
|
||||||
|
- 全局摘要:
|
||||||
|
{global_summary}
|
||||||
|
|
||||||
|
- 最新章节内容:
|
||||||
|
{chapter_text}
|
||||||
|
|
||||||
|
如果存在冲突或不一致,请说明;否则请返回“无明显冲突”。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check_consistency(
|
||||||
|
novel_setting: str,
|
||||||
|
character_state: str,
|
||||||
|
global_summary: str,
|
||||||
|
chapter_text: str,
|
||||||
|
api_key: str,
|
||||||
|
base_url: str,
|
||||||
|
model_name: str
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
调用模型做简单的一致性检查。可扩展更多提示或校验规则。
|
||||||
|
"""
|
||||||
|
prompt = CONSISTENCY_PROMPT.format(
|
||||||
|
novel_setting=novel_setting,
|
||||||
|
character_state=character_state,
|
||||||
|
global_summary=global_summary,
|
||||||
|
chapter_text=chapter_text
|
||||||
|
)
|
||||||
|
model = ChatOpenAI(
|
||||||
|
model=model_name,
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url,
|
||||||
|
temperature=0.3
|
||||||
|
)
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return "审校Agent无回复"
|
||||||
|
return response.content.strip()
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from ui import NovelGeneratorGUI
|
||||||
|
|
||||||
|
def main():
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("Novel Generator - Innovative Flow")
|
||||||
|
app = NovelGeneratorGUI(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
import os
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
from langgraph.graph import StateGraph, START, END
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from utils import (
|
||||||
|
read_file, append_text_to_file, clear_file_content, save_string_to_txt
|
||||||
|
)
|
||||||
|
from prompt_definitions import (
|
||||||
|
set_prompt, character_prompt, dark_lines_prompt,
|
||||||
|
finalize_setting_prompt, novel_directory_prompt,
|
||||||
|
summary_prompt, update_character_state_prompt,
|
||||||
|
chapter_outline_prompt, chapter_write_prompt
|
||||||
|
)
|
||||||
|
|
||||||
|
# 向量检索相关 (以Chroma为例),需要安装 langchain, chromadb 等
|
||||||
|
from langchain.embeddings.openai import OpenAIEmbeddings
|
||||||
|
from langchain.vectorstores import Chroma
|
||||||
|
from langchain.docstore.document import Document
|
||||||
|
|
||||||
|
# 默认用此目录存放向量库
|
||||||
|
VECTOR_STORE_DIR = "vectorstore"
|
||||||
|
|
||||||
|
# =============== 多步生成:设置 & 目录 ===============
|
||||||
|
class OverallState(TypedDict):
|
||||||
|
topic: str
|
||||||
|
genre: str
|
||||||
|
number_of_chapters: int
|
||||||
|
word_number: int
|
||||||
|
novel_setting_base: str
|
||||||
|
character_setting: str
|
||||||
|
dark_lines: str
|
||||||
|
final_novel_setting: str
|
||||||
|
novel_directory: str
|
||||||
|
|
||||||
|
def Novel_novel_directory_generate(
|
||||||
|
api_key: str,
|
||||||
|
base_url: str,
|
||||||
|
llm_model: str,
|
||||||
|
topic: str,
|
||||||
|
genre: str,
|
||||||
|
number_of_chapters: int,
|
||||||
|
word_number: int,
|
||||||
|
filepath: str
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
使用多步流程,生成 Novel_setting.txt 与 Novel_directory.txt
|
||||||
|
"""
|
||||||
|
model = ChatOpenAI(
|
||||||
|
model=llm_model,
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_base_setting(state: OverallState):
|
||||||
|
prompt = set_prompt.format(
|
||||||
|
topic=state["topic"],
|
||||||
|
genre=state["genre"],
|
||||||
|
number_of_chapters=state["number_of_chapters"],
|
||||||
|
word_number=state["word_number"],
|
||||||
|
)
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return {"novel_setting_base": ""}
|
||||||
|
return {"novel_setting_base": response.content.strip()}
|
||||||
|
|
||||||
|
def generate_character_setting(state: OverallState):
|
||||||
|
prompt = character_prompt.format(novel_setting=state["novel_setting_base"])
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return {"character_setting": ""}
|
||||||
|
return {"character_setting": response.content.strip()}
|
||||||
|
|
||||||
|
def generate_dark_lines(state: OverallState):
|
||||||
|
prompt = dark_lines_prompt.format(character_info=state["character_setting"])
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return {"dark_lines": ""}
|
||||||
|
return {"dark_lines": response.content.strip()}
|
||||||
|
|
||||||
|
def finalize_novel_setting(state: OverallState):
|
||||||
|
prompt = finalize_setting_prompt.format(
|
||||||
|
novel_setting_base=state["novel_setting_base"],
|
||||||
|
character_setting=state["character_setting"],
|
||||||
|
dark_lines=state["dark_lines"]
|
||||||
|
)
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return {"final_novel_setting": ""}
|
||||||
|
return {"final_novel_setting": response.content.strip()}
|
||||||
|
|
||||||
|
def generate_novel_directory(state: OverallState):
|
||||||
|
prompt = novel_directory_prompt.format(
|
||||||
|
final_novel_setting=state["final_novel_setting"],
|
||||||
|
number_of_chapters=state["number_of_chapters"]
|
||||||
|
)
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return {"novel_directory": ""}
|
||||||
|
return {"novel_directory": response.content.strip()}
|
||||||
|
|
||||||
|
graph = StateGraph(OverallState)
|
||||||
|
graph.add_node("generate_base_setting", generate_base_setting)
|
||||||
|
graph.add_node("generate_character_setting", generate_character_setting)
|
||||||
|
graph.add_node("generate_dark_lines", generate_dark_lines)
|
||||||
|
graph.add_node("finalize_novel_setting", finalize_novel_setting)
|
||||||
|
graph.add_node("generate_novel_directory", generate_novel_directory)
|
||||||
|
|
||||||
|
graph.add_edge(START, "generate_base_setting")
|
||||||
|
graph.add_edge("generate_base_setting", "generate_character_setting")
|
||||||
|
graph.add_edge("generate_character_setting", "generate_dark_lines")
|
||||||
|
graph.add_edge("generate_dark_lines", "finalize_novel_setting")
|
||||||
|
graph.add_edge("finalize_novel_setting", "generate_novel_directory")
|
||||||
|
graph.add_edge("generate_novel_directory", END)
|
||||||
|
|
||||||
|
app = graph.compile()
|
||||||
|
|
||||||
|
input_params = {
|
||||||
|
"topic": topic,
|
||||||
|
"genre": genre,
|
||||||
|
"number_of_chapters": number_of_chapters,
|
||||||
|
"word_number": word_number
|
||||||
|
}
|
||||||
|
result = app.invoke(input_params)
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
print("⚠️ invoke() 结果为空,生成失败。")
|
||||||
|
return
|
||||||
|
|
||||||
|
final_novel_setting = result.get("final_novel_setting", "")
|
||||||
|
final_novel_directory = result.get("novel_directory", "")
|
||||||
|
if not final_novel_setting or not final_novel_directory:
|
||||||
|
print("⚠️ 生成失败:缺少 final_novel_setting 或 novel_directory。")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 写入文件
|
||||||
|
filename_set = os.path.join(filepath, "Novel_setting.txt")
|
||||||
|
filename_novel_directory = os.path.join(filepath, "Novel_directory.txt")
|
||||||
|
|
||||||
|
final_novel_setting_cleaned = final_novel_setting.replace('#', '').replace('*', '')
|
||||||
|
final_novel_directory_cleaned = final_novel_directory.replace('#', '').replace('*', '')
|
||||||
|
|
||||||
|
append_text_to_file(final_novel_setting_cleaned, filename_set)
|
||||||
|
append_text_to_file(final_novel_directory_cleaned, filename_novel_directory)
|
||||||
|
|
||||||
|
|
||||||
|
# =============== 生成章节(含角色状态 & 全局摘要 & 向量检索) ===============
|
||||||
|
|
||||||
|
def init_vector_store(api_key: str, texts: list[str]) -> Chroma:
|
||||||
|
"""
|
||||||
|
初始化并返回一个Chroma向量库,将传入的文本进行嵌入。
|
||||||
|
若需要可对 texts 做分句或分块处理;这里只演示简单用法。
|
||||||
|
"""
|
||||||
|
embeddings = OpenAIEmbeddings(openai_api_key=api_key)
|
||||||
|
documents = [Document(page_content=t) for t in texts]
|
||||||
|
vectorstore = Chroma.from_documents(documents, embedding=embeddings, persist_directory=VECTOR_STORE_DIR)
|
||||||
|
vectorstore.persist()
|
||||||
|
return vectorstore
|
||||||
|
|
||||||
|
def load_vector_store(api_key: str) -> Chroma:
|
||||||
|
"""
|
||||||
|
读取已存在的向量库。若不存在则返回None或新建一个空的。
|
||||||
|
"""
|
||||||
|
if not os.path.exists(VECTOR_STORE_DIR):
|
||||||
|
return None
|
||||||
|
embeddings = OpenAIEmbeddings(openai_api_key=api_key)
|
||||||
|
return Chroma(persist_directory=VECTOR_STORE_DIR, embedding_function=embeddings)
|
||||||
|
|
||||||
|
|
||||||
|
def update_vector_store(api_key: str, new_chapter: str):
|
||||||
|
"""
|
||||||
|
将最新章节文本插入到向量库里,用于后续检索参考。
|
||||||
|
可根据实际需求做分块处理。此处仅作简单示范。
|
||||||
|
"""
|
||||||
|
store = load_vector_store(api_key)
|
||||||
|
if not store:
|
||||||
|
# 如果vector store不存在,先初始化
|
||||||
|
store = init_vector_store(api_key, [new_chapter])
|
||||||
|
return
|
||||||
|
|
||||||
|
embeddings = OpenAIEmbeddings(openai_api_key=api_key)
|
||||||
|
new_doc = Document(page_content=new_chapter)
|
||||||
|
store.add_documents([new_doc])
|
||||||
|
store.persist()
|
||||||
|
|
||||||
|
|
||||||
|
def get_relevant_context_from_vector_store(api_key: str, query: str, k: int=2) -> str:
|
||||||
|
"""
|
||||||
|
从向量库中检索与 query 最相关的 k 条文本,拼接后返回。
|
||||||
|
用于在生成大纲或写正文时,为大模型提供更多上下文。
|
||||||
|
"""
|
||||||
|
store = load_vector_store(api_key)
|
||||||
|
if not store:
|
||||||
|
return ""
|
||||||
|
docs = store.similarity_search(query, k=k)
|
||||||
|
# 简单拼接
|
||||||
|
combined = "\n".join([d.page_content for d in docs])
|
||||||
|
return combined
|
||||||
|
|
||||||
|
def generate_chapter_with_state(
|
||||||
|
novel_settings: str,
|
||||||
|
novel_novel_directory: str,
|
||||||
|
api_key: str,
|
||||||
|
base_url: str,
|
||||||
|
model_name: str,
|
||||||
|
novel_number: int,
|
||||||
|
filepath: str,
|
||||||
|
word_number: int,
|
||||||
|
lastchapter: str
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
多步流程:
|
||||||
|
1) 更新/创建全局摘要
|
||||||
|
2) 更新/生成角色状态文档
|
||||||
|
3) 根据向量检索获取往期章节相关内容
|
||||||
|
4) 大纲 -> 正文
|
||||||
|
5) 更新向量库
|
||||||
|
最终写入 chapter.txt、lastchapter.txt、character_state.txt、global_summary.txt
|
||||||
|
"""
|
||||||
|
model = ChatOpenAI(
|
||||||
|
model=model_name,
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url,
|
||||||
|
temperature=0.9
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- 文件名定义 ---
|
||||||
|
character_state_file = os.path.join(filepath, "character_state.txt")
|
||||||
|
global_summary_file = os.path.join(filepath, "global_summary.txt")
|
||||||
|
chapter_file = os.path.join(filepath, "chapter.txt")
|
||||||
|
lastchapter_file = os.path.join(filepath, "lastchapter.txt")
|
||||||
|
|
||||||
|
# --- 读取现有文档(可能为空) ---
|
||||||
|
old_char_state = read_file(character_state_file)
|
||||||
|
old_global_summary = read_file(global_summary_file)
|
||||||
|
|
||||||
|
# --- 1) 更新全局摘要 (若上一章文本不为空) ---
|
||||||
|
def update_global_summary(chapter_text: str, old_summary: str) -> str:
|
||||||
|
prompt = summary_prompt.format(chapter_text=chapter_text, global_summary=old_summary)
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return old_summary
|
||||||
|
return response.content.strip()
|
||||||
|
|
||||||
|
if lastchapter.strip():
|
||||||
|
# 用上一章内容更新全局摘要
|
||||||
|
new_global_summary = update_global_summary(lastchapter, old_global_summary)
|
||||||
|
else:
|
||||||
|
new_global_summary = old_global_summary
|
||||||
|
|
||||||
|
# --- 2) 更新角色状态文档 ---
|
||||||
|
def update_character_state(chapter_text: str, old_state: str) -> str:
|
||||||
|
prompt = update_character_state_prompt.format(chapter_text=chapter_text, old_state=old_state)
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return old_state
|
||||||
|
return response.content.strip()
|
||||||
|
|
||||||
|
if lastchapter.strip():
|
||||||
|
new_char_state = update_character_state(lastchapter, old_char_state)
|
||||||
|
else:
|
||||||
|
new_char_state = old_char_state
|
||||||
|
|
||||||
|
# --- 3) 从向量库检索相关上下文,用来帮助生成新的大纲 ---
|
||||||
|
# 例如,可以根据“角色状态”或“本章关键词”来查询。
|
||||||
|
# 简单示范:以 "回顾剧情" 作为检索Query
|
||||||
|
relevant_context = get_relevant_context_from_vector_store(api_key, "回顾剧情", k=2)
|
||||||
|
|
||||||
|
# --- 4) 大纲 -> 正文 ---
|
||||||
|
def outline_chapter(novel_setting: str, char_state: str, global_summary: str, chap_num: int, extra_context: str) -> str:
|
||||||
|
prompt = chapter_outline_prompt.format(
|
||||||
|
novel_setting=novel_setting,
|
||||||
|
character_state=char_state + "\n\n【历史上下文】\n" + extra_context,
|
||||||
|
global_summary=global_summary,
|
||||||
|
novel_number=chap_num
|
||||||
|
)
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return ""
|
||||||
|
return response.content.strip()
|
||||||
|
|
||||||
|
chap_outline = outline_chapter(novel_settings, new_char_state, new_global_summary, novel_number, relevant_context)
|
||||||
|
|
||||||
|
def write_chapter(novel_setting: str, char_state: str, global_summary: str, outline: str, wnum: int, extra_context: str) -> str:
|
||||||
|
prompt = chapter_write_prompt.format(
|
||||||
|
novel_setting=novel_setting,
|
||||||
|
character_state=char_state + "\n\n【历史上下文】\n" + extra_context,
|
||||||
|
global_summary=global_summary,
|
||||||
|
chapter_outline=outline,
|
||||||
|
word_number=wnum
|
||||||
|
)
|
||||||
|
response = model.invoke(prompt)
|
||||||
|
if not response:
|
||||||
|
return ""
|
||||||
|
return response.content.strip()
|
||||||
|
|
||||||
|
chapter_content = write_chapter(novel_settings, new_char_state, new_global_summary, chap_outline, word_number, relevant_context)
|
||||||
|
|
||||||
|
if chapter_content:
|
||||||
|
# --- 写入 chapter.txt 与 lastchapter.txt ---
|
||||||
|
append_text_to_file(chapter_content, chapter_file)
|
||||||
|
|
||||||
|
clear_file_content(lastchapter_file)
|
||||||
|
save_string_to_txt(chapter_content, lastchapter_file)
|
||||||
|
|
||||||
|
# --- 更新全局摘要、角色状态到文件 ---
|
||||||
|
clear_file_content(character_state_file)
|
||||||
|
save_string_to_txt(new_char_state, character_state_file)
|
||||||
|
|
||||||
|
clear_file_content(global_summary_file)
|
||||||
|
save_string_to_txt(new_global_summary, global_summary_file)
|
||||||
|
|
||||||
|
# --- 5) 更新向量检索库 ---
|
||||||
|
update_vector_store(api_key, chapter_content)
|
||||||
|
|
||||||
|
return chapter_content
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
"""
|
||||||
|
集中存放所有提示词(Prompt),便于统一管理和修改。
|
||||||
|
"""
|
||||||
|
|
||||||
|
# =============== 提示词:设定 & 目录 ===================
|
||||||
|
|
||||||
|
set_prompt = """\
|
||||||
|
请根据主题:{topic}、类型:{genre}、章数:{number_of_chapters}、每章字数:{word_number}来完善小说整体设定。
|
||||||
|
需要包含以下信息:
|
||||||
|
1. 小说名称、总字数走向(大致范围即可)。
|
||||||
|
2. 小说类型与基调(如:都市、穿越、战争等类型,以及轻松、爆笑、暗黑等基调)。
|
||||||
|
3. 写作风格(正式 / 轻松;细腻 / 简洁;抒情 / 客观;叙事视角等)。
|
||||||
|
4. 整体世界观(时间背景、地理环境、社会结构、科技或魔法水平、重要历史传说或事件等)。
|
||||||
|
5. 核心内容梗概(可以使用常见叙事结构,如三幕结构、英雄之旅等)。
|
||||||
|
6. 初步的情节安排设想(主线、副线、交织等关键思路)。
|
||||||
|
7. 初步的人物关系与主要角色设定(角色定位、主要冲突或关系)。
|
||||||
|
8. 结尾可能的方向(圆满、悲剧、开放式等)。
|
||||||
|
|
||||||
|
请按照上述要点详细输出,但不用标数字。要清晰、有逻辑、有条理。
|
||||||
|
"""
|
||||||
|
|
||||||
|
character_prompt = """\
|
||||||
|
基于已生成的小说整体设定:
|
||||||
|
{novel_setting}
|
||||||
|
请你完善以下内容,帮助我们更好地维持人物形象和成长轨迹:
|
||||||
|
1. 列出核心角色(至少3个),并对每个角色进行详细性格特征描述。
|
||||||
|
2. 强调每个角色的潜在内心冲突、目标与动机。
|
||||||
|
3. 为每个角色添加至少一个“暗线”或隐藏秘密,以及在故事进行中如何可能被揭示的思路。
|
||||||
|
4. 指出主要角色之间的关键关系和冲突点,为后续情节埋下伏笔。
|
||||||
|
"""
|
||||||
|
|
||||||
|
dark_lines_prompt = """\
|
||||||
|
在当前设定中已出现以下角色与背景:
|
||||||
|
{character_info}
|
||||||
|
请帮助我们构思若干暗线、伏笔或隐藏冲突,以便在后续章节中逐渐揭示并影响故事走向。要求:
|
||||||
|
1. 每个暗线至少说明其最初的表现、发展走向,以及揭示或爆发的条件。
|
||||||
|
2. 这些暗线可以与角色背景、世界观、关键事件等有关。
|
||||||
|
3. 需注意保留悬念,与已知设定不冲突。
|
||||||
|
4. 在后续创作中可多次提及这些暗线,并在中后期通过角色行为或剧情变化逐步揭示。
|
||||||
|
"""
|
||||||
|
|
||||||
|
finalize_setting_prompt = """\
|
||||||
|
请基于以下信息,整合并输出最终的《小说设定》:
|
||||||
|
1. 之前的“整体设定”:
|
||||||
|
{novel_setting_base}
|
||||||
|
2. 扩充的“角色设定”:
|
||||||
|
{character_setting}
|
||||||
|
3. 暗线与伏笔构思:
|
||||||
|
{dark_lines}
|
||||||
|
|
||||||
|
要求:
|
||||||
|
1. 结构清晰,将以上内容融合为一个完整的设定说明。
|
||||||
|
2. 着重强调角色与暗线的衔接、世界观与角色动机的结合,方便后续写作保持前后一致。
|
||||||
|
3. 语言通畅,不使用Markdown格式,直接输出文本内容。
|
||||||
|
"""
|
||||||
|
|
||||||
|
novel_directory_prompt = """\
|
||||||
|
根据以下最终《小说设定》:
|
||||||
|
{final_novel_setting}
|
||||||
|
并按照下面的小说目录模板生成 {number_of_chapters} 章的目录,同时确保目录符合小说设定中的叙事结构、角色发展及暗线伏笔。
|
||||||
|
目录模板:
|
||||||
|
第1章 :< text >
|
||||||
|
第2章 :< text >
|
||||||
|
...
|
||||||
|
第{number_of_chapters}章 :< text >
|
||||||
|
|
||||||
|
请严格按照上述格式输出每一章的名称,且勿使用Markdown语法。
|
||||||
|
"""
|
||||||
|
|
||||||
|
# =============== 提示词:章节+角色状态流程 ===================
|
||||||
|
|
||||||
|
summary_prompt = """\
|
||||||
|
这是新生成的章节文本:
|
||||||
|
{chapter_text}
|
||||||
|
|
||||||
|
这是当前的全局摘要(可能为空):
|
||||||
|
{global_summary}
|
||||||
|
|
||||||
|
请在不超过1000字的前提下,基于当前全局摘要和本章新增剧情,更新全局摘要。
|
||||||
|
保留原有重要信息,并融入本章的新内容。
|
||||||
|
不要透露结局,不要过度展开未来剧情。
|
||||||
|
"""
|
||||||
|
|
||||||
|
update_character_state_prompt = """\
|
||||||
|
这是新生成的章节文本:
|
||||||
|
{chapter_text}
|
||||||
|
|
||||||
|
这是当前角色状态文档(可能为空):
|
||||||
|
{old_state}
|
||||||
|
|
||||||
|
请更新角色状态,包括:
|
||||||
|
1. 角色持有的物品或能力变化。
|
||||||
|
2. 角色间关系、冲突或合作的新动向。
|
||||||
|
3. 正在发生的重要事件列表,有无进展或新事件产生。
|
||||||
|
4. 任意新增角色或出场人物等。
|
||||||
|
5. 请保证结构完整,能在后续章节继续引用。
|
||||||
|
|
||||||
|
使用简洁、易读的方式描述,可用条目或段落表示。保持与旧文档风格一致。
|
||||||
|
"""
|
||||||
|
|
||||||
|
chapter_outline_prompt = """\
|
||||||
|
以下是当前小说设定与角色状态信息:
|
||||||
|
- 小说设定:{novel_setting}
|
||||||
|
- 角色状态:{character_state}
|
||||||
|
- 全局摘要:{global_summary}
|
||||||
|
- 本章节编号:第 {novel_number} 章
|
||||||
|
|
||||||
|
请为即将写作的 第{novel_number}章 设计一个简要大纲:
|
||||||
|
1. 本章的主要冲突或事件?
|
||||||
|
2. 哪些角色会出现?情感与目标变化?
|
||||||
|
3. 如何进一步暗示或推动暗线和角色冲突?
|
||||||
|
4. 如何结尾留下悬念?
|
||||||
|
|
||||||
|
直接用1、2、3、4分点说明即可。
|
||||||
|
"""
|
||||||
|
|
||||||
|
chapter_write_prompt = """\
|
||||||
|
下面是该章写作所需信息:
|
||||||
|
1. 小说设定:{novel_setting}
|
||||||
|
2. 角色状态:{character_state}
|
||||||
|
3. 全局摘要:{global_summary}
|
||||||
|
4. 本章大纲:{chapter_outline}
|
||||||
|
|
||||||
|
请写出本章节的完整正文:
|
||||||
|
1. 确保本章字数不少于 {word_number} 字。
|
||||||
|
2. 不要使用分节标题,直接整体输出正文。
|
||||||
|
3. 可以着重描写人物心理、环境氛围等,以保证足够长度。
|
||||||
|
4. 在结尾部分保留一定悬念或剧情转折,为下一章做铺垫。
|
||||||
|
"""
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
import os
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, filedialog, scrolledtext, messagebox
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from config_manager import load_config, save_config
|
||||||
|
from utils import read_file
|
||||||
|
from novel_generator import (
|
||||||
|
Novel_novel_directory_generate,
|
||||||
|
generate_chapter_with_state
|
||||||
|
)
|
||||||
|
from consistency_checker import check_consistency
|
||||||
|
|
||||||
|
class NovelGeneratorGUI:
|
||||||
|
def __init__(self, master):
|
||||||
|
self.master = master
|
||||||
|
self.master.title("Novel Generator GUI - Advanced")
|
||||||
|
|
||||||
|
# 配置持久化
|
||||||
|
self.config_file = "config.json"
|
||||||
|
self.loaded_config = load_config(self.config_file)
|
||||||
|
|
||||||
|
# 使用 PanedWindow 实现左右布局
|
||||||
|
main_pane = ttk.PanedWindow(master, orient="horizontal")
|
||||||
|
main_pane.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# 左侧:显示区(上下分区)
|
||||||
|
self.left_frame = ttk.Frame(main_pane)
|
||||||
|
main_pane.add(self.left_frame, weight=3)
|
||||||
|
|
||||||
|
# 右侧:参数输入区
|
||||||
|
self.right_frame = ttk.Frame(main_pane, padding="10 10 10 10")
|
||||||
|
main_pane.add(self.right_frame, weight=1)
|
||||||
|
|
||||||
|
# 左侧布局:日志区 + 章节内容
|
||||||
|
self.build_left_layout()
|
||||||
|
# 右侧布局:参数输入区
|
||||||
|
self.build_right_layout()
|
||||||
|
|
||||||
|
def build_left_layout(self):
|
||||||
|
self.left_frame.rowconfigure(0, weight=1)
|
||||||
|
self.left_frame.rowconfigure(1, weight=1)
|
||||||
|
self.left_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# 日志区
|
||||||
|
log_frame = ttk.LabelFrame(self.left_frame, text="输出日志")
|
||||||
|
log_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
||||||
|
log_frame.rowconfigure(0, weight=1)
|
||||||
|
log_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.log_text = scrolledtext.ScrolledText(log_frame, width=80, height=10)
|
||||||
|
self.log_text.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
# 章节内容区
|
||||||
|
chapter_frame = ttk.LabelFrame(self.left_frame, text="本章内容")
|
||||||
|
chapter_frame.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)
|
||||||
|
chapter_frame.rowconfigure(0, weight=1)
|
||||||
|
chapter_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.chapter_result = scrolledtext.ScrolledText(chapter_frame, width=80, height=10, foreground="blue")
|
||||||
|
self.chapter_result.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
def build_right_layout(self, ):
|
||||||
|
# 行列配置
|
||||||
|
for i in range(12):
|
||||||
|
self.right_frame.rowconfigure(i, weight=0)
|
||||||
|
self.right_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# 1. API Key
|
||||||
|
ttk.Label(self.right_frame, text="API Key:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
|
||||||
|
self.api_key_var = tk.StringVar(value=self.loaded_config.get("api_key", ""))
|
||||||
|
ttk.Entry(self.right_frame, textvariable=self.api_key_var, width=32).grid(row=0, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
# 2. Base URL
|
||||||
|
ttk.Label(self.right_frame, text="Base URL:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
|
||||||
|
self.base_url_var = tk.StringVar(value=self.loaded_config.get("base_url", "https://api.agicto.cn/v1"))
|
||||||
|
ttk.Entry(self.right_frame, textvariable=self.base_url_var, width=32).grid(row=1, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
# 3. Model Name
|
||||||
|
ttk.Label(self.right_frame, text="Model Name:").grid(row=2, column=0, padx=5, pady=5, sticky="e")
|
||||||
|
self.model_name_var = tk.StringVar(value=self.loaded_config.get("model_name", "gpt-4o-mini"))
|
||||||
|
ttk.Entry(self.right_frame, textvariable=self.model_name_var, width=32).grid(row=2, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
# 4. 主题(Topic) 多行输入
|
||||||
|
ttk.Label(self.right_frame, text="主题(Topic):").grid(row=3, column=0, padx=5, pady=5, sticky="ne")
|
||||||
|
self.topic_text = scrolledtext.ScrolledText(self.right_frame, width=32, height=4)
|
||||||
|
self.topic_text.grid(row=3, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
topic_default = self.loaded_config.get("topic", "")
|
||||||
|
if topic_default:
|
||||||
|
self.topic_text.insert(tk.END, topic_default)
|
||||||
|
|
||||||
|
# 5. 类型(Genre)
|
||||||
|
ttk.Label(self.right_frame, text="类型(Genre):").grid(row=4, column=0, padx=5, pady=5, sticky="e")
|
||||||
|
self.genre_var = tk.StringVar(value=self.loaded_config.get("genre", "玄幻"))
|
||||||
|
ttk.Entry(self.right_frame, textvariable=self.genre_var, width=32).grid(row=4, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
# 6. 章节数
|
||||||
|
ttk.Label(self.right_frame, text="章节数:").grid(row=5, column=0, padx=5, pady=5, sticky="e")
|
||||||
|
self.num_chapters_var = tk.IntVar(value=self.loaded_config.get("num_chapters", 10))
|
||||||
|
ttk.Entry(self.right_frame, textvariable=self.num_chapters_var, width=8).grid(row=5, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
# 7. 每章字数
|
||||||
|
ttk.Label(self.right_frame, text="每章字数:").grid(row=6, column=0, padx=5, pady=5, sticky="e")
|
||||||
|
self.word_number_var = tk.IntVar(value=self.loaded_config.get("word_number", 3000))
|
||||||
|
ttk.Entry(self.right_frame, textvariable=self.word_number_var, width=8).grid(row=6, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
# 8. 文件保存路径
|
||||||
|
ttk.Label(self.right_frame, text="保存路径:").grid(row=7, column=0, padx=5, pady=5, sticky="e")
|
||||||
|
self.filepath_var = tk.StringVar(value=self.loaded_config.get("filepath", ""))
|
||||||
|
ttk.Entry(self.right_frame, textvariable=self.filepath_var, width=32).grid(row=7, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
ttk.Button(self.right_frame, text="浏览...", command=self.browse_folder).grid(row=7, column=2, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
# 保存/加载配置按钮
|
||||||
|
config_frame = ttk.Frame(self.right_frame)
|
||||||
|
config_frame.grid(row=8, column=1, sticky="w")
|
||||||
|
ttk.Button(config_frame, text="保存配置", command=self.save_config_btn).grid(row=0, column=0, padx=5)
|
||||||
|
ttk.Button(config_frame, text="加载配置", command=self.load_config_btn).grid(row=0, column=1, padx=5)
|
||||||
|
|
||||||
|
# 按钮区域
|
||||||
|
row_base = 9
|
||||||
|
ttk.Label(self.right_frame, text="章节号:").grid(row=row_base, column=0, sticky="e")
|
||||||
|
self.chapter_num_var = tk.IntVar(value=1)
|
||||||
|
ttk.Entry(self.right_frame, textvariable=self.chapter_num_var, width=6).grid(row=row_base, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
self.btn_generate_full = ttk.Button(self.right_frame, text="1. 生成设定 & 目录", command=self.generate_full_novel)
|
||||||
|
self.btn_generate_full.grid(row=row_base+1, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
|
||||||
|
|
||||||
|
self.btn_generate_chapter = ttk.Button(self.right_frame, text="2. 生成单章(含角色状态)", command=self.generate_chapter_text)
|
||||||
|
self.btn_generate_chapter.grid(row=row_base+2, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
|
||||||
|
|
||||||
|
# 可选:添加一个“一致性审校”按钮
|
||||||
|
self.btn_check_consistency = ttk.Button(self.right_frame, text="3. 一致性审校", command=self.do_consistency_check)
|
||||||
|
self.btn_check_consistency.grid(row=row_base+3, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
|
||||||
|
|
||||||
|
|
||||||
|
# -------------- 配置管理 --------------
|
||||||
|
def load_config_btn(self):
|
||||||
|
cfg = load_config(self.config_file)
|
||||||
|
if cfg:
|
||||||
|
self.api_key_var.set(cfg.get("api_key", ""))
|
||||||
|
self.base_url_var.set(cfg.get("base_url", ""))
|
||||||
|
self.model_name_var.set(cfg.get("model_name", ""))
|
||||||
|
self.genre_var.set(cfg.get("genre", ""))
|
||||||
|
self.num_chapters_var.set(cfg.get("num_chapters", 10))
|
||||||
|
self.word_number_var.set(cfg.get("word_number", 3000))
|
||||||
|
self.filepath_var.set(cfg.get("filepath", ""))
|
||||||
|
|
||||||
|
# 多行文本
|
||||||
|
self.topic_text.delete("1.0", tk.END)
|
||||||
|
self.topic_text.insert(tk.END, cfg.get("topic", ""))
|
||||||
|
|
||||||
|
self.log("已加载配置。")
|
||||||
|
else:
|
||||||
|
messagebox.showwarning("提示", "未找到或无法读取配置文件。")
|
||||||
|
|
||||||
|
def save_config_btn(self):
|
||||||
|
config_data = {
|
||||||
|
"api_key": self.api_key_var.get(),
|
||||||
|
"base_url": self.base_url_var.get(),
|
||||||
|
"model_name": self.model_name_var.get(),
|
||||||
|
"topic": self.topic_text.get("1.0", tk.END).strip(),
|
||||||
|
"genre": self.genre_var.get(),
|
||||||
|
"num_chapters": self.num_chapters_var.get(),
|
||||||
|
"word_number": self.word_number_var.get(),
|
||||||
|
"filepath": self.filepath_var.get()
|
||||||
|
}
|
||||||
|
if save_config(config_data, self.config_file):
|
||||||
|
messagebox.showinfo("提示", "配置已保存至 config.json")
|
||||||
|
self.log("配置已保存。")
|
||||||
|
else:
|
||||||
|
messagebox.showerror("错误", "保存配置失败。")
|
||||||
|
|
||||||
|
def browse_folder(self):
|
||||||
|
selected_dir = filedialog.askdirectory()
|
||||||
|
if selected_dir:
|
||||||
|
self.filepath_var.set(selected_dir)
|
||||||
|
|
||||||
|
# -------------- 日志输出 --------------
|
||||||
|
def log(self, message: str):
|
||||||
|
self.log_text.insert(tk.END, message + "\n")
|
||||||
|
self.log_text.see(tk.END)
|
||||||
|
|
||||||
|
# -------------- 核心功能按钮 --------------
|
||||||
|
def disable_button(self, btn):
|
||||||
|
btn.config(state=tk.DISABLED)
|
||||||
|
|
||||||
|
def enable_button(self, btn):
|
||||||
|
btn.config(state=tk.NORMAL)
|
||||||
|
|
||||||
|
def generate_full_novel(self):
|
||||||
|
"""生成小说设定 & 目录"""
|
||||||
|
def task():
|
||||||
|
self.disable_button(self.btn_generate_full)
|
||||||
|
try:
|
||||||
|
api_key = self.api_key_var.get().strip()
|
||||||
|
base_url = self.base_url_var.get().strip()
|
||||||
|
model_name = self.model_name_var.get().strip()
|
||||||
|
topic = self.topic_text.get("1.0", tk.END).strip()
|
||||||
|
genre = self.genre_var.get().strip()
|
||||||
|
num_chapters = self.num_chapters_var.get()
|
||||||
|
word_number = self.word_number_var.get()
|
||||||
|
filepath = self.filepath_var.get().strip()
|
||||||
|
|
||||||
|
if not filepath:
|
||||||
|
messagebox.showwarning("警告", "请先选择保存文件路径")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("开始生成小说设定和目录...")
|
||||||
|
Novel_novel_directory_generate(
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url,
|
||||||
|
llm_model=model_name,
|
||||||
|
topic=topic,
|
||||||
|
genre=genre,
|
||||||
|
number_of_chapters=num_chapters,
|
||||||
|
word_number=word_number,
|
||||||
|
filepath=filepath
|
||||||
|
)
|
||||||
|
self.log("✅ 小说设定和目录生成完成。查看 Novel_setting.txt 和 Novel_directory.txt。")
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"❌ 生成小说设定 & 目录时出错: {e}")
|
||||||
|
finally:
|
||||||
|
self.enable_button(self.btn_generate_full)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=task)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def generate_chapter_text(self):
|
||||||
|
"""多步生成章节:维护全局摘要+角色状态文档,向量检索辅助"""
|
||||||
|
def task():
|
||||||
|
self.disable_button(self.btn_generate_chapter)
|
||||||
|
try:
|
||||||
|
api_key = self.api_key_var.get().strip()
|
||||||
|
base_url = self.base_url_var.get().strip()
|
||||||
|
model_name = self.model_name_var.get().strip()
|
||||||
|
novel_number = self.chapter_num_var.get()
|
||||||
|
filepath = self.filepath_var.get().strip()
|
||||||
|
word_number = self.word_number_var.get()
|
||||||
|
|
||||||
|
# 读取设定 & 目录
|
||||||
|
novel_settings_file = os.path.join(filepath, "Novel_setting.txt")
|
||||||
|
novel_novel_directory_file = os.path.join(filepath, "Novel_directory.txt")
|
||||||
|
last_chapter_file = os.path.join(filepath, "lastchapter.txt")
|
||||||
|
|
||||||
|
novel_settings = read_file(novel_settings_file)
|
||||||
|
novel_novel_directory = read_file(novel_novel_directory_file)
|
||||||
|
lastchapter = read_file(last_chapter_file)
|
||||||
|
|
||||||
|
if not novel_settings.strip():
|
||||||
|
self.log("⚠️ 未找到 Novel_setting.txt,请先生成设定。")
|
||||||
|
return
|
||||||
|
if not novel_novel_directory.strip():
|
||||||
|
self.log("⚠️ 未找到 Novel_directory.txt,请先生成目录。")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log(f"开始生成第{novel_number}章内容(含角色状态文档更新)...")
|
||||||
|
chapter_text = generate_chapter_with_state(
|
||||||
|
novel_settings=novel_settings,
|
||||||
|
novel_novel_directory=novel_novel_directory,
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url,
|
||||||
|
model_name=model_name,
|
||||||
|
novel_number=novel_number,
|
||||||
|
filepath=filepath,
|
||||||
|
word_number=word_number,
|
||||||
|
lastchapter=lastchapter
|
||||||
|
)
|
||||||
|
|
||||||
|
if chapter_text:
|
||||||
|
self.log(f"✅ 第{novel_number}章内容生成完成。chapter.txt 已更新。")
|
||||||
|
self.chapter_result.delete("1.0", tk.END)
|
||||||
|
self.chapter_result.insert(tk.END, chapter_text)
|
||||||
|
self.chapter_result.see(tk.END)
|
||||||
|
else:
|
||||||
|
self.log("⚠️ 本章生成失败或无内容。")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"❌ 生成章节内容时出错: {e}")
|
||||||
|
finally:
|
||||||
|
self.enable_button(self.btn_generate_chapter)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=task)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def do_consistency_check(self):
|
||||||
|
"""使用审校Agent对最新章节进行简单一致性或冲突检查"""
|
||||||
|
def task():
|
||||||
|
self.disable_button(self.btn_check_consistency)
|
||||||
|
try:
|
||||||
|
api_key = self.api_key_var.get().strip()
|
||||||
|
base_url = self.base_url_var.get().strip()
|
||||||
|
model_name = self.model_name_var.get().strip()
|
||||||
|
filepath = self.filepath_var.get().strip()
|
||||||
|
|
||||||
|
# 读取关键文件
|
||||||
|
novel_settings_file = os.path.join(filepath, "Novel_setting.txt")
|
||||||
|
character_state_file = os.path.join(filepath, "character_state.txt")
|
||||||
|
global_summary_file = os.path.join(filepath, "global_summary.txt")
|
||||||
|
last_chapter_file = os.path.join(filepath, "lastchapter.txt")
|
||||||
|
|
||||||
|
novel_setting = read_file(novel_settings_file)
|
||||||
|
character_state = read_file(character_state_file)
|
||||||
|
global_summary = read_file(global_summary_file)
|
||||||
|
last_chapter_text = read_file(last_chapter_file)
|
||||||
|
|
||||||
|
if not last_chapter_text.strip():
|
||||||
|
self.log("⚠️ lastchapter.txt 为空,暂无可检查的章节文本。")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("开始一致性审校...")
|
||||||
|
result = check_consistency(
|
||||||
|
novel_setting=novel_setting,
|
||||||
|
character_state=character_state,
|
||||||
|
global_summary=global_summary,
|
||||||
|
chapter_text=last_chapter_text,
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url,
|
||||||
|
model_name=model_name
|
||||||
|
)
|
||||||
|
self.log("审校结果:")
|
||||||
|
self.log(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"❌ 审校时出错: {e}")
|
||||||
|
finally:
|
||||||
|
self.enable_button(self.btn_check_consistency)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=task)
|
||||||
|
thread.start()
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
def read_file(filename: str) -> str:
|
||||||
|
"""读取文件的全部内容,若文件不存在或异常则返回空字符串。"""
|
||||||
|
try:
|
||||||
|
with open(filename, 'r', encoding='utf-8') as file:
|
||||||
|
content = file.read()
|
||||||
|
return content
|
||||||
|
except FileNotFoundError:
|
||||||
|
return ""
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[read_file] 读取文件时发生错误: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def append_text_to_file(text_to_append: str, file_path: str):
|
||||||
|
"""在文件末尾追加文本(带换行)。若文本非空且无换行,则自动加换行。"""
|
||||||
|
if text_to_append and not text_to_append.startswith('\n'):
|
||||||
|
text_to_append = '\n' + text_to_append
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, 'a', encoding='utf-8') as file:
|
||||||
|
file.write(text_to_append)
|
||||||
|
except IOError as e:
|
||||||
|
print(f"[append_text_to_file] 发生错误:{e}")
|
||||||
|
|
||||||
|
def clear_file_content(filename: str):
|
||||||
|
"""清空指定文件内容。"""
|
||||||
|
try:
|
||||||
|
with open(filename, 'w', encoding='utf-8') as file:
|
||||||
|
pass
|
||||||
|
except IOError as e:
|
||||||
|
print(f"[clear_file_content] 无法清空文件 '{filename}' 的内容:{e}")
|
||||||
|
|
||||||
|
def save_string_to_txt(content: str, filename: str):
|
||||||
|
"""将字符串保存为 txt 文件(覆盖写)。"""
|
||||||
|
try:
|
||||||
|
with open(filename, 'w', encoding='utf-8') as file:
|
||||||
|
file.write(content)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[save_string_to_txt] 保存文件时发生错误: {e}")
|
||||||
|
|
||||||
|
def save_data_to_json(data: dict, file_path: str) -> bool:
|
||||||
|
"""将数据保存到 JSON 文件。"""
|
||||||
|
try:
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as json_file:
|
||||||
|
json.dump(data, json_file, ensure_ascii=False, indent=4)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[save_data_to_json] 保存数据到JSON文件时出错: {e}")
|
||||||
|
return False
|
||||||
Reference in New Issue
Block a user