Parcourir la source

feat(sidepanel): 添加截图功能并优化聊天界面布局

- 在 Chat.vue 中添加截图功能,实现截图并显示在消息列表中
- 优化聊天界面布局,调整卡片样式和工具按钮
- 引入 html2canvas 依赖,用于截图功能
- 在 background.js 中添加处理截图消息的逻辑
- 在 content.js 中实现截图功能的具体逻辑
wzg il y a 4 mois
Parent
commit
780adecdae

+ 1 - 0
package.json

@@ -20,6 +20,7 @@
     "axios": "^1.7.9",
     "element-plus": "^2.9.1",
     "highlight.js": "^11.11.1",
+    "html2canvas": "^1.4.1",
     "lodash": "^4.17.21",
     "moment": "^2.30.1",
     "openai": "^4.85.4",

+ 539 - 0
src/assets/icon/demo.css

@@ -0,0 +1,539 @@
+/* Logo 字体 */
+@font-face {
+  font-family: "iconfont logo";
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
+}
+
+.logo {
+  font-family: "iconfont logo";
+  font-size: 160px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+/* tabs */
+.nav-tabs {
+  position: relative;
+}
+
+.nav-tabs .nav-more {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  height: 42px;
+  line-height: 42px;
+  color: #666;
+}
+
+#tabs {
+  border-bottom: 1px solid #eee;
+}
+
+#tabs li {
+  cursor: pointer;
+  width: 100px;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  font-size: 16px;
+  border-bottom: 2px solid transparent;
+  position: relative;
+  z-index: 1;
+  margin-bottom: -1px;
+  color: #666;
+}
+
+
+#tabs .active {
+  border-bottom-color: #f00;
+  color: #222;
+}
+
+.tab-container .content {
+  display: none;
+}
+
+/* 页面布局 */
+.main {
+  padding: 30px 100px;
+  width: 960px;
+  margin: 0 auto;
+}
+
+.main .logo {
+  color: #333;
+  text-align: left;
+  margin-bottom: 30px;
+  line-height: 1;
+  height: 110px;
+  margin-top: -50px;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.main .logo a {
+  font-size: 160px;
+  color: #333;
+}
+
+.helps {
+  margin-top: 40px;
+}
+
+.helps pre {
+  padding: 20px;
+  margin: 10px 0;
+  border: solid 1px #e7e1cd;
+  background-color: #fffdef;
+  overflow: auto;
+}
+
+.icon_lists {
+  width: 100% !important;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.icon_lists li {
+  width: 100px;
+  margin-bottom: 10px;
+  margin-right: 20px;
+  text-align: center;
+  list-style: none !important;
+  cursor: default;
+}
+
+.icon_lists li .code-name {
+  line-height: 1.2;
+}
+
+.icon_lists .icon {
+  display: block;
+  height: 100px;
+  line-height: 100px;
+  font-size: 42px;
+  margin: 10px auto;
+  color: #333;
+  -webkit-transition: font-size 0.25s linear, width 0.25s linear;
+  -moz-transition: font-size 0.25s linear, width 0.25s linear;
+  transition: font-size 0.25s linear, width 0.25s linear;
+}
+
+.icon_lists .icon:hover {
+  font-size: 100px;
+}
+
+.icon_lists .svg-icon {
+  /* 通过设置 font-size 来改变图标大小 */
+  width: 1em;
+  /* 图标和文字相邻时,垂直对齐 */
+  vertical-align: -0.15em;
+  /* 通过设置 color 来改变 SVG 的颜色/fill */
+  fill: currentColor;
+  /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
+      normalize.css 中也包含这行 */
+  overflow: hidden;
+}
+
+.icon_lists li .name,
+.icon_lists li .code-name {
+  color: #666;
+}
+
+/* markdown 样式 */
+.markdown {
+  color: #666;
+  font-size: 14px;
+  line-height: 1.8;
+}
+
+.highlight {
+  line-height: 1.5;
+}
+
+.markdown img {
+  vertical-align: middle;
+  max-width: 100%;
+}
+
+.markdown h1 {
+  color: #404040;
+  font-weight: 500;
+  line-height: 40px;
+  margin-bottom: 24px;
+}
+
+.markdown h2,
+.markdown h3,
+.markdown h4,
+.markdown h5,
+.markdown h6 {
+  color: #404040;
+  margin: 1.6em 0 0.6em 0;
+  font-weight: 500;
+  clear: both;
+}
+
+.markdown h1 {
+  font-size: 28px;
+}
+
+.markdown h2 {
+  font-size: 22px;
+}
+
+.markdown h3 {
+  font-size: 16px;
+}
+
+.markdown h4 {
+  font-size: 14px;
+}
+
+.markdown h5 {
+  font-size: 12px;
+}
+
+.markdown h6 {
+  font-size: 12px;
+}
+
+.markdown hr {
+  height: 1px;
+  border: 0;
+  background: #e9e9e9;
+  margin: 16px 0;
+  clear: both;
+}
+
+.markdown p {
+  margin: 1em 0;
+}
+
+.markdown>p,
+.markdown>blockquote,
+.markdown>.highlight,
+.markdown>ol,
+.markdown>ul {
+  width: 80%;
+}
+
+.markdown ul>li {
+  list-style: circle;
+}
+
+.markdown>ul li,
+.markdown blockquote ul>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown>ul li p,
+.markdown>ol li p {
+  margin: 0.6em 0;
+}
+
+.markdown ol>li {
+  list-style: decimal;
+}
+
+.markdown>ol li,
+.markdown blockquote ol>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown code {
+  margin: 0 3px;
+  padding: 0 5px;
+  background: #eee;
+  border-radius: 3px;
+}
+
+.markdown strong,
+.markdown b {
+  font-weight: 600;
+}
+
+.markdown>table {
+  border-collapse: collapse;
+  border-spacing: 0px;
+  empty-cells: show;
+  border: 1px solid #e9e9e9;
+  width: 95%;
+  margin-bottom: 24px;
+}
+
+.markdown>table th {
+  white-space: nowrap;
+  color: #333;
+  font-weight: 600;
+}
+
+.markdown>table th,
+.markdown>table td {
+  border: 1px solid #e9e9e9;
+  padding: 8px 16px;
+  text-align: left;
+}
+
+.markdown>table th {
+  background: #F7F7F7;
+}
+
+.markdown blockquote {
+  font-size: 90%;
+  color: #999;
+  border-left: 4px solid #e9e9e9;
+  padding-left: 0.8em;
+  margin: 1em 0;
+}
+
+.markdown blockquote p {
+  margin: 0;
+}
+
+.markdown .anchor {
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  margin-left: 8px;
+}
+
+.markdown .waiting {
+  color: #ccc;
+}
+
+.markdown h1:hover .anchor,
+.markdown h2:hover .anchor,
+.markdown h3:hover .anchor,
+.markdown h4:hover .anchor,
+.markdown h5:hover .anchor,
+.markdown h6:hover .anchor {
+  opacity: 1;
+  display: inline-block;
+}
+
+.markdown>br,
+.markdown>p>br {
+  clear: both;
+}
+
+
+.hljs {
+  display: block;
+  background: white;
+  padding: 0.5em;
+  color: #333333;
+  overflow-x: auto;
+}
+
+.hljs-comment,
+.hljs-meta {
+  color: #969896;
+}
+
+.hljs-string,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-strong,
+.hljs-emphasis,
+.hljs-quote {
+  color: #df5000;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-type {
+  color: #a71d5d;
+}
+
+.hljs-literal,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-attribute {
+  color: #0086b3;
+}
+
+.hljs-section,
+.hljs-name {
+  color: #63a35c;
+}
+
+.hljs-tag {
+  color: #333333;
+}
+
+.hljs-title,
+.hljs-attr,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-selector-attr,
+.hljs-selector-pseudo {
+  color: #795da3;
+}
+
+.hljs-addition {
+  color: #55a532;
+  background-color: #eaffea;
+}
+
+.hljs-deletion {
+  color: #bd2c00;
+  background-color: #ffecec;
+}
+
+.hljs-link {
+  text-decoration: underline;
+}
+
+/* 代码高亮 */
+/* PrismJS 1.15.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+code[class*="language-"],
+pre[class*="language-"] {
+  color: black;
+  background: none;
+  text-shadow: 0 1px white;
+  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+  text-align: left;
+  white-space: pre;
+  word-spacing: normal;
+  word-break: normal;
+  word-wrap: normal;
+  line-height: 1.5;
+
+  -moz-tab-size: 4;
+  -o-tab-size: 4;
+  tab-size: 4;
+
+  -webkit-hyphens: none;
+  -moz-hyphens: none;
+  -ms-hyphens: none;
+  hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection,
+pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection,
+code[class*="language-"] ::-moz-selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection,
+pre[class*="language-"] ::selection,
+code[class*="language-"]::selection,
+code[class*="language-"] ::selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+@media print {
+
+  code[class*="language-"],
+  pre[class*="language-"] {
+    text-shadow: none;
+  }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+  padding: 1em;
+  margin: .5em 0;
+  overflow: auto;
+}
+
+:not(pre)>code[class*="language-"],
+pre[class*="language-"] {
+  background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre)>code[class*="language-"] {
+  padding: .1em;
+  border-radius: .3em;
+  white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+  color: slategray;
+}
+
+.token.punctuation {
+  color: #999;
+}
+
+.namespace {
+  opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+  color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+  color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+  color: #9a6e3a;
+  background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+  color: #07a;
+}
+
+.token.function,
+.token.class-name {
+  color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+  color: #e90;
+}
+
+.token.important,
+.token.bold {
+  font-weight: bold;
+}
+
+.token.italic {
+  font-style: italic;
+}
+
+.token.entity {
+  cursor: help;
+}

+ 257 - 0
src/assets/icon/demo_index.html

@@ -0,0 +1,257 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8"/>
+  <title>iconfont Demo</title>
+  <link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
+  <link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
+  <link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
+  <link rel="stylesheet" href="demo.css">
+  <link rel="stylesheet" href="iconfont.css">
+  <script src="iconfont.js"></script>
+  <!-- jQuery -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
+  <!-- 代码高亮 -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
+  <style>
+    .main .logo {
+      margin-top: 0;
+      height: auto;
+    }
+
+    .main .logo a {
+      display: flex;
+      align-items: center;
+    }
+
+    .main .logo .sub-title {
+      margin-left: 0.5em;
+      font-size: 22px;
+      color: #fff;
+      background: linear-gradient(-45deg, #3967FF, #B500FE);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+    }
+  </style>
+</head>
+<body>
+  <div class="main">
+    <h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
+      <img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
+      
+    </a></h1>
+    <div class="nav-tabs">
+      <ul id="tabs" class="dib-box">
+        <li class="dib active"><span>Unicode</span></li>
+        <li class="dib"><span>Font class</span></li>
+        <li class="dib"><span>Symbol</span></li>
+      </ul>
+      
+      <a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=4858338" target="_blank" class="nav-more">查看项目</a>
+      
+    </div>
+    <div class="tab-container">
+      <div class="content unicode" style="display: block;">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe66e;</span>
+                <div class="name">新建对话</div>
+                <div class="code-name">&amp;#xe66e;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe621;</span>
+                <div class="name">历史记录</div>
+                <div class="code-name">&amp;#xe621;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe638;</span>
+                <div class="name">智能填写</div>
+                <div class="code-name">&amp;#xe638;</div>
+              </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="unicode-">Unicode 引用</h2>
+          <hr>
+
+          <p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
+          <ul>
+            <li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
+            <li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
+          </ul>
+          <blockquote>
+            <p>注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
+          </blockquote>
+          <p>Unicode 使用步骤如下:</p>
+          <h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
+<pre><code class="language-css"
+>@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.woff2?t=1741955378129') format('woff2'),
+       url('iconfont.woff?t=1741955378129') format('woff'),
+       url('iconfont.ttf?t=1741955378129') format('truetype');
+}
+</code></pre>
+          <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
+<pre><code class="language-css"
+>.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
+<pre>
+<code class="language-html"
+>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
+</code></pre>
+          <blockquote>
+            <p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+          </blockquote>
+          </div>
+      </div>
+      <div class="content font-class">
+        <ul class="icon_lists dib-box">
+          
+          <li class="dib">
+            <span class="icon iconfont icon-xinjianduihua"></span>
+            <div class="name">
+              新建对话
+            </div>
+            <div class="code-name">.icon-xinjianduihua
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-shouye"></span>
+            <div class="name">
+              历史记录
+            </div>
+            <div class="code-name">.icon-shouye
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-zhinengtianxie"></span>
+            <div class="name">
+              智能填写
+            </div>
+            <div class="code-name">.icon-zhinengtianxie
+            </div>
+          </li>
+          
+        </ul>
+        <div class="article markdown">
+        <h2 id="font-class-">font-class 引用</h2>
+        <hr>
+
+        <p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
+        <p>与 Unicode 使用方式相比,具有如下特点:</p>
+        <ul>
+          <li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
+          <li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
+        </ul>
+        <p>使用步骤如下:</p>
+        <h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
+<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
+</code></pre>
+        <h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
+</code></pre>
+        <blockquote>
+          <p>"
+            iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+        </blockquote>
+      </div>
+      </div>
+      <div class="content symbol">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-xinjianduihua"></use>
+                </svg>
+                <div class="name">新建对话</div>
+                <div class="code-name">#icon-xinjianduihua</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-shouye"></use>
+                </svg>
+                <div class="name">历史记录</div>
+                <div class="code-name">#icon-shouye</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-zhinengtianxie"></use>
+                </svg>
+                <div class="name">智能填写</div>
+                <div class="code-name">#icon-zhinengtianxie</div>
+            </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="symbol-">Symbol 引用</h2>
+          <hr>
+
+          <p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
+            这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
+          <ul>
+            <li>支持多色图标了,不再受单色限制。</li>
+            <li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
+            <li>兼容性较差,支持 IE9+,及现代浏览器。</li>
+            <li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
+          </ul>
+          <p>使用步骤如下:</p>
+          <h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
+<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
+</code></pre>
+          <h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
+<pre><code class="language-html">&lt;style&gt;
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+&lt;/style&gt;
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
+  &lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
+&lt;/svg&gt;
+</code></pre>
+          </div>
+      </div>
+
+    </div>
+  </div>
+  <script>
+  $(document).ready(function () {
+      $('.tab-container .content:first').show()
+
+      $('#tabs li').click(function (e) {
+        var tabContent = $('.tab-container .content')
+        var index = $(this).index()
+
+        if ($(this).hasClass('active')) {
+          return
+        } else {
+          $('#tabs li').removeClass('active')
+          $(this).addClass('active')
+
+          tabContent.hide().eq(index).fadeIn()
+        }
+      })
+    })
+  </script>
+</body>
+</html>

+ 27 - 0
src/assets/icon/iconfont.css

@@ -0,0 +1,27 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 4858338 */
+  src: url('iconfont.woff2?t=1741955378129') format('woff2'),
+       url('iconfont.woff?t=1741955378129') format('woff'),
+       url('iconfont.ttf?t=1741955378129') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-xinjianduihua:before {
+  content: "\e66e";
+}
+
+.icon-shouye:before {
+  content: "\e621";
+}
+
+.icon-zhinengtianxie:before {
+  content: "\e638";
+}
+

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
src/assets/icon/iconfont.js


+ 30 - 0
src/assets/icon/iconfont.json

@@ -0,0 +1,30 @@
+{
+  "id": "4858338",
+  "name": "w",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon-",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "43574817",
+      "name": "新建对话",
+      "font_class": "xinjianduihua",
+      "unicode": "e66e",
+      "unicode_decimal": 58990
+    },
+    {
+      "icon_id": "765433",
+      "name": "历史记录",
+      "font_class": "shouye",
+      "unicode": "e621",
+      "unicode_decimal": 58913
+    },
+    {
+      "icon_id": "42069762",
+      "name": "智能填写",
+      "font_class": "zhinengtianxie",
+      "unicode": "e638",
+      "unicode_decimal": 58936
+    }
+  ]
+}

BIN
src/assets/icon/iconfont.ttf


BIN
src/assets/icon/iconfont.woff


BIN
src/assets/icon/iconfont.woff2


+ 51 - 57
src/entrypoints/background.js

@@ -8,19 +8,16 @@ export default defineBackground(() => {
     if (message.type === 'PAGE_INFO') {
       // 转发到侧边栏
       chrome.runtime.sendMessage({
-        type: 'TO_SIDE_PANEL_PAGE_INFO',
-        data: message.data
+        type: 'TO_SIDE_PANEL_PAGE_INFO', data: message.data
       })
     }
     if (message.type === 'FROM_CONTENT_TO_SEND_PAGE_FORM') {
       console.log(message)
       chrome.runtime.sendMessage({
-        type: 'TO_SIDE_PANEL_FORM_INFO',
-        data: message.data
+        type: 'TO_SIDE_PANEL_FORM_INFO', data: message.data
       })
       return true
     }
-
     if (message.type === 'FROM_SIDE_PANEL_TO_ACTION') {
       console.log(565888)
       chrome.tabs.query({ active: true }, (tabs) => {
@@ -28,19 +25,15 @@ export default defineBackground(() => {
 
         if (tabs.length === 0) return // 确保有活动标签页
         const tabId = tabs[0].id // 获取当前活动的 tabId
-        chrome.tabs.sendMessage(
-          tabId,
-          { type: 'GET_TAG_ACTION', data: message.data },
-          (response) => {
-            if (chrome.runtime.lastError) {
-              console.error('消息发送失败:', chrome.runtime.lastError.message)
-            } else {
-              console.log('收到 content script 响应:', response)
-              console.log(response, 998)
-              sendResponse(response)
-            }
+        chrome.tabs.sendMessage(tabId, { type: 'GET_TAG_ACTION', data: message.data }, (response) => {
+          if (chrome.runtime.lastError) {
+            console.error('消息发送失败:', chrome.runtime.lastError.message)
+          } else {
+            console.log('收到 content script 响应:', response)
+            console.log(response, 998)
+            sendResponse(response)
           }
-        )
+        })
       })
       return true
     }
@@ -48,20 +41,16 @@ export default defineBackground(() => {
       chrome.tabs.query({ active: true }, (tabs) => {
         if (tabs.length === 0) return // 确保有活动标签页
         const tabId = tabs[0].id // 获取当前活动的 tabId
-        chrome.tabs.sendMessage(
-          tabId,
-          { type: 'GET_PAGE_FORM', data: 'Hello from background!' },
-          (response) => {
-            if (chrome.runtime.lastError) {
-              console.error('消息发送失败:', chrome.runtime.lastError.message)
-            } else {
-              console.log('收到 content script 响应:', response)
-              sendResponse(response)
-            }
-
-            return true
+        chrome.tabs.sendMessage(tabId, { type: 'GET_PAGE_FORM', data: 'Hello from background!' }, (response) => {
+          if (chrome.runtime.lastError) {
+            console.error('消息发送失败:', chrome.runtime.lastError.message)
+          } else {
+            console.log('收到 content script 响应:', response)
+            sendResponse(response)
           }
-        )
+
+          return true
+        })
       })
       return true
     }
@@ -69,18 +58,14 @@ export default defineBackground(() => {
       chrome.tabs.query({ active: true }, (tabs) => {
         if (tabs.length === 0) return // 确保有活动标签页
         const tabId = tabs[0].id // 获取当前活动的 tabId
-        chrome.tabs.sendMessage(
-          tabId,
-          { type: 'INPUT_FORM', data: message.data },
-          (response) => {
-            if (chrome.runtime.lastError) {
-              console.error('消息发送失败:', chrome.runtime.lastError.message)
-            } else {
-              console.log('收到 content script 响应:', response)
-              sendResponse(response.data)
-            }
+        chrome.tabs.sendMessage(tabId, { type: 'INPUT_FORM', data: message.data }, (response) => {
+          if (chrome.runtime.lastError) {
+            console.error('消息发送失败:', chrome.runtime.lastError.message)
+          } else {
+            console.log('收到 content script 响应:', response)
+            sendResponse(response.data)
           }
-        )
+        })
       })
       return true
     }
@@ -88,20 +73,31 @@ export default defineBackground(() => {
       chrome.tabs.query({ active: true }, (tabs) => {
         if (tabs.length === 0) return // 确保有活动标签页
         const tabId = tabs[0].id // 获取当前活动的 tabId
-        chrome.tabs.sendMessage(
-          tabId,
-          { type: 'GET_PAGE_INFO', data: 'Hello from background!' },
-          (response) => {
-            if (chrome.runtime.lastError) {
-              console.error('消息发送失败:', chrome.runtime.lastError.message)
-            } else {
-              console.log(response, 777)
+        chrome.tabs.sendMessage(tabId, { type: 'GET_PAGE_INFO', data: 'Hello from background!' }, (response) => {
+          if (chrome.runtime.lastError) {
+            console.error('消息发送失败:', chrome.runtime.lastError.message)
+          } else {
+            console.log(response, 777)
 
-              console.log('收到 content script 响应:', response)
-              sendResponse(response.data)
-            }
+            console.log('收到 content script 响应:', response)
+            sendResponse(response.data)
+          }
+        })
+      })
+      return true
+    }
+    // 截图
+    if (message.type === 'SCREENSHOT') {
+      chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
+        if (tabs.length === 0) return // 确保有活动标签页
+        const tabId = tabs[0].id // 获取当前活动的 tabId
+        chrome.tabs.sendMessage(tabId, message, (response) => {
+          if (chrome.runtime.lastError) {
+            sendResponse(chrome.runtime.lastError.message)
+          } else {
+            sendResponse(response)
           }
-        )
+        })
       })
       return true
     }
@@ -109,8 +105,7 @@ export default defineBackground(() => {
   chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
     if (changeInfo.status === 'complete' && tab.url) {
       chrome.runtime.sendMessage({
-        type: 'TO_SIDE_PANEL_PAGE_CHANGE',
-        data: tab
+        type: 'TO_SIDE_PANEL_PAGE_CHANGE', data: tab
       })
       currentTabId = tab.id
     }
@@ -122,8 +117,7 @@ export default defineBackground(() => {
         return
       }
       chrome.runtime.sendMessage({
-        type: 'TO_SIDE_PANEL_PAGE_CHANGE',
-        data: tab
+        type: 'TO_SIDE_PANEL_PAGE_CHANGE', data: tab
       })
       console.log('用户切换到新 Tab,URL:', tab)
       currentTabId = tab.id

+ 122 - 151
src/entrypoints/content.js

@@ -1,5 +1,6 @@
 // entrypoints/content.ts
 import PageAnalyzer, { cleanPage } from '../utils/page-analyzer'
+import html2canvas from 'html2canvas'
 import {
   formatDate,
   simulateCompleteUserAction,
@@ -10,9 +11,11 @@ import {
   findLabelForTextarea,
   getPageInfo
 } from '../utils/contentUtils'
+import { defineContentScript } from 'wxt/sandbox'
+import { domToCanvas } from '@/entrypoints/sidepanel/utils/index.js'
+
 export default defineContentScript({
-  matches: ['<all_urls>'],
-  main(ctx) {
+  matches: ['<all_urls>'], main(ctx) {
     let page = document.getElementsByTagName('body')[0]
     const src = chrome.runtime.getURL('images/begin.png')
     window.pageAnalyzer = new PageAnalyzer()
@@ -23,117 +26,124 @@ export default defineContentScript({
     let form = null
     let formChildren = []
     let excelDataA = {}
-    chrome.runtime.onMessage.addListener(
-      async (message, sender, sendResponse) => {
-        if (message.type === 'GET_PAGE_INFO') {
+    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+      if (message.type === 'GET_PAGE_INFO') {
+        sendResponse({
+          data: getPageInfo()
+        })
+      }
+      let dom = null
+      if (message.type === 'GET_TAG_ACTION') {
+        console.log(message.data)
+        const data = message.data
+        if (0) {
+          dom = document.getElementById(data.id)
+        } else {
+          dom = [...document.getElementsByTagName(data.tag.toLowerCase())]
+            // .filter(_ => _.className.includes(data.class))
+            .filter((_) => _.innerText.includes(data.text))[0]
+        }
+        console.log(dom)
+        if (!dom) {
+          sendResponse({ data: '未找到元素,请重试', statue: 'error' })
+          return
+        }
+        // 添加红色边框,500ms后移除边框,然后点击
+        const originalBorder = dom.style.border
+        dom.style.border = '2px solid red'
+        setTimeout(() => {
+          dom.style.border = originalBorder
+          dom.click()
+        }, 1000)
+        sendResponse({ data: '完成' })
+      }
+      if (message.type === 'GET_PAGE_FORM') {
+        const forms = document.querySelectorAll('form')
+        if (forms.length === 0) {
           sendResponse({
-            data: getPageInfo()
+            status: 'error', message: '没有找到表单'
           })
+          return
         }
-        let dom = null
-        if (message.type === 'GET_TAG_ACTION') {
-          console.log(message.data)
-          const data = message.data
-          if (0) {
-            dom = document.getElementById(data.id)
-          } else {
-            dom = [...document.getElementsByTagName(data.tag.toLowerCase())]
-              // .filter(_ => _.className.includes(data.class))
-              .filter((_) => _.innerText.includes(data.text))[0]
-          }
-          console.log(dom)
-          if (!dom) {
-            sendResponse({ data: '未找到元素,请重试', statue: 'error' })
-            return
-          }
-          // 添加红色边框,500ms后移除边框,然后点击
-          const originalBorder = dom.style.border
-          dom.style.border = '2px solid red'
-          setTimeout(() => {
-            dom.style.border = originalBorder
-            dom.click()
-          }, 1000)
-          sendResponse({ data: '完成' })
+        if (forms.length === 1) {
+          form = forms[0]
+          const cloneForm = forms[0].cloneNode(true)
+          cloneForm.querySelectorAll('svg').forEach((el) => el.remove())
+          formChildren = [...form.elements]
+          sendResponse({
+            status: 'ok', data: cloneForm.outerHTML
+          })
+          return
         }
-        if (message.type === 'GET_PAGE_FORM') {
-          const forms = document.querySelectorAll('form')
-          if (forms.length === 0) {
-            sendResponse({
-              status: 'error',
-              message: '没有找到表单'
-            })
-            return
-          }
-          if (forms.length === 1) {
-            form = forms[0]
-            const cloneForm = forms[0].cloneNode(true)
+        sendResponse({
+          status: 'select'
+        })
+        for (const item of forms) {
+          item.style.border = '2px solid red'
+
+          function handleClick(e) {
+            e.stopPropagation()
+            console.log(this.outerHTML)
+            for (const form of forms) {
+              form.style.border = 'none'
+              form.removeEventListener('click', handleClick, true)
+            }
+            form = this
+            console.log(form, 5855)
+            const cloneForm = this.cloneNode(true)
             cloneForm.querySelectorAll('svg').forEach((el) => el.remove())
             formChildren = [...form.elements]
-            sendResponse({
-              status: 'ok',
-              data: cloneForm.outerHTML
+            // sendResponse({
+            //   type: 'FROM_CONTENT_TO_SEND_PAGE_FORM',
+            //   data: this.outerHTML
+            // })
+            chrome.runtime.sendMessage({
+              type: 'FROM_CONTENT_TO_SEND_PAGE_FORM', data: cloneForm.outerHTML
             })
-            return
-          }
-          sendResponse({
-            status: 'select'
-          })
-          for (const item of forms) {
-            item.style.border = '2px solid red'
-            function handleClick(e) {
-              e.stopPropagation()
-              console.log(this.outerHTML)
-              for (const form of forms) {
-                form.style.border = 'none'
-                form.removeEventListener('click', handleClick, true)
-              }
-              form = this
-              console.log(form, 5855)
-              const cloneForm = this.cloneNode(true)
-              cloneForm.querySelectorAll('svg').forEach((el) => el.remove())
-              formChildren = [...form.elements]
-              // sendResponse({
-              //   type: 'FROM_CONTENT_TO_SEND_PAGE_FORM',
-              //   data: this.outerHTML
-              // })
-              chrome.runtime.sendMessage({
-                type: 'FROM_CONTENT_TO_SEND_PAGE_FORM',
-                data: cloneForm.outerHTML
-              })
-            }
-            item.addEventListener('click', handleClick, true)
           }
-          // if (!form) {
-          //   sendResponse({
-          //     status: 'error',
-          //     message: '没有找到表单'
-          //   })
-          //   return
-          // }
-          // sendResponse({
-          //   status: 'ok',
-          //   data: form.outerHTML
-          // })
-          // if (!form && document.querySelector("input")) {
-          //     const arr = []
-          //     const inputs = document.querySelectorAll("input")
-          //     formChildren = [...inputs]
 
-          //     for (const element of inputs) {
-          //         arr.push(element.outerHTML)
-          //     }
-          //     form = { outerHTML: JSON.stringify(arr) }
-          // }
+          item.addEventListener('click', handleClick, true)
         }
-        if (message.type === 'INPUT_FORM') {
-          const { formData, excelData } = message.data
-          excelDataA = excelData
-          console.log(formData, excelDataA)
+        // if (!form) {
+        //   sendResponse({
+        //     status: 'error',
+        //     message: '没有找到表单'
+        //   })
+        //   return
+        // }
+        // sendResponse({
+        //   status: 'ok',
+        //   data: form.outerHTML
+        // })
+        // if (!form && document.querySelector("input")) {
+        //     const arr = []
+        //     const inputs = document.querySelectorAll("input")
+        //     formChildren = [...inputs]
+
+        //     for (const element of inputs) {
+        //         arr.push(element.outerHTML)
+        //     }
+        //     form = { outerHTML: JSON.stringify(arr) }
+        // }
+      }
+      if (message.type === 'INPUT_FORM') {
+        const { formData, excelData } = message.data
+        excelDataA = excelData
+        console.log('12',formData, excelDataA)
+        (async () => {
           await handleFillInput(formData, 0)
-        }
+        })()
+      }
+      if (message.type === 'SCREENSHOT') {
+        domToCanvas().then(res => {
+          sendResponse({ status: 'ok', data: res })
+        }).catch(err => {
+          sendResponse({ status: 'error', message: err })
+        })
         return true
       }
-    )
+      return true
+    })
     const handleFillInput = async (data, index) => {
       console.log(data, 85888)
       console.log(formChildren, form)
@@ -141,19 +151,11 @@ export default defineContentScript({
       for (let i = 0; i < data.length; i++) {
         const item = data[i]
         if (item.findBy === 'id') {
-          const input = formChildren.find(
-            (child) => child.id === item.findByValue
-          )
-          if (
-            item.type === 'text' ||
-            item.type === 'textarea' ||
-            item.type === 'number'
-          ) {
+          const input = formChildren.find((child) => child.id === item.findByValue)
+          if (item.type === 'text' || item.type === 'textarea' || item.type === 'number') {
             if (!input) {
               if (item.label) {
-                const label = [...form.getElementsByTagName('label')].find(
-                  (label) => label.innerText.includes(item.label)
-                )
+                const label = [...form.getElementsByTagName('label')].find((label) => label.innerText.includes(item.label))
                 console.log(item, label)
                 if (label) {
                   const input = findLabelForInput(label)
@@ -169,48 +171,32 @@ export default defineContentScript({
           }
           if (item.type === 'radio' || item.type === 'checkbox') {
             if (item.label) {
-              const label = [...form.getElementsByTagName('label')].find(
-                (label) => label.innerText.includes(item.label)
-              )
+              const label = [...form.getElementsByTagName('label')].find((label) => label.innerText.includes(item.label))
               if (label) {
                 const span = findLabelForSpan(label)
                 span.forEach((span) => {
-                  span.innerText === excelDataA[item.excelColumn] &&
-                    span.click()
+                  span.innerText === excelDataA[item.excelColumn] && span.click()
                 })
               }
             }
           }
 
           if (item.type === 'date') {
-            const input = formChildren.find(
-              (child) => child.id === item.findByValue
-            )
+            const input = formChildren.find((child) => child.id === item.findByValue)
             if (excelDataA[item.excelColumn]) {
-              await simulateCompleteUserAction(
-                input,
-                input,
-                formatDate(excelDataA[item.excelColumn]),
-                formatDate(excelDataA[item.excelColumn])
-              )
+              await simulateCompleteUserAction(input, input, formatDate(excelDataA[item.excelColumn]), formatDate(excelDataA[item.excelColumn]))
             }
           }
         }
         if (item.findBy === 'placeholder') {
-          const input = formChildren.find(
-            (child) => child.placeholder === item.findByValue
-          )
+          const input = formChildren.find((child) => child.placeholder === item.findByValue)
           if (input) {
             simulateUserInput(input, excelDataA[item.excelColumn])
           }
         }
         if (item.findBy === 'label') {
           if (!excelDataA[item.excelColumn]) continue
-          const label = [...form.getElementsByTagName('label')].find(
-            (label) =>
-              label.innerText.includes(item.findByValue) ||
-              item.findByValue.includes(label.innerText)
-          )
+          const label = [...form.getElementsByTagName('label')].find((label) => label.innerText.includes(item.findByValue) || item.findByValue.includes(label.innerText))
           console.log(label)
           if (!label) continue
           if (item.type === 'radio' || item.type === 'checkbox') {
@@ -221,10 +207,7 @@ export default defineContentScript({
               span.forEach((span) => {
                 console.log(span.innerText)
                 if (span.innerText) {
-                  if (
-                    span.innerText.includes(excelDataA[item.excelColumn]) ||
-                    excelDataA[item.excelColumn].includes(span.innerText)
-                  ) {
+                  if (span.innerText.includes(excelDataA[item.excelColumn]) || excelDataA[item.excelColumn].includes(span.innerText)) {
                     console.log(span)
                     span.click()
                   }
@@ -238,23 +221,11 @@ export default defineContentScript({
             console.log(input, excelDataA[item.excelColumn])
 
             if (input) {
-              await simulateCompleteUserAction(
-                input,
-                input,
-                formatDate(excelDataA[item.excelColumn]),
-                formatDate(excelDataA[item.excelColumn])
-              )
+              await simulateCompleteUserAction(input, input, formatDate(excelDataA[item.excelColumn]), formatDate(excelDataA[item.excelColumn]))
             }
           }
 
-          if (
-            item.type === 'input' ||
-            item.type === 'text' ||
-            item.type === 'number' ||
-            item.type === 'tel' ||
-            item.type === 'email' ||
-            item.type === 'textarea'
-          ) {
+          if (item.type === 'input' || item.type === 'text' || item.type === 'number' || item.type === 'tel' || item.type === 'email' || item.type === 'textarea') {
             let input
             if (item.type === 'textarea') {
               input = findLabelForTextarea(label)

+ 72 - 44
src/entrypoints/sidepanel/Chat.vue

@@ -1,11 +1,11 @@
 <!-- Chat.vue -->
 <template>
-  <div class="chat-container">
+  <div class="chat-container items-center">
     <div v-if="!messages.length" class="message-list">
       <pageMask />
     </div>
     <!-- 消息列表 -->
-    <div class="message-list" v-else>
+    <div class="message-list w-full" v-else>
       <el-scrollbar ref="scrollbar" @scroll="handleScroll">
         <div class="messages">
           <div v-for="(message, index) in messages" :key="index"
@@ -49,22 +49,40 @@
            @handle-current-change="handleCurrentChange"
            @handel-intelligent-filling-click="handelIntelligentFillingClick" />
 
-    <div>
+    <div class="w-full max-w-[720px] p-[0_12px_12px]">
       <!-- 输入区域 -->
-      <div class="input-area">
+      <div class="input-area w-full">
         <div v-show="isShowPage" style="border-bottom: 1px solid #F0F0F0;">
           <div class="card_list">
-            <div v-for="(v,i) in pageInfoList" :key="i"
-                 :class="`card-content ${pageInfoList.length > 1 ? 'card_width' : ''}`">
-              <img :src="v?.favIconUrl" style="width: 24px;display: block" />
-              <div class="title-wrapper">
-                <span class="els title-scroller">{{ v?.title }}</span>
-                <span class="els url-scroller">{{ v?.url }}</span>
+            <template v-for="(v,i) in pageInfoList" :key="i">
+              <div class="card_image " v-if="v.type === 'image'">
+                <el-image
+                  v-loading="v.isUpload"
+                  class="w-full h-full p-0.5 object-cover"
+                  :src="v.url"
+                  :zoom-rate="1.2"
+                  :max-scale="7"
+                  :min-scale="0.2"
+                  :preview-src-list="[v.url]"
+                  :initial-index="4"
+                  fit="cover"
+                />
+                <el-icon class="closeIcon" size="16px" color="#909399" @click="deletePageInfo(i)">
+                  <CircleClose />
+                </el-icon>
               </div>
-              <el-icon class="closeIcon" size="16px" color="#909399" @click="deletePageInfo(i)">
-                <CircleClose />
-              </el-icon>
-            </div>
+              <div v-else :class="`card-content ${pageInfoList.length > 1 ? 'card_width' : ''}`">
+                <img :src="v?.favIconUrl" style="width: 24px;display: block" />
+                <div class="title-wrapper">
+                  <span class="els title-scroller">{{ v?.title }}</span>
+                  <span class="els url-scroller">{{ v?.url }}</span>
+                </div>
+                <el-icon class="closeIcon" size="16px" color="#909399" @click="deletePageInfo(i)">
+                  <CircleClose />
+                </el-icon>
+              </div>
+            </template>
+
           </div>
 
           <div v-show="type !== FunctionList.Intelligent_Form_filling" class="card-btn">
@@ -268,7 +286,7 @@ function handleCurrentChange(e) {
   options.forEach((item) => {
     item.options.forEach((item2) => {
       if (item2.value === e) {
-        msgStore.updateAIModel(item2)
+        msgStore.updateAIModel({ ...item2, api_sk: item['api_sk'], api_url: item['api_url'] })
       }
     })
   })
@@ -344,7 +362,7 @@ async function handleAsk() {
   await addMessage(str)
   if (type.value === FunctionList.Intelligent_Form_filling) {
     const res = await fetchRes(str)
-    console.log(res);
+    console.log(res)
 
     if (res.status === 'ok') {
       formInfo.value = res.data
@@ -360,10 +378,20 @@ async function handleAsk() {
 }
 
 function handleCapture() {
-  ElMessage({
-    message: '开发中...',
-    grouping: true,
-    showClose: true
+  chrome.runtime.sendMessage({
+    type: 'SCREENSHOT',
+    data: 'Start taking screenshots'
+  }, (response) => {
+    if (response.status === 'ok') {
+      isShowPage.value = true
+      pageInfoList.value.unshift({
+        isUpload: false,
+        fileId: '',
+        type: 'image',
+        url: response.data
+      })
+      console.log(response, pageInfoList.value)
+    }
   })
 }
 
@@ -393,31 +421,31 @@ const handleUpload = async (file) => {
         xlsxData.value[header] = readData[1][i]
       })
       addMessage(`已上传文件:${file.name}`, buildExcelUnderstandingPrompt(readData[0], file?.name, formInfo.value))
-      const {rawContent,status} = await streamRes()
-    if (status === 'ok') {
+      const { rawContent, status } = await streamRes()
+      if (status === 'ok') {
         let form = []
-      if (rawContent.includes('json')) form = JSON.parse(rawContent.split('json')[1].split('```')[0])
-      else form = JSON.parse(rawContent)
-      handleInput(xlsxData.value, form)
-    }
+        if (rawContent.includes('json')) form = JSON.parse(rawContent.split('json')[1].split('```')[0])
+        else form = JSON.parse(rawContent)
+        handleInput(xlsxData.value, form)
+      }
     } else {
-     try {
-       const { data, msg } = await getFileValue(file)
-       const res2 = await getFormKeyAndValue(data, formInfo.value)
-       xlsxData.value = res2.data
-       msg.rawContent = buildObjPrompt(res2.data, formInfo.value)
-       const { rawContent,status } = await streamRes()
-       if (status === 'ok') {
-         let form = []
-         if (rawContent.includes('json')) form = JSON.parse(rawContent.split('json')[1].split('```')[0])
-         else form = JSON.parse(rawContent)
-         handleInput(xlsxData.value, form)
-       }
-     } catch (error) {
-       msg.content = '文件解析出错'
-     } finally {
-      sendLoading.value = false
-     }
+      try {
+        const { data, msg } = await getFileValue(file)
+        const res2 = await getFormKeyAndValue(data, formInfo.value)
+        xlsxData.value = res2.data
+        msg.rawContent = buildObjPrompt(res2.data, formInfo.value)
+        const { rawContent, status } = await streamRes()
+        if (status === 'ok') {
+          let form = []
+          if (rawContent.includes('json')) form = JSON.parse(rawContent.split('json')[1].split('```')[0])
+          else form = JSON.parse(rawContent)
+          handleInput(xlsxData.value, form)
+        }
+      } catch (error) {
+        msg.content = '文件解析出错'
+      } finally {
+        sendLoading.value = false
+      }
     }
     type.value = FunctionList.File_Operation
     isShowPage.value = false
@@ -458,7 +486,7 @@ async function getFileValue(file) {
 
 // 组件挂载时滚动到底部
 onMounted(() => {
-  msgStore.updateAIModel(options[0].options[0])
+  msgStore.updateAIModel({ ...options[0].options[0], api_sk: options[0]['api_sk'], api_url: options[0]['api_url'] })
   useAutoResizeTextarea(tareRef, inputMessage)
   chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
     if (message.type === 'TO_SIDE_PANEL_PAGE_INFO') {

+ 1 - 1
src/entrypoints/sidepanel/component/pageMask.vue

@@ -4,7 +4,7 @@
 
 <template>
   <div class="w-full h-full pt-20">
-    <div class="max-w-96 text-left m-auto p-5">
+    <div class="max-w-[720px] text-left m-auto p-5">
       <p class="font-black text-4xl mb-4 text-black">你好,</p>
       <p class="font-black text-3xl mb-4 text-black">我是PVS智能助手,</p>
       <p class="font-black text-2xl mb-4 text-black">我今天能帮你什么?</p>

+ 10 - 4
src/entrypoints/sidepanel/component/tools.vue

@@ -16,7 +16,7 @@ watchEffect(() => {
 </script>
 
 <template>
-  <div class="px-3 py-2 flex justify-between items-center">
+  <div class="px-3 py-2 flex justify-between items-center w-full max-w-[720px]">
     <div class="flex items-center">
       <el-select placement="top" v-model="selectInput" placeholder="选择"
                  @change="(e: any)=>emit('handleCurrentChange',e)"
@@ -38,16 +38,22 @@ watchEffect(() => {
         <el-button class="tools_btn" link :icon="Scissor" @click="emit('handleCapture')" />
       </el-tooltip>
       <el-tooltip effect="dark" content="智能填表:选择后,在输入框描述填表流程" placement="top">
-        <el-button class="tools_btn" link :icon="Edit" @click="emit('handelIntelligentFillingClick')" />
+        <el-button class="tools_btn" link @click="emit('handelIntelligentFillingClick')">
+          <span class="iconfont icon-zhinengtianxie"></span>
+        </el-button>
       </el-tooltip>
 
     </div>
     <div class="flex items-center">
       <el-tooltip effect="dark" content="历史记录" placement="top">
-        <el-button class="tools_btn1" link :icon="AlarmClock" @click="emit('hisRecords')" />
+        <el-button class="tools_btn1" link @click="emit('hisRecords')">
+          <span class="iconfont icon-shouye"></span>
+        </el-button>
       </el-tooltip>
       <el-tooltip effect="dark" content="新对话" placement="top">
-        <el-button class="tools_btn1" link :icon="CirclePlus" @click="emit('addNewDialogue')" />
+        <el-button class="tools_btn1" link @click="emit('addNewDialogue')">
+          <span class="iconfont icon-xinjianduihua"></span>
+        </el-button>
       </el-tooltip>
     </div>
   </div>

+ 28 - 12
src/entrypoints/sidepanel/css/chat.scss

@@ -214,7 +214,6 @@
   background-color: #fff;
   border: 1px solid rgba(102, 102, 102, 0.3);
   border-radius: 16px;
-  margin: 0 12px 12px;
 
   .card_list {
     display: flex;
@@ -225,6 +224,32 @@
     .card_width {
       width: 124px !important;
     }
+
+    .closeIcon {
+      display: none;
+      position: absolute;
+      right: -5px;
+      top: -5px;
+      background-color: #ffffff;
+      border-radius: 50%;
+      z-index: 1;
+    }
+  }
+
+  .card_image {
+    width: 50px;
+    height: 50px;
+    border-radius: 10px;
+    border: 1px solid rgba(0, 0, 0, 0.15);
+    position: relative;
+    margin: 0 8px 6px 0;
+
+    .card_image_img {
+      width: 100%;
+      height: 100%;
+      border-radius: 8px;
+      position: relative;
+    }
   }
 
   .card-content {
@@ -266,24 +291,15 @@
     .title-scroller.scroll {
       animation: scrollTitle 10s linear infinite;
     }
-
-    .closeIcon {
-      display: none;
-      position: absolute;
-      right: -5px;
-      top: -5px;
-      background-color: #ffffff;
-      border-radius: 50%;
-      z-index: 1;
-    }
   }
 
-  .card-content:hover {
+  .card_image:hover, .card-content:hover {
     .closeIcon {
       display: block;
     }
   }
 
+
   .card-btn {
     padding: 0 0 4px;
 

+ 10 - 4
src/entrypoints/sidepanel/hook/useMsg.ts

@@ -1,4 +1,3 @@
-// 消息数组
 import { ref, reactive, nextTick, inject } from 'vue'
 import { storeToRefs } from 'pinia'
 import avator from '@/public/icon/32.png'
@@ -17,7 +16,7 @@ import { useMsgStore } from '@/store/modules/msg'
 import { FunctionList } from '../mock'
 
 export function useMsg(scrollbar?: any) {
-  const { messages, msgUuid } = storeToRefs(useMsgStore())
+  const { messages, msgUuid, AIModel, cancelQueue } = storeToRefs(useMsgStore())
   const indexTemp = ref(0)
   const taklToHtml = ref<any>(false)
   const sendLoading = ref(false)
@@ -88,6 +87,7 @@ export function useMsg(scrollbar?: any) {
           }
           if (status === 'select') {
             obj.content = '检测到左侧页面中有多个表单,请选择要填写的表单。'
+
             function handle(message, sender, sendResponse) {
               if (message.type === 'TO_SIDE_PANEL_FORM_INFO') {
                 console.log('收到一次性消息:', message.data)
@@ -148,6 +148,7 @@ export function useMsg(scrollbar?: any) {
     }
   }
   let str = ''
+
   async function fetchDataAndProcess(input: any, obj: any) {
     str = input
     console.log(str)
@@ -248,11 +249,16 @@ export function useMsg(scrollbar?: any) {
         .filter((item: any) => item.addToHistory)
         .slice(-20)
         .map((item: any) => ({
-          role: item.isSelf ? 'user' : 'system',
+          role: item.isSelf ? 'user' : 'assistant',
           content: item.rawContent
         }))
     }
-
+    if (AIModel.value.startSystemMsg === true) {
+      history.unshift({
+        role: 'system',
+        content: '你好'
+      })
+    }
     messages.value.push(obj)
     nextTick(() => {
       scrollbar.value?.setScrollTop(99999)

+ 2 - 0
src/entrypoints/sidepanel/main.ts

@@ -7,6 +7,8 @@ import 'element-plus/dist/index.css'
 import locale from 'element-plus/es/locale/lang/zh-cn' // 中文语言
 import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 import store from '@/store/index'
+import '@/assets/icon/iconfont'
+import '@/assets/icon/iconfont.css'
 import 'virtual:svg-icons-register'
 import SvgIcon from '@/entrypoints/sidepanel/components/SvgIcon/index.vue'
 

+ 19 - 3
src/entrypoints/sidepanel/mock.ts

@@ -4,26 +4,42 @@ import avator from '@/public/icon/32.png'
 export const options = [
   {
     label: '通义千问',
+    api_sk: 'sk-e9855234f47346049809ce23ed3ebe3f',
+    api_url: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
     options: [
       {
         label: '通义千问-Plus',
         value: 'qwen-plus',
-        url: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
         file: false
       },
       {
         label: '通义千问-Max',
         value: 'qwen-max',
-        url: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
         file: false
       },
       {
         label: '通义千问-Long',
         value: 'qwen-long',
-        url: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
         file: true
       }
     ]
+  }, {
+    label: 'DeepSeek',
+    api_sk: 'sk-3f91d3517b3648e8b4414f34de0696ea',
+    api_url: 'https://api.deepseek.com/v1',
+    options: [
+      {
+        label: 'DeepSeek-V3',
+        value: 'deepseek-chat',
+        file: false
+      },
+      {
+        label: 'DeepSeek-R1',
+        value: 'deepseek-reasoner',
+        file: false,
+        startSystemMsg: true
+      }
+    ]
   }
 
   // {

+ 4 - 0
src/entrypoints/sidepanel/style.css

@@ -81,3 +81,7 @@ pre::-webkit-scrollbar-thumb:hover {
     background-color: #ffffff;
   }
 }
+
+.iconfont{
+  font-size: 18px;
+}

+ 10 - 6
src/entrypoints/sidepanel/utils/ai-service.js

@@ -1,23 +1,24 @@
 import OpenAI from 'openai'
 import hljs from 'highlight.js'
 import 'highlight.js/styles/atom-one-dark.css' // 导入一个暗色主题样式
-import { getActivePinia } from 'pinia'
+import { getActivePinia, storeToRefs } from 'pinia'
 import { useMsgStore } from '@/store/modules/msg.ts'
 import { ref } from 'vue'
 
 // 创建新的 AbortController
 export const controllerList = ref([])
 
+
 export async function sendMessage(message) {
   const pinia = getActivePinia()
   if (!pinia) {
     throw new Error('No active Pinia instance found')
   }
   const store = useMsgStore(pinia)
-  const { openai, AIModel } = store
+  const { openai, AIModel, msgUuid } = store
   try {
     const controller = new AbortController()
-    controllerList.value.push(controller)
+    controller.id = msgUuid // 为每个控制器添加唯一 ID
     return await openai.chat.completions.create(
       {
         //模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
@@ -40,7 +41,8 @@ export async function getFileContent(data) {
       body: data
     }).then((res) => res.json())
     return res
-  } catch (error) {}
+  } catch (error) {
+  }
 }
 
 export async function getFormKey(data) {
@@ -50,7 +52,8 @@ export async function getFormKey(data) {
       body: JSON.stringify(data)
     }).then((res) => res.json())
     return res
-  } catch (error) {}
+  } catch (error) {
+  }
 }
 
 export async function hepl(data) {
@@ -60,7 +63,8 @@ export async function hepl(data) {
       body: JSON.stringify(data)
     }).then((res) => res.json())
     return res
-  } catch (error) {}
+  } catch (error) {
+  }
 }
 
 function formatMessages(currentMessage, Summary, html) {

+ 100 - 0
src/entrypoints/sidepanel/utils/index.js

@@ -1,5 +1,7 @@
 import * as XLSX from 'xlsx'
 import { ElMessage } from 'element-plus'
+import html2canvas from 'html2canvas'
+
 export function getPageInfo() {
   return new Promise((res, rej) => {
     chrome.runtime.sendMessage(
@@ -17,6 +19,7 @@ export function getPageInfo() {
     )
   })
 }
+
 export function handleInput(xlsxData, formMap) {
   chrome.runtime.sendMessage({
     type: 'FROM_SIDE_PANEL_TO_INPUT_FORM',
@@ -26,6 +29,7 @@ export function handleInput(xlsxData, formMap) {
     }
   })
 }
+
 export function getXlsxValue(file) {
   return new Promise((res, rej) => {
     try {
@@ -56,3 +60,99 @@ export function getXlsxValue(file) {
     }
   })
 }
+
+export function domToCanvas() {
+  return new Promise((resolve, reject) => {
+    // 检查是否已经存在 Shadow Host
+    const existingHost = document.querySelector('[data-shadow-host="true"]')
+    if (existingHost) {
+      existingHost.remove()
+    }
+    const host = document.createElement('div')
+    host.setAttribute('data-shadow-host', 'true') // 标记 Shadow Host
+    document.body.appendChild(host)
+    const shadowRoot = host.attachShadow({ mode: 'open' })
+    // 创建遮罩层
+    const overlay = document.createElement('div')
+    overlay.id = 'overlay'
+    overlay.style.position = 'fixed'
+    overlay.style.top = '0'
+    overlay.style.left = '0'
+    overlay.style.width = '100%'
+    overlay.style.height = '100%'
+    overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
+    overlay.style.zIndex = '9999'
+    overlay.style.cursor = 'crosshair'
+    shadowRoot.appendChild(overlay)
+
+    // 创建选择框
+    const selectionBox = document.createElement('div')
+    selectionBox.id = 'selection-box'
+    selectionBox.style.position = 'absolute'
+    selectionBox.style.border = '2px dashed #3498db'
+    selectionBox.style.backgroundColor = 'rgba(52, 152, 219, 0.2)'
+    selectionBox.style.pointerEvents = 'none'
+    overlay.appendChild(selectionBox)
+
+    let isSelecting = false
+    let startX, startY, endX, endY
+
+    // 鼠标按下事件
+    overlay.addEventListener('mousedown', (e) => {
+      isSelecting = true
+      startX = e.clientX
+      startY = e.clientY
+      selectionBox.style.left = `${startX}px`
+      selectionBox.style.top = `${startY}px`
+      selectionBox.style.width = '0'
+      selectionBox.style.height = '0'
+    })
+
+    // 鼠标移动事件
+    overlay.addEventListener('mousemove', (e) => {
+      if (isSelecting) {
+        endX = e.clientX
+        endY = e.clientY
+        const width = Math.abs(endX - startX)
+        const height = Math.abs(endY - startY)
+        selectionBox.style.left = `${Math.min(startX, endX)}px`
+        selectionBox.style.top = `${Math.min(startY, endY)}px`
+        selectionBox.style.width = `${width}px`
+        selectionBox.style.height = `${height}px`
+      }
+    })
+
+    // 鼠标松开事件
+    overlay.addEventListener('mouseup', async () => {
+      isSelecting = false
+
+      // 获取选择区域的坐标和尺寸
+      const rect = {
+        x: parseInt(selectionBox.style.left),
+        y: parseInt(selectionBox.style.top),
+        width: parseInt(selectionBox.style.width),
+        height: parseInt(selectionBox.style.height)
+      }
+      console.log(rect)
+      // 移除遮罩层和选择框
+      overlay.remove()
+      html2canvas(document.body, {
+        x: rect.left,
+        y: rect.top,
+        width: rect.width,
+        height: rect.height,
+        backgroundColor: null, // 背景透明
+        useCORS: true,
+        allowTaint: true
+      }).then(canvas => {
+        // 直接转换为 base64
+        const base64Data = canvas.toDataURL('image/png', 0.9); // 可调整质量参数
+        resolve(base64Data);
+      }).catch((error) => {
+        reject(error)
+      })
+    })
+  })
+
+
+}

+ 4 - 3
src/store/modules/msg.ts

@@ -7,15 +7,16 @@ export const useMsgStore = defineStore('msg', {
     messages: <any[]>[],
     pageInfoList: [],
     AIModel: <any>{},
-    openai: <any>null
+    openai: <any>null,
+    cancelQueue:<any>{}
   }),
   actions: {
     updateAIModel(model: any) {
       this.AIModel = model
       this.openai = new OpenAI({
         // apiKey: import.meta.env.VITE_OPENAI_API_KEY_TONG,
-        apiKey: import.meta.env.VITE_OPENAI_API_KEY_TONG,
-        baseURL: this.AIModel.url,
+        apiKey: this.AIModel.api_sk,
+        baseURL: this.AIModel.api_url,
         // timeout: 5000,
         dangerouslyAllowBrowser: true
       })

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff