Optimize your code and the Q&A assistant page has a shortcut to send questions. 21/139921/2 15.0.1
authorkaixiliu <liukaixi@chinamobile.com>
Fri, 17 Jan 2025 09:01:17 +0000 (17:01 +0800)
committerkaixiliu <liukaixi@chinamobile.com>
Mon, 20 Jan 2025 03:35:33 +0000 (11:35 +0800)
Issue-ID: USECASEUI-844
Change-Id: I652edab1945e3c2366fea388d6e3d8e5360d2922
Signed-off-by: kaixiliu <liukaixi@chinamobile.com>
usecaseui-portal/src/app/views/maas/use/code-block.directive.ts
usecaseui-portal/src/app/views/maas/use/use-application.component.html
usecaseui-portal/src/app/views/maas/use/use-application.component.less
usecaseui-portal/src/app/views/maas/use/use-application.component.ts
usecaseui-portal/src/assets/i18n/cn.json
usecaseui-portal/src/assets/i18n/cn_maas.json
usecaseui-portal/src/assets/i18n/en.json
usecaseui-portal/src/assets/i18n/en_maas.json

index 3124718..08efe92 100644 (file)
@@ -1,4 +1,4 @@
-import { AfterViewChecked, AfterViewInit, Directive, ElementRef, Renderer2 } from '@angular/core';
+import { Directive, ElementRef, Renderer2, Input } from '@angular/core';
 import { TranslateService } from '@ngx-translate/core';
 import { NzMessageService } from 'ng-zorro-antd';
 import { ClipboardService } from 'ngx-clipboard';
@@ -6,33 +6,36 @@ import { ClipboardService } from 'ngx-clipboard';
 @Directive({
   selector: '[appCodeBlock]'
 })
-export class CodeBlockDirective implements AfterViewChecked {
+export class CodeBlockDirective {
+  @Input() set appCodeBlock(status: string) {
+    if (status === 'finished') {
+      setTimeout(() => {
+        this.setCopyButton();
+      }, 0);
+      
+    }
+  }
 
   constructor(private el: ElementRef, private renderer: Renderer2, private clipboardService: ClipboardService,
     private message: NzMessageService, private translate: TranslateService
   ) { }
 
-  ngAfterViewChecked() {
-    this.setCopyButton();
-  }
-
-setCopyButton() {
-  const preElements = this.el.nativeElement.querySelectorAll('pre');
-
-  preElements.forEach(pre => {
-    const codeElement = pre.querySelector('code');
-    const copyButtonExists = pre.querySelector('button.copy-button');
-    if (codeElement && !copyButtonExists) {
-      const copyButton = this.renderer.createElement('button');
-      this.renderer.addClass(copyButton, 'copy-button');
-      this.renderer.setProperty(copyButton, 'innerHTML', 'Copy');
-      this.renderer.listen(copyButton, 'click', () => {
-        this.clipboardService.copyFromContent(codeElement.innerText);
-        this.message.success(this.translate.instant('maas.copy_to_clipboard'));
-      });
-      this.renderer.insertBefore(pre, copyButton, codeElement);
-    }
-  });
-}
+  setCopyButton() {
+    const preElements = this.el.nativeElement.querySelectorAll('pre');
 
+    preElements.forEach(pre => {
+      const codeElement = pre.querySelector('code');
+      const copyButtonExists = pre.querySelector('button.copy-button');
+      if (codeElement && !copyButtonExists) {
+        const copyButton = this.renderer.createElement('button');
+        this.renderer.addClass(copyButton, 'copy-button');
+        this.renderer.setProperty(copyButton, 'innerHTML', 'Copy');
+        this.renderer.listen(copyButton, 'click', () => {
+          this.clipboardService.copyFromContent(codeElement.innerText);
+          this.message.success(this.translate.instant('maas.copy_to_clipboard'));
+        });
+        this.renderer.insertBefore(pre, copyButton, codeElement);
+      }
+    });
+  }
 }
index 6509fbb..25c39f7 100644 (file)
@@ -45,7 +45,7 @@
                         [nz-tooltip]="'maas.copy' | translate"></span>
                 </div>
                 <span class="answer-text">
-                    <markdown appCodeBlock class="markdown answer-markdown" [ngClass]="{'hidden-cursor': chat.status==='finished'}" *ngIf="chat.answer else default" [data]="chat.answer"></markdown>
+                    <markdown appCodeBlock="{{chat.status}}" class="markdown answer-markdown" [ngClass]="{'hidden-cursor': chat.status==='finished'}" *ngIf="chat.answer else default" [data]="chat.answer"></markdown>
                     <ng-template #default>
                         <span class="answer default" [ngClass]="{'hidden-cursor': chat.status==='finished'}" #answerText>
                         </span>
@@ -57,7 +57,7 @@
     </div>
 
     <div class="input-wrapper">
-        <textarea nzAutosize nz-input [(ngModel)]="question" class="text-input question-input"></textarea>
+        <textarea #questionInput nzAutosize nz-input [(ngModel)]="question" class="text-input question-input" placeholder="{{'maas.questionPlaceholder' | translate}}" nzSize="small"></textarea>
         <div class="send-wrapper" [ngClass]="{'stop-wrapper': isGeneratingAnswer}">
             <div class="icon" (click)="doAction()"
                 [ngClass]="{'send-disabled': !isGeneratingAnswer &&!question, 'send-enabled': question && !isGeneratingAnswer, 'stop-generating': isGeneratingAnswer}"
index b02a2d6..f0ce4f7 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, ElementRef, OnInit, Renderer2, ViewChild } from '@angular/core';
+import { Component, ElementRef, OnInit, Renderer2, ViewChild, OnDestroy } from '@angular/core';
 import { NzMessageService } from 'ng-zorro-antd';
 import { SSE } from "sse.js";
 import { ActivatedRoute } from '@angular/router';
@@ -12,7 +12,7 @@ export type Chat = { question: string, answer: string, questionId: string, statu
   templateUrl: './use-application.component.html',
   styleUrls: ['./use-application.component.less']
 })
-export class UseApplicationComponent implements OnInit {
+export class UseApplicationComponent implements OnInit, OnDestroy {
   question: string;
   communicationMessage: string;
   chatHistory: Chat[] = [];
@@ -25,12 +25,16 @@ export class UseApplicationComponent implements OnInit {
   isGeneratingAnswer: boolean = false;
   stopGenerating = this.translate.instant('maas.stopGenerating');
   questionId = '';
+  @ViewChild('questionInput') questionInput: ElementRef;
+  private keydownListener: () => void;
+  perHight = 21;
   constructor(
     private message: NzMessageService,
     private route: ActivatedRoute,
     private myhttp: MaasApi,
     private translate: TranslateService,
-    private maasService: MaasService
+    private maasService: MaasService,
+    private renderer: Renderer2
   ) { }
 
   async ngOnInit() {
@@ -39,8 +43,15 @@ export class UseApplicationComponent implements OnInit {
       this.queryParams = params;
       this.selectedName = this.queryParams.id || this.selectedName;
     });
+    this.keydownListener = this.renderer.listen(this.questionInput.nativeElement, 'keydown', this.handleKeyDown.bind(this));
   }
   
+  ngOnDestroy() {
+    if (this.keydownListener) {
+      this.keydownListener();
+    }
+  }
+
   close() {
     if (this.currentSSE) {
       this.currentSSE.close();
@@ -125,4 +136,29 @@ export class UseApplicationComponent implements OnInit {
   deleteQuestion(questionId: string): void {
     this.chatHistory = this.chatHistory.filter(item => item.questionId !== questionId);
   }
+
+  handleKeyDown(event: KeyboardEvent) {
+    if (event.key === 'Enter') {
+      if (event.shiftKey || event.ctrlKey || event.altKey) {
+        const TextareaDom = this.questionInput.nativeElement;
+        const height = parseInt(TextareaDom.style.height.split('px')[0], 10) + this.perHight;
+        TextareaDom.style.height = `${height}px`;
+        if(event.ctrlKey || event.altKey) {
+          const index = TextareaDom.selectionStart;
+          const val = TextareaDom.value;
+          TextareaDom.value = `${val.slice(0, index)}\n${val.slice(index)}`;
+          TextareaDom.selectionStart = index + 1;
+          TextareaDom.selectionEnd = index + 1;
+        }
+      } else {
+        event.preventDefault();
+        if (this.isGeneratingAnswer) {
+          this.message.warning(this.translate.instant('maas.is_chatting'));
+        } else {
+          this.doAction();
+        }
+        
+      }
+    }
+  }
 }
index 883439b..2c5567a 100644 (file)
     "application": {
       "deleteApplicationContent": "确认删除该应用?删除后数据无法恢复,请确认!",
       "promptTip": "提示词需要大于20个字符"
-    }
+    },
+    "questionPlaceholder": "输入问题,发送 [Enter]/换行 [Ctrl(Alt/Shift) + Enter]",
+    "is_chatting": "正在聊天中...请等待结束"
   }
 }
\ No newline at end of file
index a6fcab5..7aa4981 100644 (file)
@@ -17,7 +17,9 @@
         "application": {
             "deleteApplicationContent": "确认删除该应用?删除后数据无法恢复,请确认!",
             "promptTip": "提示词需要大于20个字符"
-        }
+        },
+        "questionPlaceholder" : "输入问题,发送 [Enter]/换行 [Ctrl(Alt/Shift) + Enter]",
+        "is_chatting": "正在聊天中...请等待结束"
         
     }
 }
\ No newline at end of file
index f68588f..7e2f55a 100644 (file)
     "application": {
       "deleteApplicationContent": "Confirm deletion of this application? Data cannot be recovered after deletion, please confirm!",
       "promptTip": "The prompt needs to be larger than 20 characters."
-    }
+    },
+    "questionPlaceholder": "Enter a Question, Press [Enter] to Send / Press [Ctrl(Alt/Shift) + Enter] for New Line",
+    "is_chatting": "Chatting in progress... please wait until it finishes"
   }
 }
\ No newline at end of file
index a53c56b..ed85771 100644 (file)
@@ -17,6 +17,8 @@
         "application": {
             "deleteApplicationContent": "Confirm deletion of this application? Data cannot be recovered after deletion, please confirm!",
             "promptTip": "The prompt needs to be larger than 20 characters."
-        }
+        },
+        "questionPlaceholder" : "Enter a Question, Press [Enter] to Send / Press [Ctrl(Alt/Shift) + Enter] for New Line",
+        "is_chatting": "Chatting in progress... please wait until it finishes"
     }
 }
\ No newline at end of file