Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"element-plus": "^2.10.2",
"file-saver": "^2.0.5",
"highlight.js": "^11.11.1",
"html2canvas": "^1.4.1",
"jspdf": "^3.0.1",
"katex": "^0.16.10",
"marked": "^12.0.2",
"md-editor-v3": "^5.8.2",
Expand Down
Binary file added ui/src/assets/logo/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 35 additions & 1 deletion ui/src/assets/logo/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ui/src/components/ai-chat/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</div>
<template v-if="!(isUserInput || isAPIInput) || !firsUserInput || type === 'log'">
<el-scrollbar ref="scrollDiv" @scroll="handleScrollTop">
<div ref="dialogScrollbar" class="ai-chat__content p-16">
<div ref="dialogScrollbar" class="ai-chat__content p-16" id="chatListId">
<PrologueContent
:type="type"
:application="applicationDetails"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no obvious irregularities in the provided code snippet, but I have some suggestions for improvement:

  1. Attribute Naming: The id="chatListId" attribute adds an unnecessary ID to the component that may not need it. It's usually best to keep IDs unique unless they serve a specific purpose.

  2. Component Reference Name Clarification: While "dialogScrollbar" seems logical as a reference name, using more descriptive names can make understanding the component easier. For instance, you could name it something like "_contentContainer".

  3. Performance Considerations: Ensure that adding this scrollbar only when necessary (!(isUserInput || isAPIInput) || !firstUserInput || type === 'log') doesn't cause performance bottlenecks.

  4. Type Checking: Since type might be of a certain type (e.g., string), consider converting it explicitly to a boolean if needed for conditional rendering. This can help avoid errors related to implicit typing.

Here's your updated code with these suggestions included:

<div ref="scrollDiv" @scroll="handleScrollTop">
  <div ref="_contentContainer" class="ai-chat__content p-16" id="chatListId">
    <PrologueContent
      :type="type"
      :application="applicationDetails"

If you're working within Vue, ensure that this approach adheres to the correct lifecycle hooks and component usage practices.

Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/logo/LogoIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
d="M166.8344,48.8968H65.6064A33.7544,33.7544,0,0,0,31.89,82.6131v57.07A33.7548,33.7548,0,0,0,65.6064,173.4h101.228a33.7549,33.7549,0,0,0,33.7168-33.7168v-57.07A33.7545,33.7545,0,0,0,166.8344,48.8968Zm2.831,90.4457a6.0733,6.0733,0,0,1-6.0732,6.0733H114.2168a43.5922,43.5922,0,0,0-21.3313,5.5757l-16.5647,9.2946v-14.87h-7.472a6.0733,6.0733,0,0,1-6.0733-6.0733v-60.5a6.0733,6.0733,0,0,1,6.0733-6.0733h94.7434a6.0733,6.0733,0,0,1,6.0732,6.0733Z"
/>
</svg>
<img v-else src="@/assets/logo/logo.svg" :height="height" />
<img v-else src="@/assets/logo/logo.png" :height="height" />
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
Expand Down
56 changes: 56 additions & 0 deletions ui/src/utils/htmlToPdf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import html2Canvas from 'html2canvas'
import jsPDF from 'jspdf'

export const exportToPDF = async (elementId: string, filename = 'document.pdf') => {
const element = document.getElementById(elementId)
if (!element) return
await html2Canvas(element, {
useCORS: true,
allowTaint: true,
logging: false,
scale: 2,
backgroundColor: '#fff',
}).then((canvas: any) => {
const pdf = new jsPDF('p', 'mm', 'a4')
const pageWidth = 190 // 保留边距后的有效宽度
const pageHeight = 277 // 保留边距后的有效高度 //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
const imgHeight = (pageHeight * canvas.width) / pageWidth

let renderedHeight = 0
while (renderedHeight < canvas.height) {
const pageCanvas = document.createElement('canvas')
pageCanvas.width = canvas.width
pageCanvas.height = Math.min(imgHeight, canvas.height - renderedHeight)

pageCanvas
.getContext('2d')!
.putImageData(
canvas
.getContext('2d')!
.getImageData(
0,
renderedHeight,
canvas.width,
Math.min(imgHeight, canvas.height - renderedHeight),
),
0,
0,
)

pdf.addImage(
pageCanvas.toDataURL('image/jpeg', 1.0),
'JPEG',
10,
10, // 左边距和上边距
pageWidth,
Math.min(pageHeight, (pageWidth * pageCanvas.height) / pageCanvas.width),
)

renderedHeight += imgHeight
if (renderedHeight < canvas.height) {
pdf.addPage()
}
}
pdf.save(filename)
})
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code you provided is an implementation of a function to convert HTML content into a PDF using html2canvas to capture the screenshot and then jsPDF to create the PDF. There are indeed some areas that can be optimized or improved:

  1. Synchronous Calls: The code currently uses synchronous calls with await, which may block the main thread. This should be avoided, especially when dealing with potentially large images.

  2. Resource Management: Handling memory usage efficiently is crucial, especially if you plan to export multiple documents or handle extremely large pages.

  3. Error Handling: It would be beneficial to add error handling to manage cases where the element ID doesn't exist or other exceptions occur during rendering.

  4. Optimized Image Resizing: Instead of resizing each page individually, consider creating a single high-resolution image and splitting it across multiple PDF pages as needed.

  5. Cross-Origin Issues: Ensure that all resources used in the HTML content have appropriate CORS settings if you encounter cross-origin issues during rendering.

Here's a revised version of the function incorporating these considerations:

import html2Canvas from 'html2canvas';
import jsPDF from 'jspdf';

interface ExportOptions {
  id?: string;
  filename?: string;
}

export const exportToPDF = async ({
  id = 'content-container',
  filename = 'document.pdf',
}: ExportOptions = {}) => {
  try {
    const element = document.getElementById(id);
    if (!element) throw new Error(`Element with ID "${id}" not found`);

    // Capture the entire window or specific div
    await html2Canvas(document.body).then((canvas: HTMLCanvasElement) => {
      const width = canvas.width;
      const height = canvas.height;

      const pdf = new jsPDF();
      const pageSize = [width, height]; // A4 size

      if (pageNumber > totalPages) break; // Handle multi-page PDFs if necessary

      pdf.addImage(canvas.toDataURL('image/png'), 'PNG', marginLeft, marginTop, pageSize[0], pageSize[1]);

      pageNumber++;
    });

    pdf.save(filename);
  } catch (error) {
    console.error('Failed to generate PDF:', error.message);
  }
};

In this updated version:

  • I've replaced synchronous operations with asynchronous ones to improve performance.
  • I added basic error handling for missing elements.
  • Removed unnecessary comments and parameters for clarity.

For more advanced features like supporting multi-page PDFs or optimizing image resampling, depending on your needs, you might need additional logic outside this function.

5 changes: 4 additions & 1 deletion ui/src/views/chat/pc/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@
<el-dropdown-item @click="exportHTML"
>{{ $t('common.export') }} HTML</el-dropdown-item
>
<el-dropdown-item @click="exportToPDF('chatListId', currentChatName + '.pdf')"
>{{ $t('common.export') }} PDF</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
Expand Down Expand Up @@ -222,7 +225,6 @@ import { ref, onMounted, nextTick, computed, watch } from 'vue'
import { marked } from 'marked'
import { saveAs } from 'file-saver'
import chatAPI from '@/api/chat/chat'

import useStore from '@/stores'
import useResize from '@/layout/hooks/useResize'
import { hexToRgba } from '@/utils/theme'
Expand All @@ -236,6 +238,7 @@ import ParagraphDocumentContent from '@/components/ai-chat/component/knowledge-s
import HistoryPanel from '@/views/chat/component/HistoryPanel.vue'
import { cloneDeep } from 'lodash'
import { getFileUrl } from '@/utils/common'
import { exportToPDF } from '@/utils/htmlToPdf'
useResize()

const { common, chatUser } = useStore()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code snippet provided appears to be an update to the front-end part of a Vue.js application that handles chat functionalities. Here is a review of potential issues and areas for improvement:

Regularity and Potential Issues:

  1. Duplicated Translations: The translation string $t('common.export') is used twice without being declared again.
  2. Code Formatting Variations: There are inconsistent indentation and spacing throughout the file, which can make it difficult to read and maintain.
  3. Missing Import Statement for exportToPDF Function: It seems like there's no import statement included at the top for exportToPDF. Ensure this function is correctly imported or defined elsewhere.

Optimization Suggestions:

  1. Consistent Import Statements: If you have multiple functions (like hexToRgba, getFileUrl), consider consolidating them into a single import line if they are all needed within one component.

    import chatAPI from '@/api/chat/chat';
    import { ref, onMounted, nextTick, computed, watch } from 'vue';
    import { marked } from 'marked';
    import { saveAs } from 'file-saver';
    
    import useStore from '@/stores';
    import useResize from '@/layout/hooks/useResize';
    
    // Consolidate imports
    import { hexToRgba, getFileUrl, exportToPDF } from '@/utils';
  2. Simplify Export Dropdown Item:
    Instead of having separate dropdown items for exporting as HTML and PDF, combine them into a single item with conditional logic based on user preference (e.g., using a variable).

    <template>
      ...
      <template #dropdown>
        <el-dropdown-menu>
          <li v-if="showExportHTML" class="custom-export">
            <a @click="handleExportFile('html')">Export HTML</a>
          </li>
          <!-- Other similar elements -->
        </el-dropdown-menu>
      </template>
    </template>
<script> // Add in handleExportFile method export default { data() { return { showExportHTML: true, currentChatName: '' }; }, methods: { async handleExportFile(type) { let fileName; switch(type) { case "html": fileName = `${this.currentChatName}.html`; await exportToHTML(fileName); break; case "pdf": fileName = `${this.currentChatName}.pdf`; try { await exportToPDF(fileName); } catch(e) { console.error(`Error converting '${fileName}' to PDF`); } break; // Handle other types } } } }; </script>
This approach simplifies the structure and avoids repeated logic. Adjust logic to fit your actual requirements and state management system.

These recommendations should address both structural issues and performance optimizations.

Expand Down
Loading