Jelajahi Sumber

feat: new test completion

fmode 5 hari lalu
induk
melakukan
4c5cf44a51

+ 6 - 0
src/app/app.routes.ts

@@ -43,4 +43,10 @@ export const routes: Routes = [
         loadComponent: () => import('../modules/flow/comp-flow-editor/comp-flow-editor.component').then(m => m.CompFlowEditorComponent),
         runGuardsAndResolvers: "always",
     },
+    // 测试任务模块
+    {
+      path: "task/test",
+      loadComponent: () => import('../modules/task/page-test-completion/page-test-completion.component').then(m => m.PageTestCompletionComponent),
+      runGuardsAndResolvers: "always",
+  }
 ];

+ 2 - 1
src/app/tab1/tab1.page.html

@@ -10,6 +10,7 @@
  <ion-button (click)="doPoemTask()">执行诗文意境绘制任务集</ion-button>
  <ion-button (click)="doInqueryTask()">执行问诊任务集</ion-button>
   <ion-button (click)="testJSON()">测试JSON</ion-button>
+  <ion-button (click)="testCompletion()">测试TestCompletion</ion-button>
 
 
  <ul>
@@ -36,7 +37,7 @@
       @if(step.error){
         <span style="color:red;">{{step.error}}</span>
       }
-    </div>   
+    </div>
   }
   </ul>
 

+ 9 - 4
src/app/tab1/tab1.page.ts

@@ -1,5 +1,5 @@
 import { Component,OnInit } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton,IonIcon, ModalController } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton,IonIcon, ModalController,NavController } from '@ionic/angular/standalone';
 import { AgentTaskStep } from 'src/agent/agent.task';
 import { addIcons } from 'ionicons';
 import { radioButtonOffOutline, reloadOutline, checkmarkCircleOutline, closeCircleOutline } from 'ionicons/icons';
@@ -27,7 +27,10 @@ addIcons({radioButtonOffOutline,reloadOutline,checkmarkCircleOutline,closeCircle
 export class Tab1Page  {
 
 
-  constructor(private modalCtrl:ModalController) {
+  constructor(
+    private modalCtrl:ModalController,
+    private navCtrl:NavController,
+  ) {
 
 
   }
@@ -50,7 +53,7 @@ export class Tab1Page  {
     let task1 = TaskPoemPictureDesc({shareData:this.shareData,modalCtrl:this.modalCtrl});
     // 产生: shareData.images 渲染后图片
     let task2 = TaskPoemPictureCreate({shareData:this.shareData,modalCtrl:this.modalCtrl});
-    
+
     // 定义任务集
     let PoemTaskList = [task1,task2]
     // 传递给显示组件
@@ -59,7 +62,9 @@ export class Tab1Page  {
     TaskExecutor(PoemTaskList)
   }
 
-
+  testCompletion(){
+    this.navCtrl.navigateForward(["task","test"])
+  }
   testJSON(){
     let string = `
           ''''json

+ 44 - 0
src/modules/task/page-test-completion/page-test-completion.component.html

@@ -0,0 +1,44 @@
+<ion-content class="ion-padding">
+  <div class="container">
+    <ion-text color="primary">
+      <h1 class="ion-text-center">AI医疗导诊助手</h1>
+    </ion-text>
+
+    <!-- 用户症状描述区域 -->
+    <ion-item class="ion-margin-top">
+      <ion-textarea
+        [(ngModel)]="userDescription"
+        label="请描述您的症状"
+        labelPlacement="floating"
+        placeholder="例如:近三天持续头痛,伴有轻微发烧,体温在37.5-38度之间波动..."
+        fill="solid"
+        rows="5"
+        autoGrow="true"
+      ></ion-textarea>
+    </ion-item>
+
+    <ion-button
+      expand="block"
+      shape="round"
+      (click)="startConsult()"
+      class="ion-margin-top"
+      [disabled]="isLoading || !userDescription.trim()"
+    >
+      <ion-spinner *ngIf="isLoading" name="crescent"></ion-spinner>
+      <span *ngIf="!isLoading">开始导诊</span>
+      <ion-icon slot="end" name="medical-outline"></ion-icon>
+    </ion-button>
+
+    <!-- AI返回结果区域 -->
+    <div class="ion-margin-top" *ngIf="aiResponse">
+      <ion-text color="primary">
+        <h2>导诊建议:</h2>
+      </ion-text>
+      <ion-card>
+        <ion-card-content>
+          <pre style="white-space: pre-wrap;">{{ aiResponse }}</pre>
+        </ion-card-content>
+      </ion-card>
+    </div>
+  </div>
+</ion-content>

+ 14 - 0
src/modules/task/page-test-completion/page-test-completion.component.scss

@@ -0,0 +1,14 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  height: 100%;
+  max-width: 800px;
+  margin: 0 auto;
+}
+
+h1 {
+  font-size: 2rem;
+  font-weight: bold;
+  margin-bottom: 2rem;
+}

+ 22 - 0
src/modules/task/page-test-completion/page-test-completion.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { PageTestCompletionComponent } from './page-test-completion.component';
+
+describe('PageTestCompletionComponent', () => {
+  let component: PageTestCompletionComponent;
+  let fixture: ComponentFixture<PageTestCompletionComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [PageTestCompletionComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(PageTestCompletionComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 72 - 0
src/modules/task/page-test-completion/page-test-completion.component.ts

@@ -0,0 +1,72 @@
+import { Component, OnInit } from '@angular/core';
+import { ChatMessage, TestFmodeChatCompletion } from '../test.chat.completion';
+import { IonicModule } from '@ionic/angular';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+@Component({
+  selector: 'app-page-test-completion',
+  templateUrl: './page-test-completion.component.html',
+  styleUrls: ['./page-test-completion.component.scss'],
+  imports: [IonicModule, CommonModule, FormsModule],
+  standalone: true,
+})
+export class PageTestCompletionComponent implements OnInit {
+  userDescription: string = '';
+  aiResponse: string = '';
+  isLoading: boolean = false;
+
+  constructor() { }
+
+  ngOnInit() { return; }
+
+  startConsult() {
+    if (!this.userDescription.trim()) {
+      console.warn('用户描述不能为空');
+      return;
+    }
+
+    this.isLoading = true;
+    this.aiResponse = ''; // 清空之前的响应
+
+    // 导诊系统提示词
+    const diagnosisPrompt = `你是一个专业的医疗导诊助手。
+    请根据患者描述的症状,进行以下分析:
+1. 可能的疾病方向(列出2-3个最可能的)
+2. 建议就诊的科室
+3. 是否需要立即就医的紧急程度评估
+4. 日常护理建议
+请用专业但易懂的语言回答,避免直接诊断,而是提供就医指导。
+
+结果要求:
+1.首先,直接给出就诊的科室
+2.给用户建议
+`;
+
+    const messages: ChatMessage[] = [
+      { role: 'system', content: `` },
+      { role: 'user', content: diagnosisPrompt+`患者症状描述:${this.userDescription}` }
+    ];
+
+    localStorage.setItem("token",'r:9485439b35a9ce34bb95a9f3cfec2e6e'), // 替换为您的实际token
+    TestFmodeChatCompletion(
+      {
+        messages
+      },
+      (chunk, isLast) => {
+        if (isLast) {
+          this.isLoading = false;
+          console.log('\n对话完成');
+        } else {
+          this.aiResponse += chunk; // 累积AI返回的内容
+          console.log(chunk); // 实时输出内容
+        }
+      },
+      (error) => {
+        this.isLoading = false;
+        console.error('发生错误:', error.message);
+        this.aiResponse = '获取导诊建议时出错,请稍后再试。';
+      }
+    );
+  }
+}

+ 110 - 0
src/modules/task/test.chat.completion.ts

@@ -0,0 +1,110 @@
+export interface ChatMessage {
+  role: 'system' | 'user' | 'assistant';
+  content: string;
+}
+
+export interface FmodeChatCompletionParams {
+  messages: ChatMessage[];
+  model?: string; // 默认为 "fmode-4.5-128k"
+  temperature?: number; // 默认为 0.5
+  presence_penalty?: number; // 默认为 0
+  frequency_penalty?: number; // 默认为 0
+  stream?: boolean; // 默认为 true
+  token?: string; // Bearer token
+}
+
+/**
+ * 流式调用 Fmode GPT API
+ * @param params 请求参数
+ * @param onData 接收到数据时的回调函数 (content: string, isLast: boolean) => void
+ * @param onError 错误处理函数
+ */
+export async function TestFmodeChatCompletion(
+  params: FmodeChatCompletionParams,
+  onData: (content: string, isLast: boolean) => void,
+  onError?: (error: Error) => void
+): Promise<void> {
+  const endpoint = 'https://server.fmode.cn/api/apig/aigc/gpt/v1/chat/completions';
+
+  try {
+    let token = localStorage.getItem("token") || params.token
+    const response = await fetch(endpoint, {
+      method: 'POST',
+      headers: {
+        'accept': '*/*',
+        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
+        'cache-control': 'no-cache',
+        'content-type': 'text/plain; charset=utf-8',
+        'sec-ch-ua': '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
+        'sec-ch-ua-mobile': '?1',
+        'sec-ch-ua-platform': '"Android"',
+        'sec-fetch-dest': 'empty',
+        'sec-fetch-mode': 'cors',
+        'sec-fetch-site': 'same-site',
+      },
+      referrer: 'https://ai.fmode.cn/',
+      referrerPolicy: 'strict-origin-when-cross-origin',
+      body: JSON.stringify({
+        messages: params.messages,
+        stream: params.stream ?? true,
+        model: params.model ?? 'fmode-4.5-128k',
+        temperature: params.temperature ?? 0.5,
+        presence_penalty: params.presence_penalty ?? 0,
+        frequency_penalty: params.frequency_penalty ?? 0,
+        token: `Bearer ${token}`, // 注意这里根据您的实际token格式调整
+      }),
+      mode: 'cors',
+      credentials: 'omit',
+    });
+
+    if (!response.ok) {
+      const errorData = await response.json().catch(() => null);
+      throw new Error(
+        `HTTP error! status: ${response.status}, message: ${errorData?.message || 'Unknown error'}`
+      );
+    }
+
+    if (!response.body) {
+      throw new Error('No response body');
+    }
+
+    const reader = response.body.getReader();
+    const decoder = new TextDecoder('utf-8');
+    let buffer = '';
+
+    while (true) {
+      const { done, value } = await reader.read();
+      if (done) break;
+
+      buffer += decoder.decode(value, { stream: true });
+
+      // 处理可能的多条消息
+      const lines = buffer.split('\n');
+      buffer = lines.pop() || ''; // 最后一行可能不完整,保留在buffer中
+
+      for (const line of lines) {
+        if (line.trim() === '') continue;
+
+        const message = line.replace(/^data: /, '').trim();
+        if (message === '[DONE]') {
+          onData('', true); // 通知完成
+          return;
+        }
+
+        try {
+          const parsed = JSON.parse(message);
+          const content = parsed.choices[0]?.delta?.content || '';
+          if (content) {
+            onData(content, false);
+          }
+        } catch (err) {
+          console.error('Error parsing message:', err);
+          onError?.(err as Error);
+        }
+      }
+    }
+  } catch (error) {
+    console.error('Fetch error:', error);
+    onError?.(error as Error);
+  }
+}