From 2fe5fe1b852c7ca5d6a1220d6b3a3414e8ccf84d Mon Sep 17 00:00:00 2001 From: YILING0013 Date: Fri, 31 Jan 2025 13:50:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD=EF=BC=9A?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E7=9F=A5=E8=AF=86=E5=BA=93=E3=80=81?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=8C=87=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 允许用户上传自己的知识库(建议在线版API,或上下文长的用户使用) 允许在生成下一章节时加入提前指导 模型Temperature参数支持 --- .gitignore | 4 +- README.md | 21 +- __pycache__/config_manager.cpython-310.pyc | Bin 969 -> 969 bytes .../consistency_checker.cpython-310.pyc | Bin 1421 -> 1403 bytes __pycache__/novel_generator.cpython-310.pyc | Bin 11554 -> 18027 bytes .../prompt_definitions.cpython-310.pyc | Bin 5291 -> 5294 bytes __pycache__/ui.cpython-310.pyc | Bin 10501 -> 12543 bytes __pycache__/utils.cpython-310.pyc | Bin 2480 -> 2480 bytes config_manager.py | 2 + consistency_checker.py | 18 +- main.py | 2 + main.spec | 18 +- novel_generator.py | 337 ++++++++++++++++-- prompt_definitions.py | 7 +- requirements.txt | 3 + ui.py | 127 +++++-- utils.py | 2 + 17 files changed, 461 insertions(+), 80 deletions(-) diff --git a/.gitignore b/.gitignore index 47b5f9f..f2bce34 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ /build /dist /.vscode -__pycache__ -config.json \ No newline at end of file +/__pycache__ +config.json diff --git a/README.md b/README.md index e758021..9699b88 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,22 @@ --- ## **2. 安装依赖** -**手动安装以下依赖**: + +**进入项目目录,执行**: ```bash -pip install openai langchain chromadb langchain_openai langchain_chroma langgraph typing_extensions langchain-community +pip install -r requirements.txt ``` +### 安装语句切分模型punkt(可选,默认程序运行后会自动加载) +**Python环境终端输入**: +```bash +python +import nltk +nltk.download('punkt') +``` + +等待下载完成(很小,下载很快的) + +**至此,环境配置完成。** --- @@ -82,6 +94,11 @@ pyinstaller --onefile --windowed main.py ``` 这样会在 `dist/` 目录下生成 `main.exe`(Windows)或 `main`(Linux/macOS)。 +或者使用提供的`main.spec`,执行以下打包指令: +```bash +pyinstaller main.spec +``` + --- ## **6. 使用指南** diff --git a/__pycache__/config_manager.cpython-310.pyc b/__pycache__/config_manager.cpython-310.pyc index 62b3d078372ae3c5c701c3804bc1de822c574f60..575cf01f9c3ac2e49de4b4df2c21b9b20b3a2332 100644 GIT binary patch delta 43 xcmX@fev+LxpO=@50SL|*%t`;Tk@qemBirWJj2VoK0+U0S^Vpd<7=@UmxBv<`3YGu> delta 43 xcmX@fev+LxpO=@50SF4^W~HCn$a|NOk!AC1#tcSAzR4lXdF%`vj6zIOTmbk%3G)B| diff --git a/__pycache__/consistency_checker.cpython-310.pyc b/__pycache__/consistency_checker.cpython-310.pyc index ab16c36ee0a871aeb8328be08db63ef69bd8e32d..792d4c92b1a4922621320a2cb554bcaeb84002f3 100644 GIT binary patch delta 650 zcmZuu&ubGw6rP!#ne0xIHPuqhrAD!b1)+l15S4)7rHB=jh)T#z)97Y*dAm7Sw&YO3 zQx38R4~it<#fumJ6g};=q~c#-K@Yy&7VN<{eBXO-=KbQ$tomC`!+Kp2lLD&cfp%7KWbGv}$jp!+IS@ny}o3BHL`y%2M`AZ5gU27jRSa8xSscw5cZMcNj^53e?>{a-&GxSRH}iq1#SvCCt#-TZRWg@fl_G*skYt;@ zNvqiwU*CY@)5-yI8I`S|jyJgt<|O$ufwk-ne>7dg>-ZWHuQPgb6em@uvcKf5?c^H|XKLUmi;3DN#oIoyi{IH*tX;5@JLl=u6yV=IA bO?=a#=*HKPYK$}13|-vD7Kju>VngR>+~Fk(_%VpO9{1}Z=w&<_j%RbWt~_aZFB!b>zR zvymAT9W5?HB2^r$IPI@?sa?OG)^dihHry}WqrHz#ed*9@*qxSWwg#R?zSpU$Fi%d5OK6;xsN^z>z#PKc5HofzrU~_esbQI#cNBvdegTS zRr`5YH1|cdbKJ1px8>7#@nqX)@2-0O-TM_>3zEAfg=@*lhgDZraReG0@t z^VmctHi;=QT%`*>gP4j*O_>oUf$cZ_P094N@It^0=ocBCL1ti{HsuzA7RbdrkVKsX zxH}J=l`XN=vNxaE<+@WFm2Sl(@N()2JP6|6(djD#{G5)`o}A4yEyKMExgVq#G_-6% z<4Gu7D~`d+Q2WHoroFytx2sX5)|W2TLUepu<^MV1EFTYeNtFW6XA1@odWN5CdLf_F zwQa=5YO^#}8i!?l5`=~bJ{`&*qMXfDg<^_T%rC&E+fk+GOC(r6l}Pj=nPe`VnoZ8- z68VCbOU`iuTX-0xC+P+sL~!pOLvr5p`me&(iy3VSd%se@3_?+eQbNwvV)6tT`Slle CHsL`4 diff --git a/__pycache__/novel_generator.cpython-310.pyc b/__pycache__/novel_generator.cpython-310.pyc index b8aa27c00dc477672b67df880dee732d71c2c1ef..0c40b7e000d701a7132903fe6f0db5b75319972f 100644 GIT binary patch literal 18027 zcmbV!S#%s#nr231F^m*oYM-oOc1>`-Nl%U}$+3j~c#Rae+ll2)YTv~!VfMA}## zIxTNl2Fo^JR@;R{*%()0%f%`$;65`wGpEn=IsG{2#KSxc%9M2Gp&sb!s;((uzW?5c z$i=b?I(6dS8+VWY{`bHCc9pNM4}0+Y=BeYot5P1%SM*Z(n~j%Mc=WK>>iiJgMN$Ws-;fcv(o4{lb%z zc2JIz31{onI$r9_M6&g2eKx8_vkhtk&-0_aQElYuK&C0%tTyv>Fw>H4Ra<#l%CxC% z=u679tL=zGnOWHmwS$+1GqbaE)Hys|mzkTLr_RgXpx%(3ug=fjsNTr)Bbiusfw~~O zP+gc^q%O)XRu^ZNs7tapsW)YBR&UNORhMStYFzT9q%F7LxmByzqQf%UHmJ90jj3f? z6Q1UjFSTs8M{7wn_XM?8t!=NOcDki4DSxVYwx`FhwQI9ba=Tm7fwnE0gf_FaIVib9 z)7-LoD7ztbC;D906V~QyH}3VRcRl9OV%mbe9&JG?u*a+39a^ZR+!l+_Vlkd2cy2<= zoAE3~I-a_RkJ75$qTPyK?oE5N+ceK>lD3S8ojkl3Bpy{LkJ(%RwH~wTZ8aX?XlM+^*-%!jNFpCvq#a^YJY=o zyFYY?)}#FtxldqTPugD=)Vj2%FsC1dp2oNIYJZFJXYe?XU1!&)@1XBlkN853UoXHtp*@e*4`^vEiSo)a zzqU#1M%fBYTIY#xnWkOR757@oBk69#Ql3cbh829O&q(KU$xK|bLhE<-r8FwUOL+C> zr14Z=D)-P@GzqUWl1Az=mh9VX`PQv}=$ZAFcTLwLsMjd!tjTUlX<9nht0T|9dNa#s zlV>C=_(;CHkWJ;N$Ch=7Q@QTcdX~)TJ$aT*F=RJ&=k;_hmC)1KbSBBt#?H86g;*-7 zC3@1Cl+}{#!z{Ffk$S;M7GiqTlBL;{t|xo(5|=F>FSGmv*`#6BZRgX}G1Zh6NcN=@TT?r& z;HIRWN)%Ye@)1CF%g<6qf#teEEZv_UDIcEIc=Y=a7#`DON}4n*4|p|M^X~PS@*|!- z(tu(}TVzu)y@pp)hP?wm(>J1wf?S$Dpq#JpE*ghp_!Wmv6i>fgJorZO!e>)gjuwyo zYGQnFV*ED(Q;J7kncjc6c=5I0Up`no|Kh~uk>b8nlke;;y|KS^{KKg$$EHRP7C$|R zOf;Xka-w+lt>Vym6cvx3pFDQ1_{HmA4el1>yuZ8jtK$>n!&K$?2UBCGL^(R1xctu4 z{@)Z|`k**;ta$07)8dS*M)Sb1WIAC3No}ZjCwO&B_zdSKBSFAfhv(~d1ib*xbAbT~ zK*ISlEWILclh=99NM|H9(uc)y5R^qw8S5l>-eF0`84tS!?|*)vd-=v^^%T=LzK~93 z(i06C_-^Pd5Cb|gU9!up?EQv+jDA2FhdGGAoX@ys0u!*~Y+$-;<>CoKTJ^f44 zxwMfG>yHfq1n69m$#*9+`bs367I9e(rJQYRHT7W*C@SN3+{fsoWpxPDXb-kfLbG?X zZZ-aF&Fb|}J(F06U`^tYwa-`qyMwr&#i-c=3Kk-;!tN-#8gjc}H&O+Hfe3D;s!dnT zoGR!Y2xOlu$&&xusG=a|>2HEQMPhfMu$#Wz`4K8BcsTklMZ)lGkw#?0J1XfxqJ@US z(OCvM%ZAVJnn3ActXW(sJb>y`W0#7D-za|mk-#C~p^KkDnyHshx#(3KdRriKtSg^O zp|FcBMh9`3bs|!m-49Rn6mr|!@#j`4s1 zR3MY@?L`$!N#{UB39nc^0;@i$;h1%BpWQ)id=-myKUMPavDE-U8+B_Jt;IeSDwpk1 zw9^|Agt2U4DJmh9BT__Sw4l?3)0S^rA;orD($;QTD(4qK!d<$D@!~F>Vk&yHymYh_ zrm_VnZh8sH2Yi|`qG&$N4;tanO!KD?C~lxL9XJp4yIcFO%VnCzzFdU%K1! z;q(JH)0`{d+yV#HI`fVVjl|NSwkpuek3Flmq%-<&jUi!5orcANyDKCf-i$!5XldMW+(?FO#y$0gA$^U zUkmJ24tTVnp=cpGJdrCyG*tF`_Demo7S`(aMg~IHERSgQC=Xw=JgPOIyzZLijan1R zBiAf%)>=?rZ}<%b{AtvTjs&z;Gc<~?;^{Us?55kzIyXJbjJW9zv))bzjgZ#rq)9Ax z(`{P2n}$&Are|p#Zo0#8#;P0j3^bTtaLPW@ugyNL3^bYzBN4L^)VIlO(*K81KN8jE z&|$011;uU}Y0&0rH;l>y%{)Dyr(4Ws_P@*){eSb?H#)Vkc8w!VSiKnPx4Lf&@Ybd+ z#L9*E$}Q3s?^Ooc&33aHkeP+J1#ySbY|b)U_t2_0n;m8wPt}jKXiJ>_<2Mz)0|zWl zhA*z*lrElpr*!sI>C;`4AH2-Z?RQHFW2Fy9i@$ogcw~6;^!dtmoB}zMfk1Qq@YL8R6XTbr-+QaF8mCaT)h2&;34K;p zyPAS6kJ&8J>s7;6kTvSHIQ%-QL8d8t(;K>si5X|tMN0%i|_Ml>q(sF3{@h(^^9Wq1ObYHf{2r815%dX5w%%L z?*rL$rJpJeola0!hN9!v@QgsbG(E?_JDvca4rHVpuPK#tGzABPudoW$A=#AinLmvDcsLkA^ph z^4Q9iE1!$8htX+&!v?2nY{iNd&&9gV$ozz6KSn9i&m&?)rz?m!ms&zvYEz*%0rv3_ zO1~z?tXXcC`tKCDUvr2gY!R+PD0ABr7YSVfTZAsEco-toM6g@%=){F^f(@#!S?z2mJVpaG^C&*&eL!4(T3Loro6>R+&h)T zLtQf+EN7INa{CuK-;>}Q(cb^dvGHRL5_GaBF=e(20hk#&<=A`#mOq`_p5K}hP=17R z0**EZfMDsqGhWt9wV$W5pwk`JB1O3g3fyDVYMXH)ub~nbAQj2){QV{DCq5?#Ry!*7 zKlpu@X~zDqS|(a5TBfh*SS~LUPFAUAQ9AgdaCFzelV7zc5V9&3#oxuD2$fmAz$hEi z{Y%|>UB%+mtU?!Ngx&8(2l*{~H4Pc%^O9wEd)A>&Tre%gv zKBTU61pN>E;O{Tb^{Nji(#Z({ao}YPt0V+m8&^#N=5uWGaS{M7?ka5aS7BRFU>k=i zBk({i!8b9-A}pD@@UaH8Pot^>+F6wT;W*bm^FW`YH=S0?-Pzv=`NiJXE59$y zXYT$guoGS9V5d*21v?k6s{uZj16<(%hroRmzy!0B^0AGYw!R1YhCx18f$tiG{Hjr0Q-vltuP6%E{)cO78 zcP1}iES`S_d@1Yz)dFD*Yz7odRb~Km+TSE*SHb%Bw}>ovv54=rya=k$3T!pk z;cI$EaHwnYsEio8_E8zfvC{QtX%SyqOS%R0KfqFbnPhh=F0mc-A)GpwNpzTuBROs` z`48%sxUE*H|E}wUu&k#jH}AiPw~5&wrpJ;wSnnJINX%JIEv6T`yJ3pxDP%G`JL9eF zZVYJ!v&pR~)YchsF6>zfNRVYTAr?TO21&D)gCr|bDXllff>cVvDlr4uKFG2)CzR!d zR)gI`)z(nZL;>+=@!2d!=>-)0f`S>uW2RC8F{{_aI@Q<9EnQxip43P>*H?fEiDVLP zF3}4agKtl@trA6Cu&5DY!?}(Ca8o)eTT zl1W7rL3xm;qTZ;~>?IxS z9_bO!b2mXP4$Ihf54+uvOfckR7t_33eQYV@e|f9IZZv%Z^0qKEEMC*s>bc*8UeE(_ z@NUYjo>?Ax3wcBxDmRHSR|>@Yb9tN$uyq@6R888y;fU<{VnL;?DCHDF}>65Ep* zOCfg5@*-JFsGyP)V1~68+iO$0yB?$K#2_d*UkJw>O_*PfD1IsGRlGsy%Gs+lY-`Pi z;uOz{h+JC}7>2DGCO94T+$!5g13DmF;R^{z3@qJ<2TB@mFP8K@*fr9$XkIMlL!=%b zkx+(MZ`&5ZatBNWC3g%24H>HSpy@{pWhV@vAv46*nxPS>*&%s_c?v8SZ(nD~BVNr< z3MlrVff(wSNKP>VB4yTL=_62p1_t1Nl9RU0HUsR*ZINw}UJn#nL9)PfaG7luJWpnM z_Q;sS5>j!QKA_qlVri?)bhCa0>NhhA$vK1`qq8AZpWs}`whg>2e62Dzq}7dtq5J}> zX%V1Xec}HCMjS85B*0Z3LVq9>{=hIotiwlOFDo6~Up)7YK&;u=l{pmcPsbZ{56R+FcPu?MD3@1i!sA~kZkc=@nXG43xc!WaK? z^1}}|>bET2uu*&N);LnAyJ0z4gXexi*`2q<7Z-?oaO;84fyRNnVMY@d4^E6900Eo0 zc(l0h?8Nw<;=bQbTsTo2-UZSFf`hGk&0ZH$ud9WzZliwd(v2D)zjp8&M78VGnDH}U zfvfFl=6Yi%YU(`-w!_zxN%rc3Hav(46z-*2Z1~~dzIc6s_TN{7N5t}eHTVhuULfKM zKm*L28Y7GAWdu)K2TzW^Oq#n)=v+Ltpap;yDrvw@5?=dQ7Yxy>A6mBtMgo>E^l=iw zevU3JAIC4tFC0lY&k%25uc3$?rQmg5i93NEq#LqWgN6a<+hyDx~N)Ql9F5+e%ObD4-GO1@SaaeRNLD08@JVV*QcmIuW# z&fX&stU_Q#1?6;@J*&;8q}6PT73PXRZUnLFGD-L!h?!dT1^6)}dJFJGf|pNs9r|>B z^N=9TfESP6Dcj5VsT|tC=*|7MU86~BhFziA42}dw zJ*>w>%4~wEyd@`_O#>~wO-oH1a-)k3wAyV3O{C05v}r?|#(_4Fbh66AdZ2YcVb^Yi zK;nJmNQZ-DmIm=rn}_EHJoAmPb|c~#9;ov8%o^>PEz}laln&_bJ!YHP0+QbjjeclP zU|@DtAB)j$$qfB8+x^_6-Hd+bm~-rYaQxUk1qbGubB{ zwoC9m6}E@vJSP^rGaf4)`vhJFw+r8%;=T`xzZ)#Q^48S*yX-1=xmAR;H+A~esRJKP zzVumXa2FkBqrbK*-W`t#Q?>9wu#cfH2TRA_ofvq%9s90+Uu6`h4=z+r|AC z(ZTe4CyFQesXKNVN2}e(edy!Gx8Yq8bLhw4SMoyxG`rhuV8v+jI&_8#>^|;yK;@e3 ztL!q>RKhiNkW;{=Axv!Ii{r)Dc1@l866^rY-?8jFleEn{GZL5Owp~Rq8G=jTvzpl^ z6cfJ+i_cH5YZ|Jq|1epHo_*$t$^pu>&HXSmx}IIA61?R$;$Q>%32bxOjpE_YD~GEw z*x2*K!oyZ$TW=hTO=YthqmbR~`nI78N?o2NEObC=&QQI$uXyeP){FZ$k@BQs;cS+y z^yY|Lzh*V7NWa^^K-uEhg-SNA26v%SDed{V^zrMJA6R3IvOiG3vQ)N7Ef>ylw*X?3WZgPeBsFHO*9P6G~xzBDKQzF+breW44Kjte}o% z3GD5}msjBBGbdnVvK_Ljv#WKgFjmb*+v{sm;|d>u zouQh~Q;?;A>t)JHxwaXH`y6ZaHQB?2+n(Rnt3vVx6Gt9Kw&vG?;Y{l{W5-sA1DaO4 z3Z*L{%zlG`%MfDc@Y=uF?yhEeuY$`Q`o`<3@*TKPx7UVC`^@A0Ga>O=n$QD31PC>= zE=14+e2u3^zbqa&UD~sck=()_qF^@yC{T&{XWI~1VQMTk2m38$@J&E2MOHmfmn9t^ zFDvRY;t41)3@hwXzf?DAqnsbkl8Ppe5T5h=EHed#AR7Y&1dVtd=PTGh7!t>G=)ej$mI4$IHL69q12SE^V z226eWIh+NEb8yMlSw}3U{{GO0uLy@Bw25vV*UVSK6(yWSAx*&8We9cQKad@Qw<#Wa zv3U3+mw}=};0K302~d@kgae;TX8kufKJutSGFCMf3c)bI=3Hl> zDENqik16;B0o=f=Jo0DU#tlv#dq!UYPj%Yma+D-2{>bvTP_TYWD$_8eGALR96s{ww z%-+`bd@qcy8ORFQ8J4mGDA`TQE9VzRg7A*x8J%Yalvo14IGzQn@C?|;xo`zGLf=4; zV_FcH2Gx}g8G*b*xycGXp>Pohnjst05IdOG#XiL->@y0;3I}gkT1qxf0l~K{72C7s zo@xDa#odPMt|p0!e`PhpwX1oN+FYWbc1dYK_*UANEM{EN`l?9 z{xKY>Skk+`*OFEZ)E`_ekUG}iaV<+qe~VKB7s6Oi5_ScxGv36_E6k+tCGsLHD`k5~ zBjs(S;4uo`qkt44jPQez7|#8R%f2n#?jN7WeoaLuDTq<9fPzyLa5V|Hn><0U?^AG^ z0y3jO?`yLSba%jgNF1 zVdh>38MYI>pnVfvQe}h*M*UH#U5-L?6D7TkG|wB1L?lIaVi~#}|94RzH^h8r)0b~# zMg9veM*Kh7FaKE%$p0Y+8~-HVh?V5b+nQ@#x195cdWjB=BfDR4E`V&|pIs z&3QI{ivXq$C|tnJ5n<7x;6NSW4g<3%^x|HWDo~T)h=ke%Mil*i)CrMp9SVdn<(rCg zC&6}Vhh`qF{HAZD&gA}1NJZ#Y0&j^U)xMt)EF8oX`Ef}{B|o{Q2M>@A+qrmT%VA@e zYi=tL6MyLx_;?5ckhUT4mfrkKs0*AE6H+^!xW_+ub}gA<4j-BL@>uEMf$3Mzl}?<3 z7aEOs_GEGY7ckE_Z3@IwO}=|-YV1Yabr6HQHy&{G6^PxcDR+)<64=mB|4*yD=D*)X z2sqZW*w$Qr2dGPLDwfXn<(Yxo5#7L5_eX3x+2GdUVM{2uiGm0MaM()`SyErEIC2}Z zHMC^Qkm!$#o)hj+e3zF6k@D4~0@bhm($7DFs8oFEUGNrIkE#71yXpyic)VT^MNS3p zMe7QOCYK&s?MV&J`rX*%T$k+L%({;n{Fs8fC|FLx9SEvC@Bfi8 zvq!0jxBwB{ij>thq`O2Om>R~BeWu{F5?>YjlRHfpdJnoBc8HTTF= zkJk;GuyDfa*<}Vsg3#`woVFi$3SJjna9}Qa+;%GrY(XtJ5;Czpc@17yj7qI|odDZX zTMskDCWjepHuSxIXeL2mU|rn}(h$J~r4a7B)SGo61Tbkyo^3g#gYfgV(-05Cg_8h{ zLjW~hhD^fpWOQy>`kSS3#H+6Q)6c*E*!uOn2F1m;T>yV zPdqXNuV&PQ9?Y#-UK>#frXhHyicV?Q^MP&EmVKB&19s?LT$QxvQdU>k)n-BhxDFp1 zpWcmIYkbapCLm%p^PGNbs5l5!A#PY6h5~}f&){X;!NI-A;$_Tv4Ej?dedj)%I`T#3 z%*tP}JOfGq$IkG!XeYOt@4u09^7~~9c z9Df-WYd}>fEnOv!fJ^bz7w#;ILvO+u$mgQ^`U<(N#(J_tZ7ZZxM*l3@g?*bDw0O&& z#J#~Mo?Q0Ceap7rRkmRF0{`i^>d#UPqTYW;G9Iy%T*la{2DSW-99^VS>vghO8}LKT z!Ai_%Qu!iy)1OeH!)82b@mk+m)wRlwcPyRQmNdu}O z>YlDW>%w34PReY;-REAAp)w&+n;h%8Tfdy;-XpZ6wCV*4;65hzPSR7obh}ztaiNeU z)rel$1P6SPS3{84f5E_8i2M+ZjMhUOfpLhAd4&_4W|@pbQMp-e#5?7JK!NEXKM(xL zikJ6bDWu1#(kT#`anJqrc)_-mHGZxOmx!$%wiJ#K@aCQc!b46HIL+w}CN4zmgYPm< zb{XWzH{u-?1VSO6z-g<8O>hHoxj=$N^;QMGP3}8g#!s=!9Q2|?dV1HD;=W;lufRfJ zs&m>-U3nX~4(X6~Fm_`6B2=goUkr#>fDr5Aiv0PW&q(fgnL=!GJ18k{OA#_JSIqEAKpE zm)5T4zo7joT212T62c?`Hh`@5TS(EmY1?=u^BM~R^3|8EUaNH&(tPSF~N6&PM4mD{m=D+gHPuY z;!ZUeSkwl}<_Bme54kf~+3}0tg;immaN5ETEK5!$J3GbAI)2qs7dMmVy45=AE*!t@ z?A+Dl*EiWeW2X2=A7EG2HfM7spwDss34zY{yxO5}rGF}r>&(KtEZwbl_9fG7M;bOw z+WVHTV5BoTL^V0aqvzrzi9l z^N>>O;5nb}UY*bNq;dOnZP%kui95m1QZ>$!a1Lb!y>dy83wDp=m5lOvn4))4(IE<6 zrQkgZh*#%K1mCzEBPy_nvn!la39f+E1J~GA;wbd!_)jPv591DKyjwpw}m7j$X{10vp+FmUnk=R3XkpEq-lm8$`!vE&^hdJxy OMY2@x%a6Wd5Bz@#`iOJ@ delta 5049 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 diff --git a/__pycache__/prompt_definitions.cpython-310.pyc b/__pycache__/prompt_definitions.cpython-310.pyc index 3e98f89f20ffd35837428d4b5604ae519b8cef80..44af9b959844b16765b425e8480bcb518f4c74af 100644 GIT binary patch delta 74 zcmZ3jxlWTepO=@50SHVD=cETuwdqd bb8|m8C#NtI3mXeR3l|Xb10fHKAcp_|o>&$3 delta 72 zcmZ3dxmuGqpO=@50SKz(W~Iwam7gCXPJ|Uaa5ptYg~deN30x8Rt%(#Px0S Z6mE7-VFng97Je2kAmj%^9u`3k0RUem6jlHL diff --git a/__pycache__/ui.cpython-310.pyc b/__pycache__/ui.cpython-310.pyc index f3470fdbffe9c097647765e1a1f1afa4b76a6ea5..af2051ec73ac7222b967bcd9c1506bb51982b46d 100644 GIT binary patch literal 12543 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 delta 5103 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 ZWYnFk$l1ip#K9=UB*eqVEyN+k0RV#p59$B_ delta 70 zcmdlWyg`^ZpO=@50SKDqW~EQr$Xn0E$g;VY=>sF9z~(xZOh!iO%`aHn85xx~7qD+& ZWYnIl$l1ipz`-cQB*eqVEyN+k0RVpV56=Jq diff --git a/config_manager.py b/config_manager.py index 892fa90..c3b45f3 100644 --- a/config_manager.py +++ b/config_manager.py @@ -1,3 +1,5 @@ +# config_manager.py +# -*- coding: utf-8 -*- import json import os diff --git a/consistency_checker.py b/consistency_checker.py index 2cf6afe..cf34349 100644 --- a/consistency_checker.py +++ b/consistency_checker.py @@ -1,7 +1,5 @@ -""" -演示多Agent思路中的“审校Agent”,对最新章节进行简单的一致性或逻辑冲突检查。 -可根据需要进行扩展。 -""" +# consistency_checker.py +# -*- coding: utf-8 -*- from langchain_openai import ChatOpenAI CONSISTENCY_PROMPT = """\ @@ -28,7 +26,8 @@ def check_consistency( chapter_text: str, api_key: str, base_url: str, - model_name: str + model_name: str, + temperature: float = 0.3 ) -> str: """ 调用模型做简单的一致性检查。可扩展更多提示或校验规则。 @@ -43,9 +42,16 @@ def check_consistency( model=model_name, api_key=api_key, base_url=base_url, - temperature=0.3 + temperature=temperature ) + # 调试日志 + print("\n[ConsistencyChecker] Prompt >>>", prompt) + response = model.invoke(prompt) if not response: return "审校Agent无回复" + + # 调试日志 + print("[ConsistencyChecker] Response <<<", response.content.strip()) + return response.content.strip() diff --git a/main.py b/main.py index 2639413..52e3da3 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +# main.py +# -*- coding: utf-8 -*- import tkinter as tk from ui import NovelGeneratorGUI diff --git a/main.spec b/main.spec index bfd34cb..57e1776 100644 --- a/main.spec +++ b/main.spec @@ -6,7 +6,19 @@ a = Analysis( pathex=[], binaries=[], datas=[], - hiddenimports=['typing_extensions', 'langchain-openai', 'langgraph', 'openai', 'chromadb','langchain-community','pydantic','pydantic.deprecated.decorator'], + hiddenimports=['typing_extensions', + 'langchain-openai', + 'langgraph', + 'openai', + 'chromadb', + 'nltk', + 'sentence_transformers', + 'scikit-learn', + 'langchain-community', + 'pydantic', + 'pydantic.deprecated.decorator', + 'chromadb.utils.embedding_functions.onnx_mini_lm_l6_v2' + ], hookspath=[], hooksconfig={}, runtime_hooks=[], @@ -21,7 +33,7 @@ exe = EXE( a.scripts, [], exclude_binaries=True, - name='AI_NovelGenerator_V1.0', + name='AI_NovelGenerator_V1.1', debug=False, bootloader_ignore_signals=False, strip=False, @@ -41,5 +53,5 @@ coll = COLLECT( strip=False, upx=True, upx_exclude=[], - name='AI_NovelGenerator_V1.0', + name='AI_NovelGenerator_V1.1', ) diff --git a/novel_generator.py b/novel_generator.py index 2ac9aee..35d07b6 100644 --- a/novel_generator.py +++ b/novel_generator.py @@ -1,5 +1,8 @@ +# novel_generator.py +# -*- coding: utf-8 -*- import os import logging +import re from typing import Dict, List, Optional try: from typing import TypedDict # Python 3.8+ 直接可用;若是3.7可改用 typing_extensions @@ -12,6 +15,12 @@ from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma from langchain.docstore.document import Document +# +import nltk +import math +from sentence_transformers import SentenceTransformer +from sklearn.metrics.pairwise import cosine_similarity + from utils import ( read_file, append_text_to_file, clear_file_content, save_string_to_txt @@ -23,7 +32,7 @@ from prompt_definitions import ( chapter_outline_prompt, chapter_write_prompt ) -# ============ 日志配置(可选) ============ +# ============ 日志配置 ============ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") # ============ 向量检索相关函数(Chroma) ============ @@ -37,7 +46,7 @@ def init_vector_store(api_key: str, base_url: str, texts: List[str]) -> Chroma: """ embeddings = OpenAIEmbeddings( openai_api_key=api_key, - openai_api_base=base_url # <-- 这里用传进来的 base_url + openai_api_base=base_url ) documents = [Document(page_content=t) for t in texts] vectorstore = Chroma.from_documents( @@ -48,18 +57,16 @@ def init_vector_store(api_key: str, base_url: str, texts: List[str]) -> Chroma: vectorstore.persist() return vectorstore - def load_vector_store(api_key: str, base_url: str) -> Optional[Chroma]: """读取已存在的向量库。若不存在则返回 None。""" if not os.path.exists(VECTOR_STORE_DIR): return None embeddings = OpenAIEmbeddings( openai_api_key=api_key, - openai_api_base=base_url # <-- 使用 base_url + openai_api_base=base_url ) return Chroma(persist_directory=VECTOR_STORE_DIR, embedding_function=embeddings) - def update_vector_store(api_key: str, base_url: str, new_chapter: str) -> None: """将最新章节文本插入到向量库里,用于后续检索参考。若库不存在则初始化。""" store = load_vector_store(api_key, base_url) @@ -72,7 +79,6 @@ def update_vector_store(api_key: str, base_url: str, new_chapter: str) -> None: store.add_documents([new_doc]) store.persist() - def get_relevant_context_from_vector_store(api_key: str, base_url: str, query: str, k: int = 2) -> str: """ 从向量库中检索与 query 最相关的 k 条文本,拼接后返回。 @@ -86,7 +92,6 @@ def get_relevant_context_from_vector_store(api_key: str, base_url: str, query: s combined = "\n".join([d.page_content for d in docs]) return combined - # ============ 多步生成:设置 & 目录 ============ class OverallState(TypedDict): @@ -100,7 +105,6 @@ class OverallState(TypedDict): final_novel_setting: str novel_directory: str - def Novel_novel_directory_generate( api_key: str, base_url: str, @@ -109,7 +113,8 @@ def Novel_novel_directory_generate( genre: str, number_of_chapters: int, word_number: int, - filepath: str + filepath: str, + temperature: float = 0.7 ) -> None: """ 使用多步流程,生成 Novel_setting.txt 与 Novel_directory.txt 并保存到 filepath。 @@ -122,6 +127,7 @@ def Novel_novel_directory_generate( :param number_of_chapters: 章节数 :param word_number: 单章目标字数 :param filepath: 存放生成文件的目录路径 + :param temperature: 生成温度 """ # 确保文件夹存在 os.makedirs(filepath, exist_ok=True) @@ -129,9 +135,15 @@ def Novel_novel_directory_generate( model = ChatOpenAI( model=llm_model, api_key=api_key, - base_url=base_url + base_url=base_url, + temperature=temperature ) + def debug_log(prompt: str, response_content: str): + """在控制台打印或记录下每次Prompt与Response,[调试]""" + logging.info(f"\n[Prompt >>>] {prompt}\n") + logging.info(f"[Response <<<] {response_content}\n") + def generate_base_setting(state: OverallState) -> Dict[str, str]: prompt = set_prompt.format( topic=state["topic"], @@ -143,6 +155,7 @@ def Novel_novel_directory_generate( if not response: logging.warning("generate_base_setting: No response.") return {"novel_setting_base": ""} + debug_log(prompt, response.content) return {"novel_setting_base": response.content.strip()} def generate_character_setting(state: OverallState) -> Dict[str, str]: @@ -153,6 +166,7 @@ def Novel_novel_directory_generate( if not response: logging.warning("generate_character_setting: No response.") return {"character_setting": ""} + debug_log(prompt, response.content) return {"character_setting": response.content.strip()} def generate_dark_lines(state: OverallState) -> Dict[str, str]: @@ -163,6 +177,7 @@ def Novel_novel_directory_generate( if not response: logging.warning("generate_dark_lines: No response.") return {"dark_lines": ""} + debug_log(prompt, response.content) return {"dark_lines": response.content.strip()} def finalize_novel_setting(state: OverallState) -> Dict[str, str]: @@ -175,6 +190,7 @@ def Novel_novel_directory_generate( if not response: logging.warning("finalize_novel_setting: No response.") return {"final_novel_setting": ""} + debug_log(prompt, response.content) return {"final_novel_setting": response.content.strip()} def generate_novel_directory(state: OverallState) -> Dict[str, str]: @@ -186,6 +202,7 @@ def Novel_novel_directory_generate( if not response: logging.warning("generate_novel_directory: No response.") return {"novel_directory": ""} + debug_log(prompt, response.content) return {"novel_directory": response.content.strip()} # 构建状态图 @@ -196,7 +213,6 @@ def Novel_novel_directory_generate( 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") @@ -229,22 +245,132 @@ def Novel_novel_directory_generate( filename_set = os.path.join(filepath, "Novel_setting.txt") filename_novel_directory = os.path.join(filepath, "Novel_directory.txt") - # 清理文本(去除多余 # 或 * 等) + # 清理文本(可根据需要去除多余字符) def clean_text(txt: str) -> str: return txt.replace('#', '').replace('*', '') final_novel_setting_cleaned = clean_text(final_novel_setting) final_novel_directory_cleaned = clean_text(final_novel_directory) - # 以追加方式保存;如果希望覆盖可改为 save_string_to_txt() append_text_to_file(final_novel_setting_cleaned, filename_set) append_text_to_file(final_novel_directory_cleaned, filename_novel_directory) logging.info("Novel settings and directory generated successfully.") - # ============ 生成章节(每章独立文件) ============ +CHINESE_NUM_MAP = { + '零': 0, '○': 0, '〇': 0, + '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, + '六': 6, '七': 7, '八': 8, '九': 9, + '十': 10, '百': 100, '千': 1000, '万': 10000 +} + +def chinese_to_arabic(chinese_str: str) -> int: + """ + 只能处理到万(10000)以内的中文数字,正常小说章节应该够用了 + """ + total = 0 + current_unit = 1 # 记录当前单位 + tmp_val = 0 # 暂存本轮数字 + + for char in reversed(chinese_str): + if char in CHINESE_NUM_MAP: + val = CHINESE_NUM_MAP[char] + if val >= 10: + if val > current_unit: + # 如 100, 1000, 10000 + current_unit = val + else: + # 比如 “十二” -> 2 * 10 + 1 + # 如果 val <= current_unit, 那么相当于在这个单位下加 + total += tmp_val * val + tmp_val = 0 + else: + # 0~9 + tmp_val = tmp_val + val * current_unit + else: + # 非中文数字字符,视情况决定怎么处理,这里直接跳过 + pass + + total += tmp_val + return total + +def parse_chapter_title_from_directory(novel_directory_text: str, + novel_number: int, + range_size: int = 1) -> str: + """ + 从小说目录文本中,提取指定章节(以及前后几章)的目录信息。 + range_size=1,表示获取当前章节、前一章和后一章的目录信息(若存在)。 + 支持多种常见的章节格式。 + """ + + lines = novel_directory_text.splitlines() + + # 可以根据需求自行扩展,这里列举了几种常见的章节标题格式,如果模型实在不听话,可以适当调整 + # 每个pattern都应该捕获两个组: + # 1. chapter_num_str:章节数字(可能是中文也可能是阿拉伯数字) + # 2. chapter_title :章节标题(.*) + patterns = [ + # 1) 第12章 标题 + r"^第\s*([\d]+)\s*章[::]?\s*(.*)$", + # 2) 第十二章 标题(中文数字) + r"^第\s*([零○〇一二三四五六七八九十百千万]+)\s*章[::]?\s*(.*)$", + # 3) Chapter 12 标题 + r"^Chapter\s+(\d+)\s*[::]?\s*(.*)$", + # 4) Ch 12 标题 + r"^Ch\s+(\d+)\s*[::]?\s*(.*)$", + # 5) 第12节 标题 + r"^第\s*([\d]+)\s*节[::]?\s*(.*)$", + # 6) 第12话 标题 + r"^第\s*([\d]+)\s*话[::]?\s*(.*)$", + # ... 更多模式 ... + ] + + # 用来存储匹配结果: chapter_num -> title + directory_map = {} + + for line in lines: + line = line.strip() + if not line: + continue + + # 依次尝试每一种pattern + matched = False + for pat in patterns: + match = re.match(pat, line, flags=re.IGNORECASE) + if match: + chapter_num_str = match.group(1) + chapter_title = match.group(2).strip() + + # 如果是中文数字,需要转换 + # 如果是阿拉伯数字,直接转 int 即可 + if re.match(r"^[零○〇一二三四五六七八九十百千万]+$", chapter_num_str): + chapter_num = chinese_to_arabic(chapter_num_str) + else: + chapter_num = int(chapter_num_str) + + directory_map[chapter_num] = chapter_title + matched = True + break + + # 如果已经匹配到其中一个pattern,就不需要继续匹配剩余pattern + if matched: + continue + + # 收集需要的章节范围 + chapters_info = [] + for cnum in range(novel_number - range_size, novel_number + range_size + 1): + if cnum in directory_map: + if cnum == novel_number: + chapters_info.append(f"【当前】第{cnum}章:{directory_map[cnum]}") + else: + chapters_info.append(f"第{cnum}章:{directory_map[cnum]}") + + if chapters_info: + return "\n".join(chapters_info) + return "" + def generate_chapter_with_state( novel_settings: str, novel_novel_directory: str, @@ -254,19 +380,22 @@ def generate_chapter_with_state( novel_number: int, filepath: str, word_number: int, - lastchapter: str + lastchapter: str, + user_guidance: str = "", + temperature: float = 0.7 ) -> str: """ 多步流程: 1) 更新/创建全局摘要 2) 更新/生成角色状态文档 3) 向量检索获取往期上下文 - 4) 大纲 -> 正文 - 5) 写入 chapter_{novel_number}.txt, 更新 last_chapter.txt - 6) 更新向量库 + 4) 从Novel_directory.txt中获取当前(和前后几章)的目录信息 + 5) 大纲 -> 正文(可结合用户给出的额外指导) + 6) 写入 chapter_{novel_number}.txt, 更新 last_chapter.txt + 7) 更新向量库 :param novel_settings: 最终的作品设定(字符串) - :param novel_novel_directory: 小说目录信息(此处暂时未使用,可根据需求做扩展) + :param novel_novel_directory: 小说目录信息 :param api_key: OpenAI API Key :param base_url: OpenAI Base URL :param model_name: LLM 模型名称 @@ -274,6 +403,8 @@ def generate_chapter_with_state( :param filepath: 文件存放的目录 :param word_number: 单章目标字数 :param lastchapter: 上一章内容(若为空字符串,表示无上一章) + :param user_guidance: 用户对当前章节的额外指导或想法 + :param temperature: 生成温度 :return: 本章生成的正文内容 """ # 确保文件夹存在 @@ -283,9 +414,15 @@ def generate_chapter_with_state( model=model_name, api_key=api_key, base_url=base_url, - temperature=0.9 + temperature=temperature ) + # 调试输出函数 + def debug_log(prompt: str, response_content: str): + """在控制台打印或记录下每次的 Prompt 与 Response,便于观察生成过程。""" + logging.info(f"\n[Prompt >>>]\n{prompt}\n") + logging.info(f"[Response <<<]\n{response_content}\n") + # --- 文件路径定义 --- chapters_dir = os.path.join(filepath, "chapters") os.makedirs(chapters_dir, exist_ok=True) @@ -308,6 +445,7 @@ def generate_chapter_with_state( if not response: logging.warning("update_global_summary: No response.") return old_summary + debug_log(prompt, response.content) return response.content.strip() if lastchapter.strip(): @@ -325,6 +463,7 @@ def generate_chapter_with_state( if not response: logging.warning("update_character_state: No response.") return old_state + debug_log(prompt, response.content) return response.content.strip() if lastchapter.strip(): @@ -334,53 +473,78 @@ def generate_chapter_with_state( # 3) 从向量库检索上下文 relevant_context = get_relevant_context_from_vector_store( - api_key, base_url, "回顾剧情", k=2 # <-- 多传一个 base_url + api_key, base_url, "回顾剧情", k=2 ) - # 4) 生成大纲 + # 4) 解析本章及前后章节目录信息 + this_and_related_chapters = parse_chapter_title_from_directory(novel_novel_directory, novel_number, range_size=1) + + # 5) 生成大纲 def outline_chapter( novel_setting: str, char_state: str, global_summary: str, chap_num: int, - extra_context: str + extra_context: str, + directory_hint: str, + user_guide: str ) -> str: - prompt = chapter_outline_prompt.format( + """ + 将目录提示以及用户额外指导内容一起放入 Prompt 中。 + """ + # 适度修改章节提纲提示词,以整合目录信息 & 用户指导 + outline_prompt = ( + chapter_outline_prompt + + "\n\n【目录参考】\n" + directory_hint + + "\n\n【用户指导】\n" + user_guide + ).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) + + response = model.invoke(outline_prompt) if not response: logging.warning("outline_chapter: No response.") return "" + debug_log(outline_prompt, response.content) return response.content.strip() chap_outline = outline_chapter( - novel_settings, new_char_state, new_global_summary, novel_number, relevant_context + novel_settings, new_char_state, new_global_summary, novel_number, + relevant_context, this_and_related_chapters, user_guidance ) - # 5) 生成正文 + # 6) 生成正文 def write_chapter( novel_setting: str, char_state: str, global_summary: str, outline: str, wnum: int, - extra_context: str + extra_context: str, + directory_hint: str, + user_guide: str ) -> str: - prompt = chapter_write_prompt.format( + # 同理,整合目录信息和用户指导 + writing_prompt = ( + chapter_write_prompt + + "\n\n【目录参考】\n" + directory_hint + + "\n\n【用户指导】\n" + user_guide + ).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) + + response = model.invoke(writing_prompt) if not response: logging.warning("write_chapter: No response.") return "" + debug_log(writing_prompt, response.content) return response.content.strip() chapter_content = write_chapter( @@ -389,7 +553,9 @@ def generate_chapter_with_state( new_global_summary, chap_outline, word_number, - relevant_context + relevant_context, + this_and_related_chapters, + user_guidance ) # 写入文件并更新记录 @@ -407,10 +573,117 @@ def generate_chapter_with_state( clear_file_content(global_summary_file) save_string_to_txt(new_global_summary, global_summary_file) - # 6) 更新向量检索库 + # 7) 更新向量检索库 update_vector_store(api_key, base_url, chapter_content) logging.info(f"Chapter {novel_number} generated successfully.") else: logging.warning(f"Chapter {novel_number} generation failed.") return chapter_content + +def import_knowledge_file(api_key: str, base_url: str, file_path: str) -> None: + """ + 将用户选定的文本文件导入到向量库,以便在写作时检索。 + 可以在UI中提供按钮来调用此函数。 + """ + + # 1. 检查文件路径是否有效 + if not os.path.exists(file_path): + logging.warning(f"知识库文件不存在: {file_path}") + return + + # 2. 读取文件内容 + content = read_file(file_path) + if not content.strip(): + logging.warning("知识库文件内容为空。") + return + + # 3. 对内容进行高级切分处理 + paragraphs = advanced_split_content(content) + + # 4. 加载或初始化向量存储 + store = load_vector_store(api_key, base_url) + if not store: + logging.info("Vector store does not exist. Initializing a new one for knowledge import...") + init_vector_store(api_key, base_url, paragraphs) + return + + # 5. 创建Document对象并更新到向量库 + docs = [Document(page_content=p) for p in paragraphs] + store.add_documents(docs) + store.persist() + logging.info("知识库文件已成功导入至向量库。") + + +def advanced_split_content(content: str, + similarity_threshold: float = 0.7, + max_length: int = 500) -> List[str]: + """ + 将文本先按句子切分,然后根据语义相似度进行合并,最后根据max_length进行二次切分。 + + :param content: 原始文本内容 + :param similarity_threshold: 相邻句子合并的语义相似度阈值,小于此值则会开启新的段落 + :param max_length: 每个段落的最大长度(按字符数计算,超过则进一步拆分) + :return: 切分好的段落列表 + """ + + # 1. 按句子切分 + nltk.download('punkt', quiet=True) # 确保 punkt 数据可用 + sentences = nltk.sent_tokenize(content) + + if not sentences: + return [] + + # 2. 加载 SentenceTransformer 模型,用于计算语义相似度 + model = SentenceTransformer('paraphrase-MiniLM-L6-v2') + embeddings = model.encode(sentences) + + # 3. 根据相邻句子的语义相似度合并段落 + merged_paragraphs = [] + current_sentences = [sentences[0]] + current_embedding = embeddings[0] + + for i in range(1, len(sentences)): + sim = cosine_similarity([current_embedding], [embeddings[i]])[0][0] + if sim >= similarity_threshold: + # 语义相似则并入当前段落 + current_sentences.append(sentences[i]) + # 更新current_embedding为合并后的平均值(可选,也可只采用最后一句做比较) + current_embedding = (current_embedding + embeddings[i]) / 2.0 + else: + # 语义相似度不足,另起一个新段落 + merged_paragraphs.append(" ".join(current_sentences)) + current_sentences = [sentences[i]] + current_embedding = embeddings[i] + + # 把最后一段加进去 + if current_sentences: + merged_paragraphs.append(" ".join(current_sentences)) + + # 4. 根据最大长度 max_length 做二次拆分,避免段落过长 + final_segments = [] + for para in merged_paragraphs: + # 如果段落长度超过max_length,进一步切分 + if len(para) > max_length: + sub_segments = split_by_length(para, max_length=max_length) + final_segments.extend(sub_segments) + else: + final_segments.append(para) + + # 返回最终段落列表 + return final_segments + + +def split_by_length(text: str, max_length: int = 500) -> List[str]: + """ + 将文本按照max_length进行拆分,以避免段落过长。 + 这里以字符数为单位进行简单的拆分,也可以改为按词数或token数等。 + """ + segments = [] + start_idx = 0 + while start_idx < len(text): + end_idx = min(start_idx + max_length, len(text)) + segment = text[start_idx:end_idx] + segments.append(segment.strip()) + start_idx = end_idx + return segments diff --git a/prompt_definitions.py b/prompt_definitions.py index b1d9af3..cd48375 100644 --- a/prompt_definitions.py +++ b/prompt_definitions.py @@ -1,3 +1,5 @@ +# prompt_definitions.py +# -*- coding: utf-8 -*- """ 集中存放所有提示词(Prompt),便于统一管理和修改。 """ @@ -10,7 +12,7 @@ set_prompt = """\ 1. 小说名称、总字数走向(大致范围即可)。 2. 小说类型与基调(如:都市、穿越、战争等类型,以及轻松、爆笑、暗黑等基调)。 3. 写作风格(正式 / 轻松;细腻 / 简洁;抒情 / 客观;叙事视角等)。 -4. 整体世界观(时间背景、地理环境、社会结构、科技或魔法水平、重要历史传说或事件等)。 +4. 整体世界观(时间背景、地理环境、社会结构、科技或魔法水平、重要历史事件等)。 5. 核心内容梗概(可以使用常见叙事结构,如三幕结构、英雄之旅等)。 6. 初步的情节安排设想(主线、副线、交织等关键思路)。 7. 初步的人物关系与主要角色设定(角色定位、主要冲突或关系)。 @@ -58,7 +60,7 @@ novel_directory_prompt = """\ 根据以下最终《小说设定》: {final_novel_setting} 并按照下面的小说目录模板生成 {number_of_chapters} 章的目录,同时确保目录符合小说设定中的叙事结构、角色发展及暗线伏笔。 -目录模板: +目录模板(示例): 第1章 :< text > 第2章 :< text > ... @@ -127,3 +129,4 @@ chapter_write_prompt = """\ 3. 可以着重描写人物心理、环境氛围等,以保证足够长度。 4. 在结尾部分保留一定悬念或剧情转折,为下一章做铺垫。 """ + diff --git a/requirements.txt b/requirements.txt index 693dcb1..d492909 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ langgraph openai chromadb langchain-community +sentence_transformers +scikit-learn +nltk \ No newline at end of file diff --git a/ui.py b/ui.py index 08b3768..4af9c40 100644 --- a/ui.py +++ b/ui.py @@ -1,3 +1,5 @@ +# ui.py +# -*- coding: utf-8 -*- import os import tkinter as tk from tkinter import ttk, filedialog, scrolledtext, messagebox @@ -7,7 +9,8 @@ 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 + generate_chapter_with_state, + import_knowledge_file ) from consistency_checker import check_consistency @@ -60,9 +63,9 @@ class NovelGeneratorGUI: 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, ): + def build_right_layout(self): # 行列配置 - for i in range(12): + for i in range(15): self.right_frame.rowconfigure(i, weight=0) self.right_frame.columnconfigure(1, weight=1) @@ -81,57 +84,77 @@ class NovelGeneratorGUI: 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") + # 4. Temperature + ttk.Label(self.right_frame, text="Temperature:").grid(row=3, column=0, padx=5, pady=5, sticky="e") + self.temperature_var = tk.DoubleVar(value=self.loaded_config.get("temperature", 0.7)) + self.temp_value_label = ttk.Label(self.right_frame, text=f"{self.temperature_var.get():.2f}") + self.temp_value_label.grid(row=3, column=2, padx=5, pady=5, sticky="w") + + temp_scale = ttk.Scale(self.right_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL, variable=self.temperature_var) + temp_scale.grid(row=3, column=1, padx=5, pady=5, sticky="we") + def update_temp_label(*args): + self.temp_value_label.config(text=f"{self.temperature_var.get():.2f}") + self.temperature_var.trace("w", update_temp_label) + + # 5. 主题(Topic) 多行输入 + ttk.Label(self.right_frame, text="主题(Topic):").grid(row=4, 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") + self.topic_text.grid(row=4, 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") + # 6. 类型(Genre) + ttk.Label(self.right_frame, text="类型(Genre):").grid(row=5, 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") + ttk.Entry(self.right_frame, textvariable=self.genre_var, width=32).grid(row=5, 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") + # 7. 章节数 + ttk.Label(self.right_frame, text="章节数:").grid(row=6, 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") + ttk.Entry(self.right_frame, textvariable=self.num_chapters_var, width=8).grid(row=6, 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") + # 8. 每章字数 + ttk.Label(self.right_frame, text="每章字数:").grid(row=7, 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") + ttk.Entry(self.right_frame, textvariable=self.word_number_var, width=8).grid(row=7, 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") + # 9. 文件保存路径 + ttk.Label(self.right_frame, text="保存路径:").grid(row=8, 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") + ttk.Entry(self.right_frame, textvariable=self.filepath_var, width=32).grid(row=8, column=1, padx=5, pady=5, sticky="w") + ttk.Button(self.right_frame, text="浏览...", command=self.browse_folder).grid(row=8, column=2, padx=5, pady=5, sticky="w") # 保存/加载配置按钮 config_frame = ttk.Frame(self.right_frame) - config_frame.grid(row=8, column=1, sticky="w") + config_frame.grid(row=9, 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") + # 10. 章节号 + ttk.Label(self.right_frame, text="章节号:").grid(row=10, 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") + ttk.Entry(self.right_frame, textvariable=self.chapter_num_var, width=6).grid(row=10, column=1, padx=5, pady=5, sticky="w") + # 11. “用户指导” 多行输入 + ttk.Label(self.right_frame, text="本章指导:").grid(row=11, column=0, padx=5, pady=5, sticky="ne") + self.user_guide_text = scrolledtext.ScrolledText(self.right_frame, width=32, height=4) + self.user_guide_text.grid(row=11, column=1, padx=5, pady=5, sticky="w") + + # 按钮区域 + row_base = 12 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_full.grid(row=row_base, 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_generate_chapter.grid(row=row_base+1, 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") + self.btn_check_consistency.grid(row=row_base+2, column=0, columnspan=2, padx=5, pady=5, sticky="ew") + # 增加一个按钮来导入自定义知识库文件 + self.btn_import_knowledge = ttk.Button(self.right_frame, text="导入知识库", command=self.import_knowledge_handler) + self.btn_import_knowledge.grid(row=row_base+3, column=0, columnspan=2, padx=5, pady=5, sticky="ew") # -------------- 配置管理 -------------- def load_config_btn(self): @@ -140,6 +163,7 @@ class NovelGeneratorGUI: 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.temperature_var.set(cfg.get("temperature", 0.7)) 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)) @@ -158,6 +182,7 @@ class NovelGeneratorGUI: "api_key": self.api_key_var.get(), "base_url": self.base_url_var.get(), "model_name": self.model_name_var.get(), + "temperature": self.temperature_var.get(), "topic": self.topic_text.get("1.0", tk.END).strip(), "genre": self.genre_var.get(), "num_chapters": self.num_chapters_var.get(), @@ -200,6 +225,7 @@ class NovelGeneratorGUI: num_chapters = self.num_chapters_var.get() word_number = self.word_number_var.get() filepath = self.filepath_var.get().strip() + temperature = self.temperature_var.get() if not filepath: messagebox.showwarning("警告", "请先选择保存文件路径") @@ -214,7 +240,8 @@ class NovelGeneratorGUI: genre=genre, number_of_chapters=num_chapters, word_number=word_number, - filepath=filepath + filepath=filepath, + temperature=temperature ) self.log("✅ 小说设定和目录生成完成。查看 Novel_setting.txt 和 Novel_directory.txt。") except Exception as e: @@ -226,7 +253,7 @@ class NovelGeneratorGUI: thread.start() def generate_chapter_text(self): - """多步生成章节:维护全局摘要+角色状态文档,向量检索辅助""" + """多步生成章节:维护全局摘要+角色状态文档,向量检索辅助,并结合目录信息和用户指导。""" def task(): self.disable_button(self.btn_generate_chapter) try: @@ -236,6 +263,7 @@ class NovelGeneratorGUI: novel_number = self.chapter_num_var.get() filepath = self.filepath_var.get().strip() word_number = self.word_number_var.get() + temperature = self.temperature_var.get() # 读取设定 & 目录 novel_settings_file = os.path.join(filepath, "Novel_setting.txt") @@ -253,6 +281,9 @@ class NovelGeneratorGUI: self.log("⚠️ 未找到 Novel_directory.txt,请先生成目录。") return + # 用户对当前章节的指导 + user_guidance = self.user_guide_text.get("1.0", tk.END).strip() + self.log(f"开始生成第{novel_number}章内容(含角色状态文档更新)...") chapter_text = generate_chapter_with_state( novel_settings=novel_settings, @@ -263,11 +294,13 @@ class NovelGeneratorGUI: novel_number=novel_number, filepath=filepath, word_number=word_number, - lastchapter=lastchapter + lastchapter=lastchapter, + user_guidance=user_guidance, + temperature=temperature ) if chapter_text: - self.log(f"✅ 第{novel_number}章内容生成完成。chapter.txt 已更新。") + self.log(f"✅ 第{novel_number}章内容生成完成。chapter_{novel_number}.txt 已更新。") self.chapter_result.delete("1.0", tk.END) self.chapter_result.insert(tk.END, chapter_text) self.chapter_result.see(tk.END) @@ -291,6 +324,7 @@ class NovelGeneratorGUI: base_url = self.base_url_var.get().strip() model_name = self.model_name_var.get().strip() filepath = self.filepath_var.get().strip() + temperature = self.temperature_var.get() # 读取关键文件 novel_settings_file = os.path.join(filepath, "Novel_setting.txt") @@ -315,7 +349,8 @@ class NovelGeneratorGUI: chapter_text=last_chapter_text, api_key=api_key, base_url=base_url, - model_name=model_name + model_name=model_name, + temperature=temperature ) self.log("审校结果:") self.log(result) @@ -327,3 +362,29 @@ class NovelGeneratorGUI: thread = threading.Thread(target=task) thread.start() + + def import_knowledge_handler(self): + """处理导入知识库文件。""" + selected_file = filedialog.askopenfilename( + title="选择要导入的知识库文件", + filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")] + ) + if selected_file: + def task(): + self.disable_button(self.btn_import_knowledge) + try: + self.log(f"开始导入知识库文件: {selected_file}") + import_knowledge_file( + api_key=self.api_key_var.get().strip(), + base_url=self.base_url_var.get().strip(), + file_path=selected_file + ) + self.log("✅ 知识库文件导入完成。") + except Exception as e: + self.log(f"❌ 导入知识库时出错: {e}") + finally: + self.enable_button(self.btn_import_knowledge) + + thread = threading.Thread(target=task) + thread.start() + diff --git a/utils.py b/utils.py index 2c21cfe..96b2e0b 100644 --- a/utils.py +++ b/utils.py @@ -1,3 +1,5 @@ +# utils.py +# -*- coding: utf-8 -*- import os import json