瀏覽代碼

add:智能体聊天页面初步创建

s202226701043 3 月之前
父節點
當前提交
9ba409a1a6

+ 59 - 54
novel-app/src/app/app.routes.ts

@@ -1,58 +1,63 @@
-import { HttpClientModule } from '@angular/common/http';
-import { NgModule } from '@angular/core';
-import { PreloadAllModules, RouteReuseStrategy, RouterModule, Routes } from '@angular/router';
-import { IonicRouteStrategy } from '@ionic/angular';
-
-export const routes: Routes = [
-  {
-    path: '',
-    loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
-  },
-  {
-    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: '',
-    loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
-  },
-  {
-    path: 'register',
-    loadComponent: () => import('./register/register.page').then(m => m.RegisterPage)
-  },
-  {
-    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)
-  },
  {
+import { HttpClientModule } from '@angular/common/http';
+import { NgModule } from '@angular/core';
+import { PreloadAllModules, RouteReuseStrategy, RouterModule, Routes } from '@angular/router';
+import { IonicRouteStrategy } from '@ionic/angular';
+
+export const routes: Routes = [
+  {
+    path: '',
+    loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
+  },
+  {
+    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: '',
+    loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
+  },
+  {
+    path: 'register',
+    loadComponent: () => import('./register/register.page').then(m => m.RegisterPage)
+  },
+  {
+    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)
+  },
+  {
     path: 'character',
     loadComponent: () => import('./character/character.page').then( m => m.CharacterPage)
+  },
+  {
+    path: 'character-creator',
+    loadComponent: () => import('./character-creator/character-creator.page').then(m => m.CharacterCreatorPage)
   }
 
-
-
-];
-
-
-
-
-
-@NgModule({
-  imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }), HttpClientModule],
-  exports: [RouterModule],
-  providers: [
-    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
-  ],
-})
-export class AppRoutingModule { }
+
+
+];
+
+
+
+
+
+@NgModule({
+  imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }), HttpClientModule],
+  exports: [RouterModule],
+  providers: [
+    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
+  ],
+})
+export class AppRoutingModule { }

+ 53 - 0
novel-app/src/app/character-creator/character-creator.page.html

@@ -0,0 +1,53 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="tabs/character"></ion-back-button>
+    </ion-buttons>
+    <ion-title>创建角色</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <form [formGroup]="characterForm" (ngSubmit)="generateCharacter()">
+    <ion-item>
+      <ion-label position="stacked">角色名称</ion-label>
+      <ion-input formControlName="name" type="text" placeholder="请输入角色名称"></ion-input>
+    </ion-item>
+
+    <ion-item>
+      <ion-label position="stacked">性别</ion-label>
+      <ion-select formControlName="gender" interface="action-sheet">
+        <ion-select-option value="male">男性</ion-select-option>
+        <ion-select-option value="female">女性</ion-select-option>
+      </ion-select>
+    </ion-item>
+
+    <ion-item>
+      <ion-label position="stacked">外貌描述</ion-label>
+      <ion-textarea 
+        formControlName="appearance" 
+        rows="4"
+        class="custom-textarea"
+        placeholder="请详细描述角色的外貌特征,至少20字...">
+      </ion-textarea>
+    </ion-item>
+
+    <ion-item>
+      <ion-label position="stacked">性格描述</ion-label>
+      <ion-textarea 
+        formControlName="personality" 
+        rows="4"
+        class="custom-textarea"
+        placeholder="请详细描述角色的性格特点,至少20字...">
+      </ion-textarea>
+    </ion-item>
+
+    <ion-button 
+      expand="block" 
+      type="submit" 
+      [disabled]="!characterForm.valid"
+      class="ion-margin-top">
+      生成角色智能体
+    </ion-button>
+  </form>
+</ion-content> 

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

@@ -0,0 +1,24 @@
+ion-item {
+    --padding-start: 0;
+    --padding-end: 0;
+    --inner-padding-end: 0;
+    margin-bottom: 16px;
+  }
+  
+  ion-label {
+    margin-bottom: 8px;
+  }
+  
+  ion-textarea {
+    --padding-top: 8px;
+    --padding-bottom: 8px;
+    --padding-start: 16px;
+    --padding-end: 16px;
+    border: 1px solid var(--ion-color-medium);
+    border-radius: 4px;
+    margin-top: 4px;
+  }
+  
+  .custom-textarea {
+    --background: var(--ion-color-light);
+  } 

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

@@ -0,0 +1,17 @@
+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();
+  });
+});

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

@@ -0,0 +1,65 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { IonicModule } from '@ionic/angular';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { CharacterService } from '../services/character.service';
+import { LoadingController } from '@ionic/angular';
+
+@Component({
+  selector: 'app-character-creator',
+  templateUrl: './character-creator.page.html',
+  styleUrls: ['./character-creator.page.scss'],
+  standalone: true,
+  imports: [CommonModule, IonicModule, FormsModule, ReactiveFormsModule]
+})
+export class CharacterCreatorPage {
+  characterForm!: FormGroup;
+
+  constructor(
+    private formBuilder: FormBuilder,
+    private router: Router,
+    private characterService: CharacterService,
+    private loadingController: LoadingController
+  ) {
+    this.initForm();
+  }
+
+  private initForm() {
+    this.characterForm = this.formBuilder.group({
+      name: ['', Validators.required],
+      gender: ['male', Validators.required],
+      appearance: ['', [Validators.required, Validators.minLength(20)]],
+      personality: ['', [Validators.required, Validators.minLength(20)]]
+    });
+  }
+
+  async generateCharacter() {
+    if (this.characterForm.valid) {
+      const loading = await this.loadingController.create({
+        message: '正在生成角色...'
+      });
+      await loading.present();
+
+      try {
+        const characterData = this.characterForm.value;
+        const avatar = await this.characterService
+          .generateCharacterAvatar(characterData.appearance)
+          .toPromise();
+
+        const character = await this.characterService.createCharacterAgent({
+          ...characterData,
+          avatar,
+          id: Date.now().toString()
+        }).toPromise();
+
+        await loading.dismiss();
+        this.router.navigate(['/character']);
+      } catch (error) {
+        console.error('Error generating character:', error);
+        await loading.dismiss();
+      }
+    }
+  }
+} 

+ 41 - 9
novel-app/src/app/character/character.page.html

@@ -1,13 +1,45 @@
-<ion-header [translucent]="true">
+<ion-header>
   <ion-toolbar>
-    <ion-title>character</ion-title>
+    <ion-title>角色管理</ion-title>
   </ion-toolbar>
 </ion-header>
 
-<ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">character</ion-title>
-    </ion-toolbar>
-  </ion-header>
-</ion-content>
+<ion-content>
+  <!-- 空状态显示 -->
+  <div class="empty-state" *ngIf="characters.length === 0">
+    <ion-icon name="people-outline" class="empty-icon"></ion-icon>
+    <h2>暂无角色</h2>
+    <p>点击下方按钮创建新角色</p>
+    <ion-button 
+      expand="block" 
+      style="--background: #ff69b4"
+      (click)="createNewCharacter()">
+      创建角色
+    </ion-button>
+  </div>
+
+  <!-- 角色列表 -->
+  <ion-grid *ngIf="characters.length > 0">
+    <ion-row>
+      <ion-col size="6" size-md="4" size-lg="3" *ngFor="let character of characters">
+        <ion-card class="character-card">
+          <img [src]="character.avatar" [alt]="character.name">
+          <ion-card-header>
+            <ion-card-title>{{character.name}}</ion-card-title>
+          </ion-card-header>
+        </ion-card>
+      </ion-col>
+    </ion-row>
+  </ion-grid>
+
+  <!-- 底部创建按钮 -->
+  <div class="bottom-button" *ngIf="characters.length > 0">
+    <ion-button 
+      expand="block" 
+      style="--background: #ff69b4" 
+      (click)="createNewCharacter()"
+      class="create-button">
+      创建角色
+    </ion-button>
+  </div>
+</ion-content> 

+ 71 - 0
novel-app/src/app/character/character.page.scss

@@ -0,0 +1,71 @@
+.empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    height: 100%;
+    padding: 2rem;
+    text-align: center;
+  
+    .empty-icon {
+      font-size: 6rem;
+      color: var(--ion-color-medium);
+      margin-bottom: 1rem;
+    }
+  
+    h2 {
+      color: var(--ion-color-dark);
+      margin-bottom: 0.5rem;
+    }
+  
+    p {
+      color: var(--ion-color-medium);
+      margin-bottom: 2rem;
+    }
+  
+    ion-button {
+      max-width: 200px;
+    }
+  }
+  
+  .character-card {
+    margin: 8px;
+    cursor: pointer;
+    transition: transform 0.2s;
+  
+    &:hover {
+      transform: translateY(-4px);
+    }
+  
+    img {
+      width: 100%;
+      height: 200px;
+      object-fit: cover;
+    }
+  
+    ion-card-header {
+      padding: 12px;
+    }
+  
+    ion-card-title {
+      font-size: 1.1rem;
+      text-align: center;
+    }
+  }
+  
+  .bottom-button {
+    position: fixed;
+    bottom: 20px;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 200px;
+    z-index: 999;
+  }
+  
+  .create-button {
+    --background: #ff69b4;  // 粉色
+    --background-hover: #ff1493;  // 深粉色
+    --border-radius: 20px;
+    font-weight: bold;
+    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+  } 

+ 22 - 3
novel-app/src/app/character/character.page.ts

@@ -2,19 +2,38 @@ import { Component, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';
+import { IonicModule } from '@ionic/angular';
+import { Router } from '@angular/router';
+import { CharacterService } from '../services/character.service';
+import type { Character } from '../services/character.service';
+
 
 @Component({
   selector: 'app-character',
   templateUrl: './character.page.html',
   styleUrls: ['./character.page.scss'],
   standalone: true,
-  imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule]
+  imports: [CommonModule, IonicModule]
 })
 export class CharacterPage implements OnInit {
+  characters: Character[] = [];
 
-  constructor() { }
+  constructor(
+    private router: Router,
+    private characterService: CharacterService
+  ) {}
 
   ngOnInit() {
+    this.loadCharacters();
+  }
+
+  loadCharacters() {
+    this.characterService.characters$.subscribe(chars => {
+      this.characters = chars;
+    });
   }
 
-}
+  createNewCharacter() {
+    this.router.navigate(['/character-creator']);
+  }
+} 

+ 30 - 0
novel-app/src/app/services/character.service.ts

@@ -0,0 +1,30 @@
+import { Injectable } from '@angular/core';
+import { BehaviorSubject, Observable, of } from 'rxjs';
+
+export interface Character {
+  id: string;
+  name: string;
+  gender: string;
+  appearance: string;
+  personality: string;
+  avatar: string;
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class CharacterService {
+  private characters = new BehaviorSubject<Character[]>([]);
+  characters$ = this.characters.asObservable();
+
+  generateCharacterAvatar(description: string): Observable<string> {
+    // TODO: 实现AI生成头像的逻辑
+    return of('assets/default-avatar.png');
+  }
+
+  createCharacterAgent(character: Character): Observable<Character> {
+    const currentCharacters = this.characters.value;
+    this.characters.next([...currentCharacters, character]);
+    return of(character);
+  }
+} 

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

@@ -11,17 +11,23 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../home/home.page').then((m) => m.HomePage),
       },
-      {
-        path: 'character',
-        loadComponent: () =>
-          import('../character/character.page').then((m) => m.CharacterPage),
-      },
       {
         path: 'person',
         loadComponent: () =>
           import('../person/person.page').then((m) => m.PersonPage),
       },
-
+      {
+        path: 'character',
+        loadComponent: () => import('../character/character.page').then( m => m.CharacterPage)
+      },
+      {
+        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',