首次提交

This commit is contained in:
Fang_Zhijian 2025-03-26 13:40:54 +08:00
commit aada5ef8c4
34 changed files with 9013 additions and 0 deletions

20
.edaignore Normal file
View File

@ -0,0 +1,20 @@
/.git/
/.husky/
/.vscode/
/build/
/config/
/coverage/
/node_modules/
/src/
/.editorconfig
/.eslintcache
/.eslintrc.js
/.gitattributes
/.gitignore
/.npmrc
/.prettierignore
/.prettierrc.js
/package-lock.json
/package.json
/tsconfig.json
debug.log

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
[*.{html,js,json,ts,css}]
indent_style = tab
indent_size = 4
[*.txt]
insert_final_newline = false
[*.{md,yml,yaml}]
indent_style = space
indent_size = 2

14
.eslintrc.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = {
plugins: ['eslint-plugin-tsdoc'],
extends: ['alloy', 'alloy/typescript'],
ignorePatterns: ['/build/dist/', '/coverage/', '/dist/', '/node_modules/', '/.eslintcache', 'debug.log'],
env: {
browser: true,
},
rules: {
'no-param-reassign': 'off',
'max-params': 'off',
'tsdoc/syntax': 'warn',
},
root: true,
};

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.json linguist-language=JSON-with-Comments

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/.vscode/*
!/.vscode/extensions.json
!/.vscode/settings.json
/coverage/
/dist/
/node_modules/
/.eslintcache
debug.log

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

3
.husky/pre-commit Normal file
View File

@ -0,0 +1,3 @@
#!/bin/sh
npm install
npx lint-staged

5
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,5 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{browse, marked, mathjs}" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/eext-light.iml" filepath="$PROJECT_DIR$/.idea/eext-light.iml" />
</modules>
</component>
</project>

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
# 位于中国大陆网络环境的用户,可以取消下行的注释,以获得更快的 NPM 安装速度
# registry=https://registry.npmmirror.com

6
.prettierignore Normal file
View File

@ -0,0 +1,6 @@
/build/dist/
/coverage/
/dist/
/node_modules/
/.eslintcache
debug.log

28
.prettierrc.js Normal file
View File

@ -0,0 +1,28 @@
/** @type {import("prettier").Options} */
module.exports = {
printWidth: 150,
tabWidth: 4,
useTabs: true,
semi: true,
singleQuote: true,
quoteProps: 'preserve',
trailingComma: 'all',
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'always',
rangeStart: 0,
rangeEnd: Infinity,
requirePragma: false,
insertPragma: false,
proseWrap: 'preserve',
htmlWhitespaceSensitivity: 'css',
endOfLine: 'lf',
embeddedLanguageFormatting: 'auto',
singleAttributePerLine: false,
plugins: ['@trivago/prettier-plugin-sort-imports'],
importOrder: ['<THIRD_PARTY_MODULES>', '^[./]'],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
importOrderGroupNamespaceSpecifiers: false,
importOrderCaseInsensitive: false,
};

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2024] JLCEDA <support@lceda.cn>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# eext-light
嘉立创EDA & EasyEDA 专业版扩展 —— 更方便地查看 LED 相较真实的色彩
<a href="https://github.com/klxf/eext-light" style="vertical-align: inherit;" target="_blank"><img src="https://img.shields.io/github/stars/klxf/eext-light" alt="GitHub Repo Stars" class="not-medium-zoom-image" style="display: inline; vertical-align: inherit;" /></a>&nbsp;<a href="https://github.com/klxf/eext-light/issues" style="vertical-align: inherit;" target="_blank"><img src="https://img.shields.io/github/issues/klxf/eext-light" alt="GitHub Issues" class="not-medium-zoom-image" style="display: inline; vertical-align: inherit;" /></a>&nbsp;<a href="https://github.com/klxf/eext-light" style="vertical-align: inherit;" target="_blank"><img src="https://img.shields.io/github/repo-size/klxf/eext-light" alt="GitHub Repo Size" class="not-medium-zoom-image" style="display: inline; vertical-align: inherit;" /></a>&nbsp;<a href="https://choosealicense.com/licenses/apache-2.0/" style="vertical-align: inherit;" target="_blank"><img src="https://img.shields.io/github/license/klxf/eext-light" alt="GitHub License" class="not-medium-zoom-image" style="display: inline; vertical-align: inherit;" /></a>&nbsp;<a href="https://www.npmjs.com/package/@jlceda/pro-api-types" style="vertical-align: inherit;" target="_blank"><img src="https://img.shields.io/npm/v/%40jlceda%2Fpro-api-types?label=pro-api-types" alt="NPM Version" class="not-medium-zoom-image" style="display: inline; vertical-align: inherit;" /></a>
> [!NOTE]
>
> 本拓展目前仍在开发阶段
## 开始使用
这是一个用于 LED 选型的拓展,你可以在 **嘉立创EDA****EasyEDA** 专业版中使用,更加方便地查看 LED 的颜色。
1. 下载本拓展,在 **嘉立创EDA****EasyEDA** 专业版中安装
2. 在首页、原理图、PCB页面中点击顶部菜单的 `LED助手`,即可打开本拓展
3. 使用很简单 —— 输入主波长,添加一个标签,点击按钮即可自动计算主波长在 sRGB 色彩空间内的颜色
![img 1](https://github.com/user-attachments/assets/db91c988-799b-4122-b5c3-75af5d77c25d)
4. 允许通过立创商城搜索 LED双击搜索结果可以将主波长、CID 自动填入表单
![img 2](https://github.com/user-attachments/assets/2553b70d-f41b-401b-9c61-0e2c00584d50)
> [!IMPORTANT]
>
> LED 的实际色彩受其实际电流、环境光影响,本拓展所展示的色彩仅为理论数值
>
> 理论基础https://fang.blog.miri.site/archives/1014/
## 开源许可
<a href="https://choosealicense.com/licenses/apache-2.0/" style="vertical-align: inherit;" target="_blank"><img src="https://img.shields.io/github/license/easyeda/pro-api-sdk" alt="GitHub License" class="not-medium-zoom-image" style="display: inline; vertical-align: inherit;" /></a>
本项目使用 [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) 开源许可协议

2
build/dist/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.zip
*.eext

90
build/packaged.ts Normal file
View File

@ -0,0 +1,90 @@
import fs from 'fs-extra';
import ignore from 'ignore';
import JSZip from 'jszip';
import * as extensionConfig from '../extension.json';
/**
*
*
* @param str -
* @returns
*/
function multiLineStrToArray(str: string): Array<string> {
return str.split(/[\r\n]+/);
}
/**
* UUID
*
* @param uuid - UUID
* @returns
*/
function testUuid(uuid?: string): uuid is string {
const regExp = /^[a-z0-9]{32}$/g;
if (uuid && uuid !== '00000000000000000000000000000000') {
return regExp.test(uuid.trim());
} else {
return false;
}
}
/**
* UUID
*
* @param uuid - UUID
* @returns UUID
*/
function fixUuid(uuid?: string): string {
uuid = uuid?.trim() || undefined;
if (testUuid(uuid)) {
return uuid.trim();
} else {
return crypto.randomUUID().replaceAll('-', '');
}
}
/**
*
*/
function main() {
if (!testUuid(extensionConfig.uuid)) {
const newExtensionConfig = { ...extensionConfig };
// @ts-ignore
delete newExtensionConfig.default;
newExtensionConfig.uuid = fixUuid(extensionConfig.uuid);
fs.writeJsonSync(__dirname + '/../extension.json', newExtensionConfig, { spaces: '\t', EOL: '\n', encoding: 'utf-8' });
}
const filepathListWithoutFilter = fs.readdirSync(__dirname + '/../', { encoding: 'utf-8', recursive: true });
const edaignoreListWithoutResolve = multiLineStrToArray(fs.readFileSync(__dirname + '/../.edaignore', { encoding: 'utf-8' }));
const edaignoreList: Array<string> = [];
for (const edaignoreLine of edaignoreListWithoutResolve) {
if (edaignoreLine.endsWith('/') || edaignoreLine.endsWith('\\')) {
edaignoreList.push(edaignoreLine.slice(0, edaignoreLine.length - 1));
} else {
edaignoreList.push(edaignoreLine);
}
}
const edaignore = ignore().add(edaignoreList);
const filepathListWithoutResolve = edaignore.filter(filepathListWithoutFilter);
const fileList: Array<string> = [];
const folderList: Array<string> = []; // 无用数据
for (const filepath of filepathListWithoutResolve) {
if (fs.lstatSync(filepath).isFile()) {
fileList.push(filepath.replace(/\\/g, '/'));
} else {
folderList.push(filepath.replace(/\\/g, '/'));
}
}
const zip = new JSZip();
for (const file of fileList) {
zip.file(file, fs.createReadStream(__dirname + '/../' + file));
}
zip.generateNodeStream({ type: 'nodebuffer', streamFiles: true }).pipe(
fs.createWriteStream(__dirname + '/dist/' + extensionConfig.name + '_v' + extensionConfig.version + '.eext'),
);
}
main();

21
config/esbuild.common.ts Normal file
View File

@ -0,0 +1,21 @@
import type esbuild from 'esbuild';
export default {
entryPoints: {
'index': './src/index',
},
entryNames: '[name]',
assetNames: '[name]',
bundle: true, // 用于内部方法调用,请勿修改
minify: false, // 用于内部方法调用,请勿修改
loader: {},
outdir: './dist/',
sourcemap: undefined,
platform: 'browser', // 用于内部方法调用,请勿修改
format: 'iife', // 用于内部方法调用,请勿修改
globalName: 'edaEsbuildExportName', // 用于内部方法调用,请勿修改
treeShaking: true,
ignoreAnnotations: true,
define: {},
external: [],
} satisfies Parameters<(typeof esbuild)['build']>[0];

13
config/esbuild.prod.ts Normal file
View File

@ -0,0 +1,13 @@
import esbuild from 'esbuild';
import common from './esbuild.common';
(async () => {
const ctx = await esbuild.context(common);
if (process.argv.includes('--watch')) {
await ctx.watch();
} else {
await ctx.rebuild();
process.exit();
}
})();

46
extension.json Normal file
View File

@ -0,0 +1,46 @@
{
"name": "eda-copilot",
"uuid": "30c8ce0d81f546fea716ea111c508ab2",
"displayName": "EDA Copilot",
"description": "嘉立创 EDA 多模态大模型 AI 助手",
"version": "1.1.0",
"publisher": "Mr_Fang <klxf@vip.qq.com>",
"engines": {
"eda": "^2.2.37"
},
"license": "Apache-2.0",
"repository": {
"type": "extension-store",
"url": ""
},
"categories": "Other",
"keywords": [],
"images": {
"logo": "./images/logo.png"
},
"homepage": "https://github.com/klxf/eda-copilot",
"bugs": "https://github.com/klxf/eda-copilot/issues",
"activationEvents": {},
"entry": "./dist/index",
"dependentExtensions": {},
"headerMenus": {
"sch": [
{
"id": "copilot-home",
"title": "Copilot",
"menuItems": [
{
"id": "copilot-iframe",
"title": "打开聊天",
"registerFn": "openIframe"
},
{
"id": "copilot-about",
"title": "关于...",
"registerFn": "openAbout"
}
]
}
]
}
}

342
iframe/css/style.css Normal file
View File

@ -0,0 +1,342 @@
:focus-visible {
outline: none;
}
::-webkit-scrollbar {
height: 0.25rem;
width: 0.25rem;
}
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
}
body {
font-family: sans-serif;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
p {
margin: 0;
}
p img {
max-width: 100%;
}
.chat-container {
width: 400px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.chat-header {
padding: 16px;
border-bottom: 1px solid #eee;
}
.chat-title {
font-size: 18px;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-subtitle {
font-size: 14px;
color: #888;
}
.chat-body {
padding: 16px;
height: 400px; /* Adjust as needed */
overflow-y: auto;
}
.message-bubble {
background-color: #e5f3ff;
border-radius: 8px;
padding: 8px 12px;
margin-bottom: 8px;
max-width: 80%;
align-self: flex-start; /* Align bot messages to the left */
word-wrap: break-word;
}
.message-bubble.user {
align-self: flex-end; /* Align user messages to the right */
margin-left: 20%;
background-color: #d9f7be; /* Different color for user messages */
}
.message-meta {
font-size: 12px;
color: #888;
margin-top: 4px;
}
.chat-footer {
padding: 8px 16px;
border-top: 1px solid #eee;
align-items: center;
}
.chat-footer .icon-group {
display: flex;
margin-right: auto; /* Push icons to the left */
}
.chat-footer .icon-group button {
border: none;
background: none;
padding: 8px;
margin-right: 8px;
border-radius: 4px;
cursor: pointer;
color: #888; /* Default icon color */
line-height: 8px;
}
.chat-footer .icon-group button:hover {
background-color: #f0f0f0; /* Hover effect */
}
.chat-footer .icon-group button img {
width: 16px;
height: 16px;
}
.chat-footer .icon-group button .button-tip {
display: none;
position: absolute;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 7px;
border-radius: 4px;
font-size: 12px;
margin-top: -32px;
margin-left: 8px;
transform: translateX(-50%);
}
.chat-footer .icon-group button:hover span {
display: block;
}
.chat-footer .input-group {
display: flex;
align-items: center;
margin-top: 4px;
}
.chat-input {
flex: 1;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
margin-right: 8px;
}
.send-button {
background-color: #007bff;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.settings-container {
position: fixed; /* Fixed position for floating */
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* Center the container */
width: 300px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 16px;
z-index: 10; /* Ensure it's on top */
display: none; /* Initially hidden */
}
.settings-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
.settings-item {
margin-bottom: 8px;
}
.settings-label {
display: block;
margin-bottom: 4px;
}
.settings-input {
width: 80%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.settings-buttons {
margin-top: 16px;
text-align: right;
}
.settings-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.settings-button.save {
background-color: #007bff;
color: white;
margin-left: 8px;
}
.file-bubble {
background-color: #d9f7be; /* Light blue */
border-radius: 8px;
padding: 8px 12px;
margin-bottom: 8px;
margin-left: 20%; /* Indent file bubbles */
max-width: 80%;
align-self: flex-end; /* Align to the right (assuming user sent) */
display: flex;
align-items: center; /* Vertically center content */
}
.file-icon {
margin-right: 8px;
width: 20px;
height: 20px;
}
.file-info {
flex: 1;
}
.file-name {
font-weight: bold;
max-width: 30vh;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-size {
font-size: 12px;
color: #777;
}
.clear-chat {
margin: 20px 0;
padding: 4px 0;
border-top: 1px solid #dedede;
border-bottom: 1px solid #dedede;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .05) inset;
display: flex ;
justify-content: center;
align-items: center;
overflow: hidden;
position: relative;
font-size: 12px;
mask-image: linear-gradient(90deg, transparent, #000, transparent);
}
.paste-image-group {
display: none;
position: absolute;
transform: translate(400%, -120%);
border: 1px solid hsla(0, 0%, 53%, .2);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
div#paste-image {
width: 72px;
height: 72px;
background-size: cover;
background-position: 50%;
}
div#paste-image-delete-btn {
width: 16px;
height: 16px;
position: absolute;
right: 0;
cursor: pointer;
filter: invert(50%) sepia(55%) saturate(1000%) hue-rotate(310deg) brightness(100%) contrast(100%);
background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDIzLjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiCgkgdmlld0JveD0iMCAwIDE2IDE2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxNiAxNiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnIGlkPSLlm77lsYJfMSI+Cgk8bGluZSBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiB4MT0iMiIgeTE9IjIiIHgyPSIxNCIgeTI9IjE0Ii8+Cgk8bGluZSBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiB4MT0iMTQiIHkxPSIyIiB4Mj0iMiIgeTI9IjE0Ii8+CjwvZz4KPGcgaWQ9IuWbvuWxgl8yIj4KPC9nPgo8L3N2Zz4K");
}
.loading {
position: relative;
display: inline-block;
width: 10px;
height: 10px;
border: 1px solid #007bff;
overflow: hidden;
animation: spin 3s ease infinite;
}
.loading::before {
content: '';
position: absolute;
top: -3px;
left: -3px;
width: 2em;
height: 2em;
background-color: hsla(211, 100%, 50%, 0.85);
transform-origin: center bottom;
transform: scaleY(1);
animation: fill 3s linear infinite;
}
@keyframes spin {
50%,
100% {
transform: rotate(360deg);
}
}
@keyframes fill {
25%,
50% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
.settings-alert {
color: #ff0000;
font-size: 12px;
margin-bottom: 10px;
padding-left: 6px;
border-left: 3px solid #ff0000;
}
.think {
color: gray;
border-left: 2px gray solid;
padding-left: 10px;
font-size: 12px;
display: block;
}
.think:before {
content: '思考...';
display: block;
}

301
iframe/index.html Normal file

File diff suppressed because one or more lines are too long

389
iframe/js/AI.js Normal file
View File

@ -0,0 +1,389 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function getAIResponse(contents) {
let response;
let thinking = document.createElement('div');
thinking.className = 'message-bubble model';
thinking.id = 'thinking';
thinking.innerHTML = '<div><div class="loading"></div> 思考中...</div>';
document.querySelector('.chat-body').appendChild(thinking);
document.querySelector('.chat-body').scrollTop = document.querySelector('.chat-body').scrollHeight;
if (MODEL_GROUP === "Gemini") {
response = await getGeminiResponse(contents);
} else if (MODEL_GROUP === "DeepSeek") {
response = await getDeepSeekResponse(contents);
} else if (MODEL_GROUP === "QWen") {
response = await getQWenResponse(contents);
} else if (MODEL_GROUP === "Custom") {
response = await getCustomResponse(contents);
} else {
response = { err_code: 1, error: "模型不被支持" };
}
console.log(response);
if (response.err_code === 0) {
document.getElementById('thinking').remove();
let content = response.response;
content = content.replace(/\u003cthink\u003e/, '<div class="think">').replace(/\u003c\/think\u003e/, '</div>');
createBubble('model', content, undefined);
if (MODEL_GROUP === "Gemini") {
chatHistory.push({ role: 'model', parts: [{ text: content }] });
} else if (MODEL_GROUP === "DeepSeek" || MODEL_GROUP === "Qwen" || MODEL_GROUP === "Custom") {
chatHistory.push({ role: 'assistant', content: content });
}
if (CHAT_TITLE === "新的对话") {
let temp = chatHistory.slice();
if (MODEL_GROUP === "Gemini") {
temp.push({ role: 'user', parts: [{ text: "用几个字总结这个对话,将其作为对话的标题" }] });
getGeminiResponse(temp).then(response => {
if (response.err_code === 0) {
const summary = response.response;
CHAT_TITLE = summary.replace('*', '');
document.getElementById('title').innerText = summary;
}
});
} else if (MODEL_GROUP === "DeepSeek") {
temp.push({ role: 'user', content: "用几个字总结这个对话,将其作为对话的标题,除此以外不要有任何其他文字" });
getDeepSeekResponse(temp).then(response => {
if (response.err_code === 0) {
const summary = response.response;
CHAT_TITLE = summary.replace(/\u003cthink\u003e.*\u003c\/think\u003e/ms, '').replace('*', '');
document.getElementById('title').innerText = CHAT_TITLE;
}
});
} else if (MODEL_GROUP === "QWen") {
temp.push({ role: 'user', content: "用几个字总结这个对话,将其作为对话的标题,除此以外不要有任何其他文字" });
getQWenResponse(temp).then(response => {
if (response.err_code === 0) {
const summary = response.response;
CHAT_TITLE = summary.replace(/\u003cthink\u003e.*\u003c\/think\u003e/ms, '').replace('*', '');
document.getElementById('title').innerText = CHAT_TITLE;
}
});
} else if (MODEL_GROUP === "Custom") {
temp.push({ role: 'user', content: "用几个字总结这个对话,将其作为对话的标题,除此以外不要有任何其他文字" });
getCustomResponse(temp).then((response) => {
if (response.err_code === 0) {
const summary = response.response;
CHAT_TITLE = summary.replace(/<think>.*<\/think>/ms, '').replaceAll('*', '').replaceAll('\n', '');
document.getElementById('title').innerText = CHAT_TITLE;
}
});
}
}
} else {
document.getElementById('thinking').remove();
createBubble('model', response.error, "异常");
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function setAICache(cache) {
if (MODEL_GROUP === "Gemini" && MODEL_NAME !== "gemini-2.0-flash-001") {
return await setGeminiCache(cache);
} else if (MODEL_GROUP === "QWen" || MODEL_GROUP === "DeepSeek" || MODEL_GROUP === "Custom") {
return await setOtherCache(decodeURIComponent(atob(cache)));
} else if (MODEL_GROUP === "Gemini" && MODEL_NAME === "gemini-2.0-flash-001") {
return await setOtherCache(decodeURIComponent(atob(cache)));
} else {
createBubble('model', "抱歉,该模型不支持缓存长上下文", "异常");
}
}
async function getGeminiResponse(contents) {
const API = GOOGLE_SETTINGS.HOST + "/v1beta/models/" + MODEL_NAME + ":generateContent?key=" + GOOGLE_SETTINGS.API_KEY;
console.log(chatHistory);
const data = {
contents: [
contents
],
safetySettings: [
[
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "BLOCK_ONLY_HIGH"
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "BLOCK_ONLY_HIGH"
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_ONLY_HIGH"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_ONLY_HIGH"
}
]
]
};
if (CACHE_NAME !== null) {
data.cachedContent = CACHE_NAME;
} else {
data.systemInstruction = { parts: [{ "text": "You are an electronic engineer. Use Chinese to answer." }] }
}
try {
const response = await eda.sys_ClientUrl.request(API, "POST", JSON.stringify(data), { headers: GOOGLE_SETTINGS.HEADER });
if (response.status !== 200) {
throw new Error(response.statusText);
}
const result = await response.json();
return {
response: result.candidates[0].content.parts[0].text,
err_code: 0
};
} catch (error) {
return {
error: error.message || "请求失败",
err_code: 1
};
}
}
async function getDeepSeekResponse(contents) {
const API = DS_SETTINGS.HOST + "/chat/completions";
let HEADERS = DS_SETTINGS.HEADER;
HEADERS['Authorization'] = 'Bearer ' + DS_SETTINGS.API_KEY;
const data = {
model: MODEL_NAME,
messages: contents,
stream: false
}
try {
const response = await eda.sys_ClientUrl.request(API, "POST", JSON.stringify(data), { headers: HEADERS });
if (response.status !== 200) {
throw new Error(response.statusText);
}
const result = await response.json();
return {
response: result.choices[0].message.content,
err_code: 0
};
} catch (error) {
return {
error: error.message || "请求失败",
err_code: 1
};
}
}
async function getQWenResponse(contents) {
const API = QWEN_SETTINGS.HOST + "/compatible-mode/v1/chat/completions";
let HEADERS = QWEN_SETTINGS.HEADER;
HEADERS['Authorization'] = 'Bearer ' + QWEN_SETTINGS.API_KEY;
const data = {
model: MODEL_NAME,
messages: contents,
}
try {
const response = await eda.sys_ClientUrl.request(API, "POST", JSON.stringify(data), { headers: HEADERS });
if (response.status !== 200) {
throw new Error(response.statusText);
}
const result = await response.json();
return {
response: result.choices[0].message.content,
err_code: 0
};
} catch (error) {
return {
error: error.message || "请求失败",
err_code: 1
};
}
}
async function getCustomResponse(contents) {
const API = CUSTOM_SETTINGS.HOST + CUSTOM_SETTINGS.ENDPOINT;
const data = {
model: CUSTOM_SETTINGS.MODEL,
messages: contents,
stream: false
}
try {
const response = await eda.sys_ClientUrl.request(API, "POST", JSON.stringify(data), { headers: CUSTOM_SETTINGS.HEADER });
if (response.status !== 200) {
throw new Error(response.statusText);
}
const result = await response.json();
return {
response: result.choices[0].message.content,
err_code: 0
};
} catch (error) {
return {
error: error.message || "请求失败",
err_code: 1
};
}
}
async function setGeminiCache(cache) {
let API = GOOGLE_SETTINGS.HOST + "/v1beta/cachedContents?key=" + GOOGLE_SETTINGS.API_KEY;
const data = {
model: "models/" + MODEL_NAME,
contents: [
{ role: 'user', parts: [{ inline_data: { mime_type: "text/plain", data: cache} }, {text: "Here's a netlist file describing the circuit diagram, and I'm going to ask you questions about it."}] }
],
systemInstruction: {
parts: [
{
"text": "You are an electronic engineer. The text describes a netlist of circuit diagrams. When asked a question about a component, it is displayed if there is a URL in the netlist, otherwise it is not. Use Chinese to answer."
}
]
},
}
let thinking = document.createElement('div');
thinking.className = 'message-bubble model';
thinking.id = 'thinking';
thinking.innerHTML = '<div><div class="loading"></div> 解读中...</div>';
document.querySelector('.chat-body').appendChild(thinking);
document.querySelector('.chat-body').scrollTop = document.querySelector('.chat-body').scrollHeight;
eda.sys_ClientUrl.request(API, "POST", JSON.stringify(data), { headers: GOOGLE_SETTINGS.HEADER }).then(response => {
if (response.status !== 200) {
throw new Error(response.statusText);
}
return response.json();
}).then(result => {
document.getElementById('thinking').remove();
createBubble('model', "好的,接下来你可以围绕这张原理图向我提问", undefined);
CACHE_NAME = result.name;
}).catch(error => {
document.getElementById('thinking').remove();
createBubble('model', error.toString(), "异常");
});
}
async function setOtherCache(cache) {
chatHistory.push({ role: 'user', content: '这是一个电路图的网表,后续回答依照这个网表回答:' + cache });
chatHistory.push({ role: 'assistant', content: '好的,我了解了'});
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function getNetlist() {
let netlist = '';
eda.sch_Netlist.getNetlist().then(data => {
netlist = data[0].toString();
const cache = btoa(encodeURIComponent(netlist));
// 获取 cache 的字节数
let size = cache.length;
let unit = 'B';
if (size > 1024) {
size /= 1024;
unit = 'KB';
}
if (size > 1024) {
size /= 1024;
unit = 'MB';
}
let fileBubble = document.createElement('div');
fileBubble.classList.add('file-bubble');
fileBubble.innerHTML = `<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMjMuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iJiN4NTZGRTsmI3g1QzQyO18xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiCgkgeT0iMHB4IiB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDE2IDE2OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxyZWN0IHN0eWxlPSJmaWxsOm5vbmU7IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiLz4KPHJlY3Qgc3R5bGU9ImZpbGw6bm9uZTsiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIvPgo8cGF0aCBkPSJNMTUsNGgtMVYyYzAtMC41NS0wLjQ1LTEtMS0xSDRDMy40NSwxLDMsMS40NSwzLDJ2NEgxLjVDMS4yMiw2LDEsNi4yMiwxLDYuNUMxLDYuNzgsMS4yMiw3LDEuNSw3SDNoMWgxLjUKCUM1Ljc4LDcsNiw2Ljc4LDYsNi41QzYsNi4yMiw1Ljc4LDYsNS41LDZINFYyaDl2MmgtMWMtMC41NSwwLTEsMC40NS0xLDF2N2MwLDAuNTUsMC40NSwxLDEsMWgxdjJINHYtNGgxLjVDNS43OCwxMSw2LDEwLjc4LDYsMTAuNQoJQzYsMTAuMjIsNS43OCwxMCw1LjUsMTBINEgzSDEuNUMxLjIyLDEwLDEsMTAuMjIsMSwxMC41QzEsMTAuNzgsMS4yMiwxMSwxLjUsMTFIM3Y0YzAsMC41NSwwLjQ1LDEsMSwxaDljMC41NSwwLDEtMC40NSwxLTF2LTJoMQoJYzAuNTUsMCwxLTAuNDUsMS0xVjVDMTYsNC40NSwxNS41NSw0LDE1LDR6IE0xNSwxMmgtMWgtMWgtMVY1aDFoMWgxVjEyeiIvPgo8L3N2Zz4K" alt="File Icon" class="file-icon">
<div class="file-info">
<div class="file-name">网表文件</div>
<div class="file-size">${size} ${unit}</div>
</div>`;
document.querySelector('.chat-body').appendChild(fileBubble);
setAICache(cache);
})
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function setAIFile() {
if (MODEL_GROUP !== "Gemini") {
eda.sys_Message.showToastMessage('当前模型尚未支持该功能', 'warn');
return;
}
const file = await eda.sys_FileSystem.openReadFileDialog("pdf");
const reader = new FileReader();
reader.onload = function (event) {
const base64 = event.target.result;
const title = file.name;
// 判断是不是 pdf 文件
if (file.type !== 'application/pdf') {
eda.sys_Message.showToastMessage('仅可上传 PDF 格式的数据手册', 'warn');
return;
}
FILE_BASE64 = base64.replaceAll('data:application/pdf;base64,', '');
// 如果 chat-body 中最后一个元素是 file-bubble替换之
const chatBody = document.querySelector('.chat-body');
const lastElement = chatBody.lastElementChild;
if (lastElement && lastElement.classList.contains('file-bubble')) {
lastElement.remove();
}
// 创建 file bubble
let size = FILE_BASE64.length;
let unit = 'B';
if (size > 1024) {
size /= 1024;
unit = 'KB';
}
if (size > 1024) {
size /= 1024;
unit = 'MB';
}
let fileBubble = document.createElement('div');
fileBubble.classList.add('file-bubble');
fileBubble.innerHTML = `<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMjMuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iJiN4NTZGRTsmI3g1QzQyO18xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiCgkgeT0iMHB4IiB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDE2IDE2OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik04LjI5NiwxSDNDMi40NDgsMSwyLDEuNDQ4LDIsMnYxM2MwLDAuNTUyLDAuNDQ4LDEsMSwxaDEwYzAuNTUyLDAsMS0wLjQ0OCwxLTFWNS43MDRjMC0wLjI2NS0wLjEwNS0wLjUyLTAuMjkzLTAuNzA3CgljLTAuMzYyLTAuMjg1LTAuNzI1LTAuNTcxLTEuMDg3LTAuODU2Yy0wLjcwNi0wLjU1Ni0xLjQxMi0xLjExMi0yLjExOC0xLjY2OGMtMC4zNjgtMC4yOS0wLjc0NC0wLjU3MS0xLjExLTAuODY0CglDOS4wNTcsMS4zNDIsOC43NTcsMSw4LjI5NiwxeiBNMTIuMjksNUg5VjIuNzFMMTIuMjksNXogTTMsMTVWMmg1djNjMCwwLjU1MiwwLjQ0OCwxLDEsMWg0djlIM3oiLz4KPHJlY3Qgc3R5bGU9ImZpbGw6bm9uZTsiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIvPgo8cmVjdCBzdHlsZT0iZmlsbDpub25lOyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+Cjwvc3ZnPgo=" alt="File Icon" class="file-icon">
<div class="file-info">
<div class="file-name">${title}</div>
<div class="file-size">${size} ${unit}</div>
</div>`;
document.querySelector('.chat-body').appendChild(fileBubble);
};
reader.readAsDataURL(file);
}
/* 废弃 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function getDatasheetUrl() {
try {
const selectedPrimitives = await eda.sch_SelectControl.getAllSelectedPrimitives_PrimitiveId();
const primitive = await eda.sch_PrimitiveComponent.get(selectedPrimitives[0]);
let url = primitive[0].otherProperty.Datasheet;
// atta.szlcsc.com -> atta-szlcsc.mirror.soraharu.com
url = url.replace('atta.szlcsc.com', 'atta-szlcsc.mirror.soraharu.com');
console.log(url);
return url;
} catch (error) {
console.error(error);
return null;
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function getDatasheetData(url) {
console.log(url);
try {
const response = await eda.sys_ClientUrl.request(url);
const data = await response.text();
console.log(data);
return data;
} catch (error) {
console.error(error);
return null;
}
}

213
iframe/js/main.js Normal file
View File

@ -0,0 +1,213 @@
let CACHE_NAME = null;
let chatHistory = [];
let CUSTOM_SETTINGS = {
HOST: "http://localhost:11434",
ENDPOINT: "/v1/chat/completions",
MODEL: "",
HEADER: {
"Content-Type": "application/json"
}
};
let GOOGLE_SETTINGS = {
HOST: "https://generativelanguage.googleapis.com",
API_KEY: "",
HEADER: {
"Content-Type": "application/json"
}
};
let DS_SETTINGS = {
HOST: "https://api.deepseek.com",
API_KEY: "",
HEADER: {
"Content-Type": "application/json"
}
};
let QWEN_SETTINGS = {
HOST: "https://dashscope.aliyuncs.com",
API_KEY: "",
HEADER: {
"Content-Type": "application/json"
}
};
let MODEL_NAME = "gemini-1.5-flash-002";
let MODEL_GROUP = "Gemini";
let CHAT_TITLE = "新的对话";
let FILE_BASE64 = null;
console.log(eda.sys_Storage.getExtensionAllUserConfigs());
if(eda.sys_Storage.getExtensionUserConfig("GOOGLE-SETTINGS") !== undefined) {
GOOGLE_SETTINGS = eda.sys_Storage.getExtensionUserConfig("GOOGLE-SETTINGS");
}
if (eda.sys_Storage.getExtensionUserConfig("DS-SETTINGS") !== undefined) {
DS_SETTINGS = eda.sys_Storage.getExtensionUserConfig("DS-SETTINGS");
}
if (eda.sys_Storage.getExtensionUserConfig("QWEN-SETTINGS") !== undefined) {
QWEN_SETTINGS = eda.sys_Storage.getExtensionUserConfig("QWEN-SETTINGS");
}
if (eda.sys_Storage.getExtensionUserConfig("CUSTOM-SETTINGS") !== undefined) {
CUSTOM_SETTINGS = eda.sys_Storage.getExtensionUserConfig("CUSTOM-SETTINGS");
}
if(GOOGLE_SETTINGS.HOST !== null && GOOGLE_SETTINGS.HOST !== "" && GOOGLE_SETTINGS.HOST !== undefined) {
document.getElementById("custom-google-host").value = GOOGLE_SETTINGS.HOST;
}
if (GOOGLE_SETTINGS.API_KEY !== null && GOOGLE_SETTINGS.API_KEY !== "" && GOOGLE_SETTINGS.API_KEY !== undefined) {
document.getElementById("google-api-key").value = GOOGLE_SETTINGS.API_KEY;
}
if (GOOGLE_SETTINGS.HEADER !== null && GOOGLE_SETTINGS.HEADER !== "" && GOOGLE_SETTINGS.HEADER !== undefined) {
document.getElementById("custom-google-header").value = JSON.stringify(GOOGLE_SETTINGS.HEADER);
}
if (DS_SETTINGS.HEADER !== null && DS_SETTINGS.HEADER !== "" && DS_SETTINGS.HEADER !== undefined) {
document.getElementById("custom-ds-header").value = JSON.stringify(DS_SETTINGS.HEADER);
}
if (DS_SETTINGS.HOST !== null && DS_SETTINGS.HOST !== "" && DS_SETTINGS.HOST !== undefined) {
document.getElementById("custom-ds-host").value = DS_SETTINGS.HOST;
}
if (DS_SETTINGS.API_KEY !== null && DS_SETTINGS.API_KEY !== "" && DS_SETTINGS.API_KEY !== undefined) {
document.getElementById("ds-api-key").value = DS_SETTINGS.API_KEY;
}
if (QWEN_SETTINGS.HEADER !== null && QWEN_SETTINGS.HEADER !== "" && QWEN_SETTINGS.HEADER !== undefined) {
document.getElementById("custom-qwen-header").value = JSON.stringify(QWEN_SETTINGS.HEADER);
}
if (QWEN_SETTINGS.HOST !== null && QWEN_SETTINGS.HOST !== "" && QWEN_SETTINGS.HOST !== undefined) {
document.getElementById("custom-qwen-host").value = QWEN_SETTINGS.HOST;
}
if (QWEN_SETTINGS.API_KEY !== null && QWEN_SETTINGS.API_KEY !== "" && QWEN_SETTINGS.API_KEY !== undefined) {
document.getElementById("qwen-api-key").value = QWEN_SETTINGS.API_KEY;
}
if (CUSTOM_SETTINGS.HOST !== null && CUSTOM_SETTINGS.HOST !== "" && CUSTOM_SETTINGS.HOST !== undefined) {
document.getElementById("custom-host").value = CUSTOM_SETTINGS.HOST;
}
if (CUSTOM_SETTINGS.ENDPOINT !== null && CUSTOM_SETTINGS.ENDPOINT !== "" && CUSTOM_SETTINGS.ENDPOINT !== undefined) {
document.getElementById("custom-endpoint").value = CUSTOM_SETTINGS.ENDPOINT;
}
if (CUSTOM_SETTINGS.HEADER !== null && CUSTOM_SETTINGS.HEADER !== "" && CUSTOM_SETTINGS.HEADER !== undefined) {
document.getElementById("custom-header").value = JSON.stringify(CUSTOM_SETTINGS.HEADER);
}
if (CUSTOM_SETTINGS.MODEL !== null && CUSTOM_SETTINGS.MODEL !== "" && CUSTOM_SETTINGS.MODEL !== undefined) {
document.getElementById("custom-model").value = CUSTOM_SETTINGS.MODEL;
}
document.getElementById('subtitle').innerText = MODEL_NAME;
function openSettings() {
document.getElementById('settingsModal').style.display = 'block';
}
function closeSettings() {
document.getElementById('settingsModal').style.display = 'none';
}
function saveSettings() {
let customSettings = {
HOST: document.getElementById('custom-host').value,
ENDPOINT: document.getElementById('custom-endpoint').value,
MODEL: document.getElementById('custom-model').value,
HEADER: JSON.parse(document.getElementById('custom-header').value)
};
let googleSettings = {
HOST: document.getElementById('custom-google-host').value,
API_KEY: document.getElementById('google-api-key').value,
HEADER: JSON.parse(document.getElementById('custom-google-header').value)
};
let dsSettings = {
HOST: document.getElementById('custom-ds-host').value,
API_KEY: document.getElementById('ds-api-key').value,
HEADER: JSON.parse(document.getElementById('custom-ds-header').value)
};
let qwenSettings = {
HOST: document.getElementById('custom-qwen-host').value,
API_KEY: document.getElementById('qwen-api-key').value,
HEADER: JSON.parse(document.getElementById('custom-qwen-header').value)
};
eda.sys_Storage.setExtensionUserConfig("CUSTOM-SETTINGS", customSettings);
eda.sys_Storage.setExtensionUserConfig("GOOGLE-SETTINGS", googleSettings);
eda.sys_Storage.setExtensionUserConfig("DS-SETTINGS", dsSettings);
eda.sys_Storage.setExtensionUserConfig("QWEN-SETTINGS", qwenSettings);
CUSTOM_SETTINGS = customSettings;
GOOGLE_SETTINGS = googleSettings;
DS_SETTINGS = dsSettings;
QWEN_SETTINGS = qwenSettings;
closeSettings();
}
function openModels() {
document.getElementById('modelsModal').style.display = 'block';
}
function closeModels() {
document.getElementById('modelsModal').style.display = 'none';
}
function saveModels() {
MODEL_NAME = document.getElementById('model-name').value;
document.getElementById('subtitle').innerText = MODEL_NAME;
const select = document.getElementById('model-name');
const selectedOption = select.options[select.selectedIndex];
const selectedOptgroup = selectedOption.parentNode;
if (selectedOptgroup.tagName === 'OPTGROUP') {
console.log('选中的 optgroup 标签:', selectedOptgroup.label);
MODEL_GROUP = selectedOptgroup.label;
if (MODEL_GROUP === 'Gemini') {
document.getElementById('google-settings').style.display = 'block';
document.getElementById('ds-settings').style.display = 'none';
document.getElementById('qwen-settings').style.display = 'none';
document.getElementById('custom-settings').style.display = 'none';
} else if (MODEL_GROUP === 'DeepSeek') {
document.getElementById('ds-settings').style.display = 'block';
document.getElementById('google-settings').style.display = 'none';
document.getElementById('qwen-settings').style.display = 'none';
document.getElementById('custom-settings').style.display = 'none';
} else if (MODEL_GROUP === 'QWen') {
document.getElementById('qwen-settings').style.display = 'block';
document.getElementById('google-settings').style.display = 'none';
document.getElementById('ds-settings').style.display = 'none';
document.getElementById('custom-settings').style.display = 'none';
} else {
document.getElementById('custom-settings').style.display = 'block';
document.getElementById('google-settings').style.display = 'none';
document.getElementById('ds-settings').style.display = 'none';
document.getElementById('qwen-settings').style.display = 'none';
}
}
closeModels();
}
function createBubble(type, message, meta) {
let bubble = document.createElement('div');
bubble.className = 'message-bubble ' + type;
console.log(message);
bubble.innerHTML = '<div>' + marked.parse(message) + '</div>';
if (!meta) {
meta = new Date().toLocaleTimeString();
}
let metaElement = document.createElement('div');
metaElement.className = 'message-meta';
metaElement.innerHTML = meta;
bubble.appendChild(metaElement);
document.querySelector('.chat-body').appendChild(bubble);
document.querySelector('.chat-body').scrollTop = document.querySelector('.chat-body').scrollHeight;
}

2592
iframe/js/marked.umd.js Normal file

File diff suppressed because it is too large Load Diff

297
iframe/test.html Normal file

File diff suppressed because one or more lines are too long

BIN
images/lceda.logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 KiB

4
locales/en.json Normal file
View File

@ -0,0 +1,4 @@
{
"About": "About",
"EasyEDA extension SDK v": "EasyEDA extension SDK v${1}"
}

4
locales/zh-Hans.json Normal file
View File

@ -0,0 +1,4 @@
{
"About": "关于",
"EasyEDA extension SDK v": "嘉立创EDA 扩展 SDK v${1}"
}

4233
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

47
package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "eda-copilot",
"version": "1.1.0",
"description": "EDA Copilot",
"author": "Mr_Fang <klxf@vip.qq.com>",
"license": "GNU GPLv3",
"homepage": "https://github.com/klxf/eda-copilot",
"scripts": {
"compile": "rimraf ./dist/ && ts-node ./config/esbuild.prod.ts",
"prepare": "husky",
"prettier:all": "prettier --write .",
"eslint:all": "eslint --ext .ts --fix .",
"fix": "npm run prettier:all && npm run eslint:all",
"build": "npm run compile && ts-node ./build/packaged.ts"
},
"devDependencies": {
"@jlceda/pro-api-types": "^0.1.132",
"@microsoft/tsdoc": "^0.15.0",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/fs-extra": "^11.0.4",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"esbuild": "0.25.0",
"eslint": "^8.57.0",
"eslint-config-alloy": "^5.1.2",
"eslint-plugin-tsdoc": "^0.3.0",
"fs-extra": "^11.2.0",
"husky": "^9.1.6",
"ignore": "^6.0.2",
"jszip": "^3.10.1",
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.6.2"
},
"lint-staged": {
"*.ts": "eslint --cache --fix",
"*.{js,ts,html,css,json,md}": "prettier --write"
},
"engines": {
"node": ">=20.5.0"
},
"dependencies": {
"@google/generative-ai": "^0.21.0"
}
}

33
src/index.ts Normal file
View File

@ -0,0 +1,33 @@
/**
*
*
*
* `extension.json` `entry`
*
* 使 `export` `headerMenus`
* `headerMenus`
*
*
* https://prodocs.lceda.cn/cn/api/guide/
*/
import * as extensionConfig from '../extension.json';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function activate(status?: 'onStartupFinished', arg?: string): void {}
export function openAbout(): void {
eda.sys_Dialog.showInformationMessage(
extensionConfig.description + '\n' +
'版本:' + extensionConfig.version + '\n' +
'作者:' + extensionConfig.publisher + '\n' +
'————————————————————\n' +
'本拓展使用 ' + extensionConfig.license + ' 开源许可协议\n' +
'开源:' + extensionConfig.homepage + '\n' +
'反馈:' + extensionConfig.bugs + '\n',
'关于 ' + extensionConfig.displayName
);
}
export function openIframe(): void {
eda.sys_IFrame.openIFrame('/iframe/index.html', 400, 600);
}

28
tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"incremental": true,
"target": "ESNext",
"lib": ["ESNext", "DOM"],
"experimentalDecorators": true,
"module": "CommonJS",
"moduleResolution": "Node10",
"resolveJsonModule": true,
"allowJs": true,
"sourceMap": false,
"outDir": "./dist/",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
"isolatedModules": true
},
"include": ["./src/", "./node_modules/@jlceda/pro-api-types/"]
}