2 次代码提交 d28cdfccf6 ... 21b71d9d26

作者 SHA1 备注 提交日期
  祝雨婧 21b71d9d26 update: merch 3 月之前
  祝雨婧 b203b8d0dc add: toolbox 3 月之前

+ 8 - 4
novel-app/src/app/app.routes.ts

@@ -53,12 +53,16 @@ export const routes: Routes = [
     loadComponent: () => import('./tab1/tab1.page').then(m => m.Tab1Page)
   },
   {
-    path: 'agent-create',
-    loadComponent: () => import('./agent-create/agent-create.page').then( m => m.AgentCreatePage)
+    path: 'world-setup',
+    loadComponent: () => import('./world-setup/world-setup.page').then(m => m.WorldSetupPage)
   },
   {
-    path: 'atest',
-    loadComponent: () => import('./atest/atest.page').then( m => m.AtestPage)
+    path: 'character-generator',
+    loadComponent: () => import('./character-generator/character-generator.page').then(m => m.CharacterGeneratorPage)
+  },
+  {
+    path: 'name-generator',
+    loadComponent: () => import('./name-generator/name-generator.page').then(m => m.NameGeneratorPage)
   }
 
 

+ 1 - 2
novel-app/src/app/chapter-generator/chapter-generator.page.html

@@ -12,9 +12,8 @@
 
     <!-- 保存作品按钮 -->
     <ion-buttons slot="end">
-      <ion-button (click)="saveWork()" color="primary">保存作品</ion-button>
+      <ion-button color="primary">保存作品</ion-button>
     </ion-buttons>
-
   </ion-toolbar>
 </ion-header>
 

+ 8 - 55
novel-app/src/app/chapter-generator/chapter-generator.page.ts

@@ -1,7 +1,7 @@
 // chapter-generator.page.ts
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
 import { FormsModule } from '@angular/forms'; // 导入 FormsModule
-import { IonRouterOutlet, NavController } from '@ionic/angular/standalone';
+import { IonRouterOutlet } from '@ionic/angular/standalone';
 import { addIcons } from 'ionicons';
 import { chevronForward } from 'ionicons/icons';
 import { IonicModule, ModalController } from '@ionic/angular';
@@ -22,31 +22,17 @@ addIcons({ chevronForward });
     IonRouterOutlet, CommonModule, IonicModule, AiExpandModalComponent, AiPolishModalComponent, AiContinueModalComponent
   ]
 })
-export class ChapterGeneratorPage implements OnInit {
-  chapters: { title: string, content: string }[] = [];
+export class ChapterGeneratorPage {
+  chapters = [
+    { title: 'Chapter 1', content: '这是第一章的内容。' },
+    // 其他章节...
+  ];
   isSideShow: boolean = true;
   selectedChapterIndex: number | null = null;
   selectedChapterTitle: string = '';
   selectedChapterContent: string = '';
-  novelTitle: string = '';
-  workId: string | null = null;
 
-  constructor(
-    private modalCtrl: ModalController,
-    private navCtrl: NavController
-  ) { }
-
-  ngOnInit() {
-    // 从本地存储获取当前作品信息
-    const currentWork = localStorage.getItem('currentWork');
-    if (currentWork) {
-      const work = JSON.parse(currentWork);
-      this.workId = work.id;
-      this.novelTitle = work.title;
-      this.chapters = work.chapters;
-      this.selectChapter(0); // 默认选择第一个章节
-    }
-  }
+  constructor(private modalCtrl: ModalController) { }
 
   async openAiExpandModal() {
     const modal = await this.modalCtrl.create({
@@ -126,37 +112,4 @@ export class ChapterGeneratorPage implements OnInit {
       this.selectedChapterContent += text; // 更新显示的内容
     }
   }
-
-  saveWork() {
-    if (!this.workId) {
-      // 如果没有 workId,则创建一个新的作品
-      this.workId = Date.now().toString(); // 使用当前时间戳作为唯一ID
-    }
-
-    const work = {
-      id: this.workId,
-      cover: 'default-cover.jpg', // 默认封面,可以根据需要修改
-      title: this.novelTitle,
-      createTime: new Date(),
-      type: '长篇小说',
-      chapters: this.chapters,
-    };
-
-    // 从本地存储获取所有作品信息
-    const existingWorks = JSON.parse(localStorage.getItem('works') || '[]');
-
-    // 找到并更新对应的作品信息
-    const workIndex = existingWorks.findIndex((w: { id: string | null; }) => w.id === this.workId);
-    if (workIndex !== -1) {
-      existingWorks[workIndex] = work;
-    } else {
-      existingWorks.push(work);
-    }
-
-    // 将更新后的工作信息存储在本地存储
-    localStorage.setItem('works', JSON.stringify(existingWorks));
-
-    // 跳转到home页面
-    this.navCtrl.navigateRoot(['/']);
-  }
 }

+ 42 - 0
novel-app/src/app/character-generator/character-generator.page.html

@@ -0,0 +1,42 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/toolbox"></ion-back-button>
+    </ion-buttons>
+    <ion-title>人物设定生成</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true" class="ion-padding">
+
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>小说类型</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-input [(ngModel)]="type" (ionInput)="typeInput($event)" placeholder="请输入小说类型"></ion-input>
+    </ion-card-content>
+  </ion-card>
+
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>背景</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-textarea [value]="userPrompt" (ionChange)="promptInput($event)" placeholder="请输入小说背景"></ion-textarea>
+    </ion-card-content>
+  </ion-card>
+
+  <ion-button expand="block" (click)="sendMessage()">生成人物设定</ion-button>
+
+  <ion-card *ngIf="responseMsg">
+    <ion-card-header>
+      <ion-card-title>生成的人物设定</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-textarea [value]="responseMsg" readonly class="response-textarea"></ion-textarea>
+      <ion-button expand="block" (click)="copyToClipboard()">复制</ion-button>
+    </ion-card-content>
+  </ion-card>
+
+</ion-content>

+ 82 - 0
novel-app/src/app/character-generator/character-generator.page.scss

@@ -0,0 +1,82 @@
+ion-card {
+    margin-bottom: 16px;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+    border-radius: 8px;
+}
+
+ion-card-header {
+    background-color: #f8f9fa;
+    border-bottom: 1px solid #dee2e6;
+}
+
+ion-card-title {
+    font-size: 1.25rem;
+    font-weight: bold;
+    color: #343a40;
+}
+
+ion-card-content {
+    padding: 16px;
+}
+
+ion-button {
+    --background: #007bff;
+    --color: #fff;
+    --border-radius: 8px;
+    --padding-top: 12px;
+    --padding-bottom: 12px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    margin-top: 16px;
+}
+
+ion-button:hover {
+    --background: #0056b3;
+}
+
+ion-textarea {
+    --background: #ffffff;
+    --color: #343a40;
+    --border-color: #ced4da;
+    --border-radius: 8px;
+    --padding-top: 12px;
+    --padding-bottom: 12px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    border: 1px solid #ced4da;
+    border-radius: 8px;
+    --min-height: 100px;
+}
+
+ion-textarea:focus {
+    --border-color: #80bdff;
+    --box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+ion-input {
+    --background: #ffffff;
+    --color: #343a40;
+    --border-color: #ced4da;
+    --border-radius: 8px;
+    --padding-top: 12px;
+    --padding-bottom: 12px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    border: 1px solid #ced4da;
+    border-radius: 8px;
+}
+
+ion-input:focus {
+    --border-color: #80bdff;
+    --box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+/* 设置生成的人物设定文本框的高度 */
+.response-textarea {
+    --min-height: 200px;
+    /* 设置更高的最小高度 */
+    border-color: #80bdff;
+    /* 可选:设置不同的边框颜色以区分 */
+    background-color: #f8f9fa;
+    /* 可选:设置不同的背景颜色以区分 */
+}

+ 17 - 0
novel-app/src/app/character-generator/character-generator.page.spec.ts

@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { CharacterGeneratorPage } from './character-generator.page';
+
+describe('CharacterGeneratorPage', () => {
+  let component: CharacterGeneratorPage;
+  let fixture: ComponentFixture<CharacterGeneratorPage>;
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CharacterGeneratorPage);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 76 - 0
novel-app/src/app/character-generator/character-generator.page.ts

@@ -0,0 +1,76 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput, IonBackButton, IonButtons, IonCardContent, IonCardTitle, IonCardHeader, IonCard } from '@ionic/angular/standalone';
+import { FmodeChatCompletion, MarkdownPreviewModule } from 'fmode-ng';
+
+@Component({
+  selector: 'app-character-generator',
+  templateUrl: './character-generator.page.html',
+  styleUrls: ['./character-generator.page.scss'],
+  standalone: true,
+  imports: [
+    CommonModule, // 确保 CommonModule 已经导入
+    FormsModule,
+    IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput, IonBackButton, IonButtons,
+    IonCardContent, IonCardTitle, IonCardHeader, IonCard,
+    // 引入fm-markdown-preview组件模块
+    MarkdownPreviewModule
+  ],
+})
+export class CharacterGeneratorPage implements OnInit {
+
+  constructor() { }
+  ngOnInit() { }
+
+  // 用户输入提示词
+  type: string = "东方玄幻";
+  typeInput(ev: any) {
+    this.type = ev.detail.value;
+  }
+
+  // 用户输入提示词
+  userPrompt: string = "古代";
+  promptInput(ev: any) {
+    this.userPrompt = ev.detail.value;
+  }
+
+  // 属性:组件内用于展示消息内容的变量
+  responseMsg: any = "";
+
+  // 方法:实例化completion对象,传入消息数组,并订阅生成的可观察对象。
+  isComplete: boolean = false; // 定义完成状态属性,用来标记是否补全完成
+  sendMessage() {
+    console.log("create");
+
+    let PromptTemplate = `
+    您作为一名专业的${this.type}小说作者,请您根据用户提供的小说背景,给出多个人物设定。
+    以下是用户的口述:${this.userPrompt}
+    `;
+
+    let completion = new FmodeChatCompletion([
+      { role: "system", content: "" },
+      { role: "user", content: PromptTemplate }
+    ]);
+    completion.sendCompletion().subscribe((message: any) => {
+      // 打印消息体
+      console.log(message.content);
+      // 赋值消息内容给组件内属性
+      this.responseMsg = message.content;
+      if (message?.complete) { // 判断message为完成状态,则设置isComplete为完成
+        this.isComplete = true;
+      }
+    });
+  }
+
+  // 复制到剪贴板
+  copyToClipboard() {
+    const textarea = document.createElement('textarea');
+    textarea.value = this.responseMsg;
+    document.body.appendChild(textarea);
+    textarea.select();
+    document.execCommand('copy');
+    document.body.removeChild(textarea);
+    alert('内容已复制到剪贴板');
+  }
+}

+ 13 - 22
novel-app/src/app/home/home.page.html

@@ -57,28 +57,19 @@
 
 
       <ion-card-content>
-        <ion-list *ngIf="stories.length > 0">
-          <ion-item *ngFor="let story of stories; let i = index">
-            <ion-label>
-              <h2>{{story.title}}</h2>
-              <p>{{story.type}} | {{formatDate(story.createTime)}} | {{story.wordCount}}字</p>
-            </ion-label>
-            <ion-row>
-              <ion-col size="6">
-                <ion-button fill="clear" size="small" (click)="editWork(story.id)">
-                  <ion-icon slot="start" name="create"></ion-icon>
-                  编辑
-                </ion-button>
-              </ion-col>
-              <ion-col size="6">
-                <ion-button fill="clear" size="small" (click)="deleteWork(i, story.id)">
-                  <ion-icon slot="start" name="trash"></ion-icon>
-                  删除
-                </ion-button>
-              </ion-col>
-            </ion-row>
-          </ion-item>
-        </ion-list>
+        <ion-grid *ngIf="stories.length > 0">
+          <ion-row>
+            <ion-col size="6" *ngFor="let story of stories">
+              <div class="story-card">
+                <ion-img [src]="story.cover" alt="story cover"></ion-img>
+                <div class="story-info">
+                  <h3>{{story.title}}</h3>
+                  <p>{{formatDate(story.createTime)}}</p>
+                </div>
+              </div>
+            </ion-col>
+          </ion-row>
+        </ion-grid>
 
         <div class="empty-state" *ngIf="stories.length === 0">
           <ion-icon name="document-text-outline"></ion-icon>

+ 4 - 54
novel-app/src/app/home/home.page.ts

@@ -1,18 +1,13 @@
-// home.page.ts
-import { Component, OnInit } from '@angular/core';
+import { Component, ViewChild } from '@angular/core';
 import { CommonModule } from '@angular/common';
-import { IonicModule, PopoverController, AlertController } from '@ionic/angular'; // 确保导入 AlertController
+import { IonicModule, PopoverController } from '@ionic/angular';
 import { Router } from '@angular/router';
-import { NavController } from '@ionic/angular';
 
 interface Story {
   id: string;
   cover: string;
   title: string;
   createTime: Date;
-  wordCount: number;
-  type: 'long' | 'short';
-  chapters: { title: string, content: string }[];
 }
 
 @Component({
@@ -22,27 +17,14 @@ interface Story {
   standalone: true,
   imports: [IonicModule, CommonModule]
 })
-export class HomePage implements OnInit {
+export class HomePage {
   stories: Story[] = [];
 
   constructor(
     private router: Router,
-    private popoverController: PopoverController,
-    private alertController: AlertController, // 确保在这里注入 AlertController
-    private navCtrl: NavController
+    private popoverController: PopoverController
   ) { }
 
-  ngOnInit() {
-    // 从本地存储获取作品信息
-    const savedWorks = localStorage.getItem('works');
-    if (savedWorks) {
-      this.stories = JSON.parse(savedWorks).map((story: { createTime: string | number | Date; chapters: { title: string, content: string }[] }) => ({
-        ...story,
-        createTime: new Date(story.createTime)
-      }));
-    }
-  }
-
   navigateTo(path: string) {
     this.router.navigate([path]);
   }
@@ -72,38 +54,6 @@ export class HomePage implements OnInit {
     this.router.navigate([targetPath]);
   }
 
-  editWork(workId: string) {
-    // 找到对应的作品并存储在本地存储
-    const work = this.stories.find(story => story.id === workId);
-    if (work) {
-      // 将作品信息存储在本地存储,以便在 chapter-generator 页面加载时使用
-      localStorage.setItem('currentWork', JSON.stringify(work));
-      this.navCtrl.navigateRoot(['/chapter-generator']);
-    }
-  }
-
-  async deleteWork(index: number, workId: string) {
-    const alert = await this.alertController.create({
-      header: '确认删除?',
-      message: '确定要删除该作品吗?',
-      buttons: [
-        {
-          text: '取消',
-          role: 'cancel'
-        },
-        {
-          text: '删除',
-          handler: () => {
-            this.stories.splice(index, 1);
-            localStorage.setItem('works', JSON.stringify(this.stories));
-          }
-        }
-      ]
-    });
-
-    await alert.present();
-  }
-
   formatDate(date: Date): string {
     return date.toLocaleDateString('zh-CN');
   }

+ 42 - 0
novel-app/src/app/name-generator/name-generator.page.html

@@ -0,0 +1,42 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/toolbox"></ion-back-button>
+    </ion-buttons>
+    <ion-title>小说书名生成</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true" class="ion-padding">
+
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>小说类型</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-input [(ngModel)]="type" (ionInput)="typeInput($event)" placeholder="请输入小说类型"></ion-input>
+    </ion-card-content>
+  </ion-card>
+
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>故事梗概</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-textarea [value]="userPrompt" (ionChange)="promptInput($event)" placeholder="请输入小说故事梗概"></ion-textarea>
+    </ion-card-content>
+  </ion-card>
+
+  <ion-button expand="block" (click)="sendMessage()">生成小说书名</ion-button>
+
+  <ion-card *ngIf="responseMsg">
+    <ion-card-header>
+      <ion-card-title>生成的小说书名</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-textarea [value]="responseMsg" readonly class="response-textarea"></ion-textarea>
+      <ion-button expand="block" (click)="copyToClipboard()">复制</ion-button>
+    </ion-card-content>
+  </ion-card>
+
+</ion-content>

+ 82 - 0
novel-app/src/app/name-generator/name-generator.page.scss

@@ -0,0 +1,82 @@
+ion-card {
+    margin-bottom: 16px;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+    border-radius: 8px;
+}
+
+ion-card-header {
+    background-color: #f8f9fa;
+    border-bottom: 1px solid #dee2e6;
+}
+
+ion-card-title {
+    font-size: 1.25rem;
+    font-weight: bold;
+    color: #343a40;
+}
+
+ion-card-content {
+    padding: 30px;
+}
+
+ion-button {
+    --background: #007bff;
+    --color: #fff;
+    --border-radius: 8px;
+    --padding-top: 12px;
+    --padding-bottom: 12px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    margin-top: 16px;
+}
+
+ion-button:hover {
+    --background: #0056b3;
+}
+
+ion-textarea {
+    --background: #ffffff;
+    --color: #343a40;
+    --border-color: #ced4da;
+    --border-radius: 8px;
+    --padding-top: 12px;
+    --padding-bottom: 12px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    border: 1px solid #ced4da;
+    border-radius: 8px;
+    --min-height: 100px;
+}
+
+ion-textarea:focus {
+    --border-color: #80bdff;
+    --box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+ion-input {
+    --background: #ffffff;
+    --color: #343a40;
+    --border-color: #ced4da;
+    --border-radius: 8px;
+    --padding-top: 12px;
+    --padding-bottom: 12px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    border: 1px solid #ced4da;
+    border-radius: 8px;
+}
+
+ion-input:focus {
+    --border-color: #80bdff;
+    --box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+/* 设置生成的人物设定文本框的高度 */
+.response-textarea {
+    --min-height: 200px;
+    /* 设置更高的最小高度 */
+    border-color: #80bdff;
+    /* 可选:设置不同的边框颜色以区分 */
+    background-color: #f8f9fa;
+    /* 可选:设置不同的背景颜色以区分 */
+}

+ 17 - 0
novel-app/src/app/name-generator/name-generator.page.spec.ts

@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { NameGeneratorPage } from './name-generator.page';
+
+describe('NameGeneratorPage', () => {
+  let component: NameGeneratorPage;
+  let fixture: ComponentFixture<NameGeneratorPage>;
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(NameGeneratorPage);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 76 - 0
novel-app/src/app/name-generator/name-generator.page.ts

@@ -0,0 +1,76 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput, IonBackButton, IonButtons, IonCardContent, IonCardTitle, IonCardHeader, IonCard } from '@ionic/angular/standalone';
+import { FmodeChatCompletion, MarkdownPreviewModule } from 'fmode-ng';
+
+@Component({
+  selector: 'app-name-generator',
+  templateUrl: './name-generator.page.html',
+  styleUrls: ['./name-generator.page.scss'],
+  standalone: true,
+  imports: [
+    CommonModule, // 确保 CommonModule 已经导入
+    FormsModule,
+    IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput, IonBackButton, IonButtons,
+    IonCardContent, IonCardTitle, IonCardHeader, IonCard,
+    // 引入fm-markdown-preview组件模块
+    MarkdownPreviewModule
+  ],
+})
+export class NameGeneratorPage implements OnInit {
+  constructor() { }
+  ngOnInit() { }
+
+  // 用户输入提示词
+  type: string = "东方玄幻";
+  typeInput(ev: any) {
+    this.type = ev.detail.value;
+  }
+
+  // 用户输入提示词
+  userPrompt: string = "年轻女子意外获时光之匙,穿梭古今阻止历史篡改,与时空守护者共斗邪恶势力,守护时间线安全。";
+  promptInput(ev: any) {
+    this.userPrompt = ev.detail.value;
+  }
+
+  // 属性:组件内用于展示消息内容的变量
+  responseMsg: any = "";
+
+  // 方法:实例化completion对象,传入消息数组,并订阅生成的可观察对象。
+  isComplete: boolean = false; // 定义完成状态属性,用来标记是否补全完成
+  sendMessage() {
+    console.log("create");
+
+    let PromptTemplate = `
+    您作为一名专业的${this.type}小说作者,请您根据用户提供的小说故事梗概,给出多个小说书名。
+    以下是用户的口述:${this.userPrompt}
+    `;
+
+    let completion = new FmodeChatCompletion([
+      { role: "system", content: "" },
+      { role: "user", content: PromptTemplate }
+    ]);
+    completion.sendCompletion().subscribe((message: any) => {
+      // 打印消息体
+      console.log(message.content);
+      // 赋值消息内容给组件内属性
+      this.responseMsg = message.content;
+      if (message?.complete) { // 判断message为完成状态,则设置isComplete为完成
+        this.isComplete = true;
+      }
+    });
+  }
+
+  // 复制到剪贴板
+  copyToClipboard() {
+    const textarea = document.createElement('textarea');
+    textarea.value = this.responseMsg;
+    document.body.appendChild(textarea);
+    textarea.select();
+    document.execCommand('copy');
+    document.body.removeChild(textarea);
+    alert('内容已复制到剪贴板');
+  }
+
+}

+ 8 - 10
novel-app/src/app/story-generator/story-generator.page.ts

@@ -1,4 +1,3 @@
-// story-generator.page.ts
 import { Component } from '@angular/core';
 import { IonicModule } from '@ionic/angular';
 import { FormsModule } from '@angular/forms';
@@ -14,16 +13,15 @@ import { Router } from '@angular/router';
 export class StoryGeneratorPage {
   title: string = '';
   description: string = '';
-
-  constructor(private router: Router) { }
+  constructor(
+    private router: Router,
+  ) { }
 
   nextStep() {
-    if (this.title.trim() === '') {
-      console.log('作品名称是必填项');
-      return;
-    }
-
-    // 导航到 chapter-generator 页面并传递作品名称
-    this.router.navigate(['/chapter-generator'], { queryParams: { title: this.title } });
+    // 处理下一步逻辑
+    console.log('Title:', this.title);
+    console.log('Description:', this.description);
+    // 导航到 chapter-generator 页面
+    this.router.navigate(['/chapter-generator']);
   }
 }

+ 36 - 8
novel-app/src/app/toolbox/toolbox.page.html

@@ -1,13 +1,41 @@
-<ion-header [translucent]="true">
+<ion-header>
   <ion-toolbar>
-    <ion-title>toolbox</ion-title>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/"></ion-back-button>
+    </ion-buttons>
+    <ion-title>工具箱</ion-title>
   </ion-toolbar>
 </ion-header>
 
 <ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">toolbox</ion-title>
-    </ion-toolbar>
-  </ion-header>
-</ion-content>
+
+  <!-- 小说书名生成按钮 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>小说书名生成</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-button (click)="navigateTo('/name-generator')">生成书名</ion-button>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 人物设定生成按钮 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>人物设定生成</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-button (click)="navigateTo('/character-generator')">生成角色</ion-button>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 世界架构设定按钮 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>世界架构设定</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-button (click)="navigateTo('/world-setup')">生成世界架构</ion-button>
+    </ion-card-content>
+  </ion-card>
+</ion-content>

+ 53 - 0
novel-app/src/app/toolbox/toolbox.page.scss

@@ -0,0 +1,53 @@
+/* 全局样式 */
+ion-content {
+    --background: #f7f7f7;
+    padding: 16px;
+}
+
+/* 卡片样式 */
+ion-card {
+    margin-bottom: 16px;
+    border-radius: 8px;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* 卡片标题样式 */
+ion-card-title {
+    color: #333;
+    font-size: 1.25rem;
+    font-weight: bold;
+}
+
+/* 卡片内容样式 */
+ion-card-content {
+    padding: 16px;
+}
+
+/* 按钮样式 */
+ion-button {
+    --background: #007bff;
+    --color: #fff;
+    --border-radius: 4px;
+    --padding-top: 10px;
+    --padding-bottom: 10px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    font-size: 1rem;
+    font-weight: 500;
+}
+
+ion-button:hover {
+    --background: #0056b3;
+}
+
+/* 工具栏样式 */
+ion-toolbar {
+    --background: #ffffff;
+    --color: #333;
+    border-bottom: 1px solid #e0e0e0;
+}
+
+/* 返回按钮样式 */
+ion-back-button {
+    --color: #007bff;
+}

+ 9 - 9
novel-app/src/app/toolbox/toolbox.page.ts

@@ -1,20 +1,20 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';
+import { IonBackButton, IonButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';
 
 @Component({
   selector: 'app-toolbox',
   templateUrl: './toolbox.page.html',
   styleUrls: ['./toolbox.page.scss'],
   standalone: true,
-  imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule]
+  imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule, IonButton, IonCardContent, IonCardTitle, IonCardHeader, IonCard, IonBackButton, IonButtons]
 })
-export class ToolboxPage implements OnInit {
+export class ToolboxPage {
+  constructor(private router: Router) { }
 
-  constructor() { }
-
-  ngOnInit() {
+  navigateTo(path: string) {
+    this.router.navigate([path]);
   }
-
-}
+}

+ 42 - 0
novel-app/src/app/world-setup/world-setup.page.html

@@ -0,0 +1,42 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/toolbox"></ion-back-button>
+    </ion-buttons>
+    <ion-title>人物设定生成</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true" class="ion-padding">
+
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>小说类型</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-input [(ngModel)]="type" (ionInput)="typeInput($event)" placeholder="请输入小说类型"></ion-input>
+    </ion-card-content>
+  </ion-card>
+
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>期望描述</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-textarea [value]="userPrompt" (ionChange)="promptInput($event)" placeholder="请输入小说背景"></ion-textarea>
+    </ion-card-content>
+  </ion-card>
+
+  <ion-button expand="block" (click)="sendMessage()">生成世界架构</ion-button>
+
+  <ion-card *ngIf="responseMsg">
+    <ion-card-header>
+      <ion-card-title>生成的世界架构</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-textarea [value]="responseMsg" readonly class="response-textarea"></ion-textarea>
+      <ion-button expand="block" (click)="copyToClipboard()">复制</ion-button>
+    </ion-card-content>
+  </ion-card>
+
+</ion-content>

+ 82 - 0
novel-app/src/app/world-setup/world-setup.page.scss

@@ -0,0 +1,82 @@
+ion-card {
+    margin-bottom: 16px;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+    border-radius: 8px;
+}
+
+ion-card-header {
+    background-color: #f8f9fa;
+    border-bottom: 1px solid #dee2e6;
+}
+
+ion-card-title {
+    font-size: 1.25rem;
+    font-weight: bold;
+    color: #343a40;
+}
+
+ion-card-content {
+    padding: 30px;
+}
+
+ion-button {
+    --background: #007bff;
+    --color: #fff;
+    --border-radius: 8px;
+    --padding-top: 12px;
+    --padding-bottom: 12px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    margin-top: 16px;
+}
+
+ion-button:hover {
+    --background: #0056b3;
+}
+
+ion-textarea {
+    --background: #ffffff;
+    --color: #343a40;
+    --border-color: #ced4da;
+    --border-radius: 8px;
+    --padding-top: 12px;
+    --padding-bottom: 12px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    border: 1px solid #ced4da;
+    border-radius: 8px;
+    --min-height: 100px;
+}
+
+ion-textarea:focus {
+    --border-color: #80bdff;
+    --box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+ion-input {
+    --background: #ffffff;
+    --color: #343a40;
+    --border-color: #ced4da;
+    --border-radius: 8px;
+    --padding-top: 12px;
+    --padding-bottom: 12px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    border: 1px solid #ced4da;
+    border-radius: 8px;
+}
+
+ion-input:focus {
+    --border-color: #80bdff;
+    --box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+/* 设置生成的人物设定文本框的高度 */
+.response-textarea {
+    --min-height: 200px;
+    /* 设置更高的最小高度 */
+    border-color: #80bdff;
+    /* 可选:设置不同的边框颜色以区分 */
+    background-color: #f8f9fa;
+    /* 可选:设置不同的背景颜色以区分 */
+}

+ 17 - 0
novel-app/src/app/world-setup/world-setup.page.spec.ts

@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { WorldSetupPage } from './world-setup.page';
+
+describe('WorldSetupPage', () => {
+  let component: WorldSetupPage;
+  let fixture: ComponentFixture<WorldSetupPage>;
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(WorldSetupPage);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 76 - 0
novel-app/src/app/world-setup/world-setup.page.ts

@@ -0,0 +1,76 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput, IonBackButton, IonButtons, IonCardContent, IonCardTitle, IonCardHeader, IonCard } from '@ionic/angular/standalone';
+import { FmodeChatCompletion, MarkdownPreviewModule } from 'fmode-ng';
+
+@Component({
+  selector: 'app-world-setup',
+  templateUrl: './world-setup.page.html',
+  styleUrls: ['./world-setup.page.scss'],
+  standalone: true,
+  imports: [
+    CommonModule, // 确保 CommonModule 已经导入
+    FormsModule,
+    IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput, IonBackButton, IonButtons,
+    IonCardContent, IonCardTitle, IonCardHeader, IonCard,
+    // 引入fm-markdown-preview组件模块
+    MarkdownPreviewModule
+  ],
+})
+export class WorldSetupPage implements OnInit {
+
+  constructor() { }
+  ngOnInit() { }
+
+  // 用户输入提示词
+  type: string = "东方玄幻";
+  typeInput(ev: any) {
+    this.type = ev.detail.value;
+  }
+
+  // 用户输入提示词
+  userPrompt: string = "古代";
+  promptInput(ev: any) {
+    this.userPrompt = ev.detail.value;
+  }
+
+  // 属性:组件内用于展示消息内容的变量
+  responseMsg: any = "";
+
+  // 方法:实例化completion对象,传入消息数组,并订阅生成的可观察对象。
+  isComplete: boolean = false; // 定义完成状态属性,用来标记是否补全完成
+  sendMessage() {
+    console.log("create");
+
+    let PromptTemplate = `
+    您作为一名专业的${this.type}小说作者,请您根据用户提供的期望描述,给出一个世界架构设定。
+    以下是用户的口述:${this.userPrompt}
+    `;
+
+    let completion = new FmodeChatCompletion([
+      { role: "system", content: "" },
+      { role: "user", content: PromptTemplate }
+    ]);
+    completion.sendCompletion().subscribe((message: any) => {
+      // 打印消息体
+      console.log(message.content);
+      // 赋值消息内容给组件内属性
+      this.responseMsg = message.content;
+      if (message?.complete) { // 判断message为完成状态,则设置isComplete为完成
+        this.isComplete = true;
+      }
+    });
+  }
+
+  // 复制到剪贴板
+  copyToClipboard() {
+    const textarea = document.createElement('textarea');
+    textarea.value = this.responseMsg;
+    document.body.appendChild(textarea);
+    textarea.select();
+    document.execCommand('copy');
+    document.body.removeChild(textarea);
+    alert('内容已复制到剪贴板');
+  }
+}