feat: implement core UI tabs, API backend management, and initial service infrastructure.

This commit is contained in:
tinix-ai
2026-03-18 08:40:54 +07:00
parent 78cf08823d
commit bde89de5ce
16 changed files with 898 additions and 258 deletions
+2
View File
@@ -18,6 +18,8 @@
- **🔄 Smart Rewrite**: Upload your existing text and rewrite it using 17 high-quality, preset styles (e.g., Eastern Fantasy, Hard Sci-Fi, Urban Romance, Horror, etc.). - **🔄 Smart Rewrite**: Upload your existing text and rewrite it using 17 high-quality, preset styles (e.g., Eastern Fantasy, Hard Sci-Fi, Urban Romance, Horror, etc.).
- **📖 Smart Continue**: Upload a partially written novel. The AI will analyze the context, characters, and world-building, and automatically continue the story maintaining your style and tone. - **📖 Smart Continue**: Upload a partially written novel. The AI will analyze the context, characters, and world-building, and automatically continue the story maintaining your style and tone.
- **✨ Novel Polish**: Advanced polishing tools with 8 specific modes: General Polish, Find Errors, Improvement Suggestions, Direct Edit, Remove AI Tone, Enhance Details, Optimize Dialogue, and Improve Pacing. - **✨ Novel Polish**: Advanced polishing tools with 8 specific modes: General Polish, Find Errors, Improvement Suggestions, Direct Edit, Remove AI Tone, Enhance Details, Optimize Dialogue, and Improve Pacing.
- **🎭 Diverse Web Novel Styles**: Natively supports 30+ highly-specialized web novel genres and styles like Eastern Fantasy (Xianxia/Wuxia), System Face-slapping, Sci-Fi Mecha, Infinite Flow, Sweet Pet Romance, Slice of Life, eSports, Dark Fantasy, and more. AI will meticulously adapt vocabulary, pacing, and tone to fit the chosen sub-culture.
- **🧠 Self-Reflection Check**: A built-in critique loop where the AI generates a draft, reviews it against the outline to detect logic flaws or "AI tone", and rewrites a perfect version before saving.
- **📄 Flexible File Parsing**: Upload your own files (`.txt`, `.pdf`, `.epub`, `.md`, `.docx`). The system supports multiple parsing mechanisms including auto-split, split by word count, and split by custom Regex patterns. - **📄 Flexible File Parsing**: Upload your own files (`.txt`, `.pdf`, `.epub`, `.md`, `.docx`). The system supports multiple parsing mechanisms including auto-split, split by word count, and split by custom Regex patterns.
- **💾 Multi-Format Export**: Export your completed or in-progress projects into Word (`.docx`), Plain Text (`.txt`), Markdown (`.md`), or HTML (`.html`) with a single click. - **💾 Multi-Format Export**: Export your completed or in-progress projects into Word (`.docx`), Plain Text (`.txt`), Markdown (`.md`), or HTML (`.html`) with a single click.
- **📂 Project Management**: Manage multiple novel projects simultaneously. Track completion percentage, chapter counts, update dates, and manage generation cache. - **📂 Project Management**: Manage multiple novel projects simultaneously. Track completion percentage, chapter counts, update dates, and manage generation cache.
Binary file not shown.
Binary file not shown.
+106
View File
@@ -0,0 +1,106 @@
[
{
"name": "Mượt mà tự nhiên, cốt truyện chặt chẽ, nhân vật tinh tế",
"description": "Văn phong trơn tru, dễ đọc, mạch truyện logic xuyên suốt không có sạn. Xây dựng tâm lý và hành động nhân vật sâu sắc, nhất quán."
},
{
"name": "Văn phong đẹp, ý cảnh sâu xa",
"description": "Sử dụng từ ngữ trau chuốt, giàu hình ảnh và các phép tu từ. Tập trung miêu tả cảnh vật, nội tâm nhân vật để tạo ra một không gian nghệ thuật đầy cảm xúc."
},
{
"name": "Nhịp nhanh, cốt truyện kịch tính",
"description": "Tập trung vào hành động, xung đột và diễn biến câu chuyện. Cắt giảm tối đa miêu tả thừa thãi, đưa người đọc đi từ bất ngờ này đến bất ngờ khác."
},
{
"name": "Mô tả tinh tế, cảm xúc phong phú",
"description": "Chú trọng vào việc thể hiện cảm xúc, rung động tinh vi của nhân vật. Các chi tiết nhỏ trong bối cảnh và tương tác được dùng để tô đậm không khí truyện."
},
{
"name": "Hài hước thú vị, nhẹ nhàng vui vẻ",
"description": "Sử dụng ngôn ngữ hóm hỉnh, tình huống hài hước dở khóc dở cười. Xoay quanh những mẩu chuyện đời thường hoặc hành trình nhẹ nhàng mang tính giải trí cao."
},
{
"name": "Cổ phong nhã vận cổ điển",
"description": "Ngôn từ trang nghiêm, Hán Việt dày (thiên địa vô tình, đạo hữu, tiên tử...), câu văn dài, triết lý Đạo/Phật nặng, miêu tả thơ mộng thiên địa. Ít đối thoại hiện đại. (Ví dụ: Nhĩ Căn - Tiên Nghịch, Cầu Ma)."
},
{
"name": "Sảng khoái cực độ (Sảng văn)",
"description": "Câu ngắn gọn, nhịp nhanh, miêu tả chiến đấu 'một chưởng phá thiên', lặp motif sảng (cười lạnh, mắt lóe sát cơ, giận dữ ngút trời). Đối thoại mạnh mẽ, đánh mặt liên tục."
},
{
"name": "Hắc ám u ám",
"description": "Văn lạnh lẽo, tàn nhẫn, miêu tả tâm lý đen tối, giết chóc chi tiết máu me, thế giới bi quan, ít sảng khoái. (Ví dụ: Cổ Chân Nhân)."
},
{
"name": "Hài hước lầy lội",
"description": "Đối thoại troll, main mặt dày tự trào, pha meme + cổ phong, câu văn nhẹ nhàng vui nhộn, nhiều tình huống 'lầy'. (Ví dụ: Nhất Niệm Vĩnh Hằng)."
},
{
"name": "Miêu tả chi tiết chậm rãi",
"description": "Xây dựng cực kỹ (luyện đan, trận pháp, bí cảnh), câu dài, chậm burn, ít hành động nhanh, tập trung 'nghề nghiệp tu luyện'. (Ví dụ: Phàm Nhân Tu Tiên)."
},
{
"name": "Triết lý nội tâm sâu sắc",
"description": "Đoạn độc thoại dài về đạo tâm, nhân quả, nhân sinh, văn trầm lắng suy tư. (Ví dụ: Tiên Nghịch phần sau)."
},
{
"name": "Hỗn hợp hiện đại cổ phong",
"description": "Pha từ hiện đại (level up, cheat, vibe), đối thoại hơi Gen Z nhưng giữ khung cổ. (Xu hướng truyện mới lai)."
},
{
"name": "Ngọt sủng rắc đường (甜宠文)",
"description": "Văn phong mềm mại, ấm áp, tương tác nam nữ chính cực kỳ \"ngọt ngào\", không cẩu huyết, không ngược, mọi hiểu lầm đều giải quyết nhanh gọn."
},
{
"name": "Hồi hộp giật gân (Huyền nghi)",
"description": "Câu văn ngắn gọn, chú trọng miêu tả không khí u ám, lạnh lẽo, dẫn dắt tâm lý rùng rợn, tạo điểm mù và plot twist liên tục. Tốc độ dồn dập."
},
{
"name": "Điền văn sinh hoạt (Chậm nhiệt / Slice of Life)",
"description": "Nhịp điệu cực kỳ chậm rãi. Tập trung vào miêu tả nấu ăn, trồng trọt, đối thoại gia đình ấm cúng. Câu văn bình dị, không drama to tát, mang tính \"chữa lành\"."
},
{
"name": "Trang bức vả mặt (Hệ thống / Đô thị sảng văn)",
"description": "Kết cấu chương kiểu \"bị khinh bỉ -> thể hiện tài năng -> mọi người khiếp sợ\". Ngôn từ mang tính phóng đại cảm xúc của đám đông (hít một hơi cạn khí, mở to hai mắt, run lẩy bẩy). Nhấn mạnh vào sự chênh lệch đẳng cấp."
},
{
"name": "Thế giới tinh tế / Sci-fi (Cơ giáp)",
"description": "Sử dụng nhiều thuật ngữ khoa học viễn tưởng, không gian, thiết bị công nghệ cao, gen, tinh hạm. Câu văn gãy gọn, logic, tập trung vào chiến đấu mô phỏng và chiến thuật vĩ mô."
},
{
"name": "Quan trường quyền mưu (Cung đấu / Gia đấu)",
"description": "Từ ngữ hoa mỹ, vòng vèo, một câu mang nhiều tầng ý nghĩa ẩn dụ. Đối thoại đầy tính toán, giấu giếm sát cơ, ít hành động bạo lực trực tiếp, chủ yếu đấu trí bằng ngôn từ và lợi ích."
},
{
"name": "Thanh xuân vườn trường",
"description": "Câu văn trong sáng, nhẹ nhàng, đan xen những suy nghĩ ngây ngô hoặc bồng bột của tuổi trẻ. Miêu tả nhiều về trường lớp, thầy cô, rung động đầu đời, áp lực thi cử."
},
{
"name": "Ngược luyến tàn tâm (Ngược văn)",
"description": "Cốt truyện đầy hiểu lầm, bi kịch dồn dập. Câu văn xoáy sâu vào nỗi đau thể xác lẫn tinh thần của nhân vật, miêu tả nước mắt, sự tuyệt vọng, sự giằng xé nội tâm. Kết cấu thường đẩy nhân vật vào đường cùng."
},
{
"name": "Vô hạn lưu (Nhiệm vụ sinh tử)",
"description": "Nhịp độ cực nhanh, dồn dập. Chuyển cảnh logic giữa các thế giới khác nhau. Tập trung vào tính toán luật chơi (rules), giải đố sinh tử. Áp lực thời gian đè nặng trong từng câu chữ."
},
{
"name": "Quần tượng liệt truyện (Nhiều nhân vật chính)",
"description": "Không tập trung vào một cá nhân mà miêu tả đan xen nhiều góc nhìn (POV). Cần sự tinh tế trong việc thay đổi giọng văn tùy theo nhân vật đang làm trung tâm. Bối cảnh rộng lớn, sử thi."
},
{
"name": "Quân sự chiến tranh",
"description": "Sử dụng thuật ngữ quân sự, vũ khí học, chiến lược chiến thuật. Văn phong trầm hùng, miêu tả sắc nét sự khốc liệt của chiến trường, máu và mồ hôi, tinh thần đồng đội nồng đậm."
},
{
"name": "Đam mỹ/Bách hợp tinh tế",
"description": "Đào sâu vào cảm xúc, sự phát triển tình cảm tự nhiên giữa các nhân vật đồng giới. Miêu tả sự thấu hiểu nội tâm, những rung động nhỏ nhất, có thể lồng ghép bối cảnh hiện đại, cổ trang hoặc viễn tưởng."
},
{
"name": "Đồng nhân (Fanfiction)",
"description": "Bắt chước sát nhất có thể giọng văn và tính cách nhân vật của tác phẩm gốc (Ví dụ: HP, Naruto, Marvel). Kết hợp các tình tiết nguyên tác với sự phát triển sự kiện mới của tác giả."
},
{
"name": "Võng du eSports",
"description": "Sử dụng ngôn ngữ game thủ (combat, gank, AP, AD, loot đồ...). Tốc độ trận đấu nhanh, mô phỏng thao tác bàn phím, con chuột, tâm sinh lý thi đấu đỉnh cao và tiếng hò reo của khán đài."
}
]
+16 -8
View File
@@ -59,29 +59,37 @@ Trước khi bắt đầu sáng tác, bạn **bắt buộc** phải cấu hình
--- ---
## ✍️ 3. Các tính năng sáng tác cốt lõi ## 💎 3. Các tính năng Nổi bật Khác Biệt
### 3.1. Sáng tác từ con số không (Create from Scratch) - **🎭 Đa dạng văn phong Truyện Mạng**: Hỗ trợ xuất sắc hơn 30+ thể loại và văn phong chuyên sâu nhất của giới tiểu thuyết mạng hiện nay: Cổ phong nhã vận (Tiên Hiệp), Sảng khoái cực độ (Hệ thống vả mặt), Hắc ám u ám, Ngọt sủng rắc đường, Hồi hộp giật gân, Điền văn sinh hoạt, Thế giới tinh tế (Cơ giáp/Sci-fi), Quan trường quyền mưu, Thanh xuân vườn trường, Ngược luyến tàn tâm, Vô hạn lưu, Quần tượng liệt truyện, eSports, Đồng nhân, v.v. AI sẽ tự động thay đổi từ vựng, nhịp độ và giọng điệu chuẩn xác với văn hóa của từng dòng truyện.
- **🧠 Chế độ Tự Kiểm Duyệt (Self-Reflection)**: Không như các công cụ tạo văn bản thông thường, TiniX Story tích hợp quy trình "Biên tập viên AI" ẩn. Ở chế độ này, AI viết nháp một bản, tự đối chiếu với dàn ý để bắt lỗi logic, sự lan man, sai tính cách nhân vật hoặc "văn phong robot", sau đó tự động đập đi viết lại một phiên bản hoàn hảo 100% tiếng Việt thuần túy trước khi lưu.
- **🛡️ Mạch truyện nhất quán**: Hệ thống tích hợp khả năng nhét ngữ cảnh (context window) siêu việt qua cơ chế tóm tắt linh hoạt (theo Toàn văn hoặc Tóm tắt các chương), kết hợp với bảng check-list mạch lạc nghiêm ngặt để đảm bảo AI phát triển tiếp tuyến truyện chính mà không bị "quên" nhân vật cũ hay lặp đi lặp lại một tình tiết.
---
## ✍️ 4. Các tính năng sáng tác thường quy
### 4.1. Sáng tác từ con số không (Create from Scratch)
Chức năng này dành cho việc xây dựng một bộ tiểu thuyết hoàn toàn mới. Chức năng này dành cho việc xây dựng một bộ tiểu thuyết hoàn toàn mới.
- Điền các thông tin cơ bản: Tên tiểu thuyết, Thể loại, Chủ đề phụ. - Điền các thông tin cơ bản: Tên tiểu thuyết, Thể loại, Chủ đề phụ.
- Nhấp **✨ Nhận cảm hứng trí tuệ nhân tạo** để gợi ý Tên sách, Tuyến nhân vật, Thế giới quan. - Nhấp **✨ Nhận cảm hứng trí tuệ nhân tạo** để gợi ý Tên sách, Tuyến nhân vật, Thế giới quan.
- Sau khi có đủ Thiết lập, nhấp **Tạo dàn ý**. Phải đảm bảo dàn ý tuân thủ cấu trúc mỗi dòng một chương để hệ thống nhận diện đúng. - Sau khi có đủ Thiết lập, nhấp **Tạo dàn ý**. Phải đảm bảo dàn ý tuân thủ cấu trúc mỗi dòng một chương để hệ thống nhận diện đúng.
- Bấm **Bắt đầu tạo / Tiếp tục** để AI bắt đầu viết từng chương. Hệ thống tự động ghi nhớ bối cảnh (context) từ các chương trước. - Bấm **Bắt đầu tạo / Tiếp tục** để AI bắt đầu viết từng chương. Hệ thống tự động ghi nhớ bối cảnh (context) từ các chương trước.
### 3.2. Chế độ Viết lại (Rewrite Mode) ### 4.2. Chế độ Viết lại (Rewrite Mode)
Chức năng dùng để thay đổi văn phong của một đoạn văn bản có sẵn. Chức năng dùng để thay đổi văn phong của một đoạn văn bản có sẵn.
- Tải tệp lên (TXT, DOCX, v.v.) hoặc dán trực tiếp. - Tải tệp lên (TXT, DOCX, v.v.) hoặc dán trực tiếp.
- Lựa chọn cách phân đoạn để AI không bị quá tải: Tự động, theo số lượng chữ, hoặc theo biểu thức/cụm từ tùy chỉnh (VD: Phân tách theo "Chương"). - Lựa chọn cách phân đoạn để AI không bị quá tải: Tự động, theo số lượng chữ, hoặc theo biểu thức/cụm từ tùy chỉnh (VD: Phân tách theo "Chương").
- Chọn một trong **17 phong cách văn học** (Tiên hiệp, Lãng mạn đô thị, Kinh dị, Khoa học viễn tưởng, Hardcore...). - Chọn một trong **hơn 30 phong cách văn học** (Tiên hiệp, Cổ phong, Lãng mạn đô thị, Kinh dị, Sảng văn, Vô hạn lưu, v.v...).
- Bấm **Bắt đầu viết lại**. Hệ thống sẽ chạy và bạn có thể theo dõi trực tiếp. - Bấm **Bắt đầu viết lại**. Hệ thống sẽ chạy và bạn có thể theo dõi trực tiếp.
### 3.3. Chế độ Viết tiếp (Continue Mode) ### 4.3. Chế độ Viết tiếp (Continue Mode)
Dành cho trường hợp bạn viết dở hoặc có một tác phẩm đang dang dở. Dành cho trường hợp bạn viết dở hoặc có một tác phẩm đang dang dở.
- Điền các thiết lập nhân vật, thế giới quan (nếu có, để AI nắm bắt tốt hơn). - Điền các thiết lập nhân vật, thế giới quan (nếu có, để AI nắm bắt tốt hơn).
- Cung cấp nội dung văn bản phần trước. - Cung cấp nội dung văn bản phần trước.
- AI sẽ phân tích văn phong, giọng văn và tự động viết tiếp cốt truyện một cách logic. - AI sẽ phân tích văn phong, giọng văn và tự động viết tiếp cốt truyện một cách logic.
### 3.4. Trau chuốt tiểu thuyết (Novel Polish) ### 4.4. Trau chuốt tiểu thuyết (Novel Polish)
Giống như một biên tập viên chuyên nghiệp rà soát lại tác phẩm của bạn. Giống như một biên tập viên chuyên nghiệp rà soát lại tác phẩm của bạn.
- Cung cấp văn bản thô. - Cung cấp văn bản thô.
- Chọn loại hình trau chuốt: - Chọn loại hình trau chuốt:
@@ -93,14 +101,14 @@ Giống như một biên tập viên chuyên nghiệp rà soát lại tác phẩ
--- ---
## 📂 4. Quản lý Dự án & Xuất file ## 📂 5. Quản lý Dự án & Xuất file
- **Thẻ Quản lý Dự án:** Mọi dự án bạn tạo (Sáng tác, Viết lại, Viết tiếp) đều được tự động lưu trữ tại đây. Bạn có thể xem **Tiến độ hoàn thành**, số lượng chương. Nếu bạn muốn tạm ngưng, cứ tắt app; sau đó vào đây chọn dự án và bấm **Viết tiếp (Continue)** để khôi phục tiến trình. - **Thẻ Quản lý Dự án:** Mọi dự án bạn tạo (Sáng tác, Viết lại, Viết tiếp) đều được tự động lưu trữ tại đây. Bạn có thể xem **Tiến độ hoàn thành**, số lượng chương. Nếu bạn muốn tạm ngưng, cứ tắt app; sau đó vào đây chọn dự án và bấm **Viết tiếp (Continue)** để khôi phục tiến trình.
- **Thẻ Xuất (Export):** Cho phép bạn xuất dự án hoàn chỉnh ra nhiều định dạng chuyên nghiệp như `.docx` (Word), `.txt`, `.md` hoặc `.html`. Rất tiện lợi để gửi cho tòa soạn hoặc đăng tải lên các nền tảng đọc truyện online. - **Thẻ Xuất (Export):** Cho phép bạn xuất dự án hoàn chỉnh ra nhiều định dạng chuyên nghiệp như `.docx` (Word), `.txt`, `.md` hoặc `.html`. Rất tiện lợi để gửi cho tòa soạn hoặc đăng tải lên các nền tảng đọc truyện online.
--- ---
## 🛠 5. Khắc phục sự cố (Troubleshooting) ## 🛠 6. Khắc phục sự cố (Troubleshooting)
| Vấn đề | Nguyên nhân & Cách giải quyết | | Vấn đề | Nguyên nhân & Cách giải quyết |
|--------|------------------------------| |--------|------------------------------|
+5 -3
View File
@@ -589,7 +589,7 @@
"prompts": { "prompts": {
"outline_system": "Bạn là nhà hoạch định dàn ý tiểu thuyết chuyên nghiệp, giỏi xây dựng khung truyện hấp dẫn.", "outline_system": "Bạn là nhà hoạch định dàn ý tiểu thuyết chuyên nghiệp, giỏi xây dựng khung truyện hấp dẫn.",
"outline_user": "Hãy tạo dàn ý đầy đủ cho một tiểu thuyết thể loại {genre}, tiêu đề: «{title}».\n\nThiết lập nhân vật: {character_setting}\n\nThế giới quan: {world_setting}\n\nCốt truyện chính: {plot_idea}\n\nYêu cầu phong cách: {style_desc}\n\nYêu cầu:\n1. Tổng cộng khoảng {total_chapters} chương\n2. Mỗi chương theo định dạng nghiêm ngặt: Chương X: Tiêu đề chương - Mô tả cốt truyện ngắn gọn (50-100 từ)\n3. Cốt truyện mạch lạc, có mở đầu - phát triển - cao trào - kết thúc, nhân vật phát triển hợp lý\n4. Chỉ xuất ra danh sách dàn ý, không có nội dung khác\n5. Dàn ý phải hấp dẫn, lôi cuốn, có yếu tố hồi hộp\n6. Phân bổ chương theo cấu trúc 3 hồi:\n - Hồi 1 (25% số chương): Giới thiệu, xây dựng thế giới, thiết lập xung đột\n - Hồi 2 (50% số chương): Phát triển, leo thang, bước ngoặt giữa truyện\n - Hồi 3 (25% số chương): Cao trào, giải quyết, kết thúc\n7. Mỗi chương phải có xung đột nhỏ hoặc tiến triển rõ ràng, không có chương 'chữ lót'", "outline_user": "Hãy tạo dàn ý đầy đủ cho một tiểu thuyết thể loại {genre}, tiêu đề: «{title}».\n\nThiết lập nhân vật: {character_setting}\n\nThế giới quan: {world_setting}\n\nCốt truyện chính: {plot_idea}\n\nYêu cầu phong cách: {style_desc}\n\nYêu cầu:\n1. Tổng cộng khoảng {total_chapters} chương\n2. Mỗi chương theo định dạng nghiêm ngặt: Chương X: Tiêu đề chương - Mô tả cốt truyện ngắn gọn (50-100 từ)\n3. Cốt truyện mạch lạc, có mở đầu - phát triển - cao trào - kết thúc, nhân vật phát triển hợp lý\n4. Chỉ xuất ra danh sách dàn ý, không có nội dung khác\n5. Dàn ý phải hấp dẫn, lôi cuốn, có yếu tố hồi hộp\n6. Phân bổ chương theo cấu trúc 3 hồi:\n - Hồi 1 (25% số chương): Giới thiệu, xây dựng thế giới, thiết lập xung đột\n - Hồi 2 (50% số chương): Phát triển, leo thang, bước ngoặt giữa truyện\n - Hồi 3 (25% số chương): Cao trào, giải quyết, kết thúc\n7. Mỗi chương phải có xung đột nhỏ hoặc tiến triển rõ ràng, không có chương 'chữ lót'",
"chapter_system": "Bạn là nhà văn tiểu thuyết dài kỳ xuất sắc, sáng tác những câu chuyện chạm đến trái tim. Hãy viết với phong cách tự nhiên của con người. Tránh các mẫu câu AI phổ biến như: bắt đầu đoạn bằng 'Tuy nhiên', 'Ngoài ra', 'Hơn nữa' liên tục; kết thúc chương quá gọn gàng hoặc giáo điều; sử dụng cụm từ 'một cách + tính từ' quá nhiều; liệt kê ba thứ liền nhau theo kiểu 'cảm thấy X, Y, và Z'.", "chapter_system": "Bạn là nhà văn tiểu thuyết dài kỳ xuất sắc, sáng tác những câu chuyện chạm đến trái tim. Hãy viết với phong cách tự nhiên của con người. Tránh các mẫu câu AI phổ biến như: bắt đầu đoạn bằng 'Tuy nhiên', 'Ngoài ra', 'Hơn nữa' liên tục; kết thúc chương quá gọn gàng hoặc giáo điều; sử dụng cụm từ 'một cách + tính từ' quá nhiều; liệt kê ba thứ liền nhau theo kiểu 'cảm thấy X, Y, và Z'. TUYỆT ĐỐI KHÔNG sử dụng ký tự tiếng Trung hoặc văn phong lạm dụng Hán Việt. Phải dùng 100% tiếng Việt thuần túy, mượt mà tự nhiên.",
"chapter_user": "Hãy viết Chương {chapter_num} của tiểu thuyết «{novel_title}».\n\nTiêu đề chương: {chapter_title}\nDàn ý chương này: {chapter_desc}\n\nThiết lập tổng thể:\nNhân vật: {character_setting}\nThế giới quan: {world_setting}\nCốt truyện chính: {plot_idea}\n\nYêu cầu phong cách: {style_desc}\n\nYêu cầu cụ thể:\n1. Nội dung khoảng {target_words} từ\n2. Cốt truyện tuân thủ nghiêm ngặt dàn ý chương, mạch lạc với toàn bộ sách\n3. Đối thoại tự nhiên mang cá tính riêng của từng nhân vật, miêu tả tâm lý tinh tế, miêu tả cảnh vật sinh động\n4. Kết thúc để lại yếu tố hồi hộp hoặc gợi mở cho chương tiếp\n5. Sử dụng kỹ thuật 'Show don't Tell': thể hiện cảm xúc qua hành động và chi tiết, không chỉ mô tả trực tiếp\n6. Cân bằng giữa hành động, đối thoại và miêu tả nội tâm\n7. Chỉ xuất ra nội dung chính, KHÔNG có tiêu đề chương, KHÔNG giải thích, KHÔNG có phần suy nghĩ/phân tích, KHÔNG có meta-talk.\n8. TUYỆT ĐỐI KHÔNG lặp lại bất kỳ đoạn văn hay lời dẫn nào đã xuất hiện ở phần trước.{continuity_prompt}{context_prompt}", "chapter_user": "Hãy viết Chương {chapter_num} của tiểu thuyết «{novel_title}».\n\nTiêu đề chương: {chapter_title}\nDàn ý chương này: {chapter_desc}\n\nThiết lập tổng thể:\nNhân vật: {character_setting}\nThế giới quan: {world_setting}\nCốt truyện chính: {plot_idea}\n\nYêu cầu phong cách: {style_desc}\n\nYêu cầu cụ thể:\n1. Nội dung khoảng {target_words} từ\n2. Cốt truyện tuân thủ nghiêm ngặt dàn ý chương, mạch lạc với toàn bộ sách\n3. Đối thoại tự nhiên mang cá tính riêng của từng nhân vật, miêu tả tâm lý tinh tế, miêu tả cảnh vật sinh động\n4. Kết thúc để lại yếu tố hồi hộp hoặc gợi mở cho chương tiếp\n5. Sử dụng kỹ thuật 'Show don't Tell': thể hiện cảm xúc qua hành động và chi tiết, không chỉ mô tả trực tiếp\n6. Cân bằng giữa hành động, đối thoại và miêu tả nội tâm\n7. Chỉ xuất ra nội dung chính, KHÔNG có tiêu đề chương, KHÔNG giải thích, KHÔNG có phần suy nghĩ/phân tích, KHÔNG có meta-talk.\n8. TUYỆT ĐỐI KHÔNG lặp lại bất kỳ đoạn văn hay lời dẫn nào đã xuất hiện ở phần trước.{continuity_prompt}{context_prompt}",
"continuity_prompt": "\n\n【Ôn lại trước đó】\n{previous_content}\n\n【Danh sách kiểm tra tính mạch lạc】\n✓ Đảm bảo hướng cốt truyện nhất quán với phần trước\n✓ Trạng thái và vị trí nhân vật tương ứng với phần trước\n✓ Các yếu tố hồi hộp đã có được hưởng ứng hoặc đẩy lên trong chương này\n✓ Phong cách đối thoại nhân vật giữ nhất quán\n✓ TUYỆT ĐỐI KHÔNG lặp lại nội dung đã có. Không bắt đầu bằng việc nhắc lại cảnh cũ.\n✓ Bắt đầu viết thẳng vào diễn biến mới từ điểm kết thúc của phần trước.\n✓ Yếu tố hồi hộp mới mở đường cho các chương sau\n✓ Kiểm tra tiến độ so với dàn ý tổng thể, đảm bảo không đi lệch hướng\n✓ Phát triển các tuyến truyện phụ song song với tuyến chính\n\nHãy tuân thủ nghiêm ngặt danh sách kiểm tra trên để đảm bảo nội dung mạch lạc và KHÔNG TRÙNG LẶP.", "continuity_prompt": "\n\n【Ôn lại trước đó】\n{previous_content}\n\n【Danh sách kiểm tra tính mạch lạc】\n✓ Đảm bảo hướng cốt truyện nhất quán với phần trước\n✓ Trạng thái và vị trí nhân vật tương ứng với phần trước\n✓ Các yếu tố hồi hộp đã có được hưởng ứng hoặc đẩy lên trong chương này\n✓ Phong cách đối thoại nhân vật giữ nhất quán\n✓ TUYỆT ĐỐI KHÔNG lặp lại nội dung đã có. Không bắt đầu bằng việc nhắc lại cảnh cũ.\n✓ Bắt đầu viết thẳng vào diễn biến mới từ điểm kết thúc của phần trước.\n✓ Yếu tố hồi hộp mới mở đường cho các chương sau\n✓ Kiểm tra tiến độ so với dàn ý tổng thể, đảm bảo không đi lệch hướng\n✓ Phát triển các tuyến truyện phụ song song với tuyến chính\n\nHãy tuân thủ nghiêm ngặt danh sách kiểm tra trên để đảm bảo nội dung mạch lạc và KHÔNG TRÙNG LẶP.",
"context_prompt": "\n\n{context_summary}\n\nHãy dựa vào tóm tắt trên để nắm bắt cốt truyện chính phần trước, đảm bảo chương này mạch lạc nhưng KHÔNG nhắc lại những gì đã tóm tắt.", "context_prompt": "\n\n{context_summary}\n\nHãy dựa vào tóm tắt trên để nắm bắt cốt truyện chính phần trước, đảm bảo chương này mạch lạc nhưng KHÔNG nhắc lại những gì đã tóm tắt.",
@@ -615,7 +615,7 @@
"suggest_char_user": "Hãy đưa ra ý tưởng thiết lập chi tiết về các nhân vật cho một tiểu thuyết.\n\nTiêu đề dự kiến: {title}\nThể loại: {genre}\nSố lượng yêu cầu:\n- Nhân vật chính: {num_main_chars} người\n- Nhân vật phụ/phản diện: {num_sub_chars} người\n\nYêu cầu đối với mỗi nhân vật:\n1. Nêu rõ vai trò (Chính/Phụ/Phản diện)\n2. Tên, ngoại hình và đặc điểm tính cách nổi bật\n3. Trình độ, kỹ năng hoặc sức mạnh đặc biệt\n4. Bối cảnh xuất thân và động cơ cốt lõi\n\nHãy mô tả sâu sắc và có chiều sâu. Chỉ trả về nội dung ý tưởng, không giải thích dài dòng.", "suggest_char_user": "Hãy đưa ra ý tưởng thiết lập chi tiết về các nhân vật cho một tiểu thuyết.\n\nTiêu đề dự kiến: {title}\nThể loại: {genre}\nSố lượng yêu cầu:\n- Nhân vật chính: {num_main_chars} người\n- Nhân vật phụ/phản diện: {num_sub_chars} người\n\nYêu cầu đối với mỗi nhân vật:\n1. Nêu rõ vai trò (Chính/Phụ/Phản diện)\n2. Tên, ngoại hình và đặc điểm tính cách nổi bật\n3. Trình độ, kỹ năng hoặc sức mạnh đặc biệt\n4. Bối cảnh xuất thân và động cơ cốt lõi\n\nHãy mô tả sâu sắc và có chiều sâu. Chỉ trả về nội dung ý tưởng, không giải thích dài dòng.",
"suggest_world_user": "Hãy đưa ra ý tưởng thiết lập thế giới quan (khoảng 150-200 từ) cho một tiểu thuyết.\n\nTiêu đề dự kiến: {title}\nThể loại: {genre}\n\nYêu cầu: Chỉ ra các quy luật độc đáo, sức mạnh, cấu trúc xã hội hoặc bối cảnh lịch sử. Chỉ trả về nội dung ý tưởng.", "suggest_world_user": "Hãy đưa ra ý tưởng thiết lập thế giới quan (khoảng 150-200 từ) cho một tiểu thuyết.\n\nTiêu đề dự kiến: {title}\nThể loại: {genre}\n\nYêu cầu: Chỉ ra các quy luật độc đáo, sức mạnh, cấu trúc xã hội hoặc bối cảnh lịch sử. Chỉ trả về nội dung ý tưởng.",
"suggest_plot_user": "Hãy đưa ra ý tưởng cốt truyện chính (khoảng 200-250 từ) cho tiểu thuyết này để làm nền tảng phát triển.\n\nTiêu đề: {title}\nThể loại: {genre}\n\n(Nếu có) Thiết lập nhân vật: {character_setting}\n(Nếu có) Thế giới quan: {world_setting}\n\nYêu cầu:\n- Cốt truyện cần có điểm nhấn đầu truyện, bước ngoặt giữa truyện và xung đột cốt lõi rõ ràng\n- Xung đột bên ngoài (đối thủ, thế lực, nhiệm vụ) và xung đột nội tâm (mâu thuẫn, lựa chọn khó khăn)\n- Ít nhất 2-3 bước ngoặt bất ngờ nhưng hợp logic\n- Kết thúc để lại dư vị (happy ending hoặc open ending tùy thể loại)\n- Chỉ trả về nội dung ý tưởng.", "suggest_plot_user": "Hãy đưa ra ý tưởng cốt truyện chính (khoảng 200-250 từ) cho tiểu thuyết này để làm nền tảng phát triển.\n\nTiêu đề: {title}\nThể loại: {genre}\n\n(Nếu có) Thiết lập nhân vật: {character_setting}\n(Nếu có) Thế giới quan: {world_setting}\n\nYêu cầu:\n- Cốt truyện cần có điểm nhấn đầu truyện, bước ngoặt giữa truyện và xung đột cốt lõi rõ ràng\n- Xung đột bên ngoài (đối thủ, thế lực, nhiệm vụ) và xung đột nội tâm (mâu thuẫn, lựa chọn khó khăn)\n- Ít nhất 2-3 bước ngoặt bất ngờ nhưng hợp logic\n- Kết thúc để lại dư vị (happy ending hoặc open ending tùy thể loại)\n- Chỉ trả về nội dung ý tưởng.",
"continue_system": "Bạn là nhà văn tiểu thuyết dài kỳ xuất sắc, giỏi sáng tác câu chuyện hấp dẫn và kết nối cốt truyện tự nhiên.", "continue_system": "Bạn là nhà văn tiểu thuyết dài kỳ xuất sắc, giỏi sáng tác câu chuyện hấp dẫn và kết nối cốt truyện tự nhiên. TUYỆT ĐỐI KHÔNG sử dụng ký tự tiếng Trung hoặc văn phong lạm dụng Hán Việt. Phải dùng 100% tiếng Việt thuần túy, tự nhiên.",
"continue_user": "Hãy viết tiếp chương tiếp theo của tiểu thuyết «{novel_title}».\n\n【Thiết lập hiện có】\nThiết lập nhân vật: {character_setting}\nThế giới quan: {world_setting}\nCốt truyện chính: {plot_idea}\n\n【Yêu cầu phong cách】\n{style_desc}\n\n【Ôn lại trước đó】(1500 từ gần nhất\n{previous_content}\n\n【Yêu cầu viết tiếp】\n1. Viết tiếp chương mới tự nhiên dựa trên nội dung trước\n2. Giữ tính mạch lạc với phần trước, bao gồm tính cách nhân vật, phát triển cốt truyện, phong cách đối thoại v.v.\n3. Số từ khoảng {target_words}\n4. TUYỆT ĐỐI KHÔNG lặp lại nội dung đã có. Không bắt đầu bằng việc tóm tắt hoặc nhắc lại cảnh cuối cùng.\n5. Kết thúc để lại yếu tố hồi hộp hoặc gợi mở phù hợp\n6. Chỉ xuất ra nội dung viết tiếp, KHÔNG có tiêu đề chương, KHÔNG giải thích, KHÔNG có phần suy nghĩ/phân tích.", "continue_user": "Hãy viết tiếp chương tiếp theo của tiểu thuyết «{novel_title}».\n\n【Thiết lập hiện có】\nThiết lập nhân vật: {character_setting}\nThế giới quan: {world_setting}\nCốt truyện chính: {plot_idea}\n\n【Yêu cầu phong cách】\n{style_desc}\n\n【Ôn lại trước đó】(1500 từ gần nhất\n{previous_content}\n\n【Yêu cầu viết tiếp】\n1. Viết tiếp chương mới tự nhiên dựa trên nội dung trước\n2. Giữ tính mạch lạc với phần trước, bao gồm tính cách nhân vật, phát triển cốt truyện, phong cách đối thoại v.v.\n3. Số từ khoảng {target_words}\n4. TUYỆT ĐỐI KHÔNG lặp lại nội dung đã có. Không bắt đầu bằng việc tóm tắt hoặc nhắc lại cảnh cuối cùng.\n5. Kết thúc để lại yếu tố hồi hộp hoặc gợi mở phù hợp\n6. Chỉ xuất ra nội dung viết tiếp, KHÔNG có tiêu đề chương, KHÔNG giải thích, KHÔNG có phần suy nghĩ/phân tích.",
"chapter_summary_system": "Bạn là biên tập nội dung chuyên nghiệp, giỏi chiết xuất cốt truyện cốt lõi và thông tin quan trọng của chương.", "chapter_summary_system": "Bạn là biên tập nội dung chuyên nghiệp, giỏi chiết xuất cốt truyện cốt lõi và thông tin quan trọng của chương.",
"chapter_summary_user": "Hãy tạo tóm tắt ngắn gọn cho chương sau (100-200 từ).\n\nTiêu đề chương: {chapter_title}\n\nNội dung chương:\n{chapter_content}\n\nYêu cầu:\n1. Giữ lại cốt truyện chính và thông tin nhân vật\n2. Nêu bật xung đột cốt lõi và bước ngoặt của chương\n3. Ngôn ngữ ngắn gọn rõ ràng\n4. Chỉ xuất ra nội dung tóm tắt, không có giải thích khác", "chapter_summary_user": "Hãy tạo tóm tắt ngắn gọn cho chương sau (100-200 từ).\n\nTiêu đề chương: {chapter_title}\n\nNội dung chương:\n{chapter_content}\n\nYêu cầu:\n1. Giữ lại cốt truyện chính và thông tin nhân vật\n2. Nêu bật xung đột cốt lõi và bước ngoặt của chương\n3. Ngôn ngữ ngắn gọn rõ ràng\n4. Chỉ xuất ra nội dung tóm tắt, không có giải thích khác",
@@ -627,7 +627,9 @@
"polished_text_header": "Văn bản đã trau chuốt", "polished_text_header": "Văn bản đã trau chuốt",
"ai_no_format": "AI không xuất theo định dạng, vui lòng xem kết quả trau chuốt", "ai_no_format": "AI không xuất theo định dạng, vui lòng xem kết quả trau chuốt",
"image_cover_base": "Một bìa sách chuyên nghiệp chất lượng cao cho tiểu thuyết tiêu đề '{title}', thể loại '{genre}'. Ánh sáng điện ảnh, chi tiết, phong cách sử thi.", "image_cover_base": "Một bìa sách chuyên nghiệp chất lượng cao cho tiểu thuyết tiêu đề '{title}', thể loại '{genre}'. Ánh sáng điện ảnh, chi tiết, phong cách sử thi.",
"image_illustration_base": "Một minh họa đẹp cho chương truyện tiêu đề '{title}'. Mô tả cảnh: {summary}. Nghệ thuật kỹ thuật số, độ chi tiết cao." "image_illustration_base": "Một minh họa đẹp cho chương truyện tiêu đề '{title}'. Mô tả cảnh: {summary}. Nghệ thuật kỹ thuật số, độ chi tiết cao.",
"reflection_system": "Bạn là một biên tập viên xuất sắc kiêm chuyên gia thẩm định văn học. Khả năng của bạn là phát hiện các lỗi logic, sự lan man, sai tính cách nhân vật và lỗi ngôn ngữ trong các bản thảo, sau đó trực tiếp viết lại chúng thành một phiên bản hoàn hảo tuyệt đối.",
"reflection_user": "Dưới đây là Dàn Ý yêu cầu và Bản Nháp của chương truyện.\n\n【Dàn ý & Yêu cầu chương】\n{chapter_req}\n\n【Bản nháp hiện tại】\n{draft_content}\n\nHãy ĐỌC KỸ bản nháp trên, đối chiếu với dàn ý để phát hiện những chỗ lan man, sai logic, hoặc văn phong chưa đạt.\nSau đó, HÃY VIẾT LẠI một phiên bản hoàn chỉnh, súc tích, mạch lạc hơn nhưng vẫn phải giữ độ dài tương đương (khoảng {target_words} chữ).\n\nTUYỆT ĐỐI tuân thủ các quy tắc sau:\n1. CHỈ xuất kết quả là nội dung chương truyện đã được tối ưu hóa. KHÔNG thêm bất kỳ nhận xét, phân tích, tiêu đề hoặc từ ngữ dư thừa nào khác.\n2. Bắt buộc sử dụng 100% TIẾNG VIỆT thuần túy. TUYỆT ĐỐI cấm các ký tự tiếng Trung hoặc văn phong rườm rà Hán Việt."
}, },
"generator": { "generator": {
"title_empty": "Tên tiểu thuyết không được để trống", "title_empty": "Tên tiểu thuyết không được để trống",
+75
View File
@@ -0,0 +1,75 @@
import json
import os
DESC_MAP = {
"Xuyên không": "Nhân vật chính từ thế giới thực xuyên đến một thế giới khác, thời đại khác.",
"Xuyên sách": "Nhân vật chính xuyên vào cốt truyện của một bộ tiểu thuyết, thường thay đổi số phận của nguyên chủ.",
"Trọng sinh": "Nhân vật chính chết đi và sống lại ở một thời điểm trong quá khứ để làm lại cuộc đời.",
"Hệ thống": "Được gắn kết với một hệ thống (trí tuệ nhân tạo) cung cấp nhiệm vụ, kỹ năng và đạo cụ.",
"Bàn tay vàng": "Sở hữu năng lực, bảo bối hoặc lợi thế cực kỳ to lớn ngay từ đầu.",
"Không gian tùy thân": "Có một dị không gian bí mật để tàng hình, lưu trữ vật phẩm hoặc trồng trọt.",
"Linh tuyền": "Sở hữu suối nước thánh có tác dụng cường thân kiện thể, chữa bách bệnh, thúc đẩy sinh trưởng.",
"Đọc tâm thuật": "Năng lực ngoại cảm có thể nghe được suy nghĩ thực sự của người khác.",
"Vô địch lưu": "Nhân vật chính sở hữu sức mạnh vô địch, áp đảo mọi kẻ thù ngay từ đầu truyện.",
"Cẩu đạo": "Nhân vật chính theo trường phái ẩn nhẫn, phát triển âm thầm, tuyệt đối không phô trương để giấu thực lực.",
"Nhiệt huyết": "Mạch truyện sục sôi, đề cao tinh thần chiến đấu không lùi bước, dũng cảm đối mặt rủi ro.",
"Hài hước": "Văn phong mang đậm tính giải trí, tấu hài, nhiều tình huống dở khóc dở cười.",
"Sảng văn": "Truyện tập trung vào sự thăng tiến nhanh chóng, vả mặt kẻ khinh thường mình, mang lại cảm giác thỏa mãn.",
"Ngọt sủng": "Tình tiết lãng mạn, ngọt ngào, nhân vật chính được yêu thương chiều chuộng hết mực, ít bi kịch.",
"Ngược luyến": "Tình yêu nhiều oán hận, hiểu lầm, dằn vặt tổn thương sâu sắc cả thể xác lẫn tinh thần.",
"Gương vỡ lại lành": "Hai người từng yêu nhau, chia tay vì hiểu lầm hoặc hoàn cảnh, sau đó vượt qua thử thách để quay về bên nhau.",
"Cưới trước yêu sau": "Kết hôn vì bổn phận, giao dịch hoặc ép buộc, sau quá trình sống chung mới nảy sinh tình cảm thật sự.",
"Oan gia ngõ hẹp": "Cặp đôi ban đầu có ấn tượng xấu, ghét bỏ nhau, nhưng qua nhiều đụng độ lại dần yêu nhau.",
"Thanh mai trúc mã": "Nhân vật lớn lên cùng nhau từ nhỏ, có nền tảng tình cảm gắn kết sâu đậm và hiểu rõ về nhau.",
"Hào môn thế gia": "Bối cảnh giới siêu giàu, gia tộc quyền thế, đi kèm với tranh giành tài sản hoặc hôn nhân thương mại.",
"Tổng tài": "Nam chính là giám đốc, chủ tịch bá đạo, quyền thế, lạnh lùng nhưng rất sủng ái nữ chính.",
"Minh tinh": "Nhân vật chính là ngôi sao giải trí, cốt truyện khai thác ánh hào quang và góc khuất của showbiz.",
"Giới giải trí": "Bối cảnh tập trung vào mâu thuẫn, lịch trình, scandal và hoạt động nghệ thuật của giới giải trí.",
"Vườn trường": "Truyện xoay quanh cuộc sống thanh xuân học đường, tình cảm tuổi học trò trong sáng và nhiệt huyết.",
"Học bá": "Nhân vật chính có trí tuệ xuất chúng, tính toán thần sầu, giỏi giang về mặt học vấn.",
"Võng du": "Nhân vật chơi game online (có thể là thực tế ảo), cốt truyện đan xen giữa game và đời thực.",
"E-sports": "Tập trung vào thể thao điện tử chuyên nghiệp, giải đấu và sự cạnh tranh khốc liệt khao khát cúp vô địch.",
"Livestream": "Sử dụng hình thức phát sóng trực tiếp (livestream) để tương tác, kiếm tiền hoặc làm nhiệm vụ.",
"Mỹ thực": "Tập trung miêu tả các món ăn ngon, quá trình nấu nướng và kinh doanh nhà hàng xuất sắc.",
"Nông trại": "Quản lý và phát triển nông trại, thường kết hợp với kinh tế nhàn nhã, chữa lành.",
"Điền văn": "Cốt truyện bình dị, mô tả cuộc sống làm ruộng, làm nông trồng trọt chăn nuôi mang hơi thở dân dã, ấm cúng.",
"Nuôi con": "Tập trung vào hành trình chăm sóc, nuôi dưỡng con cái (con ruột, con nuôi hoặc manh bảo).",
"Làm giàu": "Quá trình nhân vật chính từ hai bàn tay trắng xây dựng sự nghiệp, kinh doanh trở nên giàu sang phú quý.",
"Cung đấu": "Những mưu mô ác hiểm tranh giành sủng ái và quyền lực chốn hậu cung giữa các phi tần.",
"Gia đấu": "Xung đột lợi ích, quyền hành và địa vị nội bộ trong các đại gia tộc danh gia vọng tộc.",
"Quyền mưu": "Sử dụng trí mưu, âm mưu quyền thuật thâm sâu để tranh đoạt vương quyền, vị thế trên triều đình.",
"Nữ cường": "Nữ chính độc lập, mạnh mẽ, thông minh, không quá luỵ nam chính, thường tự giải quyết các rắc rối mưu mô.",
"Nam cường": "Nam chính vô cùng mạnh mẽ, quyền thế, tài giỏi, cường giả vi tôn che chở và bảo vệ người mình yêu.",
"Song khiết": "Cả nam và nữ chính đều là mối tình đầu tiên về cả thể xác lẫn tâm hồn của nhau, 1v1 tuyệt đối.",
"Phế Sài": "Nhân vật chính bị coi là đồ bỏ đi, rác rưởi, không có tài năng bẩm sinh nhưng sau đó quật khởi vả mặt.",
"Thiên tài": "Có năng khiếu phi phàm vượt trội, thiên phú cực cao, tốc độ tu luyện hoặc tiếp thu nhanh hơn bất kỳ ai.",
"Mỹ cường thảm": "Nhân vật có ngoại hình đẹp, thực lực siêu cường cực hạn nhưng mang quá khứ hoặc số phận bi thương.",
"Trà xanh": "Nhân vật nữ bề ngoài tỏ ra yếu đuối, ngây thơ thánh thiện nhưng tâm can đầy mưu mô ly gián hạ bệ người khác.",
"Bạch liên hoa": "Có ý nghĩa tương tự Trà xanh, bề ngoài băng thanh ngọc khiết thiện lương nhưng tâm tính hèn mọn, tính toán thâm sâu.",
"Hắc hóa": "Nhân vật từ phe chính nghĩa thiện lương vì quá đau khổ tuyệt vọng mà chuyển biến sang tàn nhẫn, ác độc, không thủ đoạn.",
"Cứu rỗi": "Một người đang trong tăm tối, đau khổ được người kia bao dung thông qua tình yêu mang về với ánh sáng hy vọng.",
"Chữa lành": "Văn phong ấm áp ngọt ngào, nhấn mạnh vào quá trình các nhân vật xoa dịu vết thương lòng hoặc áp lực cuộc sống cho nhau.",
"Manh bảo": "Trẻ con đáng yêu, thông minh lanh lợi, đóng vai trò trợ tâm cho cha mẹ xích lại gần nhau.",
"Hậu cung": "Nhân vật chính (thường là nam) có mối quan hệ tình cảm đồng thời và kết hôn cùng nhiều nhân vật khác giới.",
"1v1": "Cốt truyện chỉ xoay quanh tình yêu duy nhất trọn đời giữa hai người, thủy chung 1 nam - 1 nữ.",
"NP": "Motif Poly (Multiple Partners), một nhân vật chính có mối quan hệ tình cảm lãng mạn chính thức với nhiều người cùng một lúc và kết thúc có hậu cùng nhau hòa bình.",
"Dị thế": "Xuyên đến một thế giới hoàn toàn khác Trái Đất, với những hệ tư tưởng, ma pháp hay nền văn minh dị thường."
}
path = r"c:\Users\lupan\Desktop\Workspace\AIStudioWorkspace\tinix-story\services\data\sub_genres.json"
default_desc = "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
for item in data:
name = item["name"]
if not item.get("description") or item["description"].strip() == "":
item["description"] = DESC_MAP.get(name, default_desc)
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4, ensure_ascii=False)
print("Done generating Sub Genre descriptions.")
except Exception as e:
print(f"Error: {e}")
+100 -100
View File
@@ -1,402 +1,402 @@
[ [
{ {
"name": "Hậu cung", "name": "Hậu cung",
"description": "" "description": "Nhân vật chính (thường là nam) có mối quan hệ tình cảm đồng thời và kết hôn cùng nhiều nhân vật khác giới."
}, },
{ {
"name": "1v1", "name": "1v1",
"description": "" "description": "Cốt truyện chỉ xoay quanh tình yêu duy nhất trọn đời giữa hai người, thủy chung 1 nam - 1 nữ."
}, },
{ {
"name": "NP", "name": "NP",
"description": "" "description": "Motif Poly (Multiple Partners), một nhân vật chính có mối quan hệ tình cảm lãng mạn chính thức với nhiều người cùng một lúc và kết thúc có hậu cùng nhau hòa bình."
}, },
{ {
"name": "Nữ phẫn nam trang", "name": "Nữ phẫn nam trang",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Nam phẫn nữ trang", "name": "Nam phẫn nữ trang",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Xuyên không", "name": "Xuyên không",
"description": "" "description": "Nhân vật chính từ thế giới thực xuyên đến một thế giới khác, thời đại khác."
}, },
{ {
"name": "Xuyên sách", "name": "Xuyên sách",
"description": "" "description": "Nhân vật chính xuyên vào cốt truyện của một bộ tiểu thuyết, thường thay đổi số phận của nguyên chủ."
}, },
{ {
"name": "Trọng sinh", "name": "Trọng sinh",
"description": "" "description": "Nhân vật chính chết đi và sống lại ở một thời điểm trong quá khứ để làm lại cuộc đời."
}, },
{ {
"name": "Hệ thống", "name": "Hệ thống",
"description": "" "description": "Được gắn kết với một hệ thống (trí tuệ nhân tạo) cung cấp nhiệm vụ, kỹ năng và đạo cụ."
}, },
{ {
"name": "Bàn tay vàng", "name": "Bàn tay vàng",
"description": "" "description": "Sở hữu năng lực, bảo bối hoặc lợi thế cực kỳ to lớn ngay từ đầu."
}, },
{ {
"name": "Không gian tùy thân", "name": "Không gian tùy thân",
"description": "" "description": "Có một dị không gian bí mật để tàng hình, lưu trữ vật phẩm hoặc trồng trọt."
}, },
{ {
"name": "Linh tuyền", "name": "Linh tuyền",
"description": "" "description": "Sở hữu suối nước thánh có tác dụng cường thân kiện thể, chữa bách bệnh, thúc đẩy sinh trưởng."
}, },
{ {
"name": "Đọc tâm thuật", "name": "Đọc tâm thuật",
"description": "" "description": "Năng lực ngoại cảm có thể nghe được suy nghĩ thực sự của người khác."
}, },
{ {
"name": "Vô địch lưu", "name": "Vô địch lưu",
"description": "" "description": "Nhân vật chính sở hữu sức mạnh vô địch, áp đảo mọi kẻ thù ngay từ đầu truyện."
}, },
{ {
"name": "Cẩu đạo", "name": "Cẩu đạo",
"description": "" "description": "Nhân vật chính theo trường phái ẩn nhẫn, phát triển âm thầm, tuyệt đối không phô trương để giấu thực lực."
}, },
{ {
"name": "Nhiệt huyết", "name": "Nhiệt huyết",
"description": "" "description": "Mạch truyện sục sôi, đề cao tinh thần chiến đấu không lùi bước, dũng cảm đối mặt rủi ro."
}, },
{ {
"name": "Hài hước", "name": "Hài hước",
"description": "" "description": "Văn phong mang đậm tính giải trí, tấu hài, nhiều tình huống dở khóc dở cười."
}, },
{ {
"name": "Sảng văn", "name": "Sảng văn",
"description": "" "description": "Truyện tập trung vào sự thăng tiến nhanh chóng, vả mặt kẻ khinh thường mình, mang lại cảm giác thỏa mãn."
}, },
{ {
"name": "Ngọt sủng", "name": "Ngọt sủng",
"description": "" "description": "Tình tiết lãng mạn, ngọt ngào, nhân vật chính được yêu thương chiều chuộng hết mực, ít bi kịch."
}, },
{ {
"name": "Ngược luyến", "name": "Ngược luyến",
"description": "" "description": "Tình yêu nhiều oán hận, hiểu lầm, dằn vặt tổn thương sâu sắc cả thể xác lẫn tinh thần."
}, },
{ {
"name": "Gương vỡ lại lành", "name": "Gương vỡ lại lành",
"description": "" "description": "Hai người từng yêu nhau, chia tay vì hiểu lầm hoặc hoàn cảnh, sau đó vượt qua thử thách để quay về bên nhau."
}, },
{ {
"name": "Cưới trước yêu sau", "name": "Cưới trước yêu sau",
"description": "" "description": "Kết hôn vì bổn phận, giao dịch hoặc ép buộc, sau quá trình sống chung mới nảy sinh tình cảm thật sự."
}, },
{ {
"name": "Oan gia ngõ hẹp", "name": "Oan gia ngõ hẹp",
"description": "" "description": "Cặp đôi ban đầu có ấn tượng xấu, ghét bỏ nhau, nhưng qua nhiều đụng độ lại dần yêu nhau."
}, },
{ {
"name": "Thanh mai trúc mã", "name": "Thanh mai trúc mã",
"description": "" "description": "Nhân vật lớn lên cùng nhau từ nhỏ, có nền tảng tình cảm gắn kết sâu đậm và hiểu rõ về nhau."
}, },
{ {
"name": "Hào môn thế gia", "name": "Hào môn thế gia",
"description": "" "description": "Bối cảnh giới siêu giàu, gia tộc quyền thế, đi kèm với tranh giành tài sản hoặc hôn nhân thương mại."
}, },
{ {
"name": "Tổng tài", "name": "Tổng tài",
"description": "" "description": "Nam chính là giám đốc, chủ tịch bá đạo, quyền thế, lạnh lùng nhưng rất sủng ái nữ chính."
}, },
{ {
"name": "Minh tinh", "name": "Minh tinh",
"description": "" "description": "Nhân vật chính là ngôi sao giải trí, cốt truyện khai thác ánh hào quang và góc khuất của showbiz."
}, },
{ {
"name": "Giới giải trí", "name": "Giới giải trí",
"description": "" "description": "Bối cảnh tập trung vào mâu thuẫn, lịch trình, scandal và hoạt động nghệ thuật của giới giải trí."
}, },
{ {
"name": "Vườn trường", "name": "Vườn trường",
"description": "" "description": "Truyện xoay quanh cuộc sống thanh xuân học đường, tình cảm tuổi học trò trong sáng và nhiệt huyết."
}, },
{ {
"name": "Học bá", "name": "Học bá",
"description": "" "description": "Nhân vật chính có trí tuệ xuất chúng, tính toán thần sầu, giỏi giang về mặt học vấn."
}, },
{ {
"name": "Võng du", "name": "Võng du",
"description": "" "description": "Nhân vật chơi game online (có thể là thực tế ảo), cốt truyện đan xen giữa game và đời thực."
}, },
{ {
"name": "E-sports", "name": "E-sports",
"description": "" "description": "Tập trung vào thể thao điện tử chuyên nghiệp, giải đấu và sự cạnh tranh khốc liệt khao khát cúp vô địch."
}, },
{ {
"name": "Livestream", "name": "Livestream",
"description": "" "description": "Sử dụng hình thức phát sóng trực tiếp (livestream) để tương tác, kiếm tiền hoặc làm nhiệm vụ."
}, },
{ {
"name": "Mỹ thực", "name": "Mỹ thực",
"description": "" "description": "Tập trung miêu tả các món ăn ngon, quá trình nấu nướng và kinh doanh nhà hàng xuất sắc."
}, },
{ {
"name": "Nông trại", "name": "Nông trại",
"description": "" "description": "Quản lý và phát triển nông trại, thường kết hợp với kinh tế nhàn nhã, chữa lành."
}, },
{ {
"name": "Điền văn", "name": "Điền văn",
"description": "" "description": "Cốt truyện bình dị, mô tả cuộc sống làm ruộng, làm nông trồng trọt chăn nuôi mang hơi thở dân dã, ấm cúng."
}, },
{ {
"name": "Nuôi con", "name": "Nuôi con",
"description": "" "description": "Tập trung vào hành trình chăm sóc, nuôi dưỡng con cái (con ruột, con nuôi hoặc manh bảo)."
}, },
{ {
"name": "Làm giàu", "name": "Làm giàu",
"description": "" "description": "Quá trình nhân vật chính từ hai bàn tay trắng xây dựng sự nghiệp, kinh doanh trở nên giàu sang phú quý."
}, },
{ {
"name": "Cung đấu", "name": "Cung đấu",
"description": "" "description": "Những mưu mô ác hiểm tranh giành sủng ái và quyền lực chốn hậu cung giữa các phi tần."
}, },
{ {
"name": "Gia đấu", "name": "Gia đấu",
"description": "" "description": "Xung đột lợi ích, quyền hành và địa vị nội bộ trong các đại gia tộc danh gia vọng tộc."
}, },
{ {
"name": "Quyền mưu", "name": "Quyền mưu",
"description": "" "description": "Sử dụng trí mưu, âm mưu quyền thuật thâm sâu để tranh đoạt vương quyền, vị thế trên triều đình."
}, },
{ {
"name": "Nữ cường", "name": "Nữ cường",
"description": "" "description": "Nữ chính độc lập, mạnh mẽ, thông minh, không quá luỵ nam chính, thường tự giải quyết các rắc rối mưu mô."
}, },
{ {
"name": "Nam cường", "name": "Nam cường",
"description": "" "description": "Nam chính vô cùng mạnh mẽ, quyền thế, tài giỏi, cường giả vi tôn che chở và bảo vệ người mình yêu."
}, },
{ {
"name": "Song khiết", "name": "Song khiết",
"description": "" "description": "Cả nam và nữ chính đều là mối tình đầu tiên về cả thể xác lẫn tâm hồn của nhau, 1v1 tuyệt đối."
}, },
{ {
"name": "Phế Sài", "name": "Phế Sài",
"description": "" "description": "Nhân vật chính bị coi là đồ bỏ đi, rác rưởi, không có tài năng bẩm sinh nhưng sau đó quật khởi vả mặt."
}, },
{ {
"name": "Thiên tài", "name": "Thiên tài",
"description": "" "description": "Có năng khiếu phi phàm vượt trội, thiên phú cực cao, tốc độ tu luyện hoặc tiếp thu nhanh hơn bất kỳ ai."
}, },
{ {
"name": "Từ hôn", "name": "Từ hôn",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Linh khí khôi phục", "name": "Linh khí khôi phục",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Mạt thế", "name": "Mạt thế",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Cơ giáp", "name": "Cơ giáp",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Tinh tế", "name": "Tinh tế",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "ABO", "name": "ABO",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Người sói", "name": "Người sói",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Ma cà rồng", "name": "Ma cà rồng",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Tây huyễn", "name": "Tây huyễn",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Ma pháp", "name": "Ma pháp",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Kiếm ma", "name": "Kiếm ma",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Pháp sư", "name": "Pháp sư",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Giả heo ăn hổ", "name": "Giả heo ăn hổ",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Bi kịch", "name": "Bi kịch",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Chữa lành", "name": "Chữa lành",
"description": "" "description": "Văn phong ấm áp ngọt ngào, nhấn mạnh vào quá trình các nhân vật xoa dịu vết thương lòng hoặc áp lực cuộc sống cho nhau."
}, },
{ {
"name": "Hắc ám", "name": "Hắc ám",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Gothic", "name": "Gothic",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Tâm lý", "name": "Tâm lý",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Tâm thần phân liệt", "name": "Tâm thần phân liệt",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Đa nhân cách", "name": "Đa nhân cách",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Trinh thám", "name": "Trinh thám",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Phá án", "name": "Phá án",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Đạo mộ", "name": "Đạo mộ",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Cương thi", "name": "Cương thi",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Quy tắc quái đàm", "name": "Quy tắc quái đàm",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Vô hạn lưu", "name": "Vô hạn lưu",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Mau xuyên", "name": "Mau xuyên",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Thực dân", "name": "Thực dân",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Chế tạo", "name": "Chế tạo",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Lĩnh chúa", "name": "Lĩnh chúa",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Trồng trọt", "name": "Trồng trọt",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Câu cá", "name": "Câu cá",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Nuôi thú", "name": "Nuôi thú",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Pokemon", "name": "Pokemon",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Anime", "name": "Anime",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Harry Potter", "name": "Harry Potter",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Marvel", "name": "Marvel",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Biển sao", "name": "Biển sao",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Truyền thuyết đô thị", "name": "Truyền thuyết đô thị",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Cổ đại", "name": "Cổ đại",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Dân quốc", "name": "Dân quốc",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Thập niên 70", "name": "Thập niên 70",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Thập niên 80", "name": "Thập niên 80",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Thập niên 90", "name": "Thập niên 90",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Dị thế", "name": "Dị thế",
"description": "" "description": "Xuyên đến một thế giới hoàn toàn khác Trái Đất, với những hệ tư tưởng, ma pháp hay nền văn minh dị thường."
}, },
{ {
"name": "Đại lục", "name": "Đại lục",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Bộ lạc", "name": "Bộ lạc",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Nguyên thủy", "name": "Nguyên thủy",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Tu tiên", "name": "Tu tiên",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Trả thù", "name": "Trả thù",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Pháo hôi", "name": "Pháo hôi",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Vai ác", "name": "Vai ác",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Tiên tri", "name": "Tiên tri",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
}, },
{ {
"name": "Phép thuật", "name": "Phép thuật",
"description": "" "description": "AI sẽ sử dụng quy chuẩn và đặc trưng của thể loại con này để tối ưu hóa tình tiết, bối cảnh và văn phong câu chuyện."
} }
] ]
+103 -16
View File
@@ -20,16 +20,16 @@ logger = logging.getLogger(__name__)
# Mẫu cài sẵn - lựa chọn kiểu phong phú (các phím được ánh xạ tới ngôn ngữ) # Mẫu cài sẵn - lựa chọn kiểu phong phú (các phím được ánh xạ tới ngôn ngữ)
def _build_preset_templates(): def _build_preset_templates():
"""Build preset templates from locale""" """Build preset templates from StyleManager"""
keys = [ from services.style_manager import StyleManager
"default", "xianxia", "romance", "thriller", "scifi", "wuxia", styles = StyleManager.load_styles()
"palace", "military", "historical", "supernatural", "campus", templates = {}
"business", "cyberpunk", "fantasy", "horror", "humor", "literary", "adventure" for g in styles:
] templates[g["name"]] = g["description"]
return {t(f"templates.name_{k}"): t(f"templates.{k}") for k in keys} return templates
def get_preset_templates(): def get_preset_templates():
"""Get preset templates (rebuilt each call to respect active language)""" """Get preset templates (rebuilt each call to respect Active Genres)"""
return _build_preset_templates() return _build_preset_templates()
@@ -375,7 +375,8 @@ class NovelGenerator:
sub_genres: List[str] = None, sub_genres: List[str] = None,
previous_content: str = "", previous_content: str = "",
context_summary: str = "", context_summary: str = "",
custom_prompt: str = "" custom_prompt: str = "",
use_reflection: bool = False
) -> Tuple[str, str]: ) -> Tuple[str, str]:
""" """
Tạo một chương đơn lẻ Tạo một chương đơn lẻ
@@ -460,6 +461,22 @@ class NovelGenerator:
logger.error(f"Chapter generation failed: {content}") logger.error(f"Chapter generation failed: {content}")
return "", content return "", content
if use_reflection:
logger.info("Applying self-reflection to the generated chapter draft...")
# Sử dụng content nháp làm nguyên liệu cho lượt tạo reflection mới
reflection_sys = t("prompts.reflection_system")
reflection_prompt = t("prompts.reflection_user", chapter_req=prompt, draft_content=content, target_words=target_words)
reflection_messages = [
{"role": "system", "content": reflection_sys},
{"role": "user", "content": reflection_prompt}
]
success, final_content = self.api_client.generate(reflection_messages, use_cache=False)
if not success:
logger.error(f"Reflection generation failed: {final_content}. Falling back to draft content.")
else:
logger.info("Reflection generation success.")
content = final_content
logger.info(f"Chapter generation success: {chapter_num}") logger.info(f"Chapter generation success: {chapter_num}")
logger.info(f"Chapter content length: {len(content)}") logger.info(f"Chapter content length: {len(content)}")
return content, t("generator.gen_success") return content, t("generator.gen_success")
@@ -477,7 +494,8 @@ class NovelGenerator:
sub_genres: List[str] = None, sub_genres: List[str] = None,
previous_content: str = "", previous_content: str = "",
context_summary: str = "", context_summary: str = "",
custom_prompt: str = "" custom_prompt: str = "",
use_reflection: bool = False
): ):
""" """
Tạo một chương đơn lẻ bằng streaming Tạo một chương đơn lẻ bằng streaming
@@ -536,13 +554,34 @@ class NovelGenerator:
if context_summary: if context_summary:
logger.info(f"Using context enhancement, context length: {len(context_summary)}") logger.info(f"Using context enhancement, context length: {len(context_summary)}")
for success, chunk in self.api_client.generate_stream(messages=messages): if not use_reflection:
yield success, chunk for success, chunk in self.api_client.generate_stream(messages=messages):
yield success, chunk
else:
logger.info("Generating draft for reflection in streaming mode...")
success, draft_content = self.api_client.generate(messages, use_cache=False)
if not success:
logger.error(f"Draft generation failed: {draft_content}")
yield False, draft_content
return
logger.info("Draft generated. Applying self-reflection stream...")
reflection_sys = t("prompts.reflection_system")
reflection_prompt = t("prompts.reflection_user", chapter_req=prompt, draft_content=draft_content, target_words=target_words)
reflection_messages = [
{"role": "system", "content": reflection_sys},
{"role": "user", "content": reflection_prompt}
]
for success, chunk in self.api_client.generate_stream(messages=reflection_messages):
yield success, chunk
def rewrite_paragraph( def rewrite_paragraph(
self, self,
text: str, text: str,
style_template: str = "" style_template: str = "",
use_reflection: bool = False
) -> Tuple[str, str]: ) -> Tuple[str, str]:
""" """
Viết lại đoạn văn (có cơ chế thử lại) Viết lại đoạn văn (có cơ chế thử lại)
@@ -617,6 +656,19 @@ class NovelGenerator:
# Xác minh nội dung đã được thông qua # Xác minh nội dung đã được thông qua
logger.info(f"Rewrite done, content length: {len(content)}, attempts: {attempt + 1}") logger.info(f"Rewrite done, content length: {len(content)}, attempts: {attempt + 1}")
logger.debug(f"Content first 200: {content[:200]}") logger.debug(f"Content first 200: {content[:200]}")
if use_reflection:
logger.info("Applying self-reflection to rewritten paragraph draft...")
reflection_sys = t("prompts.reflection_system")
reflection_prompt = t("prompts.reflection_user", chapter_req=prompt, draft_content=content, target_words=len(content))
reflection_messages = [
{"role": "system", "content": reflection_sys},
{"role": "user", "content": reflection_prompt}
]
success, final_content = self.api_client.generate(reflection_messages, use_cache=False)
if success:
content = final_content
return content, t("generator.rewrite_success") return content, t("generator.rewrite_success")
# Tất cả các lần thử lại đều thất bại # Tất cả các lần thử lại đều thất bại
@@ -653,7 +705,8 @@ class NovelGenerator:
self, self,
text: str, text: str,
polish_type: str = "general", polish_type: str = "general",
custom_requirements: str = "" custom_requirements: str = "",
use_reflection: bool = False
) -> Tuple[str, str]: ) -> Tuple[str, str]:
""" """
Văn bản tiếng Ba Lan (với cơ chế thử lại) Văn bản tiếng Ba Lan (với cơ chế thử lại)
@@ -746,6 +799,19 @@ class NovelGenerator:
# Xác minh nội dung đã được thông qua # Xác minh nội dung đã được thông qua
logger.info(f"Polish done, content length: {len(content)}, attempts: {attempt + 1}") logger.info(f"Polish done, content length: {len(content)}, attempts: {attempt + 1}")
logger.debug(f"Content first 200: {content[:200]}") logger.debug(f"Content first 200: {content[:200]}")
if use_reflection:
logger.info("Applying self-reflection to polished paragraph draft...")
reflection_sys = t("prompts.reflection_system")
reflection_prompt = t("prompts.reflection_user", chapter_req=prompt, draft_content=content, target_words=len(content))
reflection_messages = [
{"role": "system", "content": reflection_sys},
{"role": "user", "content": reflection_prompt}
]
success, final_content = self.api_client.generate(reflection_messages, use_cache=False)
if success:
content = final_content
return content, t("generator.polish_success") return content, t("generator.polish_success")
# Tất cả các lần thử lại đều thất bại # Tất cả các lần thử lại đều thất bại
@@ -755,7 +821,8 @@ class NovelGenerator:
def polish_and_suggest( def polish_and_suggest(
self, self,
text: str, text: str,
custom_requirements: str = "" custom_requirements: str = "",
use_reflection: bool = False
) -> Tuple[str, str, str]: ) -> Tuple[str, str, str]:
""" """
Trau chuốt văn bản và đưa ra đề xuất Trau chuốt văn bản và đưa ra đề xuất
@@ -784,6 +851,18 @@ class NovelGenerator:
logger.error(f"Polish failed: {content}") logger.error(f"Polish failed: {content}")
return "", "", content return "", "", content
if use_reflection:
logger.info("Applying self-reflection to polish and suggest paragraph draft...")
reflection_sys = t("prompts.reflection_system")
reflection_prompt = t("prompts.reflection_user", chapter_req=prompt, draft_content=content, target_words=len(content))
reflection_messages = [
{"role": "system", "content": reflection_sys},
{"role": "user", "content": reflection_prompt}
]
success, final_content = self.api_client.generate(reflection_messages, use_cache=False)
if success:
content = final_content
# Thêm xác minh nội dung # Thêm xác minh nội dung
if len(content) < 10: if len(content) < 10:
logger.warning(f"Polish content too short: {len(content)}, content: {content}") logger.warning(f"Polish content too short: {len(content)}, content: {content}")
@@ -995,9 +1074,17 @@ class NovelGenerator:
def _build_style_description(self) -> str: def _build_style_description(self) -> str:
"""Mô tả phong cách xây dựng""" """Mô tả phong cách xây dựng"""
from services.style_manager import StyleManager
gen = self.config.generation gen = self.config.generation
style_name = gen.writing_style
style_desc = StyleManager.get_style_description(style_name)
full_style = style_name
if style_desc:
full_style += f" ({style_desc})"
return t("prompts.style_description", return t("prompts.style_description",
writing_style=gen.writing_style, writing_style=full_style,
writing_tone=gen.writing_tone, writing_tone=gen.writing_tone,
character_development=gen.character_development, character_development=gen.character_development,
plot_complexity=gen.plot_complexity plot_complexity=gen.plot_complexity
+135
View File
@@ -0,0 +1,135 @@
import os
import json
import logging
from typing import Dict, List, Optional
from locales.i18n import t
logger = logging.getLogger(__name__)
STYLES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "data", "writing_styles.json")
class StyleManager:
"""Quản lý các phong cách viết truyện và mô tả"""
_cached_styles = None
_cached_mtime = 0
@classmethod
def get_default_styles(cls) -> List[Dict[str, str]]:
"""Lấy danh sách phong cách viết mặc định nếu chưa có file"""
return [
{
"name": "Mượt mà tự nhiên, cốt truyện chặt chẽ, nhân vật tinh tế",
"description": "Văn phong trơn tru, dễ đọc, mạch truyện logic xuyên suốt không có sạn. Xây dựng tâm lý và hành động nhân vật sâu sắc, nhất quán."
},
{
"name": "Văn phong đẹp, ý cảnh sâu xa",
"description": "Sử dụng từ ngữ trau chuốt, giàu hình ảnh và các phép tu từ. Tập trung miêu tả cảnh vật, nội tâm nhân vật để tạo ra một không gian nghệ thuật đầy cảm xúc."
},
{
"name": "Nhịp nhanh, cốt truyện kịch tính",
"description": "Tập trung vào hành động, xung đột và diễn biến câu chuyện. Cắt giảm tối đa miêu tả thừa thãi, đưa người đọc đi từ bất ngờ này đến bất ngờ khác."
},
{
"name": "Mô tả tinh tế, cảm xúc phong phú",
"description": "Chú trọng vào việc thể hiện cảm xúc, rung động tinh vi của nhân vật. Các chi tiết nhỏ trong bối cảnh và tương tác được dùng để tô đậm không khí truyện."
},
{
"name": "Hài hước thú vị, nhẹ nhàng vui vẻ",
"description": "Sử dụng ngôn ngữ hóm hỉnh, tình huống hài hước dở khóc dở cười. Xoay quanh những mẩu chuyện đời thường hoặc hành trình nhẹ nhàng mang tính giải trí cao."
}
]
@classmethod
def ensure_data_dir(cls):
"""Đảm bảo thư mục data tồn tại"""
os.makedirs(os.path.dirname(STYLES_FILE), exist_ok=True)
@classmethod
def load_styles(cls) -> List[Dict[str, str]]:
"""Tải danh sách phong cách từ file (có cache theo mtime)"""
cls.ensure_data_dir()
if not os.path.exists(STYLES_FILE):
default_styles = cls.get_default_styles()
cls.save_styles(default_styles)
return default_styles
try:
current_mtime = os.path.getmtime(STYLES_FILE)
if cls._cached_styles is not None and cls._cached_mtime == current_mtime:
return cls._cached_styles
with open(STYLES_FILE, 'r', encoding='utf-8') as f:
styles = json.load(f)
cls._cached_styles = styles
cls._cached_mtime = current_mtime
return styles
except Exception as e:
logger.error(f"Error loading styles: {e}")
return cls.get_default_styles()
@classmethod
def save_styles(cls, styles: List[Dict[str, str]]) -> bool:
"""Lưu danh sách phong cách xuống file"""
cls.ensure_data_dir()
try:
with open(STYLES_FILE, 'w', encoding='utf-8') as f:
json.dump(styles, f, ensure_ascii=False, indent=4)
# Invalidate cache
cls._cached_styles = styles
cls._cached_mtime = os.path.getmtime(STYLES_FILE)
return True
except Exception as e:
logger.error(f"Error saving styles: {e}")
return False
@classmethod
def add_style(cls, name: str, description: str = "") -> bool:
"""Thêm một phong cách mới"""
styles = cls.load_styles()
# Kiểm tra trùng tên
if any(g["name"] == name for g in styles):
return False
styles.append({"name": name, "description": description})
return cls.save_styles(styles)
@classmethod
def update_style(cls, old_name: str, new_name: str, description: str) -> bool:
"""Cập nhật thông tin phong cách"""
styles = cls.load_styles()
for i, g in enumerate(styles):
if g["name"] == old_name:
# Nếu đổi tên, kiểm tra trùng tên mới
if old_name != new_name and any(x["name"] == new_name for x in styles):
return False
styles[i] = {"name": new_name, "description": description}
return cls.save_styles(styles)
return False
@classmethod
def delete_style(cls, name: str) -> bool:
"""Xóa phong cách"""
styles = cls.load_styles()
initial_length = len(styles)
styles = [g for g in styles if g["name"] != name]
if len(styles) < initial_length:
return cls.save_styles(styles)
return False
@classmethod
def get_style_names(cls) -> List[str]:
"""Lấy danh sách tên phong cách để hiển thị UI"""
styles = cls.load_styles()
return [g["name"] for g in styles]
@classmethod
def get_style_description(cls, name: str) -> str:
"""Lấy mô tả hướng dẫn của một phong cách"""
styles = cls.load_styles()
for g in styles:
if g["name"] == name:
return g["description"]
return ""
+28 -6
View File
@@ -3,6 +3,7 @@ from datetime import datetime
from locales.i18n import t from locales.i18n import t
from services.project_manager import ProjectManager, list_project_titles from services.project_manager import ProjectManager, list_project_titles
from services.novel_generator import Chapter from services.novel_generator import Chapter
from services.style_manager import StyleManager
from core.state import app_state from core.state import app_state
import logging import logging
@@ -40,9 +41,20 @@ def build_continue_tab():
memory_type = gr.Radio(label="Cách nhớ ngữ cảnh", choices=["Toàn văn", "Tóm tắt"], value="Toàn văn") memory_type = gr.Radio(label="Cách nhớ ngữ cảnh", choices=["Toàn văn", "Tóm tắt"], value="Toàn văn")
with gr.Column(scale=1): with gr.Column(scale=1):
memory_chapters = gr.Number(label="Số chương ghi nhớ", value=3, minimum=1, maximum=20, step=1) memory_chapters = gr.Number(label="Số chương ghi nhớ", value=3, minimum=1, maximum=20, step=1)
with gr.Column(scale=1):
use_reflection_checkbox = gr.Checkbox(label="Bật chế độ Tự kiểm duyệt (Self-Reflection)", value=False, info="AI tự đọc lại và sửa lỗi nháp. Tốn gấp đôi thời gian & Token.")
with gr.Row(): with gr.Row():
continue_target_words = gr.Number(label=t("rewrite.target_words"), value=3000, minimum=100, maximum=50000, step=100) continue_target_words = gr.Number(label=t("rewrite.target_words"), value=3000, minimum=100, maximum=50000, step=100)
style_choices = StyleManager.get_style_names()
continue_style_dropdown = gr.Dropdown(
choices=style_choices,
label="Phong cách viết",
value=style_choices[0] if style_choices else None,
interactive=True
)
continue_custom_prompt = gr.Textbox(label=t("create.custom_prompt_label"), placeholder=t("create.custom_prompt_placeholder"), scale=2) continue_custom_prompt = gr.Textbox(label=t("create.custom_prompt_label"), placeholder=t("create.custom_prompt_placeholder"), scale=2)
with gr.Accordion("🚀 3. Sáng tác tiếp", open=False): with gr.Accordion("🚀 3. Sáng tác tiếp", open=False):
@@ -141,12 +153,16 @@ def build_continue_tab():
return ch.content or "" return ch.content or ""
return "" return ""
def on_continue_generate(project_title, ch_num, ch_title, ch_desc, target_words, custom_prompt, mem_type, mem_chaps): def on_continue_generate(project_title, ch_num, ch_title, ch_desc, target_words, custom_prompt, mem_type, mem_chaps, use_reflection, selected_style):
yield "⏳ Đang chuẩn bị dữ liệu...", "", gr.update(interactive=False), gr.update(), gr.update() yield "⏳ Đang chuẩn bị dữ liệu...", "", gr.update(interactive=False), gr.update(), gr.update()
if not app_state.current_project: if not app_state.current_project:
yield f"{t('continue_tab.no_project_selected')}", "", gr.update(interactive=True), gr.update(), gr.update() yield f"{t('continue_tab.no_project_selected')}", "", gr.update(interactive=True), gr.update(), gr.update()
return return
gen = app_state.get_generator()
if selected_style:
gen.config.generation.writing_style = selected_style
gen = app_state.get_generator() gen = app_state.get_generator()
project = app_state.current_project project = app_state.current_project
@@ -192,7 +208,8 @@ def build_continue_tab():
world_setting=project.world_setting, world_setting=project.world_setting,
plot_idea=project.plot_idea, genre=project.genre, plot_idea=project.plot_idea, genre=project.genre,
sub_genres=project.sub_genres, sub_genres=project.sub_genres,
previous_content=prev_content, context_summary=context_summary, custom_prompt=custom_prompt previous_content=prev_content, context_summary=context_summary, custom_prompt=custom_prompt,
use_reflection=use_reflection
) )
if content: if content:
@@ -228,12 +245,16 @@ def build_continue_tab():
else: else:
yield f"{msg}", "", gr.update(interactive=True), gr.update(), gr.update() yield f"{msg}", "", gr.update(interactive=True), gr.update(), gr.update()
def on_continue_auto_generate(project_title, target_words, custom_prompt, mem_type, mem_chaps, progress=gr.Progress()): def on_continue_auto_generate(project_title, target_words, custom_prompt, mem_type, mem_chaps, use_reflection, selected_style, progress=gr.Progress()):
yield "⏳ Đang chuẩn bị dữ liệu...", "", gr.update(interactive=False), gr.update(interactive=False), gr.update(), gr.update() yield "⏳ Đang chuẩn bị dữ liệu...", "", gr.update(interactive=False), gr.update(interactive=False), gr.update(), gr.update()
if not app_state.current_project: if not app_state.current_project:
yield f"{t('continue_tab.no_project_selected')}", "", gr.update(interactive=True), gr.update(interactive=True), gr.update(), gr.update() yield f"{t('continue_tab.no_project_selected')}", "", gr.update(interactive=True), gr.update(interactive=True), gr.update(), gr.update()
return return
gen = app_state.get_generator()
if selected_style:
gen.config.generation.writing_style = selected_style
gen = app_state.get_generator() gen = app_state.get_generator()
project = app_state.current_project project = app_state.current_project
app_state.is_generating = True app_state.is_generating = True
@@ -292,7 +313,8 @@ def build_continue_tab():
world_setting=project.world_setting, world_setting=project.world_setting,
plot_idea=project.plot_idea, genre=project.genre, plot_idea=project.plot_idea, genre=project.genre,
sub_genres=project.sub_genres, sub_genres=project.sub_genres,
previous_content=prev_content, context_summary=context_summary, custom_prompt=custom_prompt previous_content=prev_content, context_summary=context_summary, custom_prompt=custom_prompt,
use_reflection=use_reflection
) )
if content: if content:
@@ -347,12 +369,12 @@ def build_continue_tab():
continue_generate_btn.click( continue_generate_btn.click(
fn=on_continue_generate, fn=on_continue_generate,
inputs=[continue_project_selector, continue_chapter_num, continue_chapter_title, inputs=[continue_project_selector, continue_chapter_num, continue_chapter_title,
continue_chapter_desc, continue_target_words, continue_custom_prompt, memory_type, memory_chapters], continue_chapter_desc, continue_target_words, continue_custom_prompt, memory_type, memory_chapters, use_reflection_checkbox, continue_style_dropdown],
outputs=[continue_status, continue_content_display, continue_generate_btn, continue_outline_display, continue_chapter_selector] outputs=[continue_status, continue_content_display, continue_generate_btn, continue_outline_display, continue_chapter_selector]
) )
continue_auto_btn.click( continue_auto_btn.click(
fn=on_continue_auto_generate, fn=on_continue_auto_generate,
inputs=[continue_project_selector, continue_target_words, continue_custom_prompt, memory_type, memory_chapters], inputs=[continue_project_selector, continue_target_words, continue_custom_prompt, memory_type, memory_chapters, use_reflection_checkbox, continue_style_dropdown],
outputs=[continue_status, continue_content_display, continue_auto_btn, continue_generate_btn, continue_outline_display, continue_chapter_selector], outputs=[continue_status, continue_content_display, continue_auto_btn, continue_generate_btn, continue_outline_display, continue_chapter_selector],
show_progress="full" show_progress="full"
) )
+19 -3
View File
@@ -9,6 +9,7 @@ from services.novel_generator import OutlineParser
from services.project_manager import ProjectManager from services.project_manager import ProjectManager
from services.genre_manager import GenreManager from services.genre_manager import GenreManager
from services.sub_genre_manager import SubGenreManager from services.sub_genre_manager import SubGenreManager
from services.style_manager import StyleManager
from core.state import app_state from core.state import app_state
import logging import logging
@@ -37,6 +38,14 @@ def build_create_tab():
interactive=True interactive=True
) )
style_choices = StyleManager.get_style_names()
create_style_dropdown = gr.Dropdown(
choices=style_choices,
label="Phong cách viết",
value=style_choices[0] if style_choices else None,
interactive=True
)
with gr.Column(scale=2): with gr.Column(scale=2):
title_input = gr.Textbox( title_input = gr.Textbox(
label=t("create.novel_title"), label=t("create.novel_title"),
@@ -115,6 +124,8 @@ def build_create_tab():
memory_type = gr.Radio(label="Cách nhớ ngữ cảnh", choices=["Toàn văn", "Tóm tắt"], value="Toàn văn") memory_type = gr.Radio(label="Cách nhớ ngữ cảnh", choices=["Toàn văn", "Tóm tắt"], value="Toàn văn")
with gr.Column(scale=1): with gr.Column(scale=1):
memory_chapters = gr.Number(label="Số chương ghi nhớ", value=3, minimum=1, maximum=20, step=1) memory_chapters = gr.Number(label="Số chương ghi nhớ", value=3, minimum=1, maximum=20, step=1)
with gr.Column(scale=1):
use_reflection_checkbox = gr.Checkbox(label="Bật chế độ Tự kiểm duyệt (Self-Reflection)", value=False, info="AI tự đọc lại và sửa lỗi nháp. Tốn gấp đôi thời gian & Token.")
with gr.Row(): with gr.Row():
auto_generate_btn = gr.Button(t("create.start_gen_btn"), variant="primary", size="lg", interactive=False) auto_generate_btn = gr.Button(t("create.start_gen_btn"), variant="primary", size="lg", interactive=False)
stop_btn = gr.Button(t("create.pause_gen_btn"), variant="stop", size="lg", interactive=False) stop_btn = gr.Button(t("create.pause_gen_btn"), variant="stop", size="lg", interactive=False)
@@ -200,10 +211,14 @@ def build_create_tab():
) )
return content, msg return content, msg
def on_auto_generate(title, genre, sub_genres, char_setting, world_setting, plot_idea, outline_text, mem_type, mem_chaps, progress=gr.Progress()): def on_auto_generate(title, genre, sub_genres, char_setting, world_setting, plot_idea, outline_text, mem_type, mem_chaps, use_reflection, selected_style, progress=gr.Progress()):
"""Tự động tạo toàn bộ tiểu thuyết""" """Tự động tạo toàn bộ tiểu thuyết"""
gen = app_state.get_generator() gen = app_state.get_generator()
# Cập nhật style vào config hiện tại
if selected_style:
gen.config.generation.writing_style = selected_style
chapters, parse_msg = OutlineParser.parse(outline_text) chapters, parse_msg = OutlineParser.parse(outline_text)
if not chapters: if not chapters:
yield f"{parse_msg}", gr.update() yield f"{parse_msg}", gr.update()
@@ -273,7 +288,8 @@ def build_create_tab():
chapter_desc=chapter.desc, novel_title=title, chapter_desc=chapter.desc, novel_title=title,
character_setting=char_setting, world_setting=world_setting, character_setting=char_setting, world_setting=world_setting,
plot_idea=plot_idea, genre=genre, sub_genres=sub_genres, plot_idea=plot_idea, genre=genre, sub_genres=sub_genres,
previous_content=prev_content, context_summary=context_summary previous_content=prev_content, context_summary=context_summary,
use_reflection=use_reflection
) )
if content: if content:
@@ -340,7 +356,7 @@ def build_create_tab():
) )
auto_generate_btn.click( auto_generate_btn.click(
fn=on_auto_generate, fn=on_auto_generate,
inputs=[title_input, genre_dropdown, sub_genre_dropdown, character_input, world_input, plot_input, outline_output, memory_type, memory_chapters], inputs=[title_input, genre_dropdown, sub_genre_dropdown, character_input, world_input, plot_input, outline_output, memory_type, memory_chapters, use_reflection_checkbox, create_style_dropdown],
outputs=[generation_progress, chapter_selector] outputs=[generation_progress, chapter_selector]
) )
chapter_selector.change( chapter_selector.change(
+8 -6
View File
@@ -26,6 +26,8 @@ def build_polish_tab():
polish_btn = gr.Button(t("polish.start_polish"), variant="primary") polish_btn = gr.Button(t("polish.start_polish"), variant="primary")
polish_suggest_btn = gr.Button(t("polish.polish_suggest_btn"), variant="secondary") polish_suggest_btn = gr.Button(t("polish.polish_suggest_btn"), variant="secondary")
use_reflection_checkbox = gr.Checkbox(label="Bật chế độ Tự kiểm duyệt (Self-Reflection)", value=False, info="AI báo lỗi và viết lại cho hoàn chỉnh hơn.")
polish_status = gr.Textbox(label=t("polish.polish_status"), interactive=False) polish_status = gr.Textbox(label=t("polish.polish_status"), interactive=False)
polish_output = gr.Textbox(label=t("polish.polished_text"), lines=15, interactive=True) polish_output = gr.Textbox(label=t("polish.polished_text"), lines=15, interactive=True)
@@ -38,24 +40,24 @@ def build_polish_tab():
except Exception as e: except Exception as e:
return f"{str(e)}" return f"{str(e)}"
def on_polish(text, custom_req): def on_polish(text, custom_req, use_reflection):
yield "⏳ Đang gọi AI xử lý... Vui lòng chờ.", gr.update(), gr.update(interactive=False) yield "⏳ Đang gọi AI xử lý... Vui lòng chờ.", gr.update(), gr.update(interactive=False)
gen = app_state.get_generator() gen = app_state.get_generator()
content, msg = gen.polish_text(text, custom_requirements=custom_req) content, msg = gen.polish_text(text, custom_requirements=custom_req, use_reflection=use_reflection)
if content: if content:
yield f"{msg}", content, gr.update(interactive=True) yield f"{msg}", content, gr.update(interactive=True)
else: else:
yield f"{msg}", gr.update(), gr.update(interactive=True) yield f"{msg}", gr.update(), gr.update(interactive=True)
def on_polish_suggest(text, custom_req): def on_polish_suggest(text, custom_req, use_reflection):
yield "⏳ Đang gọi AI xử lý... Vui lòng chờ.", gr.update(), gr.update(interactive=False) yield "⏳ Đang gọi AI xử lý... Vui lòng chờ.", gr.update(), gr.update(interactive=False)
gen = app_state.get_generator() gen = app_state.get_generator()
content, msg = gen.polish_and_suggest(text, custom_requirements=custom_req) content, msg = gen.polish_and_suggest(text, custom_requirements=custom_req, use_reflection=use_reflection)
if content: if content:
yield f"{msg}", content, gr.update(interactive=True) yield f"{msg}", content, gr.update(interactive=True)
else: else:
yield f"{msg}", gr.update(), gr.update(interactive=True) yield f"{msg}", gr.update(), gr.update(interactive=True)
polish_file_input.change(fn=on_polish_file_upload, inputs=[polish_file_input], outputs=[polish_input]) polish_file_input.change(fn=on_polish_file_upload, inputs=[polish_file_input], outputs=[polish_input])
polish_btn.click(fn=on_polish, inputs=[polish_input, polish_custom_req], outputs=[polish_status, polish_output, polish_btn]) polish_btn.click(fn=on_polish, inputs=[polish_input, polish_custom_req, use_reflection_checkbox], outputs=[polish_status, polish_output, polish_btn])
polish_suggest_btn.click(fn=on_polish_suggest, inputs=[polish_input, polish_custom_req], outputs=[polish_status, polish_output, polish_suggest_btn]) polish_suggest_btn.click(fn=on_polish_suggest, inputs=[polish_input, polish_custom_req, use_reflection_checkbox], outputs=[polish_status, polish_output, polish_suggest_btn])
+2 -3
View File
@@ -13,9 +13,7 @@ def build_projects_tab():
headers=["ID", t("ui.col_project_name"), t("ui.col_type"), t("ui.col_created_at"), t("ui.col_chapters")], headers=["ID", t("ui.col_project_name"), t("ui.col_type"), t("ui.col_created_at"), t("ui.col_chapters")],
interactive=False interactive=False
) )
with gr.Row(): refresh_projects_btn = gr.Button(t("projects.refresh_btn"))
refresh_projects_btn = gr.Button(t("projects.refresh_btn"))
delete_project_btn = gr.Button(t("projects.delete_btn"), variant="stop")
gr.Markdown(f"#### {t('projects.delete_header')}") gr.Markdown(f"#### {t('projects.delete_header')}")
delete_project_selector = gr.Dropdown( delete_project_selector = gr.Dropdown(
@@ -23,6 +21,7 @@ def build_projects_tab():
label=t("projects.delete_select_project"), label=t("projects.delete_select_project"),
interactive=True interactive=True
) )
delete_project_btn = gr.Button(t("projects.delete_btn"), variant="stop")
project_manage_status = gr.Textbox(label=t("projects.status_label"), interactive=False) project_manage_status = gr.Textbox(label=t("projects.status_label"), interactive=False)
def on_refresh_projects(): def on_refresh_projects():
+28 -12
View File
@@ -1,6 +1,8 @@
import gradio as gr import gradio as gr
from locales.i18n import t from locales.i18n import t
from services.novel_generator import get_preset_templates from services.novel_generator import get_preset_templates
from services.genre_manager import GenreManager
from services.style_manager import StyleManager
from utils.file_parser import parse_novel_file from utils.file_parser import parse_novel_file
from core.state import app_state from core.state import app_state
import logging import logging
@@ -12,19 +14,33 @@ def build_rewrite_tab():
gr.Markdown(f"### {t('rewrite.header')}") gr.Markdown(f"### {t('rewrite.header')}")
rewrite_file_input = gr.File(label=t("rewrite.upload_file"), file_types=[".txt", ".docx", ".md", ".pdf"]) rewrite_file_input = gr.File(label=t("rewrite.upload_file"), file_types=[".txt", ".docx", ".md", ".pdf"])
genre_choices = GenreManager.get_genre_names()
with gr.Row():
rewrite_genre = gr.Dropdown(
choices=genre_choices,
label=t("create.genre_label"),
value=genre_choices[0] if genre_choices else None,
interactive=True,
scale=1
)
style_choices = StyleManager.get_style_names()
rewrite_style = gr.Dropdown(
choices=style_choices,
label=t("rewrite.preset_style"),
value=style_choices[0] if style_choices else None,
interactive=True,
scale=1
)
use_reflection_checkbox = gr.Checkbox(label="Bật chế độ Tự kiểm duyệt", value=False, info="AI báo lỗi và viết lại cho hoàn chỉnh hơn.", scale=1)
rewrite_input = gr.Textbox( rewrite_input = gr.Textbox(
label=t("polish.original_text"), label=t("polish.original_text"),
lines=10, placeholder="Dán nội dung cần viết lại..." lines=10, placeholder="Dán nội dung cần viết lại..."
) )
preset_choices = list(get_preset_templates().keys())
rewrite_style = gr.Dropdown(
choices=preset_choices,
label=t("rewrite.preset_style"),
value=preset_choices[0] if preset_choices else None,
interactive=True
)
rewrite_btn = gr.Button(t("rewrite.start_rewrite"), variant="primary") rewrite_btn = gr.Button(t("rewrite.start_rewrite"), variant="primary")
rewrite_status = gr.Textbox(label=t("rewrite.parse_status"), interactive=False) rewrite_status = gr.Textbox(label=t("rewrite.parse_status"), interactive=False)
rewrite_output = gr.Textbox(label=t("rewrite.full_rewritten"), lines=15, interactive=True) rewrite_output = gr.Textbox(label=t("rewrite.full_rewritten"), lines=15, interactive=True)
@@ -38,15 +54,15 @@ def build_rewrite_tab():
except Exception as e: except Exception as e:
return f"{str(e)}" return f"{str(e)}"
def on_rewrite(text, style): def on_rewrite(text, genre, style, use_reflection):
yield "⏳ Đang gọi AI xử lý... Vui lòng chờ.", gr.update(), gr.update(interactive=False) yield "⏳ Đang gọi AI xử lý... Vui lòng chờ.", gr.update(), gr.update(interactive=False)
instructions = get_preset_templates().get(style, "") instructions = f"Thể loại: {genre}\nPhong cách thiết lập: {style}"
gen = app_state.get_generator() gen = app_state.get_generator()
content, msg = gen.rewrite_paragraph(text, instructions) content, msg = gen.rewrite_paragraph(text, instructions, use_reflection=use_reflection)
if content: if content:
yield f"{msg}", content, gr.update(interactive=True) yield f"{msg}", content, gr.update(interactive=True)
else: else:
yield f"{msg}", gr.update(), gr.update(interactive=True) yield f"{msg}", gr.update(), gr.update(interactive=True)
rewrite_file_input.change(fn=on_file_upload, inputs=[rewrite_file_input], outputs=[rewrite_input]) rewrite_file_input.change(fn=on_file_upload, inputs=[rewrite_file_input], outputs=[rewrite_input])
rewrite_btn.click(fn=on_rewrite, inputs=[rewrite_input, rewrite_style], outputs=[rewrite_status, rewrite_output, rewrite_btn]) rewrite_btn.click(fn=on_rewrite, inputs=[rewrite_input, rewrite_genre, rewrite_style, use_reflection_checkbox], outputs=[rewrite_status, rewrite_output, rewrite_btn])
+270 -100
View File
@@ -6,6 +6,7 @@ from services.api_client import get_api_client, reinit_api_client
from services.novel_generator import get_cache_size, list_generation_caches, clear_generation_cache from services.novel_generator import get_cache_size, list_generation_caches, clear_generation_cache
from services.genre_manager import GenreManager from services.genre_manager import GenreManager
from services.sub_genre_manager import SubGenreManager from services.sub_genre_manager import SubGenreManager
from services.style_manager import StyleManager
from core.state import app_state from core.state import app_state
def build_settings_tab(): def build_settings_tab():
@@ -15,15 +16,13 @@ def build_settings_tab():
with gr.Tabs(): with gr.Tabs():
# Sub-tab: Quản lý giao diện API # Sub-tab: Quản lý giao diện API
with gr.Tab(t("settings.tab_backends")): with gr.Tab(t("settings.tab_backends")):
gr.Markdown(f"#### {t('settings.backends_header')}") gr.Markdown(f"### {t('settings.backends_header')}")
backends_display = gr.Markdown("...")
def get_backends_display(): def get_backends_display():
try: try:
result = ConfigAPIManager.list_backends() result = ConfigAPIManager.list_backends()
if result["success"] and result["data"]: if result["success"] and result["data"]:
lines = [f"### 📋 {len(result['data'])} backend(s)\n"] lines = [f"#### 📋 {len(result['data'])} backend(s)\n"]
for b in result["data"]: for b in result["data"]:
enabled = "" if b.get("enabled") else "" enabled = "" if b.get("enabled") else ""
default = "" if b.get("is_default") else "" default = "" if b.get("is_default") else ""
@@ -37,52 +36,69 @@ def build_settings_tab():
except Exception as e: except Exception as e:
return f"{str(e)}" return f"{str(e)}"
gr.Markdown("---")
gr.Markdown(f"#### {t('settings.add_backend_header')}")
provider_names = [API_PROVIDERS[k]["name"] for k in API_PROVIDERS]
with gr.Row(): with gr.Row():
api_provider_dropdown = gr.Dropdown( # Cột trái: Quản lý danh sách
choices=provider_names, with gr.Column(scale=1):
label=t("settings.provider_label"), with gr.Group():
info=t("settings.provider_info"), backends_display = gr.Markdown("...")
interactive=True api_refresh_btn = gr.Button(t("settings.refresh_list"), size="sm")
)
with gr.Row(): with gr.Group():
api_name_input = gr.Textbox(label=t("settings.backend_name"), placeholder=t("settings.backend_name_placeholder")) gr.Markdown(f"#### {t('settings.test_manage_header')}")
api_type_dropdown = gr.Dropdown( with gr.Row():
choices=ConfigAPIManager.get_backend_types(), test_name_input = gr.Textbox(label=t("settings.test_backend_name"), placeholder=t("settings.test_backend_placeholder"), scale=2)
value="openai", label=t("settings.backend_type"), interactive=True api_test_btn = gr.Button(t("settings.test_btn"), variant="secondary", scale=1)
) test_result = gr.Textbox(label=t("settings.test_result"), interactive=False)
with gr.Row(): gr.Markdown("---")
api_url_input = gr.Textbox(label=t("settings.base_url"), placeholder=t("settings.base_url_placeholder")) with gr.Row():
api_key_input = gr.Textbox(label=t("settings.api_key"), placeholder=t("settings.api_key_placeholder"), type="password") delete_name_input = gr.Textbox(label=t("settings.delete_backend_name"), placeholder=t("settings.delete_backend_placeholder"), scale=2)
api_delete_btn = gr.Button(t("settings.delete_btn"), variant="stop", scale=1)
with gr.Row(): # Cột phải: Form thay đổi
api_model_input = gr.Textbox(label=t("settings.model_name"), placeholder=t("settings.model_name_placeholder")) with gr.Column(scale=2):
api_timeout_input = gr.Slider(minimum=5, maximum=600, value=120, step=5, label=t("settings.timeout")) with gr.Group():
gr.Markdown(f"#### Thay đổi cấu hình ({t('settings.add_backend_header')})")
with gr.Row(): provider_names = [API_PROVIDERS[k]["name"] for k in API_PROVIDERS]
api_save_btn = gr.Button(t("settings.add_btn"), variant="primary")
api_refresh_btn = gr.Button(t("settings.refresh_list"), size="sm")
api_status = gr.Textbox(label=t("settings.operation_result"), interactive=False) with gr.Row():
manage_backend_select = gr.Dropdown(
choices=[b['name'] for b in ConfigAPIManager.list_backends().get("data", [])],
label="Chọn cấu hình để chỉnh sửa (bỏ trống để thêm mới)",
interactive=True,
scale=4
)
manage_backend_btn = gr.Button("Chọn", variant="secondary", scale=1)
gr.Markdown("---") with gr.Row():
gr.Markdown(f"#### {t('settings.test_manage_header')}") api_provider_dropdown = gr.Dropdown(
choices=provider_names,
label=t("settings.provider_label"),
info=t("settings.provider_info"),
interactive=True
)
with gr.Row(): with gr.Row():
test_name_input = gr.Textbox(label=t("settings.test_backend_name"), placeholder=t("settings.test_backend_placeholder")) api_name_input = gr.Textbox(label=t("settings.backend_name"), placeholder=t("settings.backend_name_placeholder"))
api_test_btn = gr.Button(t("settings.test_btn"), variant="secondary") api_type_dropdown = gr.Dropdown(
choices=ConfigAPIManager.get_backend_types(),
value="openai", label=t("settings.backend_type"), interactive=True
)
with gr.Row(): with gr.Row():
delete_name_input = gr.Textbox(label=t("settings.delete_backend_name"), placeholder=t("settings.delete_backend_placeholder")) api_url_input = gr.Textbox(label=t("settings.base_url"), placeholder=t("settings.base_url_placeholder"))
api_delete_btn = gr.Button(t("settings.delete_btn"), variant="stop") api_key_input = gr.Textbox(label=t("settings.api_key"), placeholder=t("settings.api_key_placeholder"), type="text")
test_result = gr.Textbox(label=t("settings.test_result"), interactive=False) with gr.Row():
api_model_input = gr.Textbox(label=t("settings.model_name"), placeholder=t("settings.model_name_placeholder"))
api_timeout_input = gr.Slider(minimum=5, maximum=600, value=120, step=5, label=t("settings.timeout"))
with gr.Row():
api_save_btn = gr.Button(t("settings.add_btn"), variant="primary")
api_update_btn = gr.Button("Cập nhật thay đổi", variant="secondary")
api_status = gr.Textbox(label=t("settings.operation_result"), interactive=False)
def on_provider_select(provider_name): def on_provider_select(provider_name):
for key, info in API_PROVIDERS.items(): for key, info in API_PROVIDERS.items():
@@ -96,52 +112,136 @@ def build_settings_tab():
yield result["message"] yield result["message"]
def on_api_save(name, btype, url, key, model, timeout): def on_api_save(name, btype, url, key, model, timeout):
if not name.strip():
return "❌ Vui lòng nhập tên giao diện", get_backends_display(), gr.update()
result = ConfigAPIManager.add_backend(name, btype, url, key, model, int(timeout)) result = ConfigAPIManager.add_backend(name, btype, url, key, model, int(timeout))
reinit_api_client() reinit_api_client()
app_state.generator = None app_state.generator = None
return result["message"], get_backends_display() new_choices = [b['name'] for b in ConfigAPIManager.list_backends().get("data", [])]
return result["message"], get_backends_display(), gr.update(choices=new_choices)
def on_api_update(old_name, new_name, btype, url, key, model, timeout):
if not old_name:
return "❌ Vui lòng chọn một cấu hình để chỉnh sửa trước", get_backends_display(), gr.update()
if not new_name.strip():
return "❌ Vui lòng nhập tên giao diện", get_backends_display(), gr.update()
# Handle updating the existing backend with new details
# If name was changed, it requires updating that as well but the update_backend method modifies existing object's properties in place based on its previous name
result = ConfigAPIManager.update_backend(
old_name,
name=new_name.strip(),
type=btype,
base_url=url,
api_key=key,
model=model,
timeout=int(timeout)
)
reinit_api_client()
app_state.generator = None
new_choices = [b['name'] for b in ConfigAPIManager.list_backends().get("data", [])]
# if name changed, update the dropdown selected value to the new name, otherwise clear it
new_val = new_name.strip() if result["success"] else old_name
return result["message"], get_backends_display(), gr.update(choices=new_choices, value=new_val)
def on_backend_select(selected_name):
print(f"DEBUG: on_backend_select triggered with: {selected_name}")
if not selected_name:
return gr.update(value=""), gr.update(value="openai"), gr.update(value=""), gr.update(value=""), gr.update(value=""), gr.update(value=120)
backends = ConfigAPIManager.list_backends().get("data", [])
for b in backends:
if b["name"] == selected_name:
return (
gr.update(value=b.get("name", "")),
gr.update(value=b.get("type", "openai")),
gr.update(value=b.get("base_url", "")),
gr.update(value=b.get("api_key", "")),
gr.update(value=b.get("model", "")),
gr.update(value=b.get("timeout", 120))
)
return gr.update(value=""), gr.update(value="openai"), gr.update(value=""), gr.update(value=""), gr.update(value=""), gr.update(value=120)
def on_api_delete(name): def on_api_delete(name):
result = ConfigAPIManager.delete_backend(name) result = ConfigAPIManager.delete_backend(name)
reinit_api_client() reinit_api_client()
app_state.generator = None app_state.generator = None
return result["message"], get_backends_display() new_choices = [b['name'] for b in ConfigAPIManager.list_backends().get("data", [])]
return result["message"], get_backends_display(), gr.update(choices=new_choices)
api_provider_dropdown.change( api_provider_dropdown.change(
fn=on_provider_select, fn=on_provider_select,
inputs=[api_provider_dropdown], inputs=[api_provider_dropdown],
outputs=[api_url_input, api_model_input, api_name_input] outputs=[api_url_input, api_model_input, api_name_input]
) )
manage_backend_btn.click(
fn=on_backend_select,
inputs=[manage_backend_select],
outputs=[api_name_input, api_type_dropdown, api_url_input, api_key_input, api_model_input, api_timeout_input]
)
api_save_btn.click( api_save_btn.click(
fn=on_api_save, fn=on_api_save,
inputs=[api_name_input, api_type_dropdown, api_url_input, api_key_input, api_model_input, api_timeout_input], inputs=[api_name_input, api_type_dropdown, api_url_input, api_key_input, api_model_input, api_timeout_input],
outputs=[api_status, backends_display] outputs=[api_status, backends_display, manage_backend_select]
) )
api_update_btn.click(
fn=on_api_update,
inputs=[manage_backend_select, api_name_input, api_type_dropdown, api_url_input, api_key_input, api_model_input, api_timeout_input],
outputs=[api_status, backends_display, manage_backend_select]
)
api_test_btn.click(fn=on_api_test, inputs=[test_name_input], outputs=[test_result]) api_test_btn.click(fn=on_api_test, inputs=[test_name_input], outputs=[test_result])
api_delete_btn.click(fn=on_api_delete, inputs=[delete_name_input], outputs=[api_status, backends_display]) api_delete_btn.click(fn=on_api_delete, inputs=[delete_name_input], outputs=[api_status, backends_display, manage_backend_select])
api_refresh_btn.click(fn=lambda: get_backends_display(), outputs=[backends_display])
def force_refresh_backends():
choices = [b['name'] for b in ConfigAPIManager.list_backends().get("data", [])]
return get_backends_display(), gr.update(choices=choices)
api_refresh_btn.click(fn=force_refresh_backends, outputs=[backends_display, manage_backend_select])
# Sub-tab: Tham số sinh # Sub-tab: Tham số sinh
with gr.Tab(t("settings.tab_params")): with gr.Tab(t("settings.tab_params")):
gr.Markdown(f"#### {t('settings.params_header')}") with gr.Group():
gr.Markdown(f"#### {t('settings.params_header')}")
config = get_config() config = get_config()
gen_config = config.generation gen_config = config.generation
param_temperature = gr.Slider(minimum=0.1, maximum=2.0, value=gen_config.temperature, step=0.1, label=t("settings.temperature_label"), info=t("settings.temperature_info")) with gr.Row():
param_top_p = gr.Slider(minimum=0.1, maximum=1.0, value=gen_config.top_p, step=0.05, label="Top P", info=t("settings.top_p_info")) param_temperature = gr.Slider(minimum=0.1, maximum=2.0, value=gen_config.temperature, step=0.1, label=t("settings.temperature_label"), info=t("settings.temperature_info"))
param_max_tokens = gr.Slider(minimum=100, maximum=100000, value=gen_config.max_tokens, step=100, label="Max Tokens", info=t("settings.max_tokens_info")) param_top_p = gr.Slider(minimum=0.1, maximum=1.0, value=gen_config.top_p, step=0.05, label="Top P", info=t("settings.top_p_info"))
param_chapter_words = gr.Slider(minimum=500, maximum=65536, value=gen_config.chapter_target_words, step=500, label=t("settings.chapter_target_words"))
save_params_btn = gr.Button(t("settings.save_params_btn"), variant="primary") with gr.Row():
params_status = gr.Textbox(label=t("settings.save_status"), interactive=False) param_max_tokens = gr.Slider(minimum=100, maximum=100000, value=gen_config.max_tokens, step=100, label="Max Tokens", info=t("settings.max_tokens_info"))
param_chapter_words = gr.Slider(minimum=500, maximum=65536, value=gen_config.chapter_target_words, step=500, label=t("settings.chapter_target_words"))
def on_save_params(temp, top_p, max_tokens, chapter_words): with gr.Row():
param_writing_style = gr.Dropdown(choices=StyleManager.get_style_names(), value=gen_config.writing_style, label=t("settings.writing_style"), allow_custom_value=True)
param_writing_tone = gr.Dropdown(choices=t("settings.tones"), value=gen_config.writing_tone, label=t("settings.tone_label"), allow_custom_value=True)
with gr.Row():
param_char_dev = gr.Dropdown(choices=t("settings.char_dev_options"), value=gen_config.character_development, label=t("settings.char_dev_label"), allow_custom_value=True)
param_plot_complexity = gr.Dropdown(choices=t("settings.plot_complexity_options"), value=gen_config.plot_complexity, label=t("settings.plot_complexity_label"), allow_custom_value=True)
save_params_btn = gr.Button(t("settings.save_params_btn"), variant="primary")
params_status = gr.Textbox(label=t("settings.save_status"), interactive=False)
def on_save_params(temp, top_p, max_tokens, chapter_words, style, tone, chardev, plotcomp):
cfg = get_config() cfg = get_config()
success, msg = cfg.update_generation_config( success, msg = cfg.update_generation_config(
temperature=temp, top_p=top_p, temperature=temp, top_p=top_p,
max_tokens=int(max_tokens), max_tokens=int(max_tokens),
chapter_target_words=int(chapter_words) chapter_target_words=int(chapter_words),
writing_style=style,
writing_tone=tone,
character_development=chardev,
plot_complexity=plotcomp
) )
if success: if success:
app_state.generator = None app_state.generator = None
@@ -150,32 +250,33 @@ def build_settings_tab():
save_params_btn.click( save_params_btn.click(
fn=on_save_params, fn=on_save_params,
inputs=[param_temperature, param_top_p, param_max_tokens, param_chapter_words], inputs=[param_temperature, param_top_p, param_max_tokens, param_chapter_words, param_writing_style, param_writing_tone, param_char_dev, param_plot_complexity],
outputs=[params_status] outputs=[params_status]
) )
# Sub-tab: Bộ nhớ đệm # Sub-tab: Bộ nhớ đệm
with gr.Tab(t("settings.tab_cache")): with gr.Tab(t("settings.tab_cache")):
gr.Markdown(f"#### {t('settings.cache_header')}") with gr.Group():
gr.Markdown(f"#### {t('settings.cache_header')}")
cache_info_display = gr.Markdown(t("ui.no_cache")) cache_info_display = gr.Markdown(t("ui.no_cache"))
def get_cache_info(): def get_cache_info():
try: try:
api_client = get_api_client() api_client = get_api_client()
stats = api_client.get_cache_stats() stats = api_client.get_cache_stats()
gen_caches = list_generation_caches() gen_caches = list_generation_caches()
gen_size_val = get_cache_size() gen_size_val = get_cache_size()
return f"### Thống kê bộ nhớ đệm\n- **API Cache**: {stats['total_entries']}/{stats['max_size']} ({stats['usage_rate']:.1f}%)\n- **Generation Cache**: {len(gen_caches)} files ({gen_size_val / 1024:.1f} KB)" return f"### Thống kê bộ nhớ đệm\n- **API Cache**: {stats['total_entries']}/{stats['max_size']} ({stats['usage_rate']:.1f}%)\n- **Generation Cache**: {len(gen_caches)} files ({gen_size_val / 1024:.1f} KB)"
except Exception as e: except Exception as e:
return f"{str(e)}" return f"{str(e)}"
with gr.Row(): with gr.Row():
refresh_cache_btn = gr.Button(t("settings.refresh_cache"), size="sm") refresh_cache_btn = gr.Button(t("settings.refresh_cache"), size="sm")
clear_all_cache_btn = gr.Button(t("settings.clear_all"), variant="stop") clear_all_cache_btn = gr.Button(t("settings.clear_all"), variant="stop")
cache_op_status = gr.Textbox(label=t("settings.cache_op_status"), interactive=False) cache_op_status = gr.Textbox(label=t("settings.cache_op_status"), interactive=False)
def on_clear_all_cache(): def on_clear_all_cache():
try: try:
@@ -191,22 +292,26 @@ def build_settings_tab():
# Sub-tab: Quản lý thể loại # Sub-tab: Quản lý thể loại
with gr.Tab(t("settings.tab_genre")): with gr.Tab(t("settings.tab_genre")):
gr.Markdown(f"#### {t('settings.genre_desc')}")
genre_select = gr.Dropdown(
choices=GenreManager.get_genre_names(),
label=t("settings.genre_select"),
interactive=True
)
genre_name_input = gr.Textbox(label=t("settings.genre_name"), placeholder=t("settings.genre_name_placeholder"))
genre_desc_input = gr.Textbox(label=t("settings.genre_description"), placeholder=t("settings.genre_description_placeholder"), lines=3)
with gr.Row(): with gr.Row():
genre_add_btn = gr.Button(t("settings.genre_add_btn"), variant="primary") with gr.Column(scale=1):
genre_update_btn = gr.Button(t("settings.genre_update_btn"), variant="secondary") gr.Markdown(f"#### {t('settings.genre_desc')}")
genre_delete_btn = gr.Button(t("settings.genre_delete_btn"), variant="stop") genre_select = gr.Dropdown(
choices=GenreManager.get_genre_names(),
label=t("settings.genre_select"),
interactive=True
)
genre_op_status = gr.Textbox(label=t("settings.genre_op_status"), interactive=False) with gr.Column(scale=2):
with gr.Group():
genre_name_input = gr.Textbox(label=t("settings.genre_name"), placeholder=t("settings.genre_name_placeholder"))
genre_desc_input = gr.Textbox(label=t("settings.genre_description"), placeholder=t("settings.genre_description_placeholder"), lines=3)
with gr.Row():
genre_add_btn = gr.Button(t("settings.genre_add_btn"), variant="primary")
genre_update_btn = gr.Button(t("settings.genre_update_btn"), variant="secondary")
genre_delete_btn = gr.Button(t("settings.genre_delete_btn"), variant="stop")
genre_op_status = gr.Textbox(label=t("settings.genre_op_status"), interactive=False)
def on_genre_select(name): def on_genre_select(name):
if name: if name:
@@ -245,22 +350,26 @@ def build_settings_tab():
# Sub-tab: Quản lý chủ đề con # Sub-tab: Quản lý chủ đề con
with gr.Tab(t("settings.tab_sub_genre")): with gr.Tab(t("settings.tab_sub_genre")):
gr.Markdown(f"#### {t('settings.sub_genre_desc')}")
sub_genre_select = gr.Dropdown(
choices=SubGenreManager.get_sub_genre_names(),
label=t("settings.sub_genre_select"),
interactive=True
)
sub_genre_name_input = gr.Textbox(label=t("settings.sub_genre_name"), placeholder=t("settings.sub_genre_name_placeholder"))
sub_genre_desc_input = gr.Textbox(label=t("settings.sub_genre_description"), placeholder=t("settings.sub_genre_description_placeholder"), lines=3)
with gr.Row(): with gr.Row():
sub_genre_add_btn = gr.Button(t("settings.sub_genre_add_btn"), variant="primary") with gr.Column(scale=1):
sub_genre_update_btn = gr.Button(t("settings.sub_genre_update_btn"), variant="secondary") gr.Markdown(f"#### {t('settings.sub_genre_desc')}")
sub_genre_delete_btn = gr.Button(t("settings.sub_genre_delete_btn"), variant="stop") sub_genre_select = gr.Dropdown(
choices=SubGenreManager.get_sub_genre_names(),
label=t("settings.sub_genre_select"),
interactive=True
)
sub_genre_op_status = gr.Textbox(label=t("settings.sub_genre_op_status"), interactive=False) with gr.Column(scale=2):
with gr.Group():
sub_genre_name_input = gr.Textbox(label=t("settings.sub_genre_name"), placeholder=t("settings.sub_genre_name_placeholder"))
sub_genre_desc_input = gr.Textbox(label=t("settings.sub_genre_description"), placeholder=t("settings.sub_genre_description_placeholder"), lines=3)
with gr.Row():
sub_genre_add_btn = gr.Button(t("settings.sub_genre_add_btn"), variant="primary")
sub_genre_update_btn = gr.Button(t("settings.sub_genre_update_btn"), variant="secondary")
sub_genre_delete_btn = gr.Button(t("settings.sub_genre_delete_btn"), variant="stop")
sub_genre_op_status = gr.Textbox(label=t("settings.sub_genre_op_status"), interactive=False)
def on_sub_genre_select(name): def on_sub_genre_select(name):
if name: if name:
@@ -296,3 +405,64 @@ def build_settings_tab():
sub_genre_add_btn.click(fn=on_sub_genre_add, inputs=[sub_genre_name_input, sub_genre_desc_input], outputs=[sub_genre_op_status, sub_genre_select]) sub_genre_add_btn.click(fn=on_sub_genre_add, inputs=[sub_genre_name_input, sub_genre_desc_input], outputs=[sub_genre_op_status, sub_genre_select])
sub_genre_update_btn.click(fn=on_sub_genre_update, inputs=[sub_genre_select, sub_genre_name_input, sub_genre_desc_input], outputs=[sub_genre_op_status, sub_genre_select]) sub_genre_update_btn.click(fn=on_sub_genre_update, inputs=[sub_genre_select, sub_genre_name_input, sub_genre_desc_input], outputs=[sub_genre_op_status, sub_genre_select])
sub_genre_delete_btn.click(fn=on_sub_genre_delete, inputs=[sub_genre_select], outputs=[sub_genre_op_status, sub_genre_select]) sub_genre_delete_btn.click(fn=on_sub_genre_delete, inputs=[sub_genre_select], outputs=[sub_genre_op_status, sub_genre_select])
# Sub-tab: Quản lý phong cách viết
with gr.Tab("Quản lý phong cách viết"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("#### Quản lý danh sách các phong cách viết để AI hiểu và áp dụng")
style_select = gr.Dropdown(
choices=StyleManager.get_style_names(),
label="Chọn phong cách",
interactive=True
)
with gr.Column(scale=2):
with gr.Group():
style_name_input = gr.Textbox(label="Tên phong cách", placeholder="Ví dụ: Mượt mà tự nhiên...")
style_desc_input = gr.Textbox(label="Mô tả phong cách", placeholder="Hướng dẫn chi tiết cách viết...", lines=3)
with gr.Row():
style_add_btn = gr.Button("Thêm phong cách", variant="primary")
style_update_btn = gr.Button("Cập nhật", variant="secondary")
style_delete_btn = gr.Button("Xóa", variant="stop")
style_op_status = gr.Textbox(label="Trạng thái thao tác", interactive=False)
def on_style_select(name):
if name:
desc = StyleManager.get_style_description(name)
return name, desc or ""
return "", ""
def on_style_add(name, desc):
if not name.strip():
return "❌ Tên phong cách không được trống", gr.update()
success = StyleManager.add_style(name.strip(), desc.strip())
if success:
new_choices = StyleManager.get_style_names()
return "✅ Đã thêm phong cách mới", gr.update(choices=new_choices)
return "❌ Tên phong cách đã tồn tại hoặc lỗi", gr.update()
def on_style_update(old_name, new_name, desc):
if not old_name:
return "❌ Vui lòng chọn phong cách", gr.update()
success = StyleManager.update_style(old_name, new_name.strip(), desc.strip())
if success:
new_choices = StyleManager.get_style_names()
return "✅ Đã cập nhật phong cách", gr.update(choices=new_choices)
return "❌ Cập nhật thất bại", gr.update()
def on_style_delete(name):
if not name:
return "❌ Vui lòng chọn phong cách", gr.update()
success = StyleManager.delete_style(name)
if success:
new_choices = StyleManager.get_style_names()
return "✅ Đã xóa phong cách", gr.update(choices=new_choices)
return "❌ Xóa thất bại", gr.update()
style_select.change(fn=on_style_select, inputs=[style_select], outputs=[style_name_input, style_desc_input])
style_add_btn.click(fn=on_style_add, inputs=[style_name_input, style_desc_input], outputs=[style_op_status, style_select])
style_update_btn.click(fn=on_style_update, inputs=[style_select, style_name_input, style_desc_input], outputs=[style_op_status, style_select])
style_delete_btn.click(fn=on_style_delete, inputs=[style_select], outputs=[style_op_status, style_select])