祝雨婧 3 місяців тому
батько
коміт
c64f4ac0a4

+ 4 - 4
novel-app/package-lock.json

@@ -23,7 +23,7 @@
         "@capacitor/keyboard": "6.0.3",
         "@capacitor/status-bar": "6.0.2",
         "@ionic/angular": "^8.0.0",
-        "fmode-ng": "^0.0.62",
+        "fmode-ng": "^0.0.63",
         "ionicons": "^7.2.1",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
@@ -10378,9 +10378,9 @@
       "license": "ISC"
     },
     "node_modules/fmode-ng": {
-      "version": "0.0.62",
-      "resolved": "https://registry.npmmirror.com/fmode-ng/-/fmode-ng-0.0.62.tgz",
-      "integrity": "sha512-F0RzEu47NgKpaHp/vBEzjsU4efJ1lKLAbbdPE5hltj1W1cDaeht/i6UlEidid4FAEdAg7c9rrQrLgOh/zUfCsg==",
+      "version": "0.0.63",
+      "resolved": "https://registry.npmmirror.com/fmode-ng/-/fmode-ng-0.0.63.tgz",
+      "integrity": "sha512-gTiDZO2CchcTYAmlaweapasqV/8PdhG2vizJNn5dYZyXjgtrjyW+KeW5k2EVyIDvM1+bMGjjhGmr76Fc0TElxw==",
       "license": "COPYRIGHT © 未来飞马 未来全栈 www.fmode.cn All RIGHTS RESERVED",
       "dependencies": {
         "tslib": "^2.3.0"

+ 1 - 1
novel-app/package.json

@@ -28,7 +28,7 @@
     "@capacitor/keyboard": "6.0.3",
     "@capacitor/status-bar": "6.0.2",
     "@ionic/angular": "^8.0.0",
-    "fmode-ng": "^0.0.62",
+    "fmode-ng": "^0.0.63",
     "ionicons": "^7.2.1",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",

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

@@ -8,10 +8,6 @@ export const routes: Routes = [
     path: '',
     loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
   },
-  {
-    path: 'character-creator',
-    loadComponent: () => import('./character-creator/character-creator.page').then(m => m.CharacterCreatorPage)
-  },
   {
     path: 'story-generator',
     loadComponent: () => import('./story-generator/story-generator.page').then(m => m.StoryGeneratorPage)
@@ -31,6 +27,14 @@ export const routes: Routes = [
   {
     path: 'login',
     loadComponent: () => import('./login/login.page').then(m => m.LoginPage)
+  },
+  {
+    path: 'short-generator',
+    loadComponent: () => import('./short-generator/short-generator.page').then(m => m.ShortGeneratorPage)
+  },
+  {
+    path: 'short-novel',
+    loadComponent: () => import('./short-novel/short-novel.page').then(m => m.ShortNovelPage)
   }
 
 

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

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

+ 8 - 7
novel-app/src/app/home/home.page.html

@@ -6,23 +6,23 @@
 
   <!-- 功能按钮区域 -->
   <div class="features-section">
-    <!-- 生成小说按钮 -->
+    <!-- 生成长篇小说按钮 -->
     <ion-button class="feature-button" (click)="navigateTo('/story-generator')" expand="block" fill="clear">
       <div class="button-content">
-        <div class="icon-wrapper light-blue">
+        <div class="icon-wrapper light-pink">
           <ion-icon name="book"></ion-icon>
         </div>
-        <span>生成小说</span>
+        <span>长篇生成</span>
       </div>
     </ion-button>
 
-    <!-- 创建角色按钮 -->
-    <ion-button class="feature-button" (click)="navigateTo('/character-creator')" expand="block" fill="clear">
+    <!-- 生成短篇小说按钮 -->
+    <ion-button class="feature-button" (click)="navigateTo('/short-generator')" expand="block" fill="clear">
       <div class="button-content">
-        <div class="icon-wrapper light-pink">
+        <div class="icon-wrapper light-blue">
           <ion-icon name="person-outline"></ion-icon>
         </div>
-        <span>创建角色</span>
+        <span>短篇生成</span>
       </div>
     </ion-button>
 
@@ -50,6 +50,7 @@
         </div>
       </ion-card-header>
 
+
       <ion-card-content>
         <ion-grid *ngIf="stories.length > 0">
           <ion-row>

+ 6 - 1
novel-app/src/app/home/home.page.ts

@@ -46,7 +46,12 @@ export class HomePage {
   }
 
   createStory(type: 'long' | 'short') {
-    this.router.navigate(['/create-story', { type }]);
+    // 使用查询参数传递类型
+    this.router.navigate(['/'], {
+      queryParams: { generatorType: type, path: type === 'long' ? 'story-generator' : 'short-generator' }
+    });
+    const targetPath = type === 'long' ? '/story-generator' : '/short-generator';
+    this.router.navigate([targetPath]);
   }
 
   formatDate(date: Date): string {

+ 23 - 0
novel-app/src/app/short-generator/short-generator.page.html

@@ -0,0 +1,23 @@
+<ion-content>
+  <h1>风格</h1>
+  <ion-input [value]="fengge" (ionInput)="fenggeInput($event)"></ion-input>
+
+  <!-- 文本域:生成提示词 -->
+  <h1>人物设定</h1>
+  <ion-textarea [value]="userPrompt" (ionInput)="promptInput($event)" placeholder="文本提示词"
+    autoGrow="true"></ion-textarea>
+
+  <!-- 按钮:执行消息生成函数 -->
+  <ion-button (click)="sendMessage()" expand="block">生成大纲</ion-button>
+
+  <!-- 展示:返回消息内容 -->
+  <!-- 消息传输过程中,实时预览 -->
+  @if(!isComplete){
+  <div>{{responseMsg}}</div>
+  }
+  <!-- 消息传输完成后,实时预览Markdown格式 -->
+  @if(isComplete){
+  <fm-markdown-preview class="content-style" [content]="responseMsg"></fm-markdown-preview>
+  }
+
+</ion-content>

+ 124 - 0
novel-app/src/app/short-generator/short-generator.page.scss

@@ -0,0 +1,124 @@
+/* short-generator.page.scss */
+
+ion-content {
+    padding: 32px;
+    background: linear-gradient(to bottom, #f8f9fa, #e9ecef);
+    font-family: 'Roboto', sans-serif;
+    color: #333;
+}
+
+
+ion-input,
+ion-textarea {
+    margin-bottom: 32px;
+    background-color: #fff;
+    border-radius: 8px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    border: 1px solid #dee2e6;
+    padding: 16px;
+    font-size: 18px;
+    color: #333;
+    transition: border-color 0.3s ease, box-shadow 0.3s ease;
+}
+
+ion-input:focus,
+ion-textarea:focus {
+    border-color: #007bff;
+    box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3);
+}
+
+ion-button {
+    margin-top: 32px;
+    --background: linear-gradient(to right, #007bff, #0056b3);
+    --color: #fff;
+    --border-radius: 8px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    font-size: 20px;
+    font-weight: bold;
+    transition: transform 0.3s ease, box-shadow 0.3s ease;
+}
+
+ion-button:hover {
+    --background: linear-gradient(to right, #0056b3, #007bff);
+    transform: translateY(-2px);
+    box-shadow: 0 4px 8px rgba(0, 123, 255, 0.4);
+}
+
+.content-style {
+    margin-top: 40px;
+    padding: 32px;
+    background-color: #fff;
+    border-radius: 8px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    border: 1px solid #dee2e6;
+}
+
+/* Markdown Preview Styles */
+.content-style ::ng-deep .markdown-body {
+    font-family: 'Roboto', sans-serif;
+    line-height: 1.8;
+    color: #333;
+}
+
+.content-style ::ng-deep h1 {
+    font-size: 36px;
+    color: #007bff;
+    margin-bottom: 20px;
+}
+
+.content-style ::ng-deep h2 {
+    font-size: 28px;
+    color: #0056b3;
+    margin-bottom: 16px;
+}
+
+.content-style ::ng-deep p {
+    margin-bottom: 24px;
+    font-size: 18px;
+}
+
+.content-style ::ng-deep a {
+    color: #007bff;
+    text-decoration: none;
+    transition: color 0.3s ease;
+}
+
+.content-style ::ng-deep a:hover {
+    color: #0056b3;
+    text-decoration: underline;
+}
+
+.content-style ::ng-deep ul,
+.content-style ::ng-deep ol {
+    margin-left: 32px;
+    margin-bottom: 24px;
+}
+
+.content-style ::ng-deep li {
+    margin-bottom: 16px;
+    list-style-type: disc;
+}
+
+.content-style ::ng-deep blockquote {
+    margin: 24px 0;
+    padding-left: 24px;
+    border-left: 4px solid #dee2e6;
+    color: #6c757d;
+    font-style: italic;
+}
+
+.content-style ::ng-deep code {
+    background-color: #f8f9fa;
+    padding: 4px 8px;
+    border-radius: 4px;
+    font-family: 'Roboto Mono', monospace;
+}
+
+.content-style ::ng-deep pre {
+    background-color: #f8f9fa;
+    padding: 16px;
+    border-radius: 8px;
+    overflow-x: auto;
+    font-family: 'Roboto Mono', monospace;
+    color: #333;
+}

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

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

+ 57 - 0
novel-app/src/app/short-generator/short-generator.page.ts

@@ -0,0 +1,57 @@
+import { Component, OnInit } from '@angular/core';
+import { IonIcon } from '@ionic/angular';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput } from '@ionic/angular/standalone';
+/** 引用:从fmode-ng库引用FmodeChatCompletion类 */
+import { FmodeChatCompletion, MarkdownPreviewModule } from 'fmode-ng';
+
+@Component({
+  selector: 'app-short-generator',
+  templateUrl: './short-generator.page.html',
+  styleUrls: ['./short-generator.page.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput,
+    // 引入fm-markdown-preview组件模块
+    MarkdownPreviewModule
+  ],
+})
+export class ShortGeneratorPage implements OnInit {
+  router: any;
+  constructor() { }
+  ngOnInit() { }
+  // 用户输入提示词
+  fengge: string = "风格要求"
+  fenggeInput(ev: any) {
+    this.fengge = 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.fengge}小说作家,请您根据用户描述的小说人物,生成小说大纲。
+    以下是用户的口述:${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) {
+        this.isComplete = true;
+        // 跳转到short-novel页面,并传递responseMsg作为参数
+        this.router.navigate(['/short-novel'], { state: { outline: this.responseMsg } });
+      }
+    });
+  }
+}

+ 3 - 3
novel-app/src/app/character-creator/character-creator.page.html → novel-app/src/app/short-novel/short-novel.page.html

@@ -1,13 +1,13 @@
 <ion-header [translucent]="true">
   <ion-toolbar>
-    <ion-title>character-creator</ion-title>
+    <ion-title>short-novel</ion-title>
   </ion-toolbar>
 </ion-header>
 
 <ion-content [fullscreen]="true">
   <ion-header collapse="condense">
     <ion-toolbar>
-      <ion-title size="large">character-creator</ion-title>
+      <ion-title size="large">short-novel</ion-title>
     </ion-toolbar>
   </ion-header>
-</ion-content>
+</ion-content>

+ 0 - 0
novel-app/src/app/character-creator/character-creator.page.scss → novel-app/src/app/short-novel/short-novel.page.scss


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

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

+ 4 - 4
novel-app/src/app/character-creator/character-creator.page.ts → novel-app/src/app/short-novel/short-novel.page.ts

@@ -4,13 +4,13 @@ import { FormsModule } from '@angular/forms';
 import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';
 
 @Component({
-  selector: 'app-character-creator',
-  templateUrl: './character-creator.page.html',
-  styleUrls: ['./character-creator.page.scss'],
+  selector: 'app-short-novel',
+  templateUrl: './short-novel.page.html',
+  styleUrls: ['./short-novel.page.scss'],
   standalone: true,
   imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule]
 })
-export class CharacterCreatorPage implements OnInit {
+export class ShortNovelPage implements OnInit {
 
   constructor() { }
 

+ 1 - 1
novel-app/src/app/story-generator/story-generator.page.html

@@ -10,4 +10,4 @@
       <ion-title size="large">story-generator</ion-title>
     </ion-toolbar>
   </ion-header>
-</ion-content>
+</ion-content>

+ 1 - 12
novel-app/src/app/tabs/tabs.routes.ts

@@ -22,18 +22,7 @@ export const routes: Routes = [
           import('../person/person.page').then((m) => m.PersonPage),
       },
 
-      {
-        path: 'character-creator',
-        loadComponent: () => import('../character-creator/character-creator.page').then(m => m.CharacterCreatorPage)
-      },
-      {
-        path: 'story-generator',
-        loadComponent: () => import('../story-generator/story-generator.page').then(m => m.StoryGeneratorPage)
-      },
-      {
-        path: 'toolbox',
-        loadComponent: () => import('../toolbox/toolbox.page').then(m => m.ToolboxPage)
-      },
+
       {
         path: '',
         redirectTo: '/tabs/home',