Przeglądaj źródła

未登录:长期学习规划

15270821319 6 miesięcy temu
rodzic
commit
d4377ccd2b

+ 113 - 8
AiStudy-app/src/agent/agent-user-input/agent-user-input.component.html

@@ -3,16 +3,121 @@
     <ion-buttons slot="start">
       <ion-button color="medium" (click)="cancel()">取消</ion-button>
     </ion-buttons>
-    <ion-title>用户输入</ion-title>
+    <ion-title>{{ title || '用户输入' }}</ion-title>
     <ion-buttons slot="end">
       <ion-button (click)="confirm()" [strong]="true">确认</ion-button>
     </ion-buttons>
   </ion-toolbar>
 </ion-header>
-<ion-content class="ion-padding">
-  @for(field of fieldsArray;track field.name){
-    <ion-item>
-      <ion-input labelPlacement="stacked" label="请输入 {{field.name}}" [(ngModel)]="inputData[field.name]" placeholder="{{field.desc}}"></ion-input>
-    </ion-item>
-  }
-</ion-content>
+
+<ion-content class="ion-padding custom-content">
+  <div *ngIf="subtitle" class="subtitle">
+    {{ subtitle }}
+  </div>
+  
+  <div class="input-container">
+    <!-- 按类别分组显示问题 -->
+    <ng-container *ngFor="let category of getCategories()">
+      <div class="category-section" *ngIf="category">
+        <div class="category-header">
+          <h3>{{ category }}</h3>
+          <span class="question-count">{{ getCategoryQuestions(category).length }}个问题</span>
+        </div>
+        
+        <div class="questions-group">
+          <div *ngFor="let field of getCategoryQuestions(category)" class="input-item">
+            <div class="field-label">
+              {{ field.name }}
+              <span class="optional-tag" *ngIf="field.optional">(可选)</span>
+            </div>
+            
+            <div class="field-desc" *ngIf="field.desc">
+              {{ field.desc }}
+            </div>
+
+            <ng-container [ngSwitch]="shouldUseTextarea(field.desc)">
+              <ion-textarea
+                *ngSwitchCase="true"
+                class="custom-textarea"
+                [label]="field.name"
+                labelPlacement="floating"
+                [(ngModel)]="inputData[field.name]"
+                [placeholder]="'请输入' + field.name"
+                [rows]="3"
+                [autoGrow]="true"
+              ></ion-textarea>
+              
+              <ion-input
+                *ngSwitchCase="false"
+                class="custom-input"
+                [label]="field.name"
+                labelPlacement="floating"
+                [(ngModel)]="inputData[field.name]"
+                [placeholder]="'请输入' + field.name"
+              ></ion-input>
+            </ng-container>
+          </div>
+        </div>
+      </div>
+    </ng-container>
+  </div>
+</ion-content>
+
+<style>
+.subtitle {
+  padding: 8px 16px;
+  color: var(--ion-color-medium);
+  font-size: 14px;
+  margin-bottom: 16px;
+}
+
+.input-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+}
+
+.input-item {
+  width: calc(50% - 8px);
+}
+
+.field-label {
+  font-weight: bold;
+  margin-bottom: 8px;
+}
+
+.optional-tag {
+  color: var(--ion-color-medium);
+  font-size: 12px;
+  margin-left: 8px;
+}
+
+.field-desc {
+  color: var(--ion-color-medium);
+  font-size: 12px;
+  margin-bottom: 8px;
+}
+
+.custom-input,
+.custom-textarea {
+  --padding-top: 16px;
+  --padding-bottom: 16px;
+  --padding-start: 16px;
+  --padding-end: 16px;
+  --border-radius: 8px;
+  --border-width: 1px;
+  --border-color: var(--ion-color-medium);
+  --background: var(--ion-color-light);
+}
+
+.custom-textarea {
+  --padding-top: 16px;
+  --padding-bottom: 16px;
+  --padding-start: 16px;
+  --padding-end: 16px;
+  --border-radius: 8px;
+  --border-width: 1px;
+  --border-color: var(--ion-color-medium);
+  --background: var(--ion-color-light);
+}
+</style>

+ 224 - 0
AiStudy-app/src/agent/agent-user-input/agent-user-input.component.scss

@@ -0,0 +1,224 @@
+:host {
+  ion-header ion-toolbar {
+    --background: #ffffff;
+    
+    ion-title {
+      font-size: 18px;
+      font-weight: 600;
+    }
+
+    ion-button {
+      font-size: 16px;
+      --padding-start: 16px;
+      --padding-end: 16px;
+    }
+  }
+}
+
+.custom-content {
+  --background: #f5f7fa;
+}
+
+.subtitle {
+  color: var(--ion-color-medium);
+  font-size: 14px;
+  line-height: 1.5;
+  margin-bottom: 24px;
+}
+
+.input-container {
+  .input-item {
+    background: #ffffff;
+    border-radius: 12px;
+    padding: 20px;
+    margin-bottom: 16px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+
+    .field-label {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--ion-color-dark);
+      margin-bottom: 8px;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      .optional-tag {
+        font-size: 13px;
+        font-weight: normal;
+        color: var(--ion-color-medium);
+      }
+    }
+
+    .field-desc {
+      font-size: 14px;
+      color: var(--ion-color-medium);
+      margin-bottom: 16px;
+      line-height: 1.5;
+    }
+
+    ion-input.custom-input,
+    ion-textarea.custom-textarea {
+      --background: #f8fafc;
+      --padding-start: 16px;
+      --padding-end: 16px;
+      --padding-top: 12px;
+      --padding-bottom: 12px;
+      --border-radius: 8px;
+      --placeholder-color: #a0aec0;
+      --placeholder-opacity: 1;
+      margin-top: 8px;
+      
+      &::part(native) {
+        background: #f8fafc;
+        border-radius: 8px;
+        border: 1px solid #e2e8f0;
+        transition: all 0.3s ease;
+      }
+
+      &:focus-within::part(native) {
+        border-color: var(--ion-color-primary);
+        box-shadow: 0 0 0 3px rgba(var(--ion-color-primary-rgb), 0.1);
+      }
+    }
+
+    ion-textarea.custom-textarea {
+      --min-height: 120px;
+      
+      &::part(native) {
+        line-height: 1.6;
+      }
+    }
+  }
+}
+
+// 适配深色模式
+@media (prefers-color-scheme: dark) {
+  .custom-content {
+    --background: var(--ion-background-color);
+  }
+
+  .input-container .input-item {
+    background: var(--ion-card-background);
+    
+    ion-input.custom-input,
+    ion-textarea.custom-textarea {
+      --background: var(--ion-item-background);
+      
+      &::part(native) {
+        background: var(--ion-item-background);
+        border-color: var(--ion-border-color);
+      }
+    }
+  }
+}
+
+.category-section {
+  margin-bottom: 32px;
+
+  .category-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 20px;
+    padding-bottom: 12px;
+    border-bottom: 2px solid var(--ion-color-primary);
+
+    h3 {
+      font-size: 18px;
+      font-weight: 600;
+      color: var(--ion-color-dark);
+      margin: 0;
+    }
+
+    .question-count {
+      font-size: 14px;
+      color: var(--ion-color-medium);
+    }
+  }
+
+  .questions-group {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .input-item {
+    background: #ffffff;
+    border-radius: 12px;
+    padding: 20px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+    transition: all 0.3s ease;
+    width: 100%;
+    max-width: 800px;
+    margin: 0 auto;
+
+    &:hover {
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+    }
+
+    .field-label {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--ion-color-dark);
+      margin-bottom: 8px;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      .optional-tag {
+        font-size: 13px;
+        font-weight: normal;
+        color: var(--ion-color-medium);
+      }
+    }
+
+    .field-desc {
+      font-size: 14px;
+      color: var(--ion-color-medium);
+      margin-bottom: 16px;
+      line-height: 1.5;
+      max-width: 90%;
+    }
+
+    ion-input.custom-input,
+    ion-textarea.custom-textarea {
+      --background: #f8fafc;
+      --padding-start: 16px;
+      --padding-end: 16px;
+      --padding-top: 12px;
+      --padding-bottom: 12px;
+      --border-radius: 8px;
+      --placeholder-color: #a0aec0;
+      --placeholder-opacity: 1;
+      margin-top: 8px;
+      
+      &::part(native) {
+        background: #f8fafc;
+        border-radius: 8px;
+        border: 1px solid #e2e8f0;
+        transition: all 0.3s ease;
+      }
+
+      &:focus-within::part(native) {
+        border-color: var(--ion-color-primary);
+        box-shadow: 0 0 0 3px rgba(var(--ion-color-primary-rgb), 0.1);
+      }
+    }
+
+    ion-textarea.custom-textarea {
+      --min-height: 120px;
+      
+      &::part(native) {
+        line-height: 1.6;
+      }
+    }
+  }
+}
+
+// 响应式布局调整
+@media (max-width: 768px) {
+  .questions-group {
+    grid-template-columns: 1fr !important;
+  }
+}

+ 40 - 17
AiStudy-app/src/agent/agent-user-input/agent-user-input.component.ts

@@ -1,33 +1,56 @@
 import { Component, OnInit, Input } from '@angular/core';
-import { ModalController, IonHeader, IonContent, IonInput, IonToolbar, IonItem, IonButtons, IonButton, IonTitle } from '@ionic/angular/standalone';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-export interface AgentUserInputField{
-  name:string, // 字段名称
-  type:string, // 输入类型
-  desc:string, // 字段说明
-}
+import { ModalController, IonHeader, IonContent, IonInput, IonToolbar, IonItem, IonButtons, IonButton, IonTitle, IonTextarea } from '@ionic/angular/standalone';
+import { FormsModule } from '@angular/forms';
+import { NgIf, NgFor, NgSwitch, NgSwitchCase } from '@angular/common';
+import { AgentUserInputField } from '../agent.input';
+
 @Component({
   selector: 'agent-user-input',
   templateUrl: './agent-user-input.component.html',
   styleUrls: ['./agent-user-input.component.scss'],
   standalone: true,
-  imports:[
-    IonHeader,IonContent,IonInput,IonToolbar,IonItem,IonButtons,IonButton,
+  imports: [
+    IonHeader,
+    IonContent,
+    IonInput,
+    IonTextarea,
+    IonToolbar,
+    IonItem,
+    IonButtons,
+    IonButton,
     IonTitle,
-    FormsModule,ReactiveFormsModule,
+    FormsModule,
+    NgIf,
+    NgFor,
+    NgSwitch,
+    NgSwitchCase
   ]
 })
-export class AgentUserInputComponent  implements OnInit {
+export class AgentUserInputComponent implements OnInit {
+  @Input() fieldsArray: AgentUserInputField[] = [];
+  @Input() inputData: any = {};
+  @Input() title?: string;
+  @Input() subtitle?: string;
 
-  @Input()
-  fieldsArray:AgentUserInputField[]=[]
+  shouldUseTextarea(desc: string): boolean {
+    return desc.length > 50;
+  }
 
-  @Input()
-  inputData:any = {}
+  // 获取所有类别
+  getCategories(): string[] {
+    const categories = new Set(this.fieldsArray.map(f => f.category || '其他'));
+    return Array.from(categories);
+  }
 
-  constructor(private modalCtrl: ModalController) {}
-  ngOnInit(){
+  // 获取某个类别的所有问题
+  getCategoryQuestions(category: string): AgentUserInputField[] {
+    return this.fieldsArray.filter(f => (f.category || '其他') === category);
   }
+
+  constructor(private modalCtrl: ModalController) {}
+
+  ngOnInit() {}
+
   cancel() {
     return this.modalCtrl.dismiss(null, 'cancel');
   }

+ 8 - 2
AiStudy-app/src/agent/agent.input.ts

@@ -5,16 +5,22 @@ export interface AgentUserInputField {
     name: string;
     type: string;
     desc: string;
+    optional?: boolean;
+    category?: string;
 }
 
 export async function getUserInput(modalCtrl: ModalController, options: {
-    fieldsArray: AgentUserInputField[]
+    fieldsArray: AgentUserInputField[];
+    title?: string;
+    subtitle?: string;
 }): Promise<any> {
     const modal = await modalCtrl.create({
         component: AgentUserInputComponent,
         componentProps: {
             fieldsArray: options.fieldsArray,
-            inputData: {}
+            inputData: {},
+            title: options.title,
+            subtitle: options.subtitle
         }
     });
 

+ 1 - 0
AiStudy-app/src/agent/agent.task.ts

@@ -2,6 +2,7 @@ export class AgentTaskStep {
     title: string;
     progress: number = 0;
     error?: string;
+    status?: string;
     shareData: any;
     handle?: () => Promise<boolean>;
 

+ 34 - 7
AiStudy-app/src/agent/tasks/learning-plan/1.collect-basic-info.ts

@@ -13,17 +13,44 @@ export function TaskCollectBasicInfo(options:{
         shareData: options.shareData
     });
 
+    const basicQuestions = [
+        {
+            name: "学习目标",
+            type: "text",
+            desc: "您想学习什么内容或技能?请具体描述"
+        },
+        {
+            name: "学习目的",
+            type: "text",
+            desc: "您学习这个内容的目的是什么?例如:工作需要、个人兴趣、考试备考等"
+        },
+        {
+            name: "学习时间范围",
+            type: "text",
+            desc: "您计划在多长时间内完成学习?例如:3个月、半年、1年等"
+        },
+        {
+            name: "每周学习时间",
+            type: "text",
+            desc: "您每周可以投入多少小时学习?"
+        },
+        {
+            name: "学习偏好",
+            type: "text",
+            desc: "您喜欢什么样的学习方式?例如:视频教程、书籍阅读、实践练习等"
+        },
+        {
+            name: "当前水平",
+            type: "text",
+            desc: "您目前对这个领域的了解程度如何?"
+        }
+    ];
+
     task.handle = () => {
         return new Promise(async (resolve, reject) => {
             // 获取用户输入
             let userInput = await getUserInput(options.modalCtrl, {
-                fieldsArray: [
-                    {name: "学习目标", type: "text", desc: "想学习什么内容?比如某个学科、技能或项目"},
-                    {name: "学习时间范围", type: "text", desc: "希望在多长时间内完成这个学习目标?"},
-                    {name: "每周学习时间", type: "text", desc: "每周能投入多少时间来学习?"},
-                    {name: "学习偏好", type: "text", desc: "更倾向于哪种学习方式?"},
-                    {name: "当前水平", type: "text", desc: "目前在这个领域的水平如何?"}
-                ]
+                fieldsArray: basicQuestions
             });
 
             if (!userInput?.['学习目标']) {

+ 130 - 0
AiStudy-app/src/agent/tasks/learning-plan/2.5.collect-details.ts

@@ -0,0 +1,130 @@
+import { AgentTaskStep } from '../../../agent/agent.task';
+import { ModalController } from '@ionic/angular/standalone';
+import { FmodeChatCompletion } from 'fmode-ng';
+import { extactAndParseJsonFromString } from '../../../agent/agent.json';
+import { getUserInput } from '../../../agent/agent.input';
+
+interface DetailQuestion {
+    name: string;
+    desc: string;
+    category: string;
+    optional: boolean;
+}
+
+export function TaskCollectDetails(options:{
+    modalCtrl: ModalController,
+    shareData: any
+}): AgentTaskStep {
+    let task = new AgentTaskStep({
+        title: "填写详细信息",
+        shareData: options.shareData
+    });
+
+    task.handle = () => {
+        return new Promise(async (resolve, reject) => {
+            const { basicInfo, analysis } = options.shareData;
+            
+            // 根据基本信息和分析结果生成详细问题
+            const promptTemplate = `作为专业的学习规划导师,请根据以下信息生成详细的调研问题:
+
+基本信息:
+${JSON.stringify(basicInfo, null, 2)}
+
+分析结果:
+${JSON.stringify(analysis, null, 2)}
+
+请生成至少10个详细的调研问题,问题要具体且有针对性,帮助更好地了解学习者情况。
+结果以JSON格式返回:
+{
+    "questions": [
+        {
+            "name": "问题标题",
+            "desc": "详细描述,说明为什么这个问题很重要",
+            "category": "问题类别",  // 学习基础/时间管理/目标细化/资源需求/其他
+            "optional": true
+        }
+    ]
+}`;
+
+            try {
+                task.status = "正在生成调研问题...";
+                
+                // 生成问题列表
+                const completion = new FmodeChatCompletion([
+                    {role: "system", content: ""},
+                    {role: "user", content: promptTemplate}
+                ]);
+
+                await new Promise((resolveCompletion) => {
+                    completion.sendCompletion().subscribe({
+                        next: (message: any) => {
+                            if (task.progress < 0.3) {
+                                task.progress += 0.05;
+                            }
+                            
+                            if (message.complete) {
+                                const result = extactAndParseJsonFromString(message.content);
+                                if (!result?.questions || result.questions.length < 10) {
+                                    throw new Error("问题生成失败或数量不足");
+                                }
+                                options.shareData.detailQuestions = result.questions;
+                                resolveCompletion(true);
+                            }
+                        },
+                        error: (error) => {
+                            throw error;
+                        }
+                    });
+                });
+
+                // 按类别组织问题
+                const questions = options.shareData.detailQuestions as DetailQuestion[];
+                const categorizedQuestions = questions.reduce((acc: any, q: any) => {
+                    if (!acc[q.category]) {
+                        acc[q.category] = [];
+                    }
+                    acc[q.category].push(q);
+                    return acc;
+                }, {});
+
+                task.status = "请填写详细信息(建议至少填写一半的问题)...";
+                task.progress = 0.4;
+
+                // 收集用户回答
+                const allQuestions = questions.map((q: DetailQuestion) => ({
+                    name: q.name,
+                    type: "text",
+                    desc: q.desc,
+                    optional: q.optional,
+                    category: q.category
+                }));
+
+                const answers = await getUserInput(options.modalCtrl, {
+                    fieldsArray: allQuestions,
+                    title: "详细规划问题",
+                    subtitle: "为了生成更个性化的学习计划,建议填写以下问题(所有问题均为可选)"
+                });
+
+                if (answers) {
+                    // 过滤掉空值
+                    options.shareData.detailAnswers = Object.fromEntries(
+                        Object.entries(answers)
+                            .filter(([_, value]) => typeof value === 'string' && value.trim() !== '')
+                    );
+                } else {
+                    options.shareData.detailAnswers = {};
+                }
+
+                task.status = "详细信息收集完成";
+                task.progress = 1;
+                resolve(true);
+
+            } catch (error: any) {
+                task.error = error.message || "收集详细信息时发生错误";
+                resolve(false);
+            }
+        });
+    };
+
+    return task;
+} 

+ 46 - 23
AiStudy-app/src/agent/tasks/learning-plan/2.analyze-requirements.ts

@@ -2,6 +2,7 @@ import { AgentTaskStep } from '../../../agent/agent.task';
 import { ModalController } from '@ionic/angular/standalone';
 import { FmodeChatCompletion } from 'fmode-ng';
 import { extactAndParseJsonFromString } from '../../../agent/agent.json';
+import { getUserInput } from '../../../agent/agent.input';
 
 export function TaskAnalyzeRequirements(options:{
     modalCtrl: ModalController,
@@ -16,6 +17,7 @@ export function TaskAnalyzeRequirements(options:{
         return new Promise(async (resolve, reject) => {
             const basicInfo = options.shareData.basicInfo;
             
+            // 第一步:AI分析基本需求
             const promptTemplate = `作为一名专业的学习规划导师,请根据以下信息分析学习需求:
             学习目标:${basicInfo['学习目标']}
             学习时间范围:${basicInfo['学习时间范围']}
@@ -23,12 +25,13 @@ export function TaskAnalyzeRequirements(options:{
             学习偏好:${basicInfo['学习偏好']}
             当前水平:${basicInfo['当前水平']}
 
-            请给出详细的分析,结果以JSON格式返回:
+            请给出分析结果,结果以JSON格式返回:
             {
-                "feasibility": "可行性分析",
-                "challenges": ["可能遇到的挑战1", "挑战2"],
-                "suggestions": ["建议1", "建议2"],
-                "prerequisites": ["前置知识/技能1", "前置知识/技能2"]
+                "analysis": {
+                    "feasibility": "可行性分析",
+                    "challenges": ["可能遇到的挑战1", "挑战2"],
+                    "suggestions": ["建议1", "建议2"]
+                }
             }`;
 
             let completion = new FmodeChatCompletion([
@@ -36,25 +39,45 @@ export function TaskAnalyzeRequirements(options:{
                 {role: "user", content: promptTemplate}
             ]);
 
-            completion.sendCompletion().subscribe({
-                next: (message: any) => {
-                    if (task.progress < 0.5) {
-                        task.progress += 0.1;
-                    } else if (task.progress < 0.9) {
-                        task.progress += 0.05;
-                    }
-
-                    if (message.complete) {
-                        options.shareData.analysis = extactAndParseJsonFromString(message.content);
-                        task.progress = 1;
-                        resolve(true);
-                    }
-                },
-                error: (error) => {
-                    task.error = "分析失败,请重试";
-                    resolve(false);
+            // 更新进度的辅助函数
+            const updateProgress = (current: number, target: number, speed: number = 0.05) => {
+                if (task.progress < target) {
+                    task.progress = Math.min(current + speed, target);
                 }
-            });
+            };
+
+            try {
+                // 显示正在分析的状态
+                task.status = "正在分析基本需求...";
+                
+                await new Promise((resolveCompletion) => {
+                    completion.sendCompletion().subscribe({
+                        next: (message: any) => {
+                            updateProgress(task.progress, 0.5);
+                            
+                            if (message.complete) {
+                                const result = extactAndParseJsonFromString(message.content);
+                                if (!result) {
+                                    throw new Error("AI分析结果格式错误");
+                                }
+                                options.shareData.analysis = result.analysis;
+                                resolveCompletion(true);
+                            }
+                        },
+                        error: (error) => {
+                            throw error;
+                        }
+                    });
+                });
+
+                task.status = "分析完成";
+                task.progress = 1;
+                resolve(true);
+
+            } catch (error: any) {
+                task.error = error.message || "分析需求时发生错误";
+                resolve(false);
+            }
         });
     };
 

+ 63 - 24
AiStudy-app/src/agent/tasks/learning-plan/3.generate-plan.ts

@@ -14,32 +14,71 @@ export function TaskGeneratePlan(options:{
 
     task.handle = () => {
         return new Promise(async (resolve, reject) => {
-            const { basicInfo, analysis } = options.shareData;
+            const { basicInfo, analysis, detailAnswers } = options.shareData;
             
-            const promptTemplate = `作为专业的学习规划导师,请根据以下信息生成详细的学习计划:
-            
-            基本信息:
-            ${JSON.stringify(basicInfo, null, 2)}
-            
-            需求分析:
-            ${JSON.stringify(analysis, null, 2)}
+            const promptTemplate = `作为专业的学习规划导师,请根据以下详细信息生成具体的学习计划:
 
-            请生成一个详细的学习计划,结果以JSON格式返回:
-            {
-                "title": "学习计划标题",
-                "overview": "总体概述",
-                "stages": [
-                    {
-                        "name": "阶段名称",
-                        "duration": "预计时长",
-                        "goals": ["目标1", "目标2"],
-                        "tasks": ["任务1", "任务2"],
-                        "resources": ["推荐资源1", "推荐资源2"]
-                    }
-                ],
-                "milestones": ["里程碑1", "里程碑2"],
-                "evaluationMethods": ["评估方式1", "评估方式2"]
-            }`;
+基本信息:
+${JSON.stringify(basicInfo, null, 2)}
+
+分析结果:
+${JSON.stringify(analysis, null, 2)}
+
+用户补充回答:
+${Object.entries(detailAnswers || {})
+    .map(([question, answer]) => `${question}: ${answer}`)
+    .join('\n')}
+
+请生成一个详细的学习计划,要求:
+1. 总体规划要分阶段进行
+2. 每个阶段要细分到周计划
+3. 每周的学习内容要具体且可执行
+4. 考虑用户每周可投入的时间
+5. 为每个阶段设置明确的学习目标和检验标准
+
+结果以JSON格式返回:
+{
+    "title": "学习计划标题",
+    "overview": "总体概述",
+    "totalDuration": "总学习时长",
+    "weeklyHours": "每周投入时间",
+    "stages": [
+        {
+            "name": "阶段名称",
+            "duration": "阶段时长",
+            "goals": ["本阶段目标1", "目标2"],
+            "weeklyPlans": [
+                {
+                    "week": "第1周",
+                    "focus": "本周重点",
+                    "tasks": [
+                        {
+                            "name": "具体任务",
+                            "timeNeeded": "预计用时",
+                            "details": "任务详情",
+                            "resources": ["推荐资源1", "资源2"]
+                        }
+                    ],
+                    "outcomes": ["预期成果1", "成果2"],
+                    "checkPoints": ["检查点1", "检查点2"]
+                }
+            ],
+            "evaluation": {
+                "methods": ["评估方式1", "方式2"],
+                "standards": ["达标标准1", "标准2"]
+            }
+        }
+    ],
+    "milestones": [
+        {
+            "name": "里程碑名称",
+            "timing": "预计达成时间",
+            "criteria": ["达成标准1", "标准2"]
+        }
+    ],
+    "successCriteria": ["总体成功标准1", "标准2"],
+    "additionalSuggestions": ["建议1", "建议2"]
+}`;
 
             let completion = new FmodeChatCompletion([
                 {role: "system", content: ""},

+ 2 - 2
AiStudy-app/src/app/app.routes.ts

@@ -1,4 +1,5 @@
 import { Routes } from '@angular/router';
+import { LearningDesignPage } from './pages/learning-design/learning-design.page';
 
 export const routes: Routes = [
   {
@@ -19,8 +20,7 @@ export const routes: Routes = [
   },
   {
     path: 'learning-design',
-    loadComponent: () => import('./pages/learning-design/learning-design.page')
-      .then(m => m.LearningDesignPage)
+    component: LearningDesignPage
   },
   {
     path: 'learning-history',

+ 60 - 18
AiStudy-app/src/app/pages/learning-design/learning-design.page.html

@@ -21,7 +21,7 @@
         }">
           {{ task.error ? '失败' : 
              task.progress === 0 ? '等待中' :
-             task.progress === 1 ? '完成' : '进行中'
+             task.progress === 1 ? '完成' : task.status || '进行中'
           }}
         </span>
       </div>
@@ -29,13 +29,14 @@
       <ion-progress-bar [value]="task.progress"></ion-progress-bar>
       
       <div *ngIf="task.error" class="task-error">
+        <ion-icon name="warning-outline"></ion-icon>
         {{ task.error }}
       </div>
     </div>
   </div>
 
-  <!-- 最终结果显示 -->
-  <div *ngIf="shareData.learningPlan" class="plan-result">
+  <!-- 学习计划结果显示 -->
+  <div *ngIf="showPlanResult && shareData.learningPlan" class="plan-result">
     <h2>{{ shareData.learningPlan.title }}</h2>
     
     <div class="plan-section">
@@ -46,30 +47,69 @@
     <div class="plan-section">
       <h3>学习阶段</h3>
       <div *ngFor="let stage of shareData.learningPlan.stages" class="stage-item">
-        <h4>{{ stage.name }} ({{ stage.duration }})</h4>
+        <h4 [attr.data-duration]="stage.duration">{{ stage.name }}</h4>
+        
         <div class="stage-goals">
-          <strong>目标</strong>
+          <strong>阶段目标</strong>
           <ul>
             <li *ngFor="let goal of stage.goals">{{ goal }}</li>
           </ul>
         </div>
-        <div class="stage-tasks">
-          <strong>任务:</strong>
+
+        <!-- 新增:周计划显示 -->
+        <div class="weekly-plans">
+          <strong>详细计划</strong>
+          <div *ngFor="let week of stage.weeklyPlans" class="week-item">
+            <h5>{{ week.week }} - {{ week.focus }}</h5>
+            
+            <div class="week-tasks">
+              <div *ngFor="let task of week.tasks" class="task-detail">
+                <div class="task-header">
+                  <span class="task-name">{{ task.name }}</span>
+                  <span class="task-time">预计用时: {{ task.timeNeeded }}</span>
+                </div>
+                <p class="task-description">{{ task.details }}</p>
+                <div class="task-resources">
+                  <strong>学习资源:</strong>
+                  <ul>
+                    <li *ngFor="let resource of task.resources">{{ resource }}</li>
+                  </ul>
+                </div>
+              </div>
+            </div>
+
+            <div class="week-outcomes">
+              <strong>预期成果:</strong>
+              <ul>
+                <li *ngFor="let outcome of week.outcomes">{{ outcome }}</li>
+              </ul>
+            </div>
+
+            <div class="week-checkpoints">
+              <strong>检查点:</strong>
+              <ul>
+                <li *ngFor="let checkpoint of week.checkPoints">{{ checkpoint }}</li>
+              </ul>
+            </div>
+          </div>
+        </div>
+
+        <!-- 阶段评估标准 -->
+        <div class="stage-evaluation">
+          <strong>评估方式</strong>
           <ul>
-            <li *ngFor="let task of stage.tasks">{{ task }}</li>
+            <li *ngFor="let method of stage.evaluation.methods">{{ method }}</li>
           </ul>
-        </div>
-        <div class="stage-resources">
-          <strong>推荐资源:</strong>
+          <strong>达标标准</strong>
           <ul>
-            <li *ngFor="let resource of stage.resources">{{ resource }}</li>
+            <li *ngFor="let standard of stage.evaluation.standards">{{ standard }}</li>
           </ul>
         </div>
       </div>
     </div>
 
     <div class="plan-section">
-      <h3>里程碑</h3>
+      <h3>关键里程碑</h3>
       <ul>
         <li *ngFor="let milestone of shareData.learningPlan.milestones">
           {{ milestone }}
@@ -78,7 +118,7 @@
     </div>
 
     <div class="plan-section">
-      <h3>评估方式</h3>
+      <h3>学习评估</h3>
       <ul>
         <li *ngFor="let method of shareData.learningPlan.evaluationMethods">
           {{ method }}
@@ -87,8 +127,10 @@
     </div>
   </div>
 
-  <!-- 重新开始按钮 -->
-  <ion-button *ngIf="isComplete" expand="block" (click)="restartPlan()">
-    重新规划
-  </ion-button>
+  <!-- 操作按钮 -->
+  <div class="action-buttons" *ngIf="isComplete">
+    <ion-button expand="block" (click)="restartPlan()">
+      重新规划
+    </ion-button>
+  </div>
 </ion-content> 

+ 272 - 57
AiStudy-app/src/app/pages/learning-design/learning-design.page.scss

@@ -1,98 +1,313 @@
+// 整体页面样式
+ion-content {
+  --background: #f5f7fa;
+}
+
+// 任务列表样式优化
 .task-list {
-  margin-bottom: 20px;
+  margin: 16px 0;
+  padding: 0 4px; // 添加内边距避免阴影被裁剪
 }
 
 .task-item {
-  background: var(--ion-color-light);
-  border-radius: 8px;
-  padding: 16px;
-  margin-bottom: 12px;
+  background: #fff;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 20px;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
+  transition: all 0.3s ease;
 
-  .task-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 8px;
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 6px 16px rgba(0,0,0,0.08);
+  }
+}
 
-    h3 {
-      margin: 0;
-      font-size: 16px;
-      font-weight: 500;
-    }
+.task-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+
+  h3 {
+    margin: 0;
+    font-size: 17px;
+    font-weight: 600;
+    color: var(--ion-color-dark);
   }
+}
 
-  .task-status {
-    font-size: 14px;
-    padding: 4px 8px;
-    border-radius: 4px;
-    
-    &.status-pending {
-      background: var(--ion-color-medium);
-      color: white;
-    }
-    
-    &.status-running {
-      background: var(--ion-color-primary);
-      color: white;
-    }
-    
-    &.status-complete {
-      background: var(--ion-color-success);
-      color: white;
-    }
-    
-    &.status-error {
-      background: var(--ion-color-danger);
-      color: white;
-    }
+.task-status {
+  font-size: 14px;
+  padding: 6px 12px;
+  border-radius: 20px;
+  font-weight: 500;
+  transition: all 0.3s ease;
+
+  &.status-pending {
+    background: #f3f4f6;
+    color: #6b7280;
   }
 
-  .task-error {
-    color: var(--ion-color-danger);
-    margin-top: 8px;
-    font-size: 14px;
+  &.status-running {
+    background: #dbeafe;
+    color: #2563eb;
+  }
+
+  &.status-complete {
+    background: #dcfce7;
+    color: #16a34a;
+  }
+
+  &.status-error {
+    background: #fee2e2;
+    color: #dc2626;
   }
 }
 
+ion-progress-bar {
+  border-radius: 4px;
+  height: 6px;
+  --buffer-background: #e5e7eb;
+  --progress-background: var(--ion-color-primary);
+}
+
+.task-error {
+  margin-top: 12px;
+  color: #dc2626;
+  font-size: 14px;
+  padding: 8px 12px;
+  background: #fee2e2;
+  border-radius: 8px;
+}
+
+// 计划结果显示优化
 .plan-result {
+  margin-top: 32px;
   background: white;
-  border-radius: 8px;
-  padding: 16px;
-  margin-top: 20px;
+  border-radius: 16px;
+  padding: 24px;
+  box-shadow: 0 4px 16px rgba(0,0,0,0.08);
 
   h2 {
     color: var(--ion-color-primary);
-    margin-bottom: 16px;
+    margin: 0 0 24px 0;
+    font-size: 22px;
+    font-weight: 700;
+    text-align: center;
   }
 
   .plan-section {
-    margin-bottom: 24px;
+    margin-bottom: 32px;
 
     h3 {
       color: var(--ion-color-dark);
-      font-size: 18px;
+      font-size: 19px;
+      margin-bottom: 16px;
+      font-weight: 600;
+      display: flex;
+      align-items: center;
+
+      &::before {
+        content: '';
+        display: inline-block;
+        width: 4px;
+        height: 20px;
+        background: var(--ion-color-primary);
+        margin-right: 12px;
+        border-radius: 2px;
+      }
+    }
+
+    p {
+      line-height: 1.6;
+      color: var(--ion-color-dark);
+      font-size: 15px;
+    }
+
+    ul {
+      margin: 12px 0;
+      padding-left: 24px;
+    }
+
+    li {
       margin-bottom: 12px;
+      line-height: 1.6;
+      color: var(--ion-color-dark);
+      position: relative;
+      
+      &::marker {
+        color: var(--ion-color-primary);
+      }
     }
   }
 
   .stage-item {
-    background: var(--ion-color-light);
+    background: #f8fafc;
+    border-radius: 12px;
+    padding: 20px;
+    margin-bottom: 20px;
+    border: 1px solid #e2e8f0;
+    transition: all 0.3s ease;
+
+    &:hover {
+      border-color: var(--ion-color-primary);
+      box-shadow: 0 4px 12px rgba(0,0,0,0.05);
+    }
+
+    h4 {
+      color: var(--ion-color-primary);
+      margin: 0 0 16px 0;
+      font-weight: 600;
+      font-size: 17px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+
+      &::after {
+        content: attr(data-duration);
+        font-size: 14px;
+        color: var(--ion-color-medium);
+        font-weight: normal;
+      }
+    }
+
+    .stage-goals,
+    .stage-tasks,
+    .stage-resources {
+      margin-bottom: 16px;
+
+      strong {
+        display: block;
+        margin-bottom: 12px;
+        color: var(--ion-color-dark);
+        font-weight: 600;
+        font-size: 15px;
+      }
+
+      ul {
+        background: white;
+        border-radius: 8px;
+        padding: 16px 16px 16px 36px;
+        margin: 0;
+        box-shadow: inset 0 2px 4px rgba(0,0,0,0.03);
+      }
+
+      li {
+        margin-bottom: 8px;
+        
+        &:last-child {
+          margin-bottom: 0;
+        }
+      }
+    }
+  }
+}
+
+// 操作按钮优化
+.action-buttons {
+  margin: 32px 0;
+  padding-bottom: 24px;
+
+  ion-button {
+    --border-radius: 12px;
+    --padding-top: 16px;
+    --padding-bottom: 16px;
+    font-weight: 600;
+    font-size: 16px;
+    
+    &::part(native) {
+      transition: all 0.3s ease;
+    }
+
+    &:hover::part(native) {
+      transform: translateY(-2px);
+    }
+  }
+}
+
+.weekly-plans {
+  margin-top: 20px;
+
+  .week-item {
+    background: white;
     border-radius: 8px;
     padding: 16px;
     margin-bottom: 16px;
+    border: 1px solid #e2e8f0;
 
-    h4 {
-      color: var(--ion-color-primary);
+    h5 {
+      color: var(--ion-color-dark);
+      font-size: 16px;
+      font-weight: 600;
       margin: 0 0 12px 0;
+      padding-bottom: 8px;
+      border-bottom: 1px solid #e2e8f0;
     }
 
-    ul {
-      margin: 8px 0;
-      padding-left: 20px;
+    .task-detail {
+      margin-bottom: 16px;
+      padding: 12px;
+      background: #f8fafc;
+      border-radius: 6px;
+
+      .task-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 8px;
+
+        .task-name {
+          font-weight: 600;
+          color: var(--ion-color-primary);
+        }
+
+        .task-time {
+          font-size: 13px;
+          color: var(--ion-color-medium);
+        }
+      }
+
+      .task-description {
+        font-size: 14px;
+        line-height: 1.5;
+        color: var(--ion-color-dark);
+        margin: 8px 0;
+      }
     }
 
-    li {
-      margin-bottom: 4px;
+    .week-outcomes,
+    .week-checkpoints {
+      margin-top: 12px;
+
+      strong {
+        display: block;
+        margin-bottom: 8px;
+        font-size: 14px;
+        color: var(--ion-color-dark);
+      }
+
+      ul {
+        margin: 0;
+        padding-left: 20px;
+        
+        li {
+          font-size: 14px;
+          margin-bottom: 4px;
+        }
+      }
     }
   }
+}
+
+.stage-evaluation {
+  margin-top: 20px;
+  padding-top: 16px;
+  border-top: 1px solid #e2e8f0;
+
+  strong {
+    display: block;
+    margin: 12px 0 8px;
+    color: var(--ion-color-dark);
+  }
 } 

+ 12 - 33
AiStudy-app/src/app/pages/learning-design/learning-design.page.ts

@@ -19,6 +19,7 @@ import { TaskAnalyzeRequirements } from '../../../agent/tasks/learning-plan/2.an
 import { TaskGeneratePlan } from '../../../agent/tasks/learning-plan/3.generate-plan';
 import { TaskExecutor } from '../../../agent/agent.start';
 import { AgentTaskStep } from '../../../agent/agent.task';
+import { TaskCollectDetails } from '../../../agent/tasks/learning-plan/2.5.collect-details';
 
 interface LearningPlan {
   userId: string;
@@ -56,6 +57,7 @@ export class LearningDesignPage implements OnInit {
   isComplete: boolean = false;
   taskList: AgentTaskStep[] = [];
   shareData: any = {};
+  showPlanResult: boolean = false;
 
   constructor(
     private router: Router,
@@ -68,7 +70,8 @@ export class LearningDesignPage implements OnInit {
 
   async startLearningPlanTask() {
     this.isComplete = false;
-    this.shareData = {}; // 重置共享数据
+    this.showPlanResult = false;
+    this.shareData = {};
 
     // 创建任务链
     const task1 = TaskCollectBasicInfo({
@@ -81,48 +84,24 @@ export class LearningDesignPage implements OnInit {
       shareData: this.shareData
     });
     
+    const task2_5 = TaskCollectDetails({
+      modalCtrl: this.modalCtrl,
+      shareData: this.shareData
+    });
+    
     const task3 = TaskGeneratePlan({
       modalCtrl: this.modalCtrl,
       shareData: this.shareData
     });
 
     // 设置任务列表
-    this.taskList = [task1, task2, task3];
+    this.taskList = [task1, task2, task2_5, task3];
 
     // 执行任务链
     const success = await TaskExecutor(this.taskList);
-
-    // 任务完成后保存学习计划
-    if (success && this.shareData.learningPlan) {
-      await this.saveLearningPlan();
-    }
-
     this.isComplete = true;
-  }
-
-  private async saveLearningPlan() {
-    try {
-      const currentUser = Parse.User.current();
-      if (!currentUser) {
-        throw new Error('User not logged in');
-      }
-
-      const LearningPlan = Parse.Object.extend('LearningPlan');
-      const learningPlan = new LearningPlan();
-      
-      learningPlan.set('userId', currentUser.id);
-      learningPlan.set('subject', this.shareData.basicInfo['学习目标']);
-      learningPlan.set('timeFrame', this.shareData.basicInfo['学习时间范围']);
-      learningPlan.set('weeklyHours', this.shareData.basicInfo['每周学习时间']);
-      learningPlan.set('learningStyle', this.shareData.basicInfo['学习偏好']);
-      learningPlan.set('currentLevel', this.shareData.basicInfo['当前水平']);
-      learningPlan.set('planDetails', JSON.stringify(this.shareData.learningPlan));
-      
-      await learningPlan.save();
-      
-    } catch (error) {
-      console.error('保存学习计划失败:', error);
-      throw error;
+    if (success && this.shareData.learningPlan) {
+      this.showPlanResult = true;
     }
   }
 

+ 6 - 0
AiStudy-app/src/app/tab3/tab3.page.ts

@@ -146,6 +146,9 @@ export class Tab3Page implements OnInit {
       // 执行登录
       const user = await Parse.User.logIn(this.loginData.username, this.loginData.password);
       
+      // 只保存 Parse token,不更改 AI token
+      localStorage.setItem('parseToken', user.getSessionToken());
+      
       // 更新界面状态
       this.isLoggedIn = true;
       this.userName = user.get('username');
@@ -322,6 +325,9 @@ export class Tab3Page implements OnInit {
           handler: async () => {
             try {
               await Parse.User.logOut();
+              // 只清除 Parse token,保留 AI token
+              localStorage.removeItem('parseToken');
+              
               this.isLoggedIn = false;
               this.userName = '';
               this.userAvatar = 'assets/default-avatar.png';

+ 17 - 4
AiStudy-app/src/main.ts

@@ -13,9 +13,22 @@ import { provideHttpClient } from '@angular/common/http';
 import { Diagnostic } from '@awesome-cordova-plugins/diagnostic/ngx';
 // 设置Parse服务属性
 import Parse from "parse";
-Parse.initialize("dev");
-Parse.serverURL = "http://113.44.218.121:1337/parse";//数据库
-localStorage.setItem("NOVA_APIG_SERVER", 'aHR0cHMlM0ElMkYlMkZzZXJ2ZXIuZm1vZGUuY24lMkZhcGklMkZhcGlnJTJG')//飞马AI
+Parse.initialize("ncloudmaster");
+Parse.serverURL = "https://server.fmode.cn/parse";
+localStorage.setItem("NOVA_APIG_SERVER", 'aHR0cHMlM0ElMkYlMkZzZXJ2ZXIuZm1vZGUuY24lMkZhcGklMkZhcGlnJTJG')
+
+// 设置 AI 服务器地址和默认 Token
+localStorage.setItem("NOVA_APIG_SERVER", 'aHR0cHMlM0ElMkYlMkZzZXJ2ZXIuZm1vZGUuY24lMkZhcGklMkZhcGlnJTJG');
+localStorage.setItem("NOVA_APIG_TOKEN", "r:E4KpGvTEto-152708213191732951298");
+
+// 检查是否有存储的登录信息
+const storedToken = localStorage.getItem('parseToken');
+if (storedToken) {
+  Parse.User.become(storedToken).catch(() => {
+    // 如果 token 无效,清除存储的 token
+    localStorage.removeItem('parseToken');
+  });
+}
 
 bootstrapApplication(AppComponent, {
   providers: [
@@ -29,4 +42,4 @@ bootstrapApplication(AppComponent, {
     Diagnostic,
     
   ],
-});
+});