mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-20 01:09:26 +00:00
added theme in token css and added translations (#980)
Co-authored-by: Douglas <douglas.ym.lai@gmail.com> Co-authored-by: Wendong-Fan <w3ndong.fan@gmail.com> Co-authored-by: Wendong-Fan <133094783+Wendong-Fan@users.noreply.github.com>
This commit is contained in:
parent
3291fdf0eb
commit
dd98dc4d1c
47 changed files with 9729 additions and 9818 deletions
33
src/assets/logo/icon_black.svg
Normal file
33
src/assets/logo/icon_black.svg
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<svg width="29" height="30" viewBox="0 0 29 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_ddi_6940_13775)">
|
||||
<rect x="2.25" y="0.75" width="24" height="24" rx="6" fill="#1D1D1D"/>
|
||||
<path d="M23.3857 19.2275H17.6582V18.4121L17.793 18.4102C18.5164 18.3979 18.9463 18.322 19.0889 18.2783C19.2284 18.2296 19.3056 18.1716 19.3447 18.1162C19.367 18.0787 19.3944 17.9968 19.417 17.8516C19.439 17.7098 19.4557 17.5229 19.4648 17.2891V17.2881C19.4709 17.1624 19.4824 16.2946 19.501 14.6777V10.4131C19.501 9.57932 19.4921 8.76366 19.4736 7.9668L19.4551 7.5791C19.4471 7.46564 19.4369 7.36779 19.4258 7.28516C19.4146 7.20243 19.4026 7.13724 19.3896 7.08887C19.3763 7.03925 19.365 7.01663 19.3604 7.00977L19.3545 7.00195C19.3086 6.9273 19.227 6.86361 19.0889 6.82129L19.082 6.81934C19.0244 6.79881 18.8925 6.77676 18.6689 6.75977C18.4506 6.74317 18.1589 6.73171 17.793 6.72559L17.6582 6.72363V5.86328H23.3857V6.72363L23.252 6.72559C22.8859 6.73171 22.593 6.74317 22.373 6.75977C22.1479 6.77676 22.0126 6.79916 21.9512 6.82031L21.9482 6.82129C21.8143 6.86394 21.7385 6.9183 21.6992 6.97363L21.6631 7.06543C21.6507 7.10896 21.6383 7.16615 21.627 7.23926C21.6049 7.38098 21.5892 7.56795 21.5801 7.80176V7.80371C21.5741 7.9247 21.5615 8.79222 21.543 10.4141V14.6777C21.543 15.5117 21.5518 16.3244 21.5703 17.1152L21.5889 17.5039C21.5964 17.6179 21.6058 17.7171 21.6162 17.8008C21.6266 17.8844 21.6382 17.9507 21.6504 18.001C21.6622 18.0495 21.6736 18.0748 21.6797 18.0859C21.7335 18.1652 21.8187 18.2311 21.9482 18.2783C22.0121 18.2975 22.1486 18.3177 22.3721 18.333C22.5925 18.3481 22.8857 18.3591 23.252 18.3652L23.3857 18.3672V19.2275Z" fill="white" stroke="white" stroke-width="0.272725"/>
|
||||
<path d="M11.7344 5.86328C11.9988 5.86328 12.2185 5.94507 12.3623 6.13086L12.4121 6.19727C12.5237 6.36171 12.604 6.59382 12.6611 6.87988C12.7364 7.24188 12.9978 8.4274 13.4453 10.4375C13.8926 12.4405 14.2613 13.9515 14.5508 14.9707C14.8414 15.9937 15.0685 16.6837 15.2324 17.0488C15.3913 17.4026 15.5787 17.6033 15.7393 17.7744C15.7866 17.8249 15.8922 17.9057 16.0273 17.9951C16.1592 18.0823 16.3087 18.1707 16.4355 18.2363C16.5092 18.2745 16.6505 18.3235 16.8105 18.3623C16.9703 18.4011 17.1322 18.426 17.2461 18.4229L17.3867 18.4189V19.2275H17.25C16.9425 19.2275 16.608 19.2252 16.3066 19.2236C16.0053 19.2221 15.7375 19.2207 15.5625 19.2207H14.7598C14.464 19.2207 14.2027 19.1339 13.9834 18.957C13.7661 18.7817 13.5975 18.5249 13.4727 18.1982C13.23 17.5632 12.8065 15.8749 12.2031 13.1494L11.0439 8.11914C10.9229 7.60275 10.7785 7.27544 10.624 7.10938C10.4814 6.95602 10.2272 6.8623 9.8252 6.8623C9.75633 6.86231 9.65581 6.86402 9.52734 6.86523C9.39921 6.86645 9.24437 6.86719 9.06836 6.86719H8.93164V6.25391L9.03906 6.23047C10.2751 5.95623 11.3912 5.8633 11.7344 5.86328Z" fill="white" stroke="white" stroke-width="0.272725"/>
|
||||
<path d="M10.374 10.3096C9.65413 11.98 8.56815 14.3876 7.98438 15.6914C7.59259 16.5664 7.70123 16.313 7.49512 16.7314C7.42234 16.8792 7.23706 17.1383 7.04297 17.3877C6.8507 17.6347 6.66264 17.8568 6.59473 17.9277C6.55362 17.9707 6.46063 18.0408 6.33984 18.1191C6.22207 18.1955 6.08781 18.2726 5.97461 18.3301C5.91227 18.3617 5.78922 18.4007 5.64551 18.4307C5.50316 18.4603 5.35694 18.4784 5.25391 18.4756L5.11426 18.4717V19.2266H7.07031C7.59356 19.2265 8.20356 18.9286 8.44141 18.3184C8.6554 17.7693 10.1261 13.8285 10.835 11.5898L10.8447 11.5576L10.8398 11.5254L10.6338 10.3398L10.5557 9.88867L10.374 10.3096Z" fill="white" stroke="white" stroke-width="0.272725"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_ddi_6940_13775" x="0" y="0" width="28.5" height="29.25" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="0.75" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_6940_13775"/>
|
||||
<feOffset/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.831373 0 0 0 0 0.831373 0 0 0 0 0.831373 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6940_13775"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="0.75" operator="erode" in="SourceAlpha" result="effect2_dropShadow_6940_13775"/>
|
||||
<feOffset dy="2.25"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_6940_13775" result="effect2_dropShadow_6940_13775"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_6940_13775" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.75"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.33 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect3_innerShadow_6940_13775"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
33
src/assets/logo/icon_white.svg
Normal file
33
src/assets/logo/icon_white.svg
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<svg width="29" height="30" viewBox="0 0 29 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_ddi_6940_13779)">
|
||||
<rect x="2.25" y="0.75" width="24" height="24" rx="6" fill="white"/>
|
||||
<path d="M23.3857 19.2275H17.6582V18.4121L17.793 18.4102C18.5164 18.3979 18.9463 18.322 19.0889 18.2783C19.2284 18.2296 19.3056 18.1716 19.3447 18.1162C19.367 18.0787 19.3944 17.9968 19.417 17.8516C19.439 17.7098 19.4557 17.5229 19.4648 17.2891V17.2881C19.4709 17.1624 19.4824 16.2946 19.501 14.6777V10.4131C19.501 9.57932 19.4921 8.76366 19.4736 7.9668L19.4551 7.5791C19.4471 7.46564 19.4369 7.36779 19.4258 7.28516C19.4146 7.20243 19.4026 7.13724 19.3896 7.08887C19.3763 7.03925 19.365 7.01663 19.3604 7.00977L19.3545 7.00195C19.3086 6.9273 19.227 6.86361 19.0889 6.82129L19.082 6.81934C19.0244 6.79881 18.8925 6.77676 18.6689 6.75977C18.4506 6.74317 18.1589 6.73171 17.793 6.72559L17.6582 6.72363V5.86328H23.3857V6.72363L23.252 6.72559C22.8859 6.73171 22.593 6.74317 22.373 6.75977C22.1479 6.77676 22.0126 6.79916 21.9512 6.82031L21.9482 6.82129C21.8143 6.86394 21.7385 6.9183 21.6992 6.97363L21.6631 7.06543C21.6507 7.10896 21.6383 7.16615 21.627 7.23926C21.6049 7.38098 21.5892 7.56795 21.5801 7.80176V7.80371C21.5741 7.9247 21.5615 8.79222 21.543 10.4141V14.6777C21.543 15.5117 21.5518 16.3244 21.5703 17.1152L21.5889 17.5039C21.5964 17.6179 21.6058 17.7171 21.6162 17.8008C21.6266 17.8844 21.6382 17.9507 21.6504 18.001C21.6622 18.0495 21.6736 18.0748 21.6797 18.0859C21.7335 18.1652 21.8187 18.2311 21.9482 18.2783C22.0121 18.2975 22.1486 18.3177 22.3721 18.333C22.5925 18.3481 22.8857 18.3591 23.252 18.3652L23.3857 18.3672V19.2275Z" fill="#1D1D1D" stroke="#1D1D1D" stroke-width="0.272725"/>
|
||||
<path d="M11.7344 5.86328C11.9988 5.86328 12.2185 5.94507 12.3623 6.13086L12.4121 6.19727C12.5237 6.36171 12.604 6.59382 12.6611 6.87988C12.7364 7.24188 12.9978 8.4274 13.4453 10.4375C13.8926 12.4405 14.2613 13.9515 14.5508 14.9707C14.8414 15.9937 15.0685 16.6837 15.2324 17.0488C15.3913 17.4026 15.5787 17.6033 15.7393 17.7744C15.7866 17.8249 15.8922 17.9057 16.0273 17.9951C16.1592 18.0823 16.3087 18.1707 16.4355 18.2363C16.5092 18.2745 16.6505 18.3235 16.8105 18.3623C16.9703 18.4011 17.1322 18.426 17.2461 18.4229L17.3867 18.4189V19.2275H17.25C16.9425 19.2275 16.608 19.2252 16.3066 19.2236C16.0053 19.2221 15.7375 19.2207 15.5625 19.2207H14.7598C14.464 19.2207 14.2027 19.1339 13.9834 18.957C13.7661 18.7817 13.5975 18.5249 13.4727 18.1982C13.23 17.5632 12.8065 15.8749 12.2031 13.1494L11.0439 8.11914C10.9229 7.60275 10.7785 7.27544 10.624 7.10938C10.4814 6.95602 10.2272 6.8623 9.8252 6.8623C9.75633 6.86231 9.65581 6.86402 9.52734 6.86523C9.39921 6.86645 9.24437 6.86719 9.06836 6.86719H8.93164V6.25391L9.03906 6.23047C10.2751 5.95623 11.3912 5.8633 11.7344 5.86328Z" fill="#1D1D1D" stroke="#1D1D1D" stroke-width="0.272725"/>
|
||||
<path d="M10.374 10.3096C9.65413 11.98 8.56815 14.3876 7.98438 15.6914C7.59259 16.5664 7.70123 16.313 7.49512 16.7314C7.42234 16.8792 7.23706 17.1383 7.04297 17.3877C6.8507 17.6347 6.66264 17.8568 6.59473 17.9277C6.55362 17.9707 6.46063 18.0408 6.33984 18.1191C6.22207 18.1955 6.08781 18.2726 5.97461 18.3301C5.91227 18.3617 5.78922 18.4007 5.64551 18.4307C5.50316 18.4603 5.35694 18.4784 5.25391 18.4756L5.11426 18.4717V19.2266H7.07031C7.59356 19.2265 8.20356 18.9286 8.44141 18.3184C8.6554 17.7693 10.1261 13.8285 10.835 11.5898L10.8447 11.5576L10.8398 11.5254L10.6338 10.3398L10.5557 9.88867L10.374 10.3096Z" fill="#1D1D1D" stroke="#1D1D1D" stroke-width="0.272725"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_ddi_6940_13779" x="0" y="0" width="28.5" height="29.25" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="0.75" operator="dilate" in="SourceAlpha" result="effect1_dropShadow_6940_13779"/>
|
||||
<feOffset/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.831373 0 0 0 0 0.831373 0 0 0 0 0.831373 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6940_13779"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="0.75" operator="erode" in="SourceAlpha" result="effect2_dropShadow_6940_13779"/>
|
||||
<feOffset dy="2.25"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_6940_13779" result="effect2_dropShadow_6940_13779"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_6940_13779" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.75"/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.33 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect3_innerShadow_6940_13779"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/logo/logo_black.png
Normal file
BIN
src/assets/logo/logo_black.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/logo/logo_white.png
Normal file
BIN
src/assets/logo/logo_white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -130,7 +130,7 @@ export const MarkDown = memo(
|
|||
.join('\n')
|
||||
.trim();
|
||||
setHtml(
|
||||
`<pre class="bg-zinc-100 p-2 rounded text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all" style="word-break: break-all;"><code>${DOMPurify.sanitize(formattedHtml)}</code></pre>`
|
||||
`<pre class="bg-code-surface p-2 rounded text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all" style="word-break: break-all;"><code>${DOMPurify.sanitize(formattedHtml)}</code></pre>`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -174,7 +174,7 @@ export const MarkDown = memo(
|
|||
// Fallback: show alt text or placeholder
|
||||
const altMatch = fullTag.match(/alt=["']([^"']*)["']/);
|
||||
const alt = altMatch ? altMatch[1] : 'image';
|
||||
const placeholder = `<span class="inline-block text-sm text-zinc-500">[${alt}]</span>`;
|
||||
const placeholder = `<span class="inline-block text-sm text-text-secondary">[${alt}]</span>`;
|
||||
rawHtml = rawHtml.replace(fullTag, placeholder);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -12,133 +12,133 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { isHtmlDocument } from '@/lib/htmlFontStyles';
|
||||
import { useEffect, useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { useState, useEffect } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { isHtmlDocument } from "@/lib/htmlFontStyles";
|
||||
|
||||
export const SummaryMarkDown = ({
|
||||
content,
|
||||
speed = 15,
|
||||
onTyping,
|
||||
enableTypewriter = true,
|
||||
content,
|
||||
speed = 15,
|
||||
onTyping,
|
||||
enableTypewriter = true,
|
||||
}: {
|
||||
content: string;
|
||||
speed?: number;
|
||||
onTyping?: () => void;
|
||||
enableTypewriter?: boolean;
|
||||
content: string;
|
||||
speed?: number;
|
||||
onTyping?: () => void;
|
||||
enableTypewriter?: boolean;
|
||||
}) => {
|
||||
const [displayedContent, setDisplayedContent] = useState('');
|
||||
const [_isTyping, setIsTyping] = useState(true);
|
||||
const [displayedContent, setDisplayedContent] = useState("");
|
||||
const [isTyping, setIsTyping] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableTypewriter) {
|
||||
setDisplayedContent(content);
|
||||
setIsTyping(false);
|
||||
return;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!enableTypewriter) {
|
||||
setDisplayedContent(content);
|
||||
setIsTyping(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setDisplayedContent('');
|
||||
setIsTyping(true);
|
||||
let index = 0;
|
||||
setDisplayedContent("");
|
||||
setIsTyping(true);
|
||||
let index = 0;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (index < content.length) {
|
||||
setDisplayedContent(content.slice(0, index + 1));
|
||||
index++;
|
||||
if (onTyping) {
|
||||
onTyping();
|
||||
}
|
||||
} else {
|
||||
setIsTyping(false);
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, speed);
|
||||
const timer = setInterval(() => {
|
||||
if (index < content.length) {
|
||||
setDisplayedContent(content.slice(0, index + 1));
|
||||
index++;
|
||||
if (onTyping) {
|
||||
onTyping();
|
||||
}
|
||||
} else {
|
||||
setIsTyping(false);
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, speed);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, [content, speed, onTyping, enableTypewriter]);
|
||||
return () => clearInterval(timer);
|
||||
}, [content, speed, onTyping]);
|
||||
|
||||
// If content is a pure HTML document, render in a styled pre block
|
||||
if (isHtmlDocument(content)) {
|
||||
// Trim leading whitespace from each line for consistent alignment
|
||||
const formattedHtml = displayedContent
|
||||
.split('\n')
|
||||
.map((line) => line.trimStart())
|
||||
.join('\n')
|
||||
.trim();
|
||||
return (
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<pre className="mb-3 overflow-x-auto whitespace-pre-wrap rounded-lg border border-emerald-200 bg-emerald-50 p-3 font-mono text-xs">
|
||||
<code>{formattedHtml}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// If content is a pure HTML document, render in a styled pre block
|
||||
if (isHtmlDocument(content)) {
|
||||
// Trim leading whitespace from each line for consistent alignment
|
||||
const formattedHtml = displayedContent
|
||||
.split('\n')
|
||||
.map(line => line.trimStart())
|
||||
.join('\n')
|
||||
.trim();
|
||||
return (
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<pre className="bg-emerald-50 border border-emerald-200 p-3 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap mb-3">
|
||||
<code>{formattedHtml}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
h1: ({ children }) => (
|
||||
<h1 className="mb-3 flex items-center gap-2 border-b border-emerald-200 pb-2 text-xl font-bold text-emerald-800">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2 className="mb-3 mt-4 flex items-center gap-2 text-lg font-semibold text-emerald-700">
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="mb-2 mt-3 text-base font-medium text-emerald-600">
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p className="m-0 mb-3 whitespace-pre-wrap text-sm font-normal leading-relaxed text-gray-700">
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul className="mb-3 ml-2 list-inside list-disc space-y-1 text-sm text-gray-700">
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="mb-3 ml-2 list-inside list-decimal space-y-1 text-sm text-gray-700">
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li className="mb-1 leading-relaxed text-gray-700">{children}</li>
|
||||
),
|
||||
code: ({ children }) => (
|
||||
<code className="rounded bg-emerald-100 px-2 py-1 font-mono text-xs text-emerald-800">
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
pre: ({ children }) => (
|
||||
<pre className="mb-3 overflow-x-auto whitespace-pre-wrap rounded-lg border border-emerald-200 bg-emerald-50 p-3 font-mono text-xs">
|
||||
{children}
|
||||
</pre>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="mb-3 rounded-r-lg border-l-4 border-emerald-300 bg-emerald-50 py-2 pl-4 italic text-emerald-700">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
strong: ({ children }) => (
|
||||
<strong className="font-semibold text-emerald-800">
|
||||
{children}
|
||||
</strong>
|
||||
),
|
||||
em: ({ children }) => (
|
||||
<em className="italic text-emerald-600">{children}</em>
|
||||
),
|
||||
hr: () => <hr className="my-4 border-emerald-200" />,
|
||||
}}
|
||||
>
|
||||
{displayedContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
h1: ({ children }) => (
|
||||
<h1 className="text-xl font-bold text-emerald-800 mb-3 flex items-center gap-2 border-b border-emerald-200 pb-2">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2 className="text-lg font-semibold text-emerald-700 mb-3 mt-4 flex items-center gap-2">
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="text-base font-medium text-emerald-600 mb-2 mt-3">
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p className="m-0 text-sm font-normal text-gray-700 leading-relaxed mb-3 whitespace-pre-wrap">
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul className="list-disc list-inside text-sm text-gray-700 mb-3 space-y-1 ml-2">
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="list-decimal list-inside text-sm text-gray-700 mb-3 space-y-1 ml-2">
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li className="mb-1 text-gray-700 leading-relaxed">{children}</li>
|
||||
),
|
||||
code: ({ children }) => (
|
||||
<code className="bg-surface-success-subtle text-text-success px-2 py-1 rounded text-xs font-mono">
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
pre: ({ children }) => (
|
||||
<pre className="bg-emerald-50 border border-emerald-200 p-3 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap mb-3">
|
||||
{children}
|
||||
</pre>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="border-l-4 border-emerald-300 pl-4 italic text-emerald-700 bg-emerald-50 py-2 rounded-r-lg mb-3">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
strong: ({ children }) => (
|
||||
<strong className="font-semibold text-emerald-800">{children}</strong>
|
||||
),
|
||||
em: ({ children }) => (
|
||||
<em className="italic text-emerald-600">{children}</em>
|
||||
),
|
||||
hr: () => (
|
||||
<hr className="border-emerald-200 my-4" />
|
||||
),
|
||||
}}
|
||||
>
|
||||
{displayedContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,194 +12,175 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Copy, FileText, Image } from 'lucide-react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Button } from '../../ui/button';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../../ui/popover';
|
||||
import { Copy, FileText, X, Image } from "lucide-react";
|
||||
import { Button } from "../../ui/button";
|
||||
import { Popover, PopoverTrigger, PopoverContent } from "../../ui/popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
|
||||
interface UserMessageCardProps {
|
||||
id: string;
|
||||
content: string;
|
||||
className?: string;
|
||||
attaches?: File[];
|
||||
id: string;
|
||||
content: string;
|
||||
className?: string;
|
||||
attaches?: File[];
|
||||
}
|
||||
|
||||
export function UserMessageCard({
|
||||
id,
|
||||
content,
|
||||
className,
|
||||
attaches,
|
||||
id,
|
||||
content,
|
||||
className,
|
||||
attaches,
|
||||
}: UserMessageCardProps) {
|
||||
const [hoveredFilePath, setHoveredFilePath] = useState<string | null>(null);
|
||||
const [isRemainingOpen, setIsRemainingOpen] = useState(false);
|
||||
const hoverCloseTimerRef = useRef<number | null>(null);
|
||||
const [hoveredFilePath, setHoveredFilePath] = useState<string | null>(null);
|
||||
const [isRemainingOpen, setIsRemainingOpen] = useState(false);
|
||||
const hoverCloseTimerRef = useRef<number | null>(null);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(content);
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(content);
|
||||
};
|
||||
// Popover handles outside clicks; no manual listener needed
|
||||
const openRemainingPopover = () => {
|
||||
if (hoverCloseTimerRef.current) {
|
||||
window.clearTimeout(hoverCloseTimerRef.current);
|
||||
hoverCloseTimerRef.current = null;
|
||||
}
|
||||
setIsRemainingOpen(true);
|
||||
};
|
||||
|
||||
// Popover handles outside clicks; no manual listener needed
|
||||
const openRemainingPopover = () => {
|
||||
if (hoverCloseTimerRef.current) {
|
||||
window.clearTimeout(hoverCloseTimerRef.current);
|
||||
hoverCloseTimerRef.current = null;
|
||||
}
|
||||
setIsRemainingOpen(true);
|
||||
};
|
||||
const scheduleCloseRemainingPopover = () => {
|
||||
if (hoverCloseTimerRef.current) {
|
||||
window.clearTimeout(hoverCloseTimerRef.current);
|
||||
}
|
||||
hoverCloseTimerRef.current = window.setTimeout(() => {
|
||||
setIsRemainingOpen(false);
|
||||
hoverCloseTimerRef.current = null;
|
||||
}, 150);
|
||||
};
|
||||
|
||||
const scheduleCloseRemainingPopover = () => {
|
||||
if (hoverCloseTimerRef.current) {
|
||||
window.clearTimeout(hoverCloseTimerRef.current);
|
||||
}
|
||||
hoverCloseTimerRef.current = window.setTimeout(() => {
|
||||
setIsRemainingOpen(false);
|
||||
hoverCloseTimerRef.current = null;
|
||||
}, 150);
|
||||
};
|
||||
const getFileIcon = (fileName: string) => {
|
||||
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
||||
if (["jpg", "jpeg", "png", "gif", "webp"].includes(ext)) {
|
||||
return <Image className="w-4 h-4 text-icon-primary" />;
|
||||
}
|
||||
return <FileText className="w-4 h-4 text-icon-primary" />;
|
||||
};
|
||||
|
||||
const getFileIcon = (fileName: string) => {
|
||||
const ext = fileName.split('.').pop()?.toLowerCase() || '';
|
||||
if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) {
|
||||
return <Image className="h-4 w-4 text-icon-primary" />;
|
||||
}
|
||||
return <FileText className="h-4 w-4 text-icon-primary" />;
|
||||
};
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className={`relative bg-surface-primary w-full rounded-xl border px-sm py-2 ${className || ""} group overflow-visible`}
|
||||
>
|
||||
<div className="absolute bottom-[0px] right-1 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<Button onClick={handleCopy} variant="ghost" size="icon">
|
||||
<Copy />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-text-body text-body-sm whitespace-pre-wrap break-words">
|
||||
{content}
|
||||
</div>
|
||||
{attaches && attaches.length > 0 && (
|
||||
<div className="box-border flex flex-wrap gap-1 items-start relative w-full mt-2">
|
||||
{(() => {
|
||||
// Show max 4 files + count indicator
|
||||
const maxVisibleFiles = 4;
|
||||
const visibleFiles = attaches.slice(0, maxVisibleFiles);
|
||||
const remainingCount = attaches.length > maxVisibleFiles ? attaches.length - maxVisibleFiles : 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{visibleFiles.map((file) => {
|
||||
const isHovered = hoveredFilePath === file.filePath;
|
||||
return (
|
||||
<div
|
||||
key={"attache-" + file.fileName}
|
||||
className={cn(
|
||||
"bg-tag-surface box-border flex gap-0.5 items-center relative rounded-lg max-w-32 h-auto cursor-pointer hover:bg-tag-surface-hover transition-colors duration-300"
|
||||
)}
|
||||
onMouseEnter={() => setHoveredFilePath(file.filePath)}
|
||||
onMouseLeave={() => setHoveredFilePath((prev) => (prev === file.filePath ? null : prev))}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.ipcRenderer.invoke("reveal-in-folder", file.filePath);
|
||||
}}
|
||||
>
|
||||
{/* File icon */}
|
||||
<div className="rounded-md flex items-center justify-center w-6 h-6">
|
||||
{getFileIcon(file.fileName)}
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className={`relative w-full rounded-xl border bg-white-80% px-sm py-2 ${className || ''} group overflow-visible`}
|
||||
>
|
||||
<div className="absolute bottom-[0px] right-1 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Button onClick={handleCopy} variant="ghost" size="icon">
|
||||
<Copy />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap break-words text-body-sm text-text-body">
|
||||
{content}
|
||||
</div>
|
||||
{attaches && attaches.length > 0 && (
|
||||
<div className="relative mt-2 box-border flex w-full flex-wrap items-start gap-1">
|
||||
{(() => {
|
||||
// Show max 4 files + count indicator
|
||||
const maxVisibleFiles = 4;
|
||||
const visibleFiles = attaches.slice(0, maxVisibleFiles);
|
||||
const remainingCount =
|
||||
attaches.length > maxVisibleFiles
|
||||
? attaches.length - maxVisibleFiles
|
||||
: 0;
|
||||
{/* File Name */}
|
||||
<p
|
||||
className={cn(
|
||||
"flex-1 font-['Inter'] font-bold leading-tight min-h-px min-w-px overflow-ellipsis overflow-hidden relative text-text-body text-xs whitespace-nowrap my-0"
|
||||
)}
|
||||
title={file.fileName}
|
||||
>
|
||||
{file.fileName}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
return (
|
||||
<>
|
||||
{visibleFiles.map((file) => {
|
||||
const _isHovered = hoveredFilePath === file.filePath;
|
||||
return (
|
||||
<div
|
||||
key={'attache-' + file.fileName}
|
||||
className={cn(
|
||||
'relative box-border flex h-auto max-w-32 cursor-pointer items-center gap-0.5 rounded-lg bg-tag-surface transition-colors duration-300 hover:bg-tag-surface-hover'
|
||||
)}
|
||||
onMouseEnter={() => setHoveredFilePath(file.filePath)}
|
||||
onMouseLeave={() =>
|
||||
setHoveredFilePath((prev) =>
|
||||
prev === file.filePath ? null : prev
|
||||
)
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.ipcRenderer.invoke(
|
||||
'reveal-in-folder',
|
||||
file.filePath
|
||||
);
|
||||
}}
|
||||
>
|
||||
{/* File icon */}
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-md">
|
||||
{getFileIcon(file.fileName)}
|
||||
</div>
|
||||
|
||||
{/* File Name */}
|
||||
<p
|
||||
className={cn(
|
||||
"relative my-0 min-h-px min-w-px flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap font-['Inter'] text-xs font-bold leading-tight text-text-body"
|
||||
)}
|
||||
title={file.fileName}
|
||||
>
|
||||
{file.fileName}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Show remaining count if more than 4 files */}
|
||||
{remainingCount > 0 && (
|
||||
<Popover
|
||||
open={isRemainingOpen}
|
||||
onOpenChange={setIsRemainingOpen}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="relative box-border flex h-auto items-center rounded-lg bg-tag-surface"
|
||||
onMouseEnter={openRemainingPopover}
|
||||
onMouseLeave={scheduleCloseRemainingPopover}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<p className="my-0 whitespace-nowrap font-['Inter'] text-xs font-bold leading-tight text-text-body">
|
||||
{remainingCount}+
|
||||
</p>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
className="!w-auto max-w-40 rounded-md border border-dropdown-border bg-dropdown-bg p-1 shadow-perfect"
|
||||
onMouseEnter={openRemainingPopover}
|
||||
onMouseLeave={scheduleCloseRemainingPopover}
|
||||
>
|
||||
<div className="scrollbar-hide flex max-h-[176px] flex-col gap-1 overflow-auto">
|
||||
{attaches.slice(maxVisibleFiles).map((file) => {
|
||||
const _isHovered = hoveredFilePath === file.filePath;
|
||||
return (
|
||||
<div
|
||||
key={file.filePath}
|
||||
className="flex cursor-pointer items-center gap-1 rounded-lg bg-tag-surface py-0.5 transition-colors duration-300 hover:bg-tag-surface-hover"
|
||||
onMouseLeave={() =>
|
||||
setHoveredFilePath((prev) =>
|
||||
prev === file.filePath ? null : prev
|
||||
)
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.ipcRenderer.invoke(
|
||||
'reveal-in-folder',
|
||||
file.filePath
|
||||
);
|
||||
setIsRemainingOpen(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-md">
|
||||
{getFileIcon(file.fileName)}
|
||||
</div>
|
||||
<p className="my-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap font-['Inter'] text-xs font-bold leading-tight text-text-body">
|
||||
{file.fileName}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{/* Show remaining count if more than 4 files */}
|
||||
{remainingCount > 0 && (
|
||||
<Popover open={isRemainingOpen} onOpenChange={setIsRemainingOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="bg-tag-surface box-border flex items-center relative rounded-lg h-auto"
|
||||
onMouseEnter={openRemainingPopover}
|
||||
onMouseLeave={scheduleCloseRemainingPopover}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<p className="font-['Inter'] font-bold leading-tight text-text-body text-xs whitespace-nowrap my-0">
|
||||
{remainingCount}+
|
||||
</p>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
className="!w-auto max-w-40 p-1 rounded-md border border-dropdown-border bg-dropdown-bg shadow-perfect"
|
||||
onMouseEnter={openRemainingPopover}
|
||||
onMouseLeave={scheduleCloseRemainingPopover}
|
||||
>
|
||||
<div className="max-h-[176px] overflow-auto scrollbar-hide gap-1 flex flex-col">
|
||||
{attaches.slice(maxVisibleFiles).map((file) => {
|
||||
const isHovered = hoveredFilePath === file.filePath;
|
||||
return (
|
||||
<div
|
||||
key={file.filePath}
|
||||
className="flex items-center gap-1 py-0.5 bg-tag-surface hover:bg-tag-surface-hover transition-colors duration-300 cursor-pointer rounded-lg"
|
||||
onMouseLeave={() => setHoveredFilePath((prev) => (prev === file.filePath ? null : prev))}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.ipcRenderer.invoke("reveal-in-folder", file.filePath);
|
||||
setIsRemainingOpen(false);
|
||||
}}
|
||||
>
|
||||
<div className="rounded-md flex items-center justify-center w-6 h-6">
|
||||
{getFileIcon(file.fileName)}
|
||||
</div>
|
||||
<p className="flex-1 font-['Inter'] font-bold leading-tight text-text-body text-xs whitespace-nowrap my-0 overflow-hidden text-ellipsis">
|
||||
{file.fileName}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,456 +12,445 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TaskItem } from './TaskItem';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { TaskType } from "./TaskType";
|
||||
import { TaskItem } from "./TaskItem";
|
||||
import ShinyText from "@/components/ui/ShinyText/ShinyText";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { TaskState, TaskStateType } from '@/components/TaskState';
|
||||
import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
|
||||
import {
|
||||
ChevronDown,
|
||||
Circle,
|
||||
CircleCheckBig,
|
||||
CircleSlash,
|
||||
LoaderCircle,
|
||||
Plus,
|
||||
TriangleAlert,
|
||||
} from 'lucide-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
Bird,
|
||||
CodeXml,
|
||||
FileText,
|
||||
Globe,
|
||||
Plus,
|
||||
Image,
|
||||
CircleCheckBig,
|
||||
LoaderCircle,
|
||||
Bot,
|
||||
ChevronDown,
|
||||
Circle,
|
||||
TriangleAlert,
|
||||
CircleSlash,
|
||||
} from "lucide-react";
|
||||
import { useMemo, useState, useRef, useEffect } from "react";
|
||||
import { TaskState, TaskStateType } from "@/components/TaskState";
|
||||
import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
|
||||
|
||||
interface TaskCardProps {
|
||||
taskInfo: any[];
|
||||
taskAssigning: Agent[];
|
||||
taskRunning: TaskInfo[];
|
||||
taskType: 1 | 2 | 3;
|
||||
progressValue: number;
|
||||
summaryTask: string;
|
||||
onAddTask: () => void;
|
||||
onUpdateTask: (taskIndex: number, content: string) => void;
|
||||
onDeleteTask: (taskIndex: number) => void;
|
||||
clickable?: boolean;
|
||||
chatId?: string;
|
||||
taskInfo: any[];
|
||||
taskAssigning: Agent[];
|
||||
taskRunning: TaskInfo[];
|
||||
taskType: 1 | 2 | 3;
|
||||
progressValue: number;
|
||||
summaryTask: string;
|
||||
onAddTask: () => void;
|
||||
onUpdateTask: (taskIndex: number, content: string) => void;
|
||||
onDeleteTask: (taskIndex: number) => void;
|
||||
clickable?: boolean;
|
||||
chatId?: string;
|
||||
}
|
||||
|
||||
export function TaskCard({
|
||||
taskInfo,
|
||||
taskType,
|
||||
taskRunning,
|
||||
progressValue,
|
||||
summaryTask,
|
||||
onAddTask,
|
||||
onUpdateTask,
|
||||
onDeleteTask,
|
||||
clickable = true,
|
||||
chatId,
|
||||
taskInfo,
|
||||
taskType,
|
||||
taskRunning,
|
||||
progressValue,
|
||||
summaryTask,
|
||||
onAddTask,
|
||||
onUpdateTask,
|
||||
onDeleteTask,
|
||||
clickable = true,
|
||||
chatId,
|
||||
}: TaskCardProps) {
|
||||
const { t: _t } = useTranslation();
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [contentHeight, setContentHeight] = useState<number | 'auto'>('auto');
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [contentHeight, setContentHeight] = useState<number | "auto">("auto");
|
||||
|
||||
//Get Chatstore and ProjectStore for the active project's task
|
||||
const { chatStore, projectStore } = useChatStoreAdapter();
|
||||
//Get Chatstore and ProjectStore for the active project's task
|
||||
const { chatStore, projectStore } = useChatStoreAdapter();
|
||||
if (!chatStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const [selectedState, setSelectedState] = useState<TaskStateType>('all');
|
||||
const [filterTasks, setFilterTasks] = useState<any[]>([]);
|
||||
|
||||
// Extract activeTaskId and related values for cleaner dependencies
|
||||
const activeTaskId = chatStore?.activeTaskId as string;
|
||||
const activeTask = chatStore?.tasks?.[activeTaskId];
|
||||
const activeTaskStatus = activeTask?.status;
|
||||
const [selectedState, setSelectedState] = useState<TaskStateType>("all");
|
||||
const [filterTasks, setFilterTasks] = useState<any[]>([]);
|
||||
useEffect(() => {
|
||||
const tasks = taskRunning || [];
|
||||
|
||||
useEffect(() => {
|
||||
const tasks = taskRunning || [];
|
||||
if (selectedState === "all") {
|
||||
setFilterTasks(tasks);
|
||||
} else {
|
||||
const newFiltered = tasks.filter((task) => {
|
||||
switch (selectedState) {
|
||||
case "done":
|
||||
return task.status === "completed" && !task.reAssignTo;
|
||||
case "ongoing":
|
||||
return (
|
||||
task.status !== "failed" &&
|
||||
task.status !== "completed" &&
|
||||
task.status !== "skipped" &&
|
||||
task.status !== "waiting" &&
|
||||
task.status !== ""
|
||||
);
|
||||
case "pending":
|
||||
return (
|
||||
(task.status === "skipped" ||
|
||||
task.status === "waiting" ||
|
||||
task.status === "") &&
|
||||
!task.reAssignTo
|
||||
);
|
||||
case "failed":
|
||||
return task.status === "failed";
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
setFilterTasks(newFiltered);
|
||||
}
|
||||
}, [selectedState, taskInfo, taskRunning]);
|
||||
|
||||
if (selectedState === 'all') {
|
||||
setFilterTasks(tasks);
|
||||
} else {
|
||||
const newFiltered = tasks.filter((task) => {
|
||||
switch (selectedState) {
|
||||
case 'done':
|
||||
return task.status === 'completed' && !task.reAssignTo;
|
||||
case 'ongoing':
|
||||
return (
|
||||
task.status !== 'failed' &&
|
||||
task.status !== 'completed' &&
|
||||
task.status !== 'skipped' &&
|
||||
task.status !== 'waiting' &&
|
||||
task.status !== ''
|
||||
);
|
||||
case 'pending':
|
||||
return (
|
||||
(task.status === 'skipped' ||
|
||||
task.status === 'waiting' ||
|
||||
task.status === '') &&
|
||||
!task.reAssignTo
|
||||
);
|
||||
case 'failed':
|
||||
return task.status === 'failed';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
setFilterTasks(newFiltered);
|
||||
}
|
||||
}, [selectedState, taskInfo, taskRunning]);
|
||||
const isAllTaskFinished = useMemo(() => {
|
||||
return (
|
||||
chatStore.tasks[chatStore.activeTaskId as string].status === "finished"
|
||||
);
|
||||
}, [chatStore.tasks[chatStore.activeTaskId as string].status]);
|
||||
|
||||
const isAllTaskFinished = useMemo(() => {
|
||||
return activeTaskStatus === 'finished';
|
||||
}, [activeTaskStatus]);
|
||||
useEffect(() => {
|
||||
if (
|
||||
chatStore.tasks[chatStore.activeTaskId as string].activeWorkSpace ===
|
||||
"workflow"
|
||||
) {
|
||||
setIsExpanded(false);
|
||||
} else {
|
||||
setIsExpanded(true);
|
||||
}
|
||||
}, [chatStore.tasks[chatStore.activeTaskId as string].activeWorkSpace]);
|
||||
|
||||
// Auto-collapse task list when all tasks are completed
|
||||
useEffect(() => {
|
||||
if (isAllTaskFinished) {
|
||||
setIsExpanded(false);
|
||||
}
|
||||
}, [isAllTaskFinished]);
|
||||
// Improved height calculation logic
|
||||
useEffect(() => {
|
||||
if (!contentRef.current) return;
|
||||
|
||||
// Improved height calculation logic
|
||||
useEffect(() => {
|
||||
if (!contentRef.current) return;
|
||||
const updateHeight = () => {
|
||||
if (contentRef.current) {
|
||||
const scrollHeight = contentRef.current.scrollHeight;
|
||||
setContentHeight(scrollHeight);
|
||||
}
|
||||
};
|
||||
|
||||
const updateHeight = () => {
|
||||
if (contentRef.current) {
|
||||
const scrollHeight = contentRef.current.scrollHeight;
|
||||
setContentHeight(scrollHeight);
|
||||
}
|
||||
};
|
||||
// Update height immediately
|
||||
updateHeight();
|
||||
|
||||
// Update height immediately
|
||||
updateHeight();
|
||||
// Use ResizeObserver to monitor content changes
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
updateHeight();
|
||||
});
|
||||
|
||||
// Use ResizeObserver to monitor content changes
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
updateHeight();
|
||||
});
|
||||
resizeObserver.observe(contentRef.current);
|
||||
|
||||
resizeObserver.observe(contentRef.current);
|
||||
// Update height when taskRunning changes
|
||||
const timeoutId = setTimeout(updateHeight, 100);
|
||||
|
||||
// Update height when taskRunning changes
|
||||
const timeoutId = setTimeout(updateHeight, 100);
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, [taskRunning, isExpanded]);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, [taskRunning, isExpanded]);
|
||||
// Handle height updates specifically for expand/collapse state changes
|
||||
useEffect(() => {
|
||||
if (!contentRef.current || !isExpanded) return;
|
||||
|
||||
// Handle height updates specifically for expand/collapse state changes
|
||||
useEffect(() => {
|
||||
if (!contentRef.current || !isExpanded) return;
|
||||
const updateHeightOnExpand = () => {
|
||||
if (contentRef.current && isExpanded) {
|
||||
// Small delay to ensure DOM is fully rendered
|
||||
requestAnimationFrame(() => {
|
||||
if (contentRef.current) {
|
||||
setContentHeight(contentRef.current.scrollHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateHeightOnExpand = () => {
|
||||
if (contentRef.current && isExpanded) {
|
||||
// Small delay to ensure DOM is fully rendered
|
||||
requestAnimationFrame(() => {
|
||||
if (contentRef.current) {
|
||||
setContentHeight(contentRef.current.scrollHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// Update height immediately when expanded
|
||||
updateHeightOnExpand();
|
||||
|
||||
// Update height immediately when expanded
|
||||
updateHeightOnExpand();
|
||||
// Additional delay when expanded to ensure all animations complete
|
||||
if (isExpanded) {
|
||||
const timeoutId = setTimeout(updateHeightOnExpand, 300);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}
|
||||
}, [isExpanded]);
|
||||
|
||||
// Additional delay when expanded to ensure all animations complete
|
||||
if (isExpanded) {
|
||||
const timeoutId = setTimeout(updateHeightOnExpand, 300);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}
|
||||
}, [isExpanded]);
|
||||
return (
|
||||
<div>
|
||||
<div className="w-full h-auto flex flex-col pl-sm gap-2 transition-all duration-300">
|
||||
<div className="w-full h-auto bg-task-surface rounded-xl py-sm relative overflow-hidden">
|
||||
<div className="absolute top-0 left-0 w-full bg-transparent">
|
||||
<Progress value={progressValue} className="h-[2px] w-full" />
|
||||
</div>
|
||||
{summaryTask && (
|
||||
<div className="text-sm font-bold leading-13 mb-2.5 px-sm">
|
||||
{summaryTask.split("|")[0].replace(/"/g, "")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
if (!chatStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
{summaryTask && (
|
||||
<div className={`flex items-center justify-between gap-2 px-sm`}>
|
||||
<div className="flex items-center gap-2 ">
|
||||
{taskType === 1 && (
|
||||
<TaskState
|
||||
all={taskInfo.filter((task) => task.content !== "").length || 0}
|
||||
done={
|
||||
taskInfo.filter(
|
||||
(task) =>
|
||||
task.content !== "" && task.status === "completed"
|
||||
).length || 0
|
||||
}
|
||||
progress={
|
||||
taskInfo.filter(
|
||||
(task) =>
|
||||
task.content !== "" &&
|
||||
task.status !== "completed" &&
|
||||
task.status !== "failed" &&
|
||||
task.status !== "skipped" &&
|
||||
task.status !== "waiting" &&
|
||||
task.status !== ""
|
||||
).length || 0
|
||||
}
|
||||
skipped={
|
||||
taskInfo.filter(
|
||||
(task) =>
|
||||
task.content !== "" &&
|
||||
(task.status === "skipped" ||
|
||||
task.status === "waiting" ||
|
||||
task.status === "")
|
||||
).length || 0
|
||||
}
|
||||
failed={
|
||||
taskInfo.filter(
|
||||
(task) => task.content !== "" && task.status === "failed"
|
||||
).length || 0
|
||||
}
|
||||
forceVisible={true}
|
||||
clickable={clickable}
|
||||
/>
|
||||
)}
|
||||
{taskType !== 1 && (
|
||||
<TaskState
|
||||
all={taskRunning?.length || 0}
|
||||
done={
|
||||
taskRunning?.filter((task) => task.status === "completed")
|
||||
.length || 0
|
||||
}
|
||||
progress={
|
||||
taskRunning?.filter(
|
||||
(task) =>
|
||||
task.status !== "completed" &&
|
||||
task.status !== "failed" &&
|
||||
task.status !== "skipped" &&
|
||||
task.status !== "waiting" &&
|
||||
task.status !== ""
|
||||
).length || 0
|
||||
}
|
||||
skipped={
|
||||
taskRunning?.filter(
|
||||
(task) =>
|
||||
task.status === "skipped" ||
|
||||
task.status === "waiting" ||
|
||||
task.status === ""
|
||||
).length || 0
|
||||
}
|
||||
failed={
|
||||
taskRunning?.filter((task) => task.status === "failed")
|
||||
.length || 0
|
||||
}
|
||||
forceVisible={true}
|
||||
selectedState={selectedState}
|
||||
onStateChange={setSelectedState}
|
||||
clickable={clickable}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex h-auto w-full flex-col gap-2 pl-sm transition-all duration-300">
|
||||
<div className="relative h-auto w-full overflow-hidden rounded-xl bg-task-surface py-sm">
|
||||
<div className="absolute left-0 top-0 w-full bg-transparent">
|
||||
<Progress value={progressValue} className="h-[2px] w-full" />
|
||||
</div>
|
||||
{summaryTask && (
|
||||
<div className="mb-2.5 px-sm text-sm font-bold leading-13">
|
||||
{summaryTask.split('|')[0].replace(/"/g, '')}
|
||||
</div>
|
||||
)}
|
||||
<div className="transition-all duration-300 ease-in-out">
|
||||
{taskType === 1 && (
|
||||
<Button variant="ghost" size="icon" onClick={onAddTask}>
|
||||
<Plus size={16} />
|
||||
</Button>
|
||||
)}
|
||||
{taskType === 2 && (
|
||||
<div className="flex items-center gap-2 animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
{(isExpanded || isAllTaskFinished) && (
|
||||
<div className="text-text-tertiary text-xs font-medium leading-17">
|
||||
{taskRunning?.filter(
|
||||
(task) =>
|
||||
task.status === "completed" ||
|
||||
task.status === "failed"
|
||||
).length || 0}
|
||||
/{taskRunning?.length || 0}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className={`transition-transform duration-300 ${isExpanded ? "rotate-180" : ""
|
||||
}`}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{summaryTask && (
|
||||
<div className={`flex items-center justify-between gap-2 px-sm`}>
|
||||
<div className="flex items-center gap-2">
|
||||
{taskType === 1 && (
|
||||
<TaskState
|
||||
all={
|
||||
taskInfo.filter((task) => task.content !== '').length || 0
|
||||
}
|
||||
done={
|
||||
taskInfo.filter(
|
||||
(task) =>
|
||||
task.content !== '' && task.status === 'completed'
|
||||
).length || 0
|
||||
}
|
||||
progress={
|
||||
taskInfo.filter(
|
||||
(task) =>
|
||||
task.content !== '' &&
|
||||
task.status !== 'completed' &&
|
||||
task.status !== 'failed' &&
|
||||
task.status !== 'skipped' &&
|
||||
task.status !== 'waiting' &&
|
||||
task.status !== ''
|
||||
).length || 0
|
||||
}
|
||||
skipped={
|
||||
taskInfo.filter(
|
||||
(task) =>
|
||||
task.content !== '' &&
|
||||
(task.status === 'skipped' ||
|
||||
task.status === 'waiting' ||
|
||||
task.status === '')
|
||||
).length || 0
|
||||
}
|
||||
failed={
|
||||
taskInfo.filter(
|
||||
(task) =>
|
||||
task.content !== '' && task.status === 'failed'
|
||||
).length || 0
|
||||
}
|
||||
forceVisible={true}
|
||||
clickable={clickable}
|
||||
/>
|
||||
)}
|
||||
{taskType !== 1 && (
|
||||
<TaskState
|
||||
all={taskRunning?.length || 0}
|
||||
done={
|
||||
taskRunning?.filter((task) => task.status === 'completed')
|
||||
.length || 0
|
||||
}
|
||||
progress={
|
||||
taskRunning?.filter(
|
||||
(task) =>
|
||||
task.status !== 'completed' &&
|
||||
task.status !== 'failed' &&
|
||||
task.status !== 'skipped' &&
|
||||
task.status !== 'waiting' &&
|
||||
task.status !== ''
|
||||
).length || 0
|
||||
}
|
||||
skipped={
|
||||
taskRunning?.filter(
|
||||
(task) =>
|
||||
task.status === 'skipped' ||
|
||||
task.status === 'waiting' ||
|
||||
task.status === ''
|
||||
).length || 0
|
||||
}
|
||||
failed={
|
||||
taskRunning?.filter((task) => task.status === 'failed')
|
||||
.length || 0
|
||||
}
|
||||
forceVisible={true}
|
||||
selectedState={selectedState}
|
||||
onStateChange={setSelectedState}
|
||||
clickable={clickable}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative">
|
||||
{taskType === 1 && (
|
||||
<div className="mt-sm flex flex-col px-sm animate-in fade-in-0 slide-in-from-bottom-4 duration-500 ease-out">
|
||||
{taskInfo.map((task, taskIndex) => (
|
||||
<div
|
||||
key={`task-${taskIndex}`}
|
||||
className="animate-in fade-in-0 slide-in-from-left-2 duration-300"
|
||||
>
|
||||
<TaskItem
|
||||
taskInfo={task}
|
||||
taskIndex={taskIndex}
|
||||
onUpdate={(content) => onUpdateTask(taskIndex, content)}
|
||||
onDelete={() => onDeleteTask(taskIndex)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{taskType === 2 && (
|
||||
<div
|
||||
ref={contentRef}
|
||||
className="overflow-hidden transition-all duration-300 ease-in-out"
|
||||
style={{
|
||||
height: isExpanded ? contentHeight : 0,
|
||||
opacity: isExpanded ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<div className="mt-sm flex flex-col px-2 gap-2">
|
||||
{filterTasks.map((task: TaskInfo) => {
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
if (task.agent) {
|
||||
// Switch to the chatStore that owns this task card (for multi-turn conversations)
|
||||
if (chatId && projectStore.activeProjectId) {
|
||||
const activeProjectId = projectStore.activeProjectId;
|
||||
const activeChatStore = projectStore.getActiveChatStore();
|
||||
const currentChatId = activeChatStore ? Object.keys(projectStore.projects[activeProjectId].chatStores).find(
|
||||
id => projectStore.projects[activeProjectId].chatStores[id] === activeChatStore
|
||||
) : null;
|
||||
|
||||
<div className="transition-all duration-300 ease-in-out">
|
||||
{taskType === 1 && (
|
||||
<Button variant="ghost" size="icon" onClick={onAddTask}>
|
||||
<Plus size={16} />
|
||||
</Button>
|
||||
)}
|
||||
{taskType === 2 && (
|
||||
<div className="flex items-center gap-2 duration-300 animate-in fade-in-0 slide-in-from-right-2">
|
||||
{(isExpanded || isAllTaskFinished) && (
|
||||
<div className="text-text-tertiary text-xs font-medium leading-17">
|
||||
{taskRunning?.filter(
|
||||
(task) =>
|
||||
task.status === 'completed' ||
|
||||
task.status === 'failed'
|
||||
).length || 0}
|
||||
/{taskRunning?.length || 0}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className={`transition-transform duration-300 ${
|
||||
isExpanded ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
// Only switch if this is a different chat
|
||||
if (currentChatId && currentChatId !== chatId) {
|
||||
projectStore.setActiveChatStore(activeProjectId, chatId);
|
||||
}
|
||||
}
|
||||
|
||||
<div className="relative">
|
||||
{taskType === 1 && (
|
||||
<div className="mt-sm flex flex-col px-sm duration-500 ease-out animate-in fade-in-0 slide-in-from-bottom-4">
|
||||
{taskInfo.map((task, taskIndex) => (
|
||||
<div
|
||||
key={`task-${taskIndex}`}
|
||||
className="duration-300 animate-in fade-in-0 slide-in-from-left-2"
|
||||
>
|
||||
<TaskItem
|
||||
taskInfo={task}
|
||||
taskIndex={taskIndex}
|
||||
onUpdate={(content) => onUpdateTask(taskIndex, content)}
|
||||
onDelete={() => onDeleteTask(taskIndex)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{taskType === 2 && (
|
||||
<div
|
||||
ref={contentRef}
|
||||
className="overflow-hidden transition-all duration-300 ease-in-out"
|
||||
style={{
|
||||
height: isExpanded ? contentHeight : 0,
|
||||
opacity: isExpanded ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
<div className="mt-sm flex flex-col gap-2 px-2">
|
||||
{filterTasks.map((task: TaskInfo) => {
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
if (task.agent) {
|
||||
// Switch to the chatStore that owns this task card (for multi-turn conversations)
|
||||
if (chatId && projectStore.activeProjectId) {
|
||||
const activeProjectId =
|
||||
projectStore.activeProjectId;
|
||||
const activeChatStore =
|
||||
projectStore.getActiveChatStore();
|
||||
const currentChatId = activeChatStore
|
||||
? Object.keys(
|
||||
projectStore.projects[activeProjectId]
|
||||
.chatStores
|
||||
).find(
|
||||
(id) =>
|
||||
projectStore.projects[activeProjectId]
|
||||
.chatStores[id] === activeChatStore
|
||||
)
|
||||
: null;
|
||||
|
||||
// Only switch if this is a different chat
|
||||
if (currentChatId && currentChatId !== chatId) {
|
||||
projectStore.setActiveChatStore(
|
||||
activeProjectId,
|
||||
chatId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the active workspace and agent
|
||||
chatStore.setActiveWorkSpace(
|
||||
chatStore.activeTaskId as string,
|
||||
'workflow'
|
||||
);
|
||||
chatStore.setActiveAgent(
|
||||
chatStore.activeTaskId as string,
|
||||
task.agent?.agent_id
|
||||
);
|
||||
window.electronAPI.hideAllWebview();
|
||||
}
|
||||
}}
|
||||
key={`taskList-${task.id}`}
|
||||
className={`flex gap-2 rounded-lg px-sm py-sm transition-all duration-300 ease-in-out animate-in fade-in-0 slide-in-from-left-2 ${
|
||||
task.status === 'completed'
|
||||
? 'bg-green-50'
|
||||
: task.status === 'failed'
|
||||
? 'bg-task-fill-error'
|
||||
: task.status === 'running'
|
||||
? 'bg-zinc-50'
|
||||
: task.status === 'blocked'
|
||||
? 'bg-task-fill-warning'
|
||||
: 'bg-zinc-50'
|
||||
} cursor-pointer border border-solid border-transparent ${
|
||||
task.status === 'completed'
|
||||
? 'hover:border-bg-fill-success-primary'
|
||||
: task.status === 'failed'
|
||||
? 'hover:border-task-border-focus-error'
|
||||
: task.status === 'running'
|
||||
? 'hover:border-border-primary'
|
||||
: task.status === 'blocked'
|
||||
? 'hover:border-task-border-focus-warning'
|
||||
: 'border-transparent'
|
||||
} `}
|
||||
>
|
||||
<div className="pt-0.5">
|
||||
{task.status === 'running' && (
|
||||
<LoaderCircle
|
||||
size={16}
|
||||
className={`text-icon-information ${
|
||||
chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
].status === 'running' && 'animate-spin'
|
||||
} `}
|
||||
/>
|
||||
)}
|
||||
{task.status === 'skipped' && (
|
||||
<LoaderCircle
|
||||
size={16}
|
||||
className={`text-icon-secondary`}
|
||||
/>
|
||||
)}
|
||||
{task.status === 'completed' && (
|
||||
<CircleCheckBig
|
||||
size={16}
|
||||
className="text-icon-success"
|
||||
/>
|
||||
)}
|
||||
{task.status === 'failed' && (
|
||||
<CircleSlash
|
||||
size={16}
|
||||
className="text-icon-cuation"
|
||||
/>
|
||||
)}
|
||||
{task.status === 'blocked' && (
|
||||
<TriangleAlert
|
||||
size={16}
|
||||
className="text-icon-warning"
|
||||
/>
|
||||
)}
|
||||
{task.status === '' && (
|
||||
<Circle size={16} className="text-icon-secondary" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col items-start justify-center">
|
||||
<div
|
||||
className={`w-full whitespace-pre-line break-words ${
|
||||
task.status === 'failed'
|
||||
? 'text-text-cuation-default'
|
||||
: task.status === 'blocked'
|
||||
? 'text-text-body'
|
||||
: 'text-text-primary'
|
||||
} text-sm font-medium leading-13`}
|
||||
>
|
||||
{task.content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// Set the active workspace and agent
|
||||
chatStore.setActiveWorkSpace(
|
||||
chatStore.activeTaskId as string,
|
||||
"workflow"
|
||||
);
|
||||
chatStore.setActiveAgent(
|
||||
chatStore.activeTaskId as string,
|
||||
task.agent?.agent_id
|
||||
);
|
||||
window.electronAPI.hideAllWebview();
|
||||
}
|
||||
}}
|
||||
key={`taskList-${task.id}`}
|
||||
className={`rounded-lg flex gap-2 py-sm px-sm transition-all duration-300 ease-in-out animate-in fade-in-0 slide-in-from-left-2 ${task.status === "completed"
|
||||
? "bg-task-fill-success"
|
||||
: task.status === "failed"
|
||||
? "bg-task-fill-error"
|
||||
: task.status === "running"
|
||||
? "bg-task-fill-running"
|
||||
: task.status === "blocked"
|
||||
? "bg-task-fill-warning"
|
||||
: "bg-task-fill-running"
|
||||
} border border-solid border-transparent cursor-pointer ${task.status === "completed"
|
||||
? "hover:border-bg-fill-success-primary"
|
||||
: task.status === "failed"
|
||||
? "hover:border-task-border-focus-error"
|
||||
: task.status === "running"
|
||||
? "hover:border-border-primary"
|
||||
: task.status === "blocked"
|
||||
? "hover:border-task-border-focus-warning"
|
||||
: "border-transparent"
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div className="pt-0.5">
|
||||
{task.status === "running" && (
|
||||
<LoaderCircle
|
||||
size={16}
|
||||
className={`text-icon-information ${chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
].status === "running" && "animate-spin"
|
||||
} `}
|
||||
/>
|
||||
)}
|
||||
{task.status === "skipped" && (
|
||||
<LoaderCircle
|
||||
size={16}
|
||||
className={`text-icon-secondary `}
|
||||
/>
|
||||
)}
|
||||
{task.status === "completed" && (
|
||||
<CircleCheckBig
|
||||
size={16}
|
||||
className="text-icon-success"
|
||||
/>
|
||||
)}
|
||||
{task.status === "failed" && (
|
||||
<CircleSlash
|
||||
size={16}
|
||||
className="text-icon-cuation"
|
||||
/>
|
||||
)}
|
||||
{task.status === "blocked" && (
|
||||
<TriangleAlert
|
||||
size={16}
|
||||
className="text-icon-warning"
|
||||
/>
|
||||
)}
|
||||
{task.status === "" && (
|
||||
<Circle size={16} className="text-icon-secondary" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col items-start justify-center">
|
||||
<div
|
||||
className={` w-full break-words whitespace-pre-line ${task.status === "failed"
|
||||
? "text-text-cuation-default"
|
||||
: task.status === "blocked"
|
||||
? "text-text-body"
|
||||
: "text-text-primary"
|
||||
} text-sm font-medium leading-13 `}
|
||||
>
|
||||
{task.content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,55 +12,43 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '../ui/dialog';
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "../ui/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
trigger?: React.ReactNode;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
trigger?: React.ReactNode;
|
||||
}
|
||||
export default function CloseNoticeDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
trigger,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
const onSubmit = useCallback(() => {
|
||||
window.electronAPI.closeWindow(true);
|
||||
}, []);
|
||||
export default function CloseNoticeDialog({ open, onOpenChange, trigger }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const onSubmit = useCallback(() => {
|
||||
window.electronAPI.closeWindow(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
|
||||
<DialogContent className="gap-0 !rounded-xl border border-zinc-300 !bg-popup-surface p-0 shadow-sm sm:max-w-[600px]">
|
||||
<DialogHeader className="!rounded-t-xl !bg-popup-surface p-md">
|
||||
<DialogTitle className="m-0">{t('layout.close-notice')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-md bg-popup-bg p-md">
|
||||
{t('layout.a-task-is-currently-running')}
|
||||
</div>
|
||||
<DialogFooter className="!rounded-b-xl bg-white-100% p-md">
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost" size="md">
|
||||
{t('layout.cancel')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button size="md" onClick={onSubmit} variant="primary">
|
||||
{t('layout.yes')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
return <Dialog open={open} onOpenChange={onOpenChange}>
|
||||
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
|
||||
<DialogContent className="sm:max-w-[600px] p-0 !bg-popup-surface gap-0 !rounded-xl border border-border-subtle-strong shadow-sm">
|
||||
<DialogHeader className="!bg-popup-surface !rounded-t-xl p-md">
|
||||
<DialogTitle className="m-0">
|
||||
{t("layout.close-notice")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-md bg-popup-bg p-md">
|
||||
{t("layout.a-task-is-currently-running")}
|
||||
</div>
|
||||
<DialogFooter className="bg-white-100% !rounded-b-xl p-md">
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost" size="md">
|
||||
{t("layout.cancel")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button size="md" onClick={onSubmit} variant="primary">
|
||||
{t("layout.yes")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,65 +12,50 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
trigger?: React.ReactNode;
|
||||
onConfirm: () => void;
|
||||
loading?: boolean;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
trigger?: React.ReactNode;
|
||||
onConfirm: () => void;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export default function EndNoticeDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
trigger,
|
||||
onConfirm,
|
||||
loading = false,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
const onSubmit = useCallback(() => {
|
||||
onConfirm();
|
||||
}, [onConfirm]);
|
||||
export default function EndNoticeDialog({ open, onOpenChange, trigger, onConfirm, loading = false }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const onSubmit = useCallback(() => {
|
||||
onConfirm();
|
||||
}, [onConfirm]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
|
||||
<DialogContent className="gap-0 !rounded-xl border border-zinc-300 !bg-popup-surface p-0 shadow-sm sm:max-w-[600px]">
|
||||
<DialogHeader className="justify-start !rounded-t-xl !bg-popup-surface p-md">
|
||||
<DialogTitle className="m-0">{t('layout.end-project')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-md bg-popup-bg p-md">
|
||||
{t('layout.ending-this-project-will-stop')}
|
||||
</div>
|
||||
<DialogFooter className="!rounded-b-xl bg-white-100% p-md">
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost" size="md" disabled={loading}>
|
||||
{t('layout.cancel')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
size="md"
|
||||
onClick={onSubmit}
|
||||
variant="cuation"
|
||||
disabled={loading}
|
||||
>
|
||||
{t('layout.yes-end-project')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
|
||||
<DialogContent className="sm:max-w-[600px] p-0 !bg-popup-surface gap-0 !rounded-xl border border-border-subtle-strong shadow-sm">
|
||||
<DialogHeader className="!bg-popup-surface !rounded-t-xl p-md justify-start">
|
||||
<DialogTitle className="m-0">{t("layout.end-project")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-md bg-popup-bg p-md">
|
||||
{t("layout.ending-this-project-will-stop")}
|
||||
</div>
|
||||
<DialogFooter className="bg-white-100% !rounded-b-xl p-md">
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost" size="md" disabled={loading}>{t("layout.cancel")}</Button>
|
||||
</DialogClose>
|
||||
<Button size="md" onClick={onSubmit} variant="cuation" disabled={loading}>{t("layout.yes-end-project")}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,190 +12,186 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { proxyFetchGet, proxyFetchPut } from '@/api/http';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { proxyFetchGet, proxyFetchPut } from "@/api/http";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface PrivacyDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
trigger?: React.ReactNode;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
trigger?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function PrivacyDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
trigger,
|
||||
}: PrivacyDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const API_FIELDS = useMemo(
|
||||
() => [
|
||||
'take_screenshot',
|
||||
'access_local_software',
|
||||
'access_your_address',
|
||||
'password_storage',
|
||||
],
|
||||
[]
|
||||
);
|
||||
export function PrivacyDialog({ open, onOpenChange, trigger }: PrivacyDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const API_FIELDS = [
|
||||
"take_screenshot",
|
||||
"access_local_software",
|
||||
"access_your_address",
|
||||
"password_storage",
|
||||
];
|
||||
|
||||
const [settings, setSettings] = useState([
|
||||
{
|
||||
title: t('layout.allow-agent-to-take-screenshots'),
|
||||
description: t('layout.permit-the-agent-to-capture'),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t('layout.allow-agent-to-access-local-software'),
|
||||
description: t('layout.grant-the-agent-permission'),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t('layout.allow-agent-to-access-your-address'),
|
||||
description: t('layout.authorize-the-agent-to-view'),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t('layout.password-storage'),
|
||||
description: t('layout.determine-how-passwords-are-handled'),
|
||||
checked: false,
|
||||
},
|
||||
]);
|
||||
const [settings, setSettings] = useState([
|
||||
{
|
||||
title: t("layout.allow-agent-to-take-screenshots"),
|
||||
description: t("layout.permit-the-agent-to-capture"),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t("layout.allow-agent-to-access-local-software"),
|
||||
description: t("layout.grant-the-agent-permission"),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t("layout.allow-agent-to-access-your-address"),
|
||||
description: t("layout.authorize-the-agent-to-view"),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t("layout.password-storage"),
|
||||
description: t("layout.determine-how-passwords-are-handled"),
|
||||
checked: false,
|
||||
},
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
proxyFetchGet('/api/user/privacy')
|
||||
.then((res) => {
|
||||
setSettings((prev) =>
|
||||
prev.map((item, index) => ({
|
||||
...item,
|
||||
checked: res[API_FIELDS[index]] || false,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch((err) => console.error('Failed to fetch settings:', err));
|
||||
}, [API_FIELDS]);
|
||||
useEffect(() => {
|
||||
proxyFetchGet("/api/user/privacy")
|
||||
.then((res) => {
|
||||
setSettings((prev) =>
|
||||
prev.map((item, index) => ({
|
||||
...item,
|
||||
checked: res[API_FIELDS[index]] || false,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch((err) => console.error("Failed to fetch settings:", err));
|
||||
}, []);
|
||||
|
||||
const handleToggle = (index: number) => {
|
||||
setSettings((prev) => {
|
||||
const newSettings = [...prev];
|
||||
newSettings[index] = {
|
||||
...newSettings[index],
|
||||
checked: !newSettings[index].checked,
|
||||
};
|
||||
return newSettings;
|
||||
});
|
||||
const handleToggle = (index: number) => {
|
||||
setSettings((prev) => {
|
||||
const newSettings = [...prev];
|
||||
newSettings[index] = {
|
||||
...newSettings[index],
|
||||
checked: !newSettings[index].checked,
|
||||
};
|
||||
return newSettings;
|
||||
});
|
||||
|
||||
const requestData = {
|
||||
[API_FIELDS[0]]: settings[0].checked,
|
||||
[API_FIELDS[1]]: settings[1].checked,
|
||||
[API_FIELDS[2]]: settings[2].checked,
|
||||
[API_FIELDS[3]]: settings[3].checked,
|
||||
};
|
||||
const requestData = {
|
||||
[API_FIELDS[0]]: settings[0].checked,
|
||||
[API_FIELDS[1]]: settings[1].checked,
|
||||
[API_FIELDS[2]]: settings[2].checked,
|
||||
[API_FIELDS[3]]: settings[3].checked,
|
||||
};
|
||||
|
||||
requestData[API_FIELDS[index]] = !settings[index].checked;
|
||||
requestData[API_FIELDS[index]] = !settings[index].checked;
|
||||
|
||||
proxyFetchPut('/api/user/privacy', requestData).catch((err) =>
|
||||
console.error('Failed to update settings:', err)
|
||||
);
|
||||
};
|
||||
proxyFetchPut("/api/user/privacy", requestData).catch((err) =>
|
||||
console.error("Failed to update settings:", err)
|
||||
);
|
||||
};
|
||||
|
||||
const handleTurnOnAll = () => {
|
||||
const newSettings = settings.map((item) => ({
|
||||
...item,
|
||||
checked: true,
|
||||
}));
|
||||
setSettings(newSettings);
|
||||
const handleTurnOnAll = () => {
|
||||
const newSettings = settings.map((item) => ({
|
||||
...item,
|
||||
checked: true,
|
||||
}));
|
||||
setSettings(newSettings);
|
||||
|
||||
const requestData = {
|
||||
[API_FIELDS[0]]: true,
|
||||
[API_FIELDS[1]]: true,
|
||||
[API_FIELDS[2]]: true,
|
||||
[API_FIELDS[3]]: true,
|
||||
};
|
||||
const requestData = {
|
||||
[API_FIELDS[0]]: true,
|
||||
[API_FIELDS[1]]: true,
|
||||
[API_FIELDS[2]]: true,
|
||||
[API_FIELDS[3]]: true,
|
||||
};
|
||||
|
||||
proxyFetchPut('/api/user/privacy', requestData)
|
||||
.then(() => {
|
||||
onOpenChange(false);
|
||||
})
|
||||
.catch((err) => console.error('Failed to update settings:', err));
|
||||
};
|
||||
proxyFetchPut("/api/user/privacy", requestData)
|
||||
.then(() => {
|
||||
onOpenChange(false);
|
||||
})
|
||||
.catch((err) => console.error("Failed to update settings:", err));
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
|
||||
<DialogContent className="gap-0 !rounded-xl border border-zinc-300 !bg-popup-surface p-0 shadow-sm sm:max-w-[600px]">
|
||||
<DialogHeader className="!rounded-t-xl !bg-popup-surface p-md">
|
||||
<DialogTitle className="m-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-base font-bold leading-10 text-text-action">
|
||||
{t('layout.turn-on-all-privacy-settings')}
|
||||
</div>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<AlertCircle
|
||||
size={16}
|
||||
className="cursor-pointer text-icon-primary"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-[340px]">
|
||||
<p className="text-sm text-text-body">
|
||||
{t('layout.eigent-is-a-desktop-software')}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
|
||||
<DialogContent className="sm:max-w-[600px] p-0 !bg-popup-surface gap-0 !rounded-xl border border-border-subtle-strong shadow-sm">
|
||||
<DialogHeader className="!bg-popup-surface !rounded-t-xl p-md">
|
||||
<DialogTitle className="m-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-base font-bold leading-10 text-text-action">
|
||||
{t("layout.turn-on-all-privacy-settings")}
|
||||
</div>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<AlertCircle
|
||||
size={16}
|
||||
className="text-icon-primary cursor-pointer"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-[340px]">
|
||||
<p className="text-text-body text-sm">{t("layout.eigent-is-a-desktop-software")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex flex-col gap-md bg-popup-bg p-md">
|
||||
{settings.map((item, index) => (
|
||||
<div key={item.title} className="mb-4 flex items-start gap-md">
|
||||
<div className="flex-1">
|
||||
<div className="text-text-primary mb-1 text-sm font-bold">
|
||||
{item.title}
|
||||
</div>
|
||||
<div className="text-xs text-text-body">{item.description}</div>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<Switch
|
||||
checked={item.checked}
|
||||
onCheckedChange={() => handleToggle(index)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-md bg-popup-bg p-md">
|
||||
{settings.map((item, index) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className="flex gap-md items-start mb-4"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-bold text-text-primary mb-1">
|
||||
{item.title}
|
||||
</div>
|
||||
<div className="text-xs text-text-body">
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<Switch
|
||||
checked={item.checked}
|
||||
onCheckedChange={() => handleToggle(index)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="!rounded-b-xl bg-white-100% p-md">
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost" size="md">
|
||||
{t('layout.cancel')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button size="md" onClick={handleTurnOnAll} variant="primary">
|
||||
{t('layout.turn-on-all-and-finish')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
<DialogFooter className="bg-white-100% !rounded-b-xl p-md">
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost" size="md">
|
||||
{t("layout.cancel")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button size="md" onClick={handleTurnOnAll} variant="primary">
|
||||
{t("layout.turn-on-all-and-finish")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { RotateCcw, ZoomIn, ZoomOut } from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
import { ZoomOut, ZoomIn, RotateCcw } from "lucide-react";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
// Zoom Controls Component
|
||||
interface ZoomControlsProps {
|
||||
|
|
@ -23,47 +23,40 @@ interface ZoomControlsProps {
|
|||
onZoomReset: () => void;
|
||||
}
|
||||
|
||||
export const ZoomControls = ({
|
||||
zoom,
|
||||
onZoomIn,
|
||||
onZoomOut,
|
||||
onZoomReset,
|
||||
}: ZoomControlsProps) => {
|
||||
export const ZoomControls = ({ zoom, onZoomIn, onZoomOut, onZoomReset }: ZoomControlsProps) => {
|
||||
return (
|
||||
<div className="group absolute left-1/2 top-0 z-10 -translate-x-1/2">
|
||||
<div className="flex translate-y-[calc(-100%-8px)] items-center gap-1 rounded-full border border-gray-300/50 bg-gray-100/90 px-3 py-1.5 shadow-lg backdrop-blur-xl transition-transform duration-300 ease-out group-hover:translate-y-[20px]">
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 z-10 group">
|
||||
<div className="flex items-center gap-1 px-3 py-1.5 bg-surface-hover-subtle/90 backdrop-blur-xl rounded-full shadow-lg border border-border-subtle-strong/50 translate-y-[calc(-100%-8px)] group-hover:translate-y-[20px] transition-transform duration-300 ease-out">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={onZoomOut}
|
||||
title="Zoom Out"
|
||||
className="h-7 w-7 text-gray-700 hover:bg-gray-200/60 hover:text-gray-900"
|
||||
className="h-7 w-7 hover:bg-gray-200/60 text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
<ZoomOut className="h-3.5 w-3.5" />
|
||||
<ZoomOut className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<span className="min-w-[2.5rem] text-center text-xs font-medium tabular-nums text-gray-800">
|
||||
{zoom}%
|
||||
</span>
|
||||
<span className="text-xs text-gray-800 min-w-[2.5rem] text-center font-medium tabular-nums">{zoom}%</span>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={onZoomIn}
|
||||
title="Zoom In"
|
||||
className="h-7 w-7 text-gray-700 hover:bg-gray-200/60 hover:text-gray-900"
|
||||
className="h-7 w-7 hover:bg-gray-200/60 text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
<ZoomIn className="h-3.5 w-3.5" />
|
||||
<ZoomIn className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<div className="mx-0.5 h-4 w-px bg-gray-300/60" />
|
||||
<div className="w-px h-4 bg-gray-300/60 mx-0.5" />
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={onZoomReset}
|
||||
title="Reset Zoom"
|
||||
className="h-7 w-7 text-gray-700 hover:bg-gray-200/60 hover:text-gray-900"
|
||||
className="h-7 w-7 hover:bg-gray-200/60 text-gray-700 hover:text-gray-900"
|
||||
>
|
||||
<RotateCcw className="h-3.5 w-3.5" />
|
||||
<RotateCcw className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
@ -129,9 +129,8 @@ const FileTree: React.FC<FileTreeProps> = ({
|
|||
)}
|
||||
|
||||
<span
|
||||
className={`truncate text-[13px] leading-5 ${
|
||||
child.isFolder ? 'font-semibold' : 'font-medium'
|
||||
}`}
|
||||
className={`truncate text-[13px] leading-5 ${child.isFolder ? 'font-semibold' : 'font-medium'
|
||||
}`}
|
||||
>
|
||||
{child.name}
|
||||
</span>
|
||||
|
|
@ -422,11 +421,11 @@ export default function Folder({ data: _data }: { data?: Agent }) {
|
|||
<div
|
||||
className={`${
|
||||
isCollapsed ? 'w-16' : 'w-64'
|
||||
} flex flex-shrink-0 flex-col border-[0px] border-r !border-solid border-zinc-300 border-r-zinc-200 transition-all duration-300 ease-in-out`}
|
||||
} flex flex-shrink-0 flex-col border-[0px] border-r !border-solid border-r-border-subtle border-border-subtle-strong transition-all duration-300 ease-in-out`}
|
||||
>
|
||||
{/* head */}
|
||||
<div
|
||||
className={`flex-shrink-0 border-b border-zinc-200 py-2 ${
|
||||
className={`flex-shrink-0 border-b border-border-subtle py-2 ${
|
||||
isCollapsed ? 'px-2' : 'pl-4 pr-2'
|
||||
}`}
|
||||
>
|
||||
|
|
@ -450,9 +449,8 @@ export default function Folder({ data: _data }: { data?: Agent }) {
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
className={`${
|
||||
isCollapsed ? 'w-full' : ''
|
||||
} flex items-center justify-center`}
|
||||
className={`${isCollapsed ? 'w-full' : ''
|
||||
} flex items-center justify-center`}
|
||||
title={isCollapsed ? t('chat.open') : t('chat.close')}
|
||||
>
|
||||
<ChevronsLeft
|
||||
|
|
@ -466,13 +464,13 @@ export default function Folder({ data: _data }: { data?: Agent }) {
|
|||
|
||||
{/* Search Input*/}
|
||||
{!isCollapsed && (
|
||||
<div className="flex-shrink-0 border-b border-zinc-200 px-2">
|
||||
<div className="flex-shrink-0 border-b border-border-subtle px-2">
|
||||
<div className="relative">
|
||||
<Search className="text-primary absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 transform" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder={t('chat.search')}
|
||||
className="w-full rounded-md border border-solid border-zinc-200 py-2 pl-9 pr-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="w-full rounded-md border border-solid border-border-subtle py-2 pl-9 pr-2 text-sm focus:outline-none focus:ring-2 focus:ring-text-link"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -530,7 +528,7 @@ export default function Folder({ data: _data }: { data?: Agent }) {
|
|||
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||
{/* head */}
|
||||
{selectedFile && (
|
||||
<div className="flex-shrink-0 border-b border-zinc-200 px-4 py-2">
|
||||
<div className="flex-shrink-0 border-b border-border-subtle px-4 py-2">
|
||||
<div className="flex h-[30px] items-center justify-between gap-2">
|
||||
<div
|
||||
onClick={() => {
|
||||
|
|
@ -593,8 +591,8 @@ export default function Folder({ data: _data }: { data?: Agent }) {
|
|||
title={selectedFile.name}
|
||||
/>
|
||||
) : ['csv', 'doc', 'docx', 'pptx', 'xlsx'].includes(
|
||||
selectedFile.type
|
||||
) ? (
|
||||
selectedFile.type
|
||||
) ? (
|
||||
<FolderComponent selectedFile={selectedFile} />
|
||||
) : selectedFile.type === 'html' ? (
|
||||
isShowSourceCode ? (
|
||||
|
|
@ -985,7 +983,7 @@ function HtmlRenderer({
|
|||
|
||||
{/* Content area with zoom */}
|
||||
<div
|
||||
className="min-h-0 flex-1 overflow-hidden bg-zinc-100"
|
||||
className="min-h-0 flex-1 overflow-hidden bg-code-surface"
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ export default function IntegrationList({
|
|||
: 'flex flex-col w-full items-start justify-start gap-4';
|
||||
|
||||
const itemClassName = isSelectMode
|
||||
? 'cursor-pointer hover:bg-gray-100 px-3 py-2 flex justify-between'
|
||||
? 'cursor-pointer hover:bg-surface-hover-subtle px-3 py-2 flex justify-between'
|
||||
: 'w-full px-6 py-4 bg-surface-secondary rounded-2xl';
|
||||
|
||||
const titleClassName = isSelectMode
|
||||
|
|
|
|||
|
|
@ -12,130 +12,102 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { AnimateIcon as AnimateIconProvider } from '@/components/animate-ui/icons/icon';
|
||||
import { cn } from '@/lib/utils';
|
||||
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import * as React from 'react';
|
||||
import * as React from "react";
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimateIcon as AnimateIconProvider } from "@/components/animate-ui/icons/icon";
|
||||
|
||||
const menuButtonVariants = cva(
|
||||
'relative inline-flex items-center justify-center select-none rounded-xs transition-colors duration-200 ease-in-out outline-none disabled:opacity-30 disabled:pointer-events-none bg-menubutton-fill-default border border-solid border-menubutton-border-default hover:bg-menubutton-fill-hover hover:border-menubutton-border-hover focus:bg-menubutton-fill-active focus:border-menubutton-border-active data-[state=on]:bg-menubutton-fill-active data-[state=on]:border-menubutton-border-active text-foreground cursor-pointer data-[state=on]:shadow-button-shadow rounded-lg',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: 'py-1 px-2 gap-1 text-label-sm font-bold [&_svg]:size-[16px]',
|
||||
sm: 'p-2 gap-1 text-label-sm font-bold [&_svg]:size-[20px]',
|
||||
md: 'p-2 gap-1 text-label-md font-bold [&_svg]:size-[24px]',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'md',
|
||||
},
|
||||
}
|
||||
"relative inline-flex items-center justify-center select-none rounded-xs transition-colors duration-200 ease-in-out outline-none disabled:opacity-30 disabled:pointer-events-none bg-menubutton-fill-default border border-solid border-menubutton-border-default hover:bg-menubutton-fill-hover hover:border-menubutton-border-hover hover:text-text-primary focus:bg-menubutton-fill-active focus:border-menubutton-border-active focus:text-text-primary data-[state=on]:bg-menubutton-fill-active data-[state=on]:border-menubutton-border-active data-[state=on]:text-text-primary text-text-secondary disabled:text-text-disabled cursor-pointer data-[state=on]:shadow-button-shadow rounded-lg",
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: "py-1 px-2 gap-1 text-label-sm font-bold [&_svg]:size-[16px]",
|
||||
sm: "p-2 gap-1 text-label-sm font-bold [&_svg]:size-[20px]",
|
||||
md: "p-2 gap-1 text-label-md font-bold [&_svg]:size-[24px]",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "md",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type MenuToggleContextValue = VariantProps<typeof menuButtonVariants>;
|
||||
|
||||
const MenuToggleGroupContext = React.createContext<MenuToggleContextValue>({
|
||||
size: 'md',
|
||||
size: "md",
|
||||
});
|
||||
|
||||
type MenuToggleGroupProps = React.ComponentPropsWithoutRef<
|
||||
typeof ToggleGroupPrimitive.Root
|
||||
> &
|
||||
VariantProps<typeof menuButtonVariants>;
|
||||
type MenuToggleGroupProps = React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> & VariantProps<typeof menuButtonVariants>;
|
||||
|
||||
export const MenuToggleGroup = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
MenuToggleGroupProps
|
||||
>(({ className, size, children, orientation = 'vertical', ...props }, ref) => (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'flex items-center justify-center gap-2',
|
||||
orientation === 'vertical' ? 'flex-col' : 'flex-row',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<MenuToggleGroupContext.Provider value={{ size }}>
|
||||
{children}
|
||||
</MenuToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
MenuToggleGroupProps
|
||||
>(({ className, size, children, orientation = "vertical", ...props }, ref) => (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-2",
|
||||
orientation === "vertical" ? "flex-col" : "flex-row",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<MenuToggleGroupContext.Provider value={{ size }}>
|
||||
{children}
|
||||
</MenuToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
));
|
||||
|
||||
MenuToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||
|
||||
type MenuToggleItemProps = React.ComponentPropsWithoutRef<
|
||||
typeof ToggleGroupPrimitive.Item
|
||||
> &
|
||||
VariantProps<typeof menuButtonVariants> & {
|
||||
icon?: React.ReactNode;
|
||||
subIcon?: React.ReactNode;
|
||||
showSubIcon?: boolean;
|
||||
disableIconAnimation?: boolean;
|
||||
iconAnimateOnHover?: boolean | string;
|
||||
};
|
||||
typeof ToggleGroupPrimitive.Item
|
||||
> & VariantProps<typeof menuButtonVariants> & {
|
||||
icon?: React.ReactNode;
|
||||
subIcon?: React.ReactNode;
|
||||
showSubIcon?: boolean;
|
||||
disableIconAnimation?: boolean;
|
||||
iconAnimateOnHover?: boolean | string;
|
||||
};
|
||||
|
||||
export const MenuToggleItem = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
MenuToggleItemProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
size,
|
||||
icon,
|
||||
subIcon,
|
||||
showSubIcon = false,
|
||||
disableIconAnimation = false,
|
||||
iconAnimateOnHover = true,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const context = React.useContext(MenuToggleGroupContext);
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
MenuToggleItemProps
|
||||
>(({ className, children, size, icon, subIcon, showSubIcon = false, disableIconAnimation = false, iconAnimateOnHover = true, ...props }, ref) => {
|
||||
const context = React.useContext(MenuToggleGroupContext);
|
||||
const iconNode = icon;
|
||||
|
||||
return (
|
||||
<AnimateIconProvider
|
||||
animateOnHover={
|
||||
disableIconAnimation
|
||||
? false
|
||||
: (iconAnimateOnHover as unknown as string | boolean)
|
||||
}
|
||||
asChild
|
||||
>
|
||||
<AnimateIconProvider animateOnHover={disableIconAnimation ? false : (iconAnimateOnHover as unknown as string | boolean)} asChild>
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'group',
|
||||
menuButtonVariants({ size: context.size || size }),
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={cn("group", menuButtonVariants({ size: context.size || size }), className)}
|
||||
{...props}
|
||||
>
|
||||
{showSubIcon && subIcon ? (
|
||||
<>
|
||||
<span className="inline-flex items-center gap-2">{children}</span>
|
||||
<span className="absolute right-1 top-1 inline-flex items-center justify-center [&_svg]:shrink-0">
|
||||
{subIcon}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
{iconNode}
|
||||
{children}
|
||||
</span>
|
||||
)}
|
||||
{showSubIcon && subIcon ? (
|
||||
<>
|
||||
<span className="inline-flex items-center gap-2">{children}</span>
|
||||
<span className="absolute right-1 top-1 inline-flex items-center justify-center [&_svg]:shrink-0">
|
||||
{subIcon}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
{iconNode}
|
||||
{children}
|
||||
</span>
|
||||
)}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
</AnimateIconProvider>
|
||||
</AnimateIconProvider>
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
MenuToggleItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||
|
||||
export { menuButtonVariants };
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,70 +12,74 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import giftIcon from '@/assets/gift.svg';
|
||||
import {
|
||||
MenuToggleGroup,
|
||||
MenuToggleItem,
|
||||
} from '@/components/MenuButton/MenuButton';
|
||||
import {
|
||||
FileDown,
|
||||
Inbox,
|
||||
LayoutGrid,
|
||||
MessageCircleQuestion,
|
||||
Network,
|
||||
Settings2Icon,
|
||||
} from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import React, { useState } from "react";
|
||||
import { MenuToggleGroup, MenuToggleItem } from "@/components/MenuButton/MenuButton";
|
||||
import { FileDown, Inbox, LayoutGrid, MessageCircleQuestion, Network, Settings2Icon } from "lucide-react";
|
||||
import giftIcon from "@/assets/gift.svg";
|
||||
import giftWhiteIcon from "@/assets/gift-white.svg";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
|
||||
// Icons - you can replace these with actual icon components
|
||||
const HomeIcon = () => <LayoutGrid />;
|
||||
|
||||
const WorkflowIcon = () => <Network />;
|
||||
|
||||
const InboxIcon = () => <Inbox />;
|
||||
|
||||
const SettingsIcon = () => <Settings2Icon />;
|
||||
|
||||
const BugIcon = () => <FileDown />;
|
||||
|
||||
const ReferIcon = () => (
|
||||
<img src={giftIcon} alt="gift-icon" className="h-[20px] w-[20px]" />
|
||||
const HomeIcon = () => (
|
||||
<LayoutGrid/>
|
||||
);
|
||||
|
||||
const SupportIcon = () => <MessageCircleQuestion />;
|
||||
const WorkflowIcon = () => (
|
||||
<Network/>
|
||||
);
|
||||
|
||||
const InboxIcon = () => (
|
||||
<Inbox/>
|
||||
);
|
||||
|
||||
const SettingsIcon = () => (
|
||||
<Settings2Icon/>
|
||||
);
|
||||
|
||||
const BugIcon = () => (
|
||||
<FileDown/>
|
||||
);
|
||||
|
||||
const SupportIcon = () => (
|
||||
<MessageCircleQuestion/>
|
||||
);
|
||||
|
||||
interface SideBarProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function SideBar({ className }: SideBarProps) {
|
||||
const [activeItem, setActiveItem] = useState('home');
|
||||
const [activeItem, setActiveItem] = useState("home");
|
||||
const { appearance } = useAuthStore();
|
||||
|
||||
const menuItems = [
|
||||
{ id: 'home', icon: <HomeIcon />, label: 'Home' },
|
||||
{ id: 'workflow', icon: <WorkflowIcon />, label: 'Workflow' },
|
||||
{ id: 'inbox', icon: <InboxIcon />, label: 'Inbox' },
|
||||
{ id: 'settings', icon: <SettingsIcon />, label: 'Settings' },
|
||||
{ id: "home", icon: <HomeIcon />, label: "Home" },
|
||||
{ id: "workflow", icon: <WorkflowIcon />, label: "Workflow" },
|
||||
{ id: "inbox", icon: <InboxIcon />, label: "Inbox" },
|
||||
{ id: "settings", icon: <SettingsIcon />, label: "Settings" },
|
||||
];
|
||||
|
||||
const bottomItems = [
|
||||
{ id: 'bug', icon: <BugIcon />, label: 'Bug' },
|
||||
{ id: 'refer', icon: <ReferIcon />, label: 'Refer' },
|
||||
{ id: 'support', icon: <SupportIcon />, label: 'Support' },
|
||||
{ id: "bug", icon: <BugIcon />, label: "Bug" },
|
||||
{
|
||||
id: "refer",
|
||||
icon: (
|
||||
<img
|
||||
src={appearance === "light" ? giftIcon : giftWhiteIcon}
|
||||
alt="gift-icon"
|
||||
className="w-[20px] h-[20px]"
|
||||
/>
|
||||
),
|
||||
label: "Refer",
|
||||
},
|
||||
{ id: "support", icon: <SupportIcon />, label: "Support" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex h-full flex-col items-center gap-1 pl-1 ${className}`}
|
||||
>
|
||||
<div className={`h-full flex flex-col items-center pl-1 gap-1 ${className}`}>
|
||||
{/* Main menu items */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<MenuToggleGroup
|
||||
type="single"
|
||||
orientation="vertical"
|
||||
value={activeItem}
|
||||
onValueChange={setActiveItem}
|
||||
>
|
||||
<MenuToggleGroup type="single" orientation="vertical" value={activeItem} onValueChange={setActiveItem}>
|
||||
{menuItems.map((item) => (
|
||||
<MenuToggleItem
|
||||
key={item.id}
|
||||
|
|
@ -88,13 +92,8 @@ export default function SideBar({ className }: SideBarProps) {
|
|||
</div>
|
||||
|
||||
{/* Bottom menu items */}
|
||||
<div className="flex flex-1 flex-col justify-end">
|
||||
<MenuToggleGroup
|
||||
type="single"
|
||||
orientation="vertical"
|
||||
value={activeItem}
|
||||
onValueChange={setActiveItem}
|
||||
>
|
||||
<div className="flex-1 flex flex-col justify-end">
|
||||
<MenuToggleGroup type="single" orientation="vertical" value={activeItem} onValueChange={setActiveItem}>
|
||||
{bottomItems.map((item) => (
|
||||
<MenuToggleItem
|
||||
key={item.id}
|
||||
|
|
|
|||
|
|
@ -12,387 +12,374 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import { WebLinksAddon } from "@xterm/addon-web-links";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
|
||||
|
||||
// Terminal Component Properties Interface
|
||||
interface TerminalComponentProps {
|
||||
content?: string[]; // terminal content array
|
||||
instanceId?: string; // terminal instance identifier, for multiple terminals
|
||||
showWelcome?: boolean; // whether to show welcome information
|
||||
content?: string[]; // terminal content array
|
||||
instanceId?: string; // terminal instance identifier, for multiple terminals
|
||||
showWelcome?: boolean; // whether to show welcome information
|
||||
}
|
||||
|
||||
export default function TerminalComponent({
|
||||
content,
|
||||
instanceId = 'default',
|
||||
showWelcome = false,
|
||||
content,
|
||||
instanceId = "default",
|
||||
showWelcome = false,
|
||||
}: TerminalComponentProps) {
|
||||
//Get Chatstore for the active project's task
|
||||
const { chatStore } = useChatStoreAdapter();
|
||||
//Get Chatstore for the active project's task
|
||||
const { chatStore } = useChatStoreAdapter();
|
||||
if (!chatStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
// DOM references
|
||||
const terminalContainerRef = useRef<HTMLDivElement>(null); // terminal container reference
|
||||
const terminalRef = useRef<HTMLDivElement>(null); // terminal element reference
|
||||
|
||||
// xterm.js related references
|
||||
const xtermRef = useRef<Terminal | null>(null); // xterm instance reference
|
||||
const fitAddonRef = useRef<FitAddon | null>(null); // fit addon reference
|
||||
// DOM references
|
||||
const terminalContainerRef = useRef<HTMLDivElement>(null); // terminal container reference
|
||||
const terminalRef = useRef<HTMLDivElement>(null); // terminal element reference
|
||||
|
||||
// state management
|
||||
const lastTerminalLength = useRef<number>(0); // record last content length, for incremental update
|
||||
const [currentLine, setCurrentLine] = useState<string>(''); // current input line
|
||||
const [cursorPos, setCursorPos] = useState<number>(0); // cursor position
|
||||
const currentLineRef = useRef<string>(''); // current input line ref, for event handling
|
||||
const cursorPosRef = useRef<number>(0); // cursor position ref, for event handling
|
||||
// xterm.js related references
|
||||
const xtermRef = useRef<Terminal | null>(null); // xterm instance reference
|
||||
const fitAddonRef = useRef<FitAddon | null>(null); // fit addon reference
|
||||
|
||||
// terminal configuration
|
||||
const promptText = 'Eigent:~$ '; // prompt text
|
||||
const isInitialized = useRef<boolean>(false); // initialization identifier, prevent duplicate initialization
|
||||
// state management
|
||||
const lastTerminalLength = useRef<number>(0); // record last content length, for incremental update
|
||||
const [currentLine, setCurrentLine] = useState<string>(""); // current input line
|
||||
const [cursorPos, setCursorPos] = useState<number>(0); // cursor position
|
||||
const currentLineRef = useRef<string>(""); // current input line ref, for event handling
|
||||
const cursorPosRef = useRef<number>(0); // cursor position ref, for event handling
|
||||
|
||||
// synchronize state to ref, for event handling
|
||||
useEffect(() => {
|
||||
currentLineRef.current = currentLine;
|
||||
}, [currentLine]);
|
||||
// terminal configuration
|
||||
const promptText = "Eigent:~$ "; // prompt text
|
||||
const isInitialized = useRef<boolean>(false); // initialization identifier, prevent duplicate initialization
|
||||
|
||||
useEffect(() => {
|
||||
cursorPosRef.current = cursorPos;
|
||||
}, [cursorPos]);
|
||||
// synchronize state to ref, for event handling
|
||||
useEffect(() => {
|
||||
currentLineRef.current = currentLine;
|
||||
}, [currentLine]);
|
||||
|
||||
// keyboard input handling function
|
||||
const handleKeyInput = useCallback(
|
||||
({ key, domEvent }: { key: string; domEvent: KeyboardEvent }) => {
|
||||
const ev = domEvent;
|
||||
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey; // check if it is printable character
|
||||
const terminal = xtermRef.current;
|
||||
if (!terminal) return;
|
||||
useEffect(() => {
|
||||
cursorPosRef.current = cursorPos;
|
||||
}, [cursorPos]);
|
||||
|
||||
if (ev.keyCode === 13) {
|
||||
// Enter key: execute command
|
||||
terminal.writeln('');
|
||||
if (currentLineRef.current.trim()) {
|
||||
terminal.writeln(
|
||||
`\x1b[90m# Executed: ${currentLineRef.current}\x1b[0m`
|
||||
);
|
||||
terminal.writeln(
|
||||
`\x1b[33m⚠ Interactive mode not fully implemented\x1b[0m`
|
||||
);
|
||||
}
|
||||
setCurrentLine('');
|
||||
setCursorPos(0);
|
||||
terminal.write(promptText);
|
||||
} else if (ev.keyCode === 8) {
|
||||
// Backspace key: delete character
|
||||
if (cursorPosRef.current > 0) {
|
||||
const newLine =
|
||||
currentLineRef.current.slice(0, cursorPosRef.current - 1) +
|
||||
currentLineRef.current.slice(cursorPosRef.current);
|
||||
setCurrentLine(newLine);
|
||||
setCursorPos(cursorPosRef.current - 1);
|
||||
terminal.write('\b \b'); // delete character before cursor
|
||||
}
|
||||
} else if (ev.keyCode === 37) {
|
||||
// left arrow: move cursor left
|
||||
if (cursorPosRef.current > 0) {
|
||||
setCursorPos(cursorPosRef.current - 1);
|
||||
terminal.write('\x1b[D'); // ANSI escape sequence: move cursor left
|
||||
}
|
||||
} else if (ev.keyCode === 39) {
|
||||
// right arrow: move cursor right
|
||||
if (cursorPosRef.current < currentLineRef.current.length) {
|
||||
setCursorPos(cursorPosRef.current + 1);
|
||||
terminal.write('\x1b[C'); // ANSI escape sequence: move cursor right
|
||||
}
|
||||
} else if (printable) {
|
||||
// printable character: insert at cursor position
|
||||
const newLine =
|
||||
currentLineRef.current.slice(0, cursorPosRef.current) +
|
||||
key +
|
||||
currentLineRef.current.slice(cursorPosRef.current);
|
||||
setCurrentLine(newLine);
|
||||
setCursorPos(cursorPosRef.current + 1);
|
||||
terminal.write(key);
|
||||
}
|
||||
},
|
||||
[promptText]
|
||||
);
|
||||
// keyboard input handling function
|
||||
const handleKeyInput = useCallback(
|
||||
({ key, domEvent }: { key: string; domEvent: KeyboardEvent }) => {
|
||||
const ev = domEvent;
|
||||
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey; // check if it is printable character
|
||||
const terminal = xtermRef.current;
|
||||
if (!terminal) return;
|
||||
|
||||
// initialize xterm terminal
|
||||
useEffect(() => {
|
||||
if (!terminalRef.current || isInitialized.current || !chatStore) return;
|
||||
console.log('isInitialized.current', isInitialized.current);
|
||||
// mark as initialized
|
||||
isInitialized.current = true;
|
||||
if (ev.keyCode === 13) {
|
||||
// Enter key: execute command
|
||||
terminal.writeln("");
|
||||
if (currentLineRef.current.trim()) {
|
||||
terminal.writeln(
|
||||
`\x1b[90m# Executed: ${currentLineRef.current}\x1b[0m`
|
||||
);
|
||||
terminal.writeln(
|
||||
`\x1b[33m⚠ Interactive mode not fully implemented\x1b[0m`
|
||||
);
|
||||
}
|
||||
setCurrentLine("");
|
||||
setCursorPos(0);
|
||||
terminal.write(promptText);
|
||||
} else if (ev.keyCode === 8) {
|
||||
// Backspace key: delete character
|
||||
if (cursorPosRef.current > 0) {
|
||||
const newLine =
|
||||
currentLineRef.current.slice(0, cursorPosRef.current - 1) +
|
||||
currentLineRef.current.slice(cursorPosRef.current);
|
||||
setCurrentLine(newLine);
|
||||
setCursorPos(cursorPosRef.current - 1);
|
||||
terminal.write("\b \b"); // delete character before cursor
|
||||
}
|
||||
} else if (ev.keyCode === 37) {
|
||||
// left arrow: move cursor left
|
||||
if (cursorPosRef.current > 0) {
|
||||
setCursorPos(cursorPosRef.current - 1);
|
||||
terminal.write("\x1b[D"); // ANSI escape sequence: move cursor left
|
||||
}
|
||||
} else if (ev.keyCode === 39) {
|
||||
// right arrow: move cursor right
|
||||
if (cursorPosRef.current < currentLineRef.current.length) {
|
||||
setCursorPos(cursorPosRef.current + 1);
|
||||
terminal.write("\x1b[C"); // ANSI escape sequence: move cursor right
|
||||
}
|
||||
} else if (printable) {
|
||||
// printable character: insert at cursor position
|
||||
const newLine =
|
||||
currentLineRef.current.slice(0, cursorPosRef.current) +
|
||||
key +
|
||||
currentLineRef.current.slice(cursorPosRef.current);
|
||||
setCurrentLine(newLine);
|
||||
setCursorPos(cursorPosRef.current + 1);
|
||||
terminal.write(key);
|
||||
}
|
||||
},
|
||||
[promptText]
|
||||
);
|
||||
|
||||
// create terminal instance
|
||||
const terminal = new Terminal({
|
||||
theme: {
|
||||
background: 'transparent', // transparent background
|
||||
foreground: '#ffffff', // white foreground
|
||||
cursor: '#00ff00', // green cursor
|
||||
cursorAccent: '#00ff00', // cursor accent
|
||||
},
|
||||
fontFamily: '"Courier New", Courier, monospace', // monospace font
|
||||
fontSize: 12, // font size
|
||||
lineHeight: 1.2, // line height
|
||||
letterSpacing: 0, // letter spacing
|
||||
cursorBlink: true, // cursor blink
|
||||
allowProposedApi: true, // allow proposed API
|
||||
scrollback: 1000, // scrollback lines
|
||||
rightClickSelectsWord: true, // right click selects word
|
||||
smoothScrollDuration: 0, // smooth scroll duration
|
||||
fastScrollModifier: 'alt', // fast scroll modifier
|
||||
convertEol: true, // convert end of line
|
||||
windowsMode: true, // Windows mode
|
||||
cols: 100, // columns
|
||||
rows: 30, // rows
|
||||
});
|
||||
// initialize xterm terminal
|
||||
useEffect(() => {
|
||||
if (!terminalRef.current || isInitialized.current) return;
|
||||
console.log("isInitialized.current", isInitialized.current);
|
||||
// mark as initialized
|
||||
isInitialized.current = true;
|
||||
|
||||
// add plugins
|
||||
const fitAddon = new FitAddon(); // fit addon
|
||||
const webLinksAddon = new WebLinksAddon(); // web links addon
|
||||
// create terminal instance
|
||||
const terminal = new Terminal({
|
||||
theme: {
|
||||
background: "transparent", // transparent background
|
||||
foreground: "#ffffff", // white foreground
|
||||
cursor: "#00ff00", // green cursor
|
||||
cursorAccent: "#00ff00", // cursor accent
|
||||
},
|
||||
fontFamily: '"Courier New", Courier, monospace', // monospace font
|
||||
fontSize: 12, // font size
|
||||
lineHeight: 1.2, // line height
|
||||
letterSpacing: 0, // letter spacing
|
||||
cursorBlink: true, // cursor blink
|
||||
allowProposedApi: true, // allow proposed API
|
||||
scrollback: 1000, // scrollback lines
|
||||
rightClickSelectsWord: true, // right click selects word
|
||||
smoothScrollDuration: 0, // smooth scroll duration
|
||||
fastScrollModifier: "alt", // fast scroll modifier
|
||||
convertEol: true, // convert end of line
|
||||
windowsMode: true, // Windows mode
|
||||
cols: 100, // columns
|
||||
rows: 30, // rows
|
||||
});
|
||||
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.loadAddon(webLinksAddon);
|
||||
// add plugins
|
||||
const fitAddon = new FitAddon(); // fit addon
|
||||
const webLinksAddon = new WebLinksAddon(); // web links addon
|
||||
|
||||
// open terminal
|
||||
terminal.open(terminalRef.current);
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.loadAddon(webLinksAddon);
|
||||
|
||||
// wait for layout to stabilize and adapt size, then write content
|
||||
setTimeout(() => {
|
||||
fitAddon.fit(); // adapt container size
|
||||
// open terminal
|
||||
terminal.open(terminalRef.current);
|
||||
|
||||
// only show welcome information when needed
|
||||
if (showWelcome) {
|
||||
terminal.writeln('\x1b[32m=== Eigent Terminal ===\x1b[0m');
|
||||
terminal.writeln(`\x1b[32mInstance: ${instanceId}\x1b[0m`);
|
||||
terminal.writeln('\x1b[32mReady for commands...\x1b[0m');
|
||||
terminal.writeln('');
|
||||
}
|
||||
// wait for layout to stabilize and adapt size, then write content
|
||||
setTimeout(() => {
|
||||
fitAddon.fit(); // adapt container size
|
||||
|
||||
// show prompt
|
||||
// terminal.write(promptText);
|
||||
}, 300);
|
||||
// only show welcome information when needed
|
||||
if (showWelcome) {
|
||||
terminal.writeln("\x1b[32m=== Eigent Terminal ===\x1b[0m");
|
||||
terminal.writeln(`\x1b[32mInstance: ${instanceId}\x1b[0m`);
|
||||
terminal.writeln("\x1b[32mReady for commands...\x1b[0m");
|
||||
terminal.writeln("");
|
||||
}
|
||||
|
||||
// save reference
|
||||
xtermRef.current = terminal;
|
||||
fitAddonRef.current = fitAddon;
|
||||
// show prompt
|
||||
// terminal.write(promptText);
|
||||
}, 300);
|
||||
|
||||
// add keyboard input handling
|
||||
terminal.onKey(handleKeyInput);
|
||||
// save reference
|
||||
xtermRef.current = terminal;
|
||||
fitAddonRef.current = fitAddon;
|
||||
|
||||
// clean up function
|
||||
return () => {
|
||||
terminal.dispose(); // destroy terminal instance
|
||||
xtermRef.current = null;
|
||||
isInitialized.current = false;
|
||||
};
|
||||
}, [handleKeyInput, promptText, showWelcome, instanceId, chatStore]);
|
||||
// add keyboard input handling
|
||||
terminal.onKey(handleKeyInput);
|
||||
|
||||
// listen to container size change
|
||||
useEffect(() => {
|
||||
if (!terminalContainerRef.current || !fitAddonRef.current || !chatStore)
|
||||
return;
|
||||
// clean up function
|
||||
return () => {
|
||||
terminal.dispose(); // destroy terminal instance
|
||||
xtermRef.current = null;
|
||||
isInitialized.current = false;
|
||||
};
|
||||
}, [handleKeyInput, promptText, showWelcome, instanceId]);
|
||||
|
||||
// use ResizeObserver to listen to container size change
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const _entry of entries) {
|
||||
// delay execution of fit to ensure layout stability
|
||||
setTimeout(() => {
|
||||
if (fitAddonRef.current) {
|
||||
fitAddonRef.current.fit();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
// listen to container size change
|
||||
useEffect(() => {
|
||||
if (!terminalContainerRef.current || !fitAddonRef.current) return;
|
||||
|
||||
resizeObserver.observe(terminalContainerRef.current);
|
||||
// use ResizeObserver to listen to container size change
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
// delay execution of fit to ensure layout stability
|
||||
setTimeout(() => {
|
||||
if (fitAddonRef.current) {
|
||||
fitAddonRef.current.fit();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
// listen to window size change
|
||||
const handleResize = () => {
|
||||
setTimeout(() => {
|
||||
if (fitAddonRef.current) {
|
||||
fitAddonRef.current.fit();
|
||||
}
|
||||
}, 150);
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
resizeObserver.observe(terminalContainerRef.current);
|
||||
|
||||
// clean up listeners
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, [chatStore]);
|
||||
// listen to window size change
|
||||
const handleResize = () => {
|
||||
setTimeout(() => {
|
||||
if (fitAddonRef.current) {
|
||||
fitAddonRef.current.fit();
|
||||
}
|
||||
}, 150);
|
||||
};
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
// listen to terminal data change and write to xterm
|
||||
useEffect(() => {
|
||||
if (!xtermRef.current || !content || !chatStore) return;
|
||||
const terminalData = content;
|
||||
const currentLength = terminalData.length;
|
||||
// clean up listeners
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// check if it is the case of component re-initialization
|
||||
// if lastTerminalLength is 0 but content has data, it means re-initialization
|
||||
if (lastTerminalLength.current === 0 && currentLength > 0) {
|
||||
console.log('component re-initialization, skip history data write');
|
||||
lastTerminalLength.current = currentLength;
|
||||
return;
|
||||
}
|
||||
// listen to terminal data change and write to xterm
|
||||
useEffect(() => {
|
||||
if (!xtermRef.current || !content) return;
|
||||
const terminalData = content;
|
||||
const currentLength = terminalData.length;
|
||||
|
||||
// only process new data (incremental update)
|
||||
if (currentLength > lastTerminalLength.current) {
|
||||
const newData = terminalData.slice(lastTerminalLength.current);
|
||||
// check if it is the case of component re-initialization
|
||||
// if lastTerminalLength is 0 but content has data, it means re-initialization
|
||||
if (lastTerminalLength.current === 0 && currentLength > 0) {
|
||||
console.log("component re-initialization, skip history data write");
|
||||
lastTerminalLength.current = currentLength;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('newData', newData);
|
||||
newData.forEach((item) => {
|
||||
if (!xtermRef.current) return;
|
||||
// only process new data (incremental update)
|
||||
if (currentLength > lastTerminalLength.current) {
|
||||
const newData = terminalData.slice(lastTerminalLength.current);
|
||||
|
||||
// move to line head and clear whole line
|
||||
xtermRef.current.write('\r');
|
||||
xtermRef.current.write('\x1b[2K'); // clear whole line
|
||||
console.log("newData", newData);
|
||||
newData.forEach((item) => {
|
||||
if (!xtermRef.current) return;
|
||||
|
||||
// process output content
|
||||
const formattedOutput = item
|
||||
.replace(/\r?\n$/, '') // remove trailing newline
|
||||
.replace(/\t/g, ' ') // convert tab to 4 spaces
|
||||
.replace(/\r/g, ''); // remove carriage return
|
||||
// move to line head and clear whole line
|
||||
xtermRef.current.write("\r");
|
||||
xtermRef.current.write("\x1b[2K"); // clear whole line
|
||||
|
||||
if (formattedOutput.trim()) {
|
||||
xtermRef.current.writeln(
|
||||
`\x1b[36m[Eigent]\x1b[0m ${formattedOutput}`
|
||||
);
|
||||
} else {
|
||||
xtermRef.current.writeln('');
|
||||
}
|
||||
// process output content
|
||||
const formattedOutput = item
|
||||
.replace(/\r?\n$/, "") // remove trailing newline
|
||||
.replace(/\t/g, " ") // convert tab to 4 spaces
|
||||
.replace(/\r/g, ""); // remove carriage return
|
||||
|
||||
// re-display prompt
|
||||
xtermRef.current.write(promptText);
|
||||
if (formattedOutput.trim()) {
|
||||
xtermRef.current.writeln(`\x1b[36m[Eigent]\x1b[0m ${formattedOutput}`);
|
||||
} else {
|
||||
xtermRef.current.writeln("");
|
||||
}
|
||||
|
||||
// re-display current input
|
||||
if (currentLineRef.current) {
|
||||
xtermRef.current.write(currentLineRef.current);
|
||||
// re-display prompt
|
||||
xtermRef.current.write(promptText);
|
||||
|
||||
// if cursor is not at the end, move to the correct position
|
||||
if (cursorPosRef.current < currentLineRef.current.length) {
|
||||
const moveBack =
|
||||
currentLineRef.current.length - cursorPosRef.current;
|
||||
for (let i = 0; i < moveBack; i++) {
|
||||
xtermRef.current.write('\x1b[D'); // move cursor left
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// re-display current input
|
||||
if (currentLineRef.current) {
|
||||
xtermRef.current.write(currentLineRef.current);
|
||||
|
||||
lastTerminalLength.current = currentLength;
|
||||
}
|
||||
}, [content, promptText, chatStore]);
|
||||
// if cursor is not at the end, move to the correct position
|
||||
if (cursorPosRef.current < currentLineRef.current.length) {
|
||||
const moveBack =
|
||||
currentLineRef.current.length - cursorPosRef.current;
|
||||
for (let i = 0; i < moveBack; i++) {
|
||||
xtermRef.current.write("\x1b[D"); // move cursor left
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// reset terminal when switching task
|
||||
useEffect(() => {
|
||||
if (!xtermRef.current || !chatStore) return;
|
||||
lastTerminalLength.current = currentLength;
|
||||
}
|
||||
}, [content, promptText]);
|
||||
|
||||
// clear terminal
|
||||
xtermRef.current.clear();
|
||||
// reset terminal when switching task
|
||||
useEffect(() => {
|
||||
if (!xtermRef.current) return;
|
||||
|
||||
// reset state
|
||||
lastTerminalLength.current = 0;
|
||||
// Use setTimeout to defer state updates and avoid cascading renders
|
||||
setTimeout(() => {
|
||||
setCurrentLine('');
|
||||
setCursorPos(0);
|
||||
}, 0);
|
||||
// clear terminal
|
||||
xtermRef.current.clear();
|
||||
|
||||
// delay re-initialization
|
||||
setTimeout(() => {
|
||||
if (!xtermRef.current || !fitAddonRef.current) return;
|
||||
// reset state
|
||||
lastTerminalLength.current = 0;
|
||||
setCurrentLine("");
|
||||
setCursorPos(0);
|
||||
|
||||
// re-adapt size
|
||||
fitAddonRef.current.fit();
|
||||
// delay re-initialization
|
||||
setTimeout(() => {
|
||||
if (!xtermRef.current || !fitAddonRef.current) return;
|
||||
|
||||
// only show switch information on main instance
|
||||
if (showWelcome) {
|
||||
xtermRef.current.writeln('\x1b[32m=== Eigent Terminal ===\x1b[0m');
|
||||
xtermRef.current.writeln(`\x1b[32mInstance: ${instanceId}\x1b[0m`);
|
||||
xtermRef.current.writeln('\x1b[32mTask switched...\x1b[0m');
|
||||
xtermRef.current.writeln('');
|
||||
}
|
||||
// re-adapt size
|
||||
fitAddonRef.current.fit();
|
||||
|
||||
// if there is history data, re-write
|
||||
if (chatStore.activeTaskId) {
|
||||
const terminalData = content || [];
|
||||
if (terminalData.length > 0) {
|
||||
xtermRef.current.writeln('\x1b[90m--- Previous Output ---\x1b[0m');
|
||||
terminalData.forEach((item) => {
|
||||
const formattedOutput = item
|
||||
.replace(/\r?\n$/, '')
|
||||
.replace(/\t/g, ' ')
|
||||
.replace(/\r/g, '');
|
||||
// only show switch information on main instance
|
||||
if (showWelcome) {
|
||||
xtermRef.current.writeln("\x1b[32m=== Eigent Terminal ===\x1b[0m");
|
||||
xtermRef.current.writeln(`\x1b[32mInstance: ${instanceId}\x1b[0m`);
|
||||
xtermRef.current.writeln("\x1b[32mTask switched...\x1b[0m");
|
||||
xtermRef.current.writeln("");
|
||||
}
|
||||
|
||||
if (formattedOutput.trim()) {
|
||||
xtermRef.current?.writeln(
|
||||
`\x1b[36m[Eigent]\x1b[0m ${formattedOutput}`
|
||||
);
|
||||
}
|
||||
});
|
||||
xtermRef.current.writeln(
|
||||
'\x1b[90m--- End Previous Output ---\x1b[0m'
|
||||
);
|
||||
xtermRef.current.writeln('');
|
||||
}
|
||||
lastTerminalLength.current = terminalData.length;
|
||||
}
|
||||
// if there is history data, re-write
|
||||
if (chatStore.activeTaskId) {
|
||||
const terminalData = content || [];
|
||||
if (terminalData.length > 0) {
|
||||
xtermRef.current.writeln("\x1b[90m--- Previous Output ---\x1b[0m");
|
||||
terminalData.forEach((item) => {
|
||||
const formattedOutput = item
|
||||
.replace(/\r?\n$/, "")
|
||||
.replace(/\t/g, " ")
|
||||
.replace(/\r/g, "");
|
||||
|
||||
// show prompt
|
||||
xtermRef.current.write(promptText);
|
||||
}, 200);
|
||||
}, [
|
||||
chatStore?.activeTaskId,
|
||||
showWelcome,
|
||||
instanceId,
|
||||
content,
|
||||
promptText,
|
||||
chatStore,
|
||||
]);
|
||||
if (formattedOutput.trim()) {
|
||||
xtermRef.current?.writeln(
|
||||
`\x1b[36m[Eigent]\x1b[0m ${formattedOutput}`
|
||||
);
|
||||
}
|
||||
});
|
||||
xtermRef.current.writeln(
|
||||
"\x1b[90m--- End Previous Output ---\x1b[0m"
|
||||
);
|
||||
xtermRef.current.writeln("");
|
||||
}
|
||||
lastTerminalLength.current = terminalData.length;
|
||||
}
|
||||
|
||||
if (!chatStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
// show prompt
|
||||
xtermRef.current.write(promptText);
|
||||
}, 200);
|
||||
}, [chatStore.activeTaskId, showWelcome, instanceId]);
|
||||
|
||||
// render terminal component
|
||||
return (
|
||||
<div
|
||||
ref={terminalContainerRef}
|
||||
className="relative flex h-full w-full flex-col overflow-hidden rounded-2xl border border-solid border-zinc-300"
|
||||
style={{ fontFamily: '"Courier New", Courier, monospace' }}
|
||||
>
|
||||
{/* background blur effect */}
|
||||
<div className="blur-bg pointer-events-none absolute inset-0 rounded-xl bg-black-100%"></div>
|
||||
// render terminal component
|
||||
return (
|
||||
<div
|
||||
ref={terminalContainerRef}
|
||||
className="w-full h-full flex flex-col rounded-2xl border border-border-subtle-strong border-solid relative overflow-hidden"
|
||||
style={{ fontFamily: '"Courier New", Courier, monospace' }}
|
||||
>
|
||||
{/* background blur effect */}
|
||||
<div className="absolute inset-0 blur-bg pointer-events-none bg-black-100% rounded-xl"></div>
|
||||
|
||||
{/* terminal container */}
|
||||
<div
|
||||
ref={terminalRef}
|
||||
className="absolute inset-0 z-10"
|
||||
style={{
|
||||
margin: '16px',
|
||||
width: 'calc(100% - 32px)',
|
||||
height: 'calc(100% - 32px)',
|
||||
fontFamily: '"Courier New", Courier, monospace',
|
||||
}}
|
||||
/>
|
||||
{/* terminal container */}
|
||||
<div
|
||||
ref={terminalRef}
|
||||
className="absolute inset-0 z-10"
|
||||
style={{
|
||||
margin: "16px",
|
||||
width: "calc(100% - 32px)",
|
||||
height: "calc(100% - 32px)",
|
||||
fontFamily: '"Courier New", Courier, monospace',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* custom style: override xterm.js character spacing */}
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
{/* custom style: override xterm.js character spacing */}
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
.xterm span {
|
||||
letter-spacing: 0.5px !important;
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,46 +12,50 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useEffect } from 'react';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const { appearance } = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
// set data-theme attribute based on appearance
|
||||
const root = document.documentElement;
|
||||
useEffect(() => {
|
||||
// set data-theme attribute based on appearance
|
||||
const root = document.documentElement;
|
||||
|
||||
// remove all possible data-theme attributes
|
||||
root.removeAttribute('data-theme');
|
||||
// remove all possible data-theme attributes
|
||||
root.removeAttribute('data-theme');
|
||||
|
||||
// set the corresponding data-theme based on appearance
|
||||
if (appearance === 'transparent') {
|
||||
root.setAttribute('data-theme', 'transparent');
|
||||
} else if (appearance === 'light') {
|
||||
root.setAttribute('data-theme', 'light');
|
||||
} else if (appearance === 'dark') {
|
||||
root.setAttribute('data-theme', 'dark');
|
||||
} else {
|
||||
root.setAttribute('data-theme', 'light');
|
||||
}
|
||||
}, [appearance]);
|
||||
switch (appearance) {
|
||||
case 'transparent':
|
||||
root.setAttribute('data-theme', 'transparent');
|
||||
break;
|
||||
case 'light':
|
||||
root.setAttribute('data-theme', 'light');
|
||||
break;
|
||||
case 'dark':
|
||||
root.setAttribute('data-theme', 'dark');
|
||||
break;
|
||||
default:
|
||||
root.setAttribute('data-theme', 'light');
|
||||
}
|
||||
}, [appearance]);
|
||||
|
||||
// initialize theme
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
const currentTheme = root.getAttribute('data-theme');
|
||||
// initialize theme
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
const currentTheme = root.getAttribute('data-theme');
|
||||
|
||||
if (!currentTheme) {
|
||||
if (appearance === 'transparent') {
|
||||
root.setAttribute('data-theme', 'transparent');
|
||||
} else if (appearance === 'dark') {
|
||||
root.setAttribute('data-theme', 'dark');
|
||||
} else {
|
||||
root.setAttribute('data-theme', 'light');
|
||||
}
|
||||
}
|
||||
}, [appearance]); // only execute once when the component is mounted
|
||||
if (!currentTheme) {
|
||||
if (appearance === 'transparent') {
|
||||
root.setAttribute('data-theme', 'transparent');
|
||||
} else if (appearance === 'dark') {
|
||||
root.setAttribute('data-theme', 'dark');
|
||||
} else {
|
||||
root.setAttribute('data-theme', 'light');
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []); // only execute once when the component is mounted
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,14 +18,16 @@ import {
|
|||
proxyFetchDelete,
|
||||
proxyFetchGet,
|
||||
} from '@/api/http';
|
||||
import folderIcon from '@/assets/Folder.svg';
|
||||
import folderIconWhite from '@/assets/logo/icon_white.svg';
|
||||
import folderIconBlack from '@/assets/logo/icon_black.svg';
|
||||
import giftIcon from '@/assets/gift.svg';
|
||||
import giftWhiteIcon from '@/assets/gift-white.svg';
|
||||
import EndNoticeDialog from '@/components/Dialog/EndNotice';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { TooltipSimple } from '@/components/ui/tooltip';
|
||||
import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
|
||||
import { share } from '@/lib/share';
|
||||
import { getAuthStore } from '@/store/authStore';
|
||||
import { getAuthStore, useAuthStore } from '@/store/authStore';
|
||||
import { useSidebarStore } from '@/store/sidebarStore';
|
||||
import {
|
||||
ChevronDown,
|
||||
|
|
@ -54,13 +56,14 @@ function HeaderWin() {
|
|||
//Get Chatstore for the active project's task
|
||||
const { chatStore, projectStore } = useChatStoreAdapter();
|
||||
const { toggle } = useSidebarStore();
|
||||
const _authStore = getAuthStore();
|
||||
const appearance = useAuthStore((state) => state.appearance);
|
||||
const [endDialogOpen, setEndDialogOpen] = useState(false);
|
||||
const [endProjectLoading, setEndProjectLoading] = useState(false);
|
||||
useEffect(() => {
|
||||
const p = window.electronAPI.getPlatform();
|
||||
setPlatform(p);
|
||||
}, []);
|
||||
const logoSrc = appearance === 'light' ? folderIconBlack : folderIconWhite;
|
||||
|
||||
const exportLog = async () => {
|
||||
try {
|
||||
|
|
@ -194,6 +197,10 @@ function HeaderWin() {
|
|||
share(taskId);
|
||||
};
|
||||
|
||||
if (!chatStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`drag absolute left-0 right-0 top-0 z-50 flex !h-9 items-center justify-between py-1 ${
|
||||
|
|
@ -221,7 +228,7 @@ function HeaderWin() {
|
|||
size="icon"
|
||||
className="no-drag h-6 w-6 p-0"
|
||||
>
|
||||
<img className="h-6 w-6" src={folderIcon} alt="folder-icon" />
|
||||
<img className="h-6 w-6" src={logoSrc} alt="folder-icon" />
|
||||
</Button>
|
||||
</div>
|
||||
{location.pathname === '/history' && (
|
||||
|
|
@ -391,7 +398,11 @@ function HeaderWin() {
|
|||
size="icon"
|
||||
className="no-drag"
|
||||
>
|
||||
<img src={giftIcon} alt="gift-icon" className="h-4 w-4" />
|
||||
<img
|
||||
src={appearance === 'light' ? giftIcon : giftWhiteIcon}
|
||||
alt="gift-icon"
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
</Button>
|
||||
</TooltipSimple>
|
||||
<TooltipSimple
|
||||
|
|
@ -426,19 +437,19 @@ function HeaderWin() {
|
|||
ref={controlsRef}
|
||||
>
|
||||
<div
|
||||
className="flex h-full w-[35px] flex-1 cursor-pointer items-center justify-center text-center leading-5 hover:bg-[#f0f0f0]"
|
||||
className="flex h-full w-[35px] flex-1 cursor-pointer items-center justify-center text-center leading-5 hover:bg-surface-hover-subtle"
|
||||
onClick={() => window.electronAPI.minimizeWindow()}
|
||||
>
|
||||
<Minus className="h-4 w-4" />
|
||||
</div>
|
||||
<div
|
||||
className="flex h-full w-[35px] flex-1 cursor-pointer items-center justify-center text-center leading-5 hover:bg-[#f0f0f0]"
|
||||
className="flex h-full w-[35px] flex-1 cursor-pointer items-center justify-center text-center leading-5 hover:bg-surface-hover-subtle"
|
||||
onClick={() => window.electronAPI.toggleMaximizeWindow()}
|
||||
>
|
||||
<Square className="h-4 w-4" />
|
||||
</div>
|
||||
<div
|
||||
className="flex h-full w-[35px] flex-1 cursor-pointer items-center justify-center text-center leading-5 hover:bg-[#f0f0f0]"
|
||||
className="flex h-full w-[35px] flex-1 cursor-pointer items-center justify-center text-center leading-5 hover:bg-surface-hover-subtle"
|
||||
onClick={() => window.electronAPI.closeWindow()}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
|
|
|
|||
|
|
@ -12,205 +12,202 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { isHtmlDocument } from '@/lib/htmlFontStyles';
|
||||
import { useEffect, useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { useState, useEffect } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { isHtmlDocument } from "@/lib/htmlFontStyles";
|
||||
|
||||
export const MarkDown = ({
|
||||
content,
|
||||
speed = 15,
|
||||
onTyping,
|
||||
enableTypewriter = true, // Whether to enable typewriter effect
|
||||
pTextSize = 'text-xs',
|
||||
olPadding = '',
|
||||
content,
|
||||
speed = 15,
|
||||
onTyping,
|
||||
enableTypewriter = true, // Whether to enable typewriter effect
|
||||
pTextSize = "text-xs",
|
||||
olPadding = "",
|
||||
}: {
|
||||
content: string;
|
||||
speed?: number;
|
||||
onTyping?: () => void;
|
||||
enableTypewriter?: boolean;
|
||||
pTextSize?: string;
|
||||
olPadding?: string;
|
||||
content: string;
|
||||
speed?: number;
|
||||
onTyping?: () => void;
|
||||
enableTypewriter?: boolean;
|
||||
pTextSize?: string;
|
||||
olPadding?: string;
|
||||
}) => {
|
||||
const [displayedContent, setDisplayedContent] = useState('');
|
||||
const [displayedContent, setDisplayedContent] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableTypewriter) {
|
||||
setDisplayedContent(content);
|
||||
return;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!enableTypewriter) {
|
||||
setDisplayedContent(content);
|
||||
return;
|
||||
}
|
||||
|
||||
setDisplayedContent('');
|
||||
let index = 0;
|
||||
setDisplayedContent("");
|
||||
let index = 0;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (index < content.length) {
|
||||
setDisplayedContent(content.slice(0, index + 1));
|
||||
index++;
|
||||
if (onTyping) {
|
||||
onTyping();
|
||||
}
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, speed);
|
||||
const timer = setInterval(() => {
|
||||
if (index < content.length) {
|
||||
setDisplayedContent(content.slice(0, index + 1));
|
||||
index++;
|
||||
if (onTyping) {
|
||||
onTyping();
|
||||
}
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, speed);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, [content, speed, enableTypewriter, onTyping]);
|
||||
return () => clearInterval(timer);
|
||||
}, [content, speed]);
|
||||
|
||||
// process line breaks, convert \n to <br> tag
|
||||
const processContent = (text: string) => {
|
||||
return text.replace(/\\n/g, ' \n '); // add two spaces before \n, so ReactMarkdown will recognize it as a line break
|
||||
};
|
||||
// process line breaks, convert \n to <br> tag
|
||||
const processContent = (text: string) => {
|
||||
return text.replace(/\\n/g, " \n "); // add two spaces before \n, so ReactMarkdown will recognize it as a line break
|
||||
};
|
||||
|
||||
// If content is a pure HTML document, render in a styled pre block
|
||||
if (isHtmlDocument(content)) {
|
||||
// Trim leading whitespace from each line for consistent alignment
|
||||
const formattedHtml = displayedContent
|
||||
.split('\n')
|
||||
.map((line) => line.trimStart())
|
||||
.join('\n')
|
||||
.trim();
|
||||
return (
|
||||
<div className="prose prose-sm markdown-container pointer-events-auto w-full select-text overflow-x-auto">
|
||||
<pre className="overflow-x-auto whitespace-pre-wrap rounded bg-zinc-100 p-2 font-mono text-xs">
|
||||
<code>{formattedHtml}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// If content is a pure HTML document, render in a styled pre block
|
||||
if (isHtmlDocument(content)) {
|
||||
// Trim leading whitespace from each line for consistent alignment
|
||||
const formattedHtml = displayedContent
|
||||
.split('\n')
|
||||
.map(line => line.trimStart())
|
||||
.join('\n')
|
||||
.trim();
|
||||
return (
|
||||
<div className="prose prose-sm w-full select-text pointer-events-auto overflow-x-auto markdown-container">
|
||||
<pre className="bg-code-surface p-2 rounded text-xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
<code>{formattedHtml}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="prose prose-sm markdown-container pointer-events-auto w-full select-text overflow-x-auto">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
h1: ({ children }) => (
|
||||
<h1 className="text-primary mb-1 break-words text-xs font-bold">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2 className="text-primary mb-1 break-words text-xs font-semibold">
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="text-primary mb-1 break-words text-xs font-medium">
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p
|
||||
className={`m-0 ${pTextSize} text-primary whitespace-pre-line break-words font-inter text-xs font-medium leading-10`}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul
|
||||
className={`text-primary mb-1 list-inside list-disc text-xs ${olPadding}`}
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
// ol: ({ children }) => (
|
||||
// <ol
|
||||
// className={`list-decimal list-inside text-xs text-primary mb-1 ${olPadding}`}
|
||||
// >
|
||||
// {children}
|
||||
// </ol>
|
||||
// ),
|
||||
li: ({ children }) => (
|
||||
<li className="mb-1 list-inside break-all">{children}</li>
|
||||
),
|
||||
a: ({ children, href }) => (
|
||||
<a
|
||||
href={href}
|
||||
className="break-all underline hover:text-blue-800"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
code: ({ children }) => (
|
||||
<code className="rounded bg-zinc-100 px-1 py-0.5 font-mono text-xs">
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
pre: ({ children }) => (
|
||||
<pre className="overflow-x-auto rounded bg-zinc-100 p-2 text-xs">
|
||||
{children}
|
||||
</pre>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="text-primary border-l-4 border-zinc-300 pl-3 text-xs italic">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
strong: ({ children }) => (
|
||||
<strong className="text-primary text-xs font-semibold">
|
||||
{children}
|
||||
</strong>
|
||||
),
|
||||
em: ({ children }) => (
|
||||
<em className="text-primary text-xs italic">{children}</em>
|
||||
),
|
||||
table: ({ children }) => (
|
||||
<div className="w-full max-w-full overflow-x-auto">
|
||||
<table
|
||||
className="mb-4 !table w-full min-w-0"
|
||||
style={{
|
||||
borderCollapse: 'collapse',
|
||||
border: '1px solid #d1d5db',
|
||||
borderSpacing: 0,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
),
|
||||
thead: ({ children }) => (
|
||||
<thead
|
||||
className="!table-header-group"
|
||||
style={{ backgroundColor: '#f9fafb' }}
|
||||
>
|
||||
{children}
|
||||
</thead>
|
||||
),
|
||||
tbody: ({ children }) => (
|
||||
<tbody className="!table-row-group">{children}</tbody>
|
||||
),
|
||||
tr: ({ children }) => <tr className="!table-row">{children}</tr>,
|
||||
th: ({ children }) => (
|
||||
<th
|
||||
className="text-primary !table-cell text-left text-[10px] font-semibold"
|
||||
style={{
|
||||
border: '1px solid #d1d5db',
|
||||
padding: '2px 5px',
|
||||
borderCollapse: 'collapse',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<td
|
||||
className="text-primary !table-cell text-[10px]"
|
||||
style={{
|
||||
border: '1px solid #d1d5db',
|
||||
padding: '2px 5px',
|
||||
borderCollapse: 'collapse',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{processContent(displayedContent)}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="prose prose-sm w-full select-text pointer-events-auto overflow-x-auto markdown-container">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
h1: ({ children }) => (
|
||||
<h1 className="text-xs font-bold text-primary mb-1 break-words">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2 className="text-xs font-semibold text-primary mb-1 break-words">
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="text-xs font-medium text-primary mb-1 break-words">
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p
|
||||
className={`m-0 ${pTextSize} font-medium text-xs text-primary leading-10 font-inter whitespace-pre-line break-words`}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul
|
||||
className={`list-disc list-inside text-xs text-primary mb-1 ${olPadding}`}
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
// ol: ({ children }) => (
|
||||
// <ol
|
||||
// className={`list-decimal list-inside text-xs text-primary mb-1 ${olPadding}`}
|
||||
// >
|
||||
// {children}
|
||||
// </ol>
|
||||
// ),
|
||||
li: ({ children }) => (
|
||||
<li className="mb-1 list-inside break-all">{children}</li>
|
||||
),
|
||||
a: ({ children, href }) => (
|
||||
<a
|
||||
href={href}
|
||||
className=" hover:text-text-link-hover underline break-all"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
code: ({ children }) => (
|
||||
<code className="bg-code-surface px-1 py-0.5 rounded text-xs font-mono">
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
pre: ({ children }) => (
|
||||
<pre className="bg-code-surface p-2 rounded text-xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
{children}
|
||||
</pre>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="border-l-4 border-border-subtle-strong pl-3 italic text-primary text-xs">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
strong: ({ children }) => (
|
||||
<strong className="font-semibold text-primary text-xs">
|
||||
{children}
|
||||
</strong>
|
||||
),
|
||||
em: ({ children }) => (
|
||||
<em className="italic text-primary text-xs">{children}</em>
|
||||
),
|
||||
table: ({ children }) => (
|
||||
<div className="overflow-x-auto w-full max-w-full">
|
||||
<table
|
||||
className="w-full mb-4 !table min-w-0"
|
||||
style={{
|
||||
borderCollapse: "collapse",
|
||||
border: "1px solid #d1d5db",
|
||||
borderSpacing: 0,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
),
|
||||
thead: ({ children }) => (
|
||||
<thead className="!table-header-group bg-code-surface">
|
||||
{children}
|
||||
</thead>
|
||||
),
|
||||
tbody: ({ children }) => (
|
||||
<tbody className="!table-row-group">{children}</tbody>
|
||||
),
|
||||
tr: ({ children }) => <tr className="!table-row">{children}</tr>,
|
||||
th: ({ children }) => (
|
||||
<th
|
||||
className="text-left font-semibold text-primary text-[10px] !table-cell"
|
||||
style={{
|
||||
border: "1px solid #d1d5db",
|
||||
padding: "2px 5px",
|
||||
borderCollapse: "collapse",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</th>
|
||||
),
|
||||
td: ({ children }) => (
|
||||
<td
|
||||
className="text-primary text-[10px] !table-cell"
|
||||
style={{
|
||||
border: "1px solid #d1d5db",
|
||||
padding: "2px 5px",
|
||||
borderCollapse: "collapse",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{processContent(displayedContent)}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -12,16 +12,16 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||
import * as React from 'react';
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider;
|
||||
const TooltipProvider = TooltipPrimitive.Provider
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root;
|
||||
const Tooltip = TooltipPrimitive.Root
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
|
|
@ -32,18 +32,18 @@ const TooltipContent = React.forwardRef<
|
|||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'text-primary-foreground z-50 origin-[--radix-tooltip-content-transform-origin] overflow-hidden rounded-md bg-off-white-100% px-1.5 py-1.5 text-xs shadow-sm animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
"z-50 overflow-hidden shadow-md rounded-md border border-border-secondary bg-surface-tertiary px-2 py-1.5 text-xs text-text-primary backdrop-blur-sm animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</TooltipPrimitive.Portal>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
|
||||
/**
|
||||
* A simpler interface for Tooltip when you just need a trigger and content.
|
||||
*
|
||||
*
|
||||
* Usage:
|
||||
* ```jsx
|
||||
* <TooltipSimple content="This is a tooltip">
|
||||
|
|
@ -51,10 +51,7 @@ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|||
* </TooltipSimple>
|
||||
* ```
|
||||
*/
|
||||
interface TooltipSimpleProps extends Omit<
|
||||
React.ComponentPropsWithoutRef<typeof TooltipContent>,
|
||||
'children' | 'content'
|
||||
> {
|
||||
interface TooltipSimpleProps extends Omit<React.ComponentPropsWithoutRef<typeof TooltipContent>, 'children' | 'content'> {
|
||||
children: React.ReactNode;
|
||||
content: React.ReactNode;
|
||||
}
|
||||
|
|
@ -65,27 +62,20 @@ const TooltipSimple = React.forwardRef<
|
|||
>(({ children, content, className, sideOffset = 4, ...props }, ref) => {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{children}
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent
|
||||
ref={ref}
|
||||
<TooltipContent ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
>
|
||||
{content}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
});
|
||||
TooltipSimple.displayName = 'TooltipSimple';
|
||||
className={cn(className)}
|
||||
{...props}>
|
||||
{content}
|
||||
</TooltipContent >
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
})
|
||||
|
||||
export {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipSimple,
|
||||
TooltipTrigger,
|
||||
};
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipSimple }
|
||||
|
|
|
|||
|
|
@ -1,171 +1,172 @@
|
|||
{
|
||||
"settings": "إعدادات",
|
||||
"general": "عام",
|
||||
"privacy": "خصوصية",
|
||||
"models": "نماذج",
|
||||
"mcp": "MCP الأدوات",
|
||||
"settings": "إعدادات",
|
||||
"general": "عام",
|
||||
"privacy": "خصوصية",
|
||||
"models": "نماذج",
|
||||
"mcp": "MCP الأدوات",
|
||||
|
||||
"account": "حساب",
|
||||
"you-are-currently-signed-in-with": "{{email}} أنت مسجل الدخول حاليًا باستخدام",
|
||||
"manage": "إدارة",
|
||||
"log-out": "تسجيل الخروج",
|
||||
"language": "اللغة",
|
||||
"select-language": "اختر اللغة",
|
||||
"system-default": "النظام الافتراضي",
|
||||
"appearance": "المظهر",
|
||||
"light": "فاتح",
|
||||
"transparent": "شفاف",
|
||||
"account": "حساب",
|
||||
"you-are-currently-signed-in-with": "{{email}} أنت مسجل الدخول حاليًا باستخدام",
|
||||
"manage": "إدارة",
|
||||
"log-out": "تسجيل الخروج",
|
||||
"language": "اللغة",
|
||||
"select-language": "اختر اللغة",
|
||||
"system-default": "النظام الافتراضي",
|
||||
"appearance": "المظهر",
|
||||
"dark": "غامق",
|
||||
"light": "فاتح",
|
||||
"transparent": "شفاف",
|
||||
|
||||
"data-privacy": "خصوصية البيانات",
|
||||
"data-privacy-description": "يعتمد أيجنت على الوضع المحلي الجديد ويضمن خصوصيتك. تبقى بياناتك على جهازك افتراضيًا. أذونات السحابة اختيارية، ويُستخدم حد البيانات لأغراض العمل فقط",
|
||||
"privacy-policy": "سياسة الخصوصية",
|
||||
"how-we-handle-your-data": "كيف نتعامل مع بياناتك",
|
||||
"how-we-handle-your-data-line-1": "نحن نستخدم فقط البيانات الأساسية اللازمة لتنفيذ مهامك",
|
||||
"how-we-handle-your-data-line-1-line-1": ".يمكنك اختيار لقطات شاشة أيجنت لتحليل عناصر واجهة المستخدم وقراءة النص وتحديد الإجراء التالي، تمامًا كما تفعل",
|
||||
"how-we-handle-your-data-line-1-line-2": ".يمكن استخدام الماوس ولوحة المفاتيح المحلية للوصول إلى البرامج والملفات المحلية التي تم تنشيطها",
|
||||
"how-we-handle-your-data-line-1-line-3": "لا يتم توفير سوى الحد الأدنى من البيانات ذات الصلة لنماذج الذكاء الاصطناعي أو عمليات التكامل مع جهات خارجية التي تمكنها؛ وليس لدينا أي احتفاظ بالبيانات",
|
||||
"how-we-handle-your-data-line-2": ".تبقى ملفات المهام والمخرجات ولقطات الشاشة في مجلد المهمة المخصص لك محليًا",
|
||||
"how-we-handle-your-data-line-3": ".يتم تخزين بيانات الاعتماد محليًا، وتشفيرها، ولا تُستخدم إلا للخطوات المعتمدة",
|
||||
"how-we-handle-your-data-line-4": ".لا تُستخدم بياناتك أبدًا لتدريب نماذج الذكاء الاصطناعي الخاصة بنا دون موافقتك الصريحة",
|
||||
"how-we-handle-your-data-line-5": ".نحن لا نبيع بياناتك لأطراف ثالثة",
|
||||
"enable-privacy-permissions-settings": "تمكين إعدادات أذونات الخصوصية",
|
||||
"enable-privacy-permissions-settings-description": ".من خلال تفعيل هذا الخيار، فإنك تقر بأنك قد قرأت ووافقت على سياسة الخصوصية الخاصة بنا بشأن كيفية جمع بيانات مهامك ومعالجتها وحمايتها",
|
||||
"data-privacy": "خصوصية البيانات",
|
||||
"data-privacy-description": "يعتمد أيجنت على الوضع المحلي الجديد ويضمن خصوصيتك. تبقى بياناتك على جهازك افتراضيًا. أذونات السحابة اختيارية، ويُستخدم حد البيانات لأغراض العمل فقط",
|
||||
"privacy-policy": "سياسة الخصوصية",
|
||||
"how-we-handle-your-data": "كيف نتعامل مع بياناتك",
|
||||
"how-we-handle-your-data-line-1": "نحن نستخدم فقط البيانات الأساسية اللازمة لتنفيذ مهامك",
|
||||
"how-we-handle-your-data-line-1-line-1": ".يمكنك اختيار لقطات شاشة أيجنت لتحليل عناصر واجهة المستخدم وقراءة النص وتحديد الإجراء التالي، تمامًا كما تفعل",
|
||||
"how-we-handle-your-data-line-1-line-2": ".يمكن استخدام الماوس ولوحة المفاتيح المحلية للوصول إلى البرامج والملفات المحلية التي تم تنشيطها",
|
||||
"how-we-handle-your-data-line-1-line-3": "لا يتم توفير سوى الحد الأدنى من البيانات ذات الصلة لنماذج الذكاء الاصطناعي أو عمليات التكامل مع جهات خارجية التي تمكنها؛ وليس لدينا أي احتفاظ بالبيانات",
|
||||
"how-we-handle-your-data-line-2":".تبقى ملفات المهام والمخرجات ولقطات الشاشة في مجلد المهمة المخصص لك محليًا",
|
||||
"how-we-handle-your-data-line-3":".يتم تخزين بيانات الاعتماد محليًا، وتشفيرها، ولا تُستخدم إلا للخطوات المعتمدة",
|
||||
"how-we-handle-your-data-line-4":".لا تُستخدم بياناتك أبدًا لتدريب نماذج الذكاء الاصطناعي الخاصة بنا دون موافقتك الصريحة",
|
||||
"how-we-handle-your-data-line-5":".نحن لا نبيع بياناتك لأطراف ثالثة",
|
||||
"enable-privacy-permissions-settings": "تمكين إعدادات أذونات الخصوصية",
|
||||
"enable-privacy-permissions-settings-description": ".من خلال تفعيل هذا الخيار، فإنك تقر بأنك قد قرأت ووافقت على سياسة الخصوصية الخاصة بنا بشأن كيفية جمع بيانات مهامك ومعالجتها وحمايتها",
|
||||
|
||||
"api-key-can-not-be-empty": "!لا يمكن أن يكون مفتاح واجهة برمجة التطبيقات فارغًا",
|
||||
"api-host-can-not-be-empty": "!لا يمكن أن يكون مضيف واجهة برمجة التطبيقات فارغًا",
|
||||
"model-type-can-not-be-empty": "!لا يمكن أن يكون نوع النموذج فارغًا",
|
||||
"validate-success": "التحقق ناجح",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "ِ.تم التحقق من أن النموذج يدعم استدعاء الوظيفة، وهو أمر مطلوب للاستخدام",
|
||||
"validate-failed": "فشل التحقق",
|
||||
"copy": "نسخ",
|
||||
"copied-to-clipboard": "تم النسخ إلى الحافظة",
|
||||
"endpoint-url-can-not-be-empty": "!لا يمكن أن يكون عنوان يورل لنقطة النهاية فارغًا",
|
||||
"verification-failed-please-check-endpoint-url": "فشل التحقق، يرجى المراجعة على النقطة النهائية يورل",
|
||||
"eigent-cloud-version": "إصدار أيجنت السحابي",
|
||||
"you-are-currently-subscribed-to-the": "أنت مشترك حاليًا في",
|
||||
"discover-more-about-our": "اكتشف المزيد حول",
|
||||
"pricing-options": "خيارات التسعير",
|
||||
"credits": "رصيد",
|
||||
"select-model-type": "اختر نوع النموذج",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: تكلفة أقل، استجابات أسرع، ولكن جودة مخرجات أقل",
|
||||
"gpt-4.1": "GPT-4.1: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"gpt-5": "GPT-5: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"gpt-5-mini": "GPT-5 mini: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"gpt-5-nano": "GPT-5 nano: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"custom-model": "نموذج مخصص",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": ".استخدم مفاتيح واجهة برمجة التطبيقات الخاصة بك أو قم بإعداد نموذج محلي",
|
||||
"verify": "تحقق",
|
||||
"local-model": "نموذج محلي",
|
||||
"model-platform": "منصة النموذج",
|
||||
"model-endpoint-url": "للنموذج URL النقطة نهاية" ,
|
||||
"model-type": "نوع النموذج",
|
||||
"enter-your-local-model-type": "أدخل نوع نموذجك المحلي",
|
||||
"you-are-on-selft-host-mode": "أنت في وضع الاستضافة الذاتية",
|
||||
"you-are-using-self-hosted-mode": ".أنت تستخدم وضع الاستضافة الذاتية. للحصول على أفضل أداء لهذا المنتج، يرجى إدخال مفتاح بحث Google في \"MCP والأدوات\" لضمان عمل Eigent بشكل صحيح",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": ".مفتاح بحث Google ضروري لتقديم نتائج بحث دقيقة. مفتاح بحث Exa اختياري ولكنه موصى به بشدة للحصول على أداء أفضل",
|
||||
"close": "إغلاق",
|
||||
"enter-your-api-key": "أدخل مفتاح واجهة برمجة التطبيقات الخاص بك",
|
||||
"key": "مفتاح",
|
||||
"enter-your-api-host": "أدخل مضيف واجهة برمجة التطبيقات الخاص بك",
|
||||
"url": "عنوان URL",
|
||||
"enter-your-model-type": "أدخل نوع النموذج الخاص بك",
|
||||
"verifying": "جارٍ التحقق...",
|
||||
|
||||
"api-key-can-not-be-empty": "!لا يمكن أن يكون مفتاح واجهة برمجة التطبيقات فارغًا",
|
||||
"api-host-can-not-be-empty": "!لا يمكن أن يكون مضيف واجهة برمجة التطبيقات فارغًا",
|
||||
"model-type-can-not-be-empty": "!لا يمكن أن يكون نوع النموذج فارغًا",
|
||||
"validate-success": "التحقق ناجح",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "ِ.تم التحقق من أن النموذج يدعم استدعاء الوظيفة، وهو أمر مطلوب للاستخدام",
|
||||
"validate-failed": "فشل التحقق",
|
||||
"copy": "نسخ",
|
||||
"copied-to-clipboard": "تم النسخ إلى الحافظة",
|
||||
"endpoint-url-can-not-be-empty": "!لا يمكن أن يكون عنوان يورل لنقطة النهاية فارغًا",
|
||||
"verification-failed-please-check-endpoint-url": "فشل التحقق، يرجى المراجعة على النقطة النهائية يورل",
|
||||
"eigent-cloud-version": "إصدار أيجنت السحابي",
|
||||
"you-are-currently-subscribed-to-the": "أنت مشترك حاليًا في",
|
||||
"discover-more-about-our": "اكتشف المزيد حول",
|
||||
"pricing-options": "خيارات التسعير",
|
||||
"credits": "رصيد",
|
||||
"select-model-type": "اختر نوع النموذج",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: تكلفة أقل، استجابات أسرع، ولكن جودة مخرجات أقل",
|
||||
"gpt-4.1": "GPT-4.1: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"gpt-5": "GPT-5: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"gpt-5-mini": "GPT-5 mini: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"gpt-5-nano": "GPT-5 nano: تكلفة أعلى، استجابات أبطأ، ولكن جودة واستدلال ممتازين",
|
||||
"custom-model": "نموذج مخصص",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": ".استخدم مفاتيح واجهة برمجة التطبيقات الخاصة بك أو قم بإعداد نموذج محلي",
|
||||
"verify": "تحقق",
|
||||
"local-model": "نموذج محلي",
|
||||
"model-platform": "منصة النموذج",
|
||||
"model-endpoint-url": "للنموذج URL النقطة نهاية",
|
||||
"model-type": "نوع النموذج",
|
||||
"enter-your-local-model-type": "أدخل نوع نموذجك المحلي",
|
||||
"you-are-on-selft-host-mode": "أنت في وضع الاستضافة الذاتية",
|
||||
"you-are-using-self-hosted-mode": ".أنت تستخدم وضع الاستضافة الذاتية. للحصول على أفضل أداء لهذا المنتج، يرجى إدخال مفتاح بحث Google في \"MCP والأدوات\" لضمان عمل Eigent بشكل صحيح",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": ".مفتاح بحث Google ضروري لتقديم نتائج بحث دقيقة. مفتاح بحث Exa اختياري ولكنه موصى به بشدة للحصول على أداء أفضل",
|
||||
"close": "إغلاق",
|
||||
"enter-your-api-key": "أدخل مفتاح واجهة برمجة التطبيقات الخاص بك",
|
||||
"key": "مفتاح",
|
||||
"enter-your-api-host": "أدخل مضيف واجهة برمجة التطبيقات الخاص بك",
|
||||
"url": "عنوان URL",
|
||||
"enter-your-model-type": "أدخل نوع النموذج الخاص بك",
|
||||
"verifying": "جارٍ التحقق...",
|
||||
"mcp-and-tools": "MCP الأدوات",
|
||||
"add-mcp-server": "MCP إضافة خادم",
|
||||
"market": "السوق",
|
||||
"tools": "الأدوات",
|
||||
"added-external-servers": "تمت إضافة خوادم خارجية",
|
||||
"loading": "جارٍ التحميل...",
|
||||
"no-mcp-servers": "MCP لا توجد خوادم",
|
||||
"environmental-variables-required": "المتغيرات البيئية مطلوبة",
|
||||
"load-failed": "فشل التحميل",
|
||||
"save-failed": "فشل الحفظ",
|
||||
"invalid-json": "غير صالح JSON",
|
||||
"coming-soon": "قريبًا",
|
||||
"uninstall": "إلغاء التثبيت",
|
||||
"install": "تثبيت",
|
||||
"add-your-agent": "أضف وكيلك",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": ".أضف مضيف مسيبي محليًا عن طريق توفير جسون صارم صالح",
|
||||
"learn-more": "اتعلم اكثر",
|
||||
"installing": "جارٍ التثبيت...",
|
||||
"edit-mcp-config": "MCP تعديل تكوين",
|
||||
"name": "الاسم",
|
||||
"description": "الوصف",
|
||||
"command": "الأمر",
|
||||
"args-one-per-line": "الوسائط (واحد في كل سطر)",
|
||||
"cancel": "إلغاء",
|
||||
"save": "حفظ",
|
||||
"confirm-delete": "تأكيد الحذف",
|
||||
"are-you-sure-you-want-to-delete": "هل أنت متأكد أنك تريد حذف",
|
||||
"deleting": "جارٍ الحذف...",
|
||||
"delete": "حذف",
|
||||
"configure {name} Toolkit": "{name} تكوين مجموعة أدوات",
|
||||
"get-it-from": "احصل عليها من",
|
||||
"google-custom-search-api": "Google واجهة برمجة تطبيقات البحث المخصص من",
|
||||
"google-cloud-console": "Google Cloud وحدة تحكم",
|
||||
"connect": "اتصال",
|
||||
"setting": "إعداد",
|
||||
"all": "الكل",
|
||||
"mcp-market": "MCP سوق",
|
||||
"no-mcp-services": "MCP لا توجد خدمات",
|
||||
"loading-more": "جارٍ تحميل المزيد...",
|
||||
"no-more-mcp-servers": "MCP لا مزيد من خوادم",
|
||||
"search-mcp": "MCP بحث",
|
||||
"installed": "مثبت",
|
||||
"worker-name-cannot-be-empty": "لا يمكن أن يكون اسم العامل فارغًا",
|
||||
"worker-name-already-exists": "اسم العامل موجود بالفعل",
|
||||
"warning-google-search-not-configured": "تحذير: بحث Google غير مكوّن",
|
||||
"search-functionality-may-be-limited-without-google-api": "قد تكون وظائف البحث محدودة بدون مفتاح Google API ومعرف محرك البحث. يمكنك تكوين هذه في إعدادات MCP والأدوات.",
|
||||
"search-engine": "محرك البحث",
|
||||
"allow-agent-to-take-screenshots": "السماح للوكيل بالتقاط لقطات الشاشة",
|
||||
"allow-agent-to-take-screenshots-description": "السماح للوكيل بالتقاط لقطات شاشة لشاشة الكمبيوتر الخاص بك. يمكن استخدام هذا للدعم أو التشخيص أو أغراض المراقبة. قد تتضمن لقطات الشاشة معلومات شخصية مرئية، لذا يرجى التمكين بحذر.",
|
||||
"allow-agent-to-access-local-software": "السماح للوكيل بالوصول إلى البرامج المحلية",
|
||||
"allow-agent-to-access-local-software-description": "منح الوكيل إذناً للتفاعل مع واستخدام البرامج المثبتة على جهازك المحلي. قد يكون هذا ضرورياً لاستكشاف الأخطاء وإصلاحها أو تشغيل التشخيصات أو أداء مهام محددة.",
|
||||
"allow-agent-to-access-your-address": "السماح للوكيل بالوصول إلى عنوانك",
|
||||
"allow-agent-to-access-your-address-description": "السماح للوكيل بعرض واستخدام تفاصيل موقعك أو عنوانك. قد يكون هذا مطلوباً للخدمات القائمة على الموقع أو الدعم الشخصي.",
|
||||
"password-storage": "تخزين كلمة المرور",
|
||||
"password-storage-description": "تحديد كيفية التعامل مع كلمات المرور وتخزينها. يمكنك اختيار تخزين كلمات المرور بأمان على الجهاز أو داخل التطبيق، أو اختيار إدخالها يدوياً في كل مرة. جميع كلمات المرور المخزنة مشفرة.",
|
||||
"notion-mcp-installed-successfully": "تم تثبيت Notion MCP بنجاح",
|
||||
"failed-to-install-notion-mcp": "فشل في تثبيت Notion MCP",
|
||||
"google-calendar-installed-successfully": "تم تثبيت Google Calendar بنجاح",
|
||||
"failed-to-install-google-calendar": "فشل في تثبيت Google Calendar",
|
||||
"notion-workspace-integration": "تكامل مساحة عمل Notion لقراءة وإدارة صفحات Notion",
|
||||
"google-calendar-integration": "تكامل Google Calendar لإدارة الأحداث والجداول الزمنية",
|
||||
"mcp-server-already-exists": "خادم MCP \"{{name}}\" موجود بالفعل",
|
||||
"google-search": "بحث Google",
|
||||
"select-default-search-engine": "اختر محرك البحث الافتراضي",
|
||||
"your-own-mcps": "MCPs الخاصة بك",
|
||||
"get-google-search-api": "احصل على Google Search API",
|
||||
"get-exa-api": "احصل على Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "تكاملات محرك البحث",
|
||||
"configured": "مكوّن",
|
||||
"incomplete": "غير مكتمل",
|
||||
"not-configured": "غير مكوّن",
|
||||
"saving": "جارٍ الحفظ...",
|
||||
"save-changes": "حفظ التغييرات",
|
||||
"enable": "تفعيل",
|
||||
"search": "بحث",
|
||||
"test-connection": "اختبار الاتصال",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "مفاتيح واجهة برمجة التطبيقات الخاصة بك محفوظة بأمان ولا يتم مشاركتها خارجيًا أبدًا.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "هذه الخدمة عامة ولا تتطلب بيانات اعتماد.",
|
||||
"this-service-does-not-require-an-api-key": "هذه الخدمة لا تتطلب مفتاح واجهة برمجة تطبيقات.",
|
||||
"connection-test-successful": "اختبار الاتصال ناجح!",
|
||||
"connection-test-failed": "فشل اختبار الاتصال.",
|
||||
"configuration-saved-successfully": "تم حفظ التكوين بنجاح!",
|
||||
"failed-to-save-configuration": "فشل في حفظ التكوين.",
|
||||
"recommended": "موصى به",
|
||||
"reset": "إعادة تعيين",
|
||||
"reset-success": "تمت إعادة التعيين بنجاح!",
|
||||
"reset-failed": "فشل إعادة التعيين!",
|
||||
|
||||
"mcp-and-tools": "MCP الأدوات",
|
||||
"add-mcp-server": "MCP إضافة خادم",
|
||||
"market": "السوق",
|
||||
"tools": "الأدوات",
|
||||
"added-external-servers": "تمت إضافة خوادم خارجية",
|
||||
"loading": "جارٍ التحميل...",
|
||||
"no-mcp-servers": "MCP لا توجد خوادم",
|
||||
"environmental-variables-required": "المتغيرات البيئية مطلوبة",
|
||||
"load-failed": "فشل التحميل",
|
||||
"save-failed": "فشل الحفظ",
|
||||
"invalid-json": "غير صالح JSON",
|
||||
"coming-soon": "قريبًا",
|
||||
"uninstall": "إلغاء التثبيت",
|
||||
"install": "تثبيت",
|
||||
"add-your-agent": "أضف وكيلك",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": ".أضف مضيف مسيبي محليًا عن طريق توفير جسون صارم صالح",
|
||||
"learn-more": "اتعلم اكثر",
|
||||
"installing": "جارٍ التثبيت...",
|
||||
"edit-mcp-config": "MCP تعديل تكوين",
|
||||
"name": "الاسم",
|
||||
"description": "الوصف",
|
||||
"command": "الأمر",
|
||||
"args-one-per-line": "الوسائط (واحد في كل سطر)",
|
||||
"cancel": "إلغاء",
|
||||
"save": "حفظ",
|
||||
"confirm-delete": "تأكيد الحذف",
|
||||
"are-you-sure-you-want-to-delete": "هل أنت متأكد أنك تريد حذف",
|
||||
"deleting": "جارٍ الحذف...",
|
||||
"delete": "حذف",
|
||||
"configure {name} Toolkit": "{name} تكوين مجموعة أدوات",
|
||||
"get-it-from": "احصل عليها من",
|
||||
"google-custom-search-api": "Google واجهة برمجة تطبيقات البحث المخصص من",
|
||||
"google-cloud-console": "Google Cloud وحدة تحكم",
|
||||
"connect": "اتصال",
|
||||
"setting": "إعداد",
|
||||
"all": "الكل",
|
||||
"mcp-market": "MCP سوق",
|
||||
"no-mcp-services": "MCP لا توجد خدمات",
|
||||
"loading-more": "جارٍ تحميل المزيد...",
|
||||
"no-more-mcp-servers": "MCP لا مزيد من خوادم",
|
||||
"search-mcp": "MCP بحث",
|
||||
"installed": "مثبت",
|
||||
"worker-name-cannot-be-empty": "لا يمكن أن يكون اسم العامل فارغًا",
|
||||
"worker-name-already-exists": "اسم العامل موجود بالفعل",
|
||||
"warning-google-search-not-configured": "تحذير: بحث Google غير مكوّن",
|
||||
"search-functionality-may-be-limited-without-google-api": "قد تكون وظائف البحث محدودة بدون مفتاح Google API ومعرف محرك البحث. يمكنك تكوين هذه في إعدادات MCP والأدوات.",
|
||||
"search-engine": "محرك البحث",
|
||||
"allow-agent-to-take-screenshots": "السماح للوكيل بالتقاط لقطات الشاشة",
|
||||
"allow-agent-to-take-screenshots-description": "السماح للوكيل بالتقاط لقطات شاشة لشاشة الكمبيوتر الخاص بك. يمكن استخدام هذا للدعم أو التشخيص أو أغراض المراقبة. قد تتضمن لقطات الشاشة معلومات شخصية مرئية، لذا يرجى التمكين بحذر.",
|
||||
"allow-agent-to-access-local-software": "السماح للوكيل بالوصول إلى البرامج المحلية",
|
||||
"allow-agent-to-access-local-software-description": "منح الوكيل إذناً للتفاعل مع واستخدام البرامج المثبتة على جهازك المحلي. قد يكون هذا ضرورياً لاستكشاف الأخطاء وإصلاحها أو تشغيل التشخيصات أو أداء مهام محددة.",
|
||||
"allow-agent-to-access-your-address": "السماح للوكيل بالوصول إلى عنوانك",
|
||||
"allow-agent-to-access-your-address-description": "السماح للوكيل بعرض واستخدام تفاصيل موقعك أو عنوانك. قد يكون هذا مطلوباً للخدمات القائمة على الموقع أو الدعم الشخصي.",
|
||||
"password-storage": "تخزين كلمة المرور",
|
||||
"password-storage-description": "تحديد كيفية التعامل مع كلمات المرور وتخزينها. يمكنك اختيار تخزين كلمات المرور بأمان على الجهاز أو داخل التطبيق، أو اختيار إدخالها يدوياً في كل مرة. جميع كلمات المرور المخزنة مشفرة.",
|
||||
"notion-mcp-installed-successfully": "تم تثبيت Notion MCP بنجاح",
|
||||
"failed-to-install-notion-mcp": "فشل في تثبيت Notion MCP",
|
||||
"google-calendar-installed-successfully": "تم تثبيت Google Calendar بنجاح",
|
||||
"failed-to-install-google-calendar": "فشل في تثبيت Google Calendar",
|
||||
"notion-workspace-integration": "تكامل مساحة عمل Notion لقراءة وإدارة صفحات Notion",
|
||||
"google-calendar-integration": "تكامل Google Calendar لإدارة الأحداث والجداول الزمنية",
|
||||
"mcp-server-already-exists": "خادم MCP \"{{name}}\" موجود بالفعل",
|
||||
"google-search": "بحث Google",
|
||||
"select-default-search-engine": "اختر محرك البحث الافتراضي",
|
||||
"your-own-mcps": "MCPs الخاصة بك",
|
||||
"get-google-search-api": "احصل على Google Search API",
|
||||
"get-exa-api": "احصل على Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "تكاملات محرك البحث",
|
||||
"configured": "مكوّن",
|
||||
"incomplete": "غير مكتمل",
|
||||
"not-configured": "غير مكوّن",
|
||||
"saving": "جارٍ الحفظ...",
|
||||
"save-changes": "حفظ التغييرات",
|
||||
"enable": "تفعيل",
|
||||
"search": "بحث",
|
||||
"test-connection": "اختبار الاتصال",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "مفاتيح واجهة برمجة التطبيقات الخاصة بك محفوظة بأمان ولا يتم مشاركتها خارجيًا أبدًا.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "هذه الخدمة عامة ولا تتطلب بيانات اعتماد.",
|
||||
"this-service-does-not-require-an-api-key": "هذه الخدمة لا تتطلب مفتاح واجهة برمجة تطبيقات.",
|
||||
"connection-test-successful": "اختبار الاتصال ناجح!",
|
||||
"connection-test-failed": "فشل اختبار الاتصال.",
|
||||
"configuration-saved-successfully": "تم حفظ التكوين بنجاح!",
|
||||
"failed-to-save-configuration": "فشل في حفظ التكوين.",
|
||||
"recommended": "موصى به",
|
||||
"reset": "إعادة تعيين",
|
||||
"reset-success": "تمت إعادة التعيين بنجاح!",
|
||||
"reset-failed": "فشل إعادة التعيين!",
|
||||
|
||||
"network-proxy": "وكيل الشبكة",
|
||||
"network-proxy-description": "قم بتكوين خادم وكيل لطلبات الشبكة. هذا مفيد إذا كنت بحاجة إلى الوصول إلى واجهات برمجة التطبيقات الخارجية عبر وكيل.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "تم حفظ تكوين الوكيل. أعد تشغيل التطبيق لتطبيق التغييرات.",
|
||||
"proxy-save-failed": "فشل حفظ تكوين الوكيل.",
|
||||
"proxy-invalid-url": "عنوان URL للوكيل غير صالح. يجب أن يبدأ بـ http:// أو https:// أو socks4:// أو socks5://.",
|
||||
"proxy-restart-hint": "يجب إعادة التشغيل لتطبيق تغييرات الوكيل."
|
||||
"network-proxy": "وكيل الشبكة",
|
||||
"network-proxy-description": "قم بتكوين خادم وكيل لطلبات الشبكة. هذا مفيد إذا كنت بحاجة إلى الوصول إلى واجهات برمجة التطبيقات الخارجية عبر وكيل.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "تم حفظ تكوين الوكيل. أعد تشغيل التطبيق لتطبيق التغييرات.",
|
||||
"proxy-save-failed": "فشل حفظ تكوين الوكيل.",
|
||||
"proxy-invalid-url": "عنوان URL للوكيل غير صالح. يجب أن يبدأ بـ http:// أو https:// أو socks4:// أو socks5://.",
|
||||
"proxy-restart-hint": "يجب إعادة التشغيل لتطبيق تغييرات الوكيل."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,171 +1,172 @@
|
|||
{
|
||||
"settings": "Einstellungen",
|
||||
"general": "Allgemein",
|
||||
"privacy": "Datenschutz",
|
||||
"models": "Modelle",
|
||||
"mcp": "MCP & Tools",
|
||||
"settings": "Einstellungen",
|
||||
"general": "Allgemein",
|
||||
"privacy": "Datenschutz",
|
||||
"models": "Modelle",
|
||||
"mcp": "MCP & Tools",
|
||||
|
||||
"account": "Konto",
|
||||
"you-are-currently-signed-in-with": "Sie sind derzeit angemeldet mit {{email}}",
|
||||
"manage": "Verwalten",
|
||||
"log-out": "Abmelden",
|
||||
"language": "Sprache",
|
||||
"select-language": "Sprache auswählen",
|
||||
"system-default": "Systemstandard",
|
||||
"appearance": "Erscheinungsbild",
|
||||
"light": "Hell",
|
||||
"transparent": "Transparent",
|
||||
"account": "Konto",
|
||||
"you-are-currently-signed-in-with": "Sie sind derzeit angemeldet mit {{email}}",
|
||||
"manage": "Verwalten",
|
||||
"log-out": "Abmelden",
|
||||
"language": "Sprache",
|
||||
"select-language": "Sprache auswählen",
|
||||
"system-default": "Systemstandard",
|
||||
"appearance": "Erscheinungsbild",
|
||||
"dark": "Dunkel",
|
||||
"light": "Hell",
|
||||
"transparent": "Transparent",
|
||||
|
||||
"data-privacy": "Datenschutz",
|
||||
"data-privacy-description": "Eigent basiert auf einem Local-First-Prinzip, um Ihre Privatsphäre zu gewährleisten. Ihre Daten verbleiben standardmäßig auf Ihrem Gerät. Cloud-Funktionen sind optional und verwenden nur die minimal erforderlichen Daten, um zu funktionieren. Für vollständige Details besuchen Sie bitte unsere",
|
||||
"privacy-policy": "Datenschutzrichtlinie",
|
||||
"how-we-handle-your-data": "Wie wir Ihre Daten behandeln",
|
||||
"how-we-handle-your-data-line-1": "Wir verwenden nur die für die Ausführung Ihrer Aufgaben erforderlichen Daten",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent kann Screenshots erfassen, um UI-Elemente zu analysieren, Text zu lesen und die nächste Aktion zu bestimmen, genau wie Sie es tun würden.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent kann Ihre Maus und Tastatur verwenden, um auf von Ihnen angegebene lokale Software und Dateien zuzugreifen.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Nur minimale Aufgabendaten werden an KI-Modellanbieter oder von Ihnen aktivierte Drittanbieter-Integrationen gesendet; wir speichern keine Daten.",
|
||||
"how-we-handle-your-data-line-2": "Aufgabendateien, Ausgaben und Screenshots verbleiben lokal in Ihrem angegebenen Aufgabenordner.",
|
||||
"how-we-handle-your-data-line-3": "Anmeldeinformationen werden lokal, verschlüsselt gespeichert und nur für genehmigte Schritte verwendet.",
|
||||
"how-we-handle-your-data-line-4": "Ihre Daten werden ohne Ihre ausdrückliche Zustimmung niemals zum Trainieren unserer KI-Modelle verwendet.",
|
||||
"how-we-handle-your-data-line-5": "Wir verkaufen Ihre Daten nicht an Dritte.",
|
||||
"enable-privacy-permissions-settings": "Datenschutzeinstellungen aktivieren",
|
||||
"enable-privacy-permissions-settings-description": "Durch die Aktivierung dieser Option bestätigen Sie, dass Sie unsere Datenschutzrichtlinie bezüglich der Erfassung, Verarbeitung und des Schutzes Ihrer Aufgabendaten gelesen und akzeptiert haben.",
|
||||
"data-privacy": "Datenschutz",
|
||||
"data-privacy-description": "Eigent basiert auf einem Local-First-Prinzip, um Ihre Privatsphäre zu gewährleisten. Ihre Daten verbleiben standardmäßig auf Ihrem Gerät. Cloud-Funktionen sind optional und verwenden nur die minimal erforderlichen Daten, um zu funktionieren. Für vollständige Details besuchen Sie bitte unsere",
|
||||
"privacy-policy": "Datenschutzrichtlinie",
|
||||
"how-we-handle-your-data": "Wie wir Ihre Daten behandeln",
|
||||
"how-we-handle-your-data-line-1": "Wir verwenden nur die für die Ausführung Ihrer Aufgaben erforderlichen Daten",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent kann Screenshots erfassen, um UI-Elemente zu analysieren, Text zu lesen und die nächste Aktion zu bestimmen, genau wie Sie es tun würden.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent kann Ihre Maus und Tastatur verwenden, um auf von Ihnen angegebene lokale Software und Dateien zuzugreifen.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Nur minimale Aufgabendaten werden an KI-Modellanbieter oder von Ihnen aktivierte Drittanbieter-Integrationen gesendet; wir speichern keine Daten.",
|
||||
"how-we-handle-your-data-line-2":"Aufgabendateien, Ausgaben und Screenshots verbleiben lokal in Ihrem angegebenen Aufgabenordner.",
|
||||
"how-we-handle-your-data-line-3":"Anmeldeinformationen werden lokal, verschlüsselt gespeichert und nur für genehmigte Schritte verwendet.",
|
||||
"how-we-handle-your-data-line-4":"Ihre Daten werden ohne Ihre ausdrückliche Zustimmung niemals zum Trainieren unserer KI-Modelle verwendet.",
|
||||
"how-we-handle-your-data-line-5":"Wir verkaufen Ihre Daten nicht an Dritte.",
|
||||
"enable-privacy-permissions-settings": "Datenschutzeinstellungen aktivieren",
|
||||
"enable-privacy-permissions-settings-description": "Durch die Aktivierung dieser Option bestätigen Sie, dass Sie unsere Datenschutzrichtlinie bezüglich der Erfassung, Verarbeitung und des Schutzes Ihrer Aufgabendaten gelesen und akzeptiert haben.",
|
||||
|
||||
"api-key-can-not-be-empty": "API-Schlüssel darf nicht leer sein!",
|
||||
"api-host-can-not-be-empty": "API-Host darf nicht leer sein!",
|
||||
"model-type-can-not-be-empty": "Modelltyp darf nicht leer sein!",
|
||||
"validate-success": "Validierung erfolgreich",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "Das Modell wurde verifiziert und unterstützt Function Calling, was für die Verwendung von Eigent erforderlich ist.",
|
||||
"validate-failed": "Validierung fehlgeschlagen",
|
||||
"copy": "Kopieren",
|
||||
"copied-to-clipboard": "In die Zwischenablage kopiert",
|
||||
"endpoint-url-can-not-be-empty": "Endpunkt-URL darf nicht leer sein!",
|
||||
"verification-failed-please-check-endpoint-url": "Verifizierung fehlgeschlagen, bitte überprüfen Sie die Endpunkt-URL",
|
||||
"eigent-cloud-version": "Eigent Cloud-Version",
|
||||
"you-are-currently-subscribed-to-the": "Sie sind derzeit abonniert für das",
|
||||
"discover-more-about-our": "Entdecken Sie mehr über unsere",
|
||||
"pricing-options": "Preismodelle",
|
||||
"credits": "Credits",
|
||||
"select-model-type": "Modelltyp auswählen",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Geringere Kosten, schnellere Antworten, aber reduzierte Ausgabequalität.",
|
||||
"gpt-4.1": "GPT-4.1: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"gpt-5": "GPT-5: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"gpt-5-mini": "GPT-5 Mini: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"gpt-5-nano": "GPT-5 Nano: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"custom-model": "Benutzerdefiniertes Modell",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Verwenden Sie Ihre eigenen API-Schlüssel oder richten Sie ein lokales Modell ein.",
|
||||
"verify": "Überprüfen",
|
||||
"local-model": "Lokales Modell",
|
||||
"model-platform": "Modellplattform",
|
||||
"model-endpoint-url": "Modell-Endpunkt-URL",
|
||||
"model-type": "Modelltyp",
|
||||
"enter-your-local-model-type": "Geben Sie Ihren lokalen Modelltyp ein",
|
||||
"you-are-on-selft-host-mode": "Sie befinden sich im Self-Host-Modus",
|
||||
"you-are-using-self-hosted-mode": "Sie verwenden den Self-hosted-Modus. Um die beste Leistung dieses Produkts zu erzielen, geben Sie bitte den Google Search Key unter „MCP und Tools“ ein, um sicherzustellen, dass Eigent ordnungsgemäß funktioniert.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Der Google Search Key ist unerlässlich für die Bereitstellung genauer Suchergebnisse. Der Exa Search Key ist optional, aber für eine bessere Leistung sehr empfehlenswert.",
|
||||
"close": "Schließen",
|
||||
"enter-your-api-key": "Geben Sie Ihren API-",
|
||||
"key": "Schlüssel ein",
|
||||
"enter-your-api-host": "Geben Sie Ihren API-Host ein",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Geben Sie Ihren Modelltyp ein",
|
||||
"verifying": "Wird überprüft...",
|
||||
|
||||
"api-key-can-not-be-empty": "API-Schlüssel darf nicht leer sein!",
|
||||
"api-host-can-not-be-empty": "API-Host darf nicht leer sein!",
|
||||
"model-type-can-not-be-empty": "Modelltyp darf nicht leer sein!",
|
||||
"validate-success": "Validierung erfolgreich",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "Das Modell wurde verifiziert und unterstützt Function Calling, was für die Verwendung von Eigent erforderlich ist.",
|
||||
"validate-failed": "Validierung fehlgeschlagen",
|
||||
"copy": "Kopieren",
|
||||
"copied-to-clipboard": "In die Zwischenablage kopiert",
|
||||
"endpoint-url-can-not-be-empty": "Endpunkt-URL darf nicht leer sein!",
|
||||
"verification-failed-please-check-endpoint-url": "Verifizierung fehlgeschlagen, bitte überprüfen Sie die Endpunkt-URL",
|
||||
"eigent-cloud-version": "Eigent Cloud-Version",
|
||||
"you-are-currently-subscribed-to-the": "Sie sind derzeit abonniert für das",
|
||||
"discover-more-about-our": "Entdecken Sie mehr über unsere",
|
||||
"pricing-options": "Preismodelle",
|
||||
"credits": "Credits",
|
||||
"select-model-type": "Modelltyp auswählen",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Geringere Kosten, schnellere Antworten, aber reduzierte Ausgabequalität.",
|
||||
"gpt-4.1": "GPT-4.1: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"gpt-5": "GPT-5: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"gpt-5-mini": "GPT-5 Mini: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"gpt-5-nano": "GPT-5 Nano: Höhere Kosten, langsamere Antworten, aber überlegene Qualität und Schlussfolgerung.",
|
||||
"custom-model": "Benutzerdefiniertes Modell",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Verwenden Sie Ihre eigenen API-Schlüssel oder richten Sie ein lokales Modell ein.",
|
||||
"verify": "Überprüfen",
|
||||
"local-model": "Lokales Modell",
|
||||
"model-platform": "Modellplattform",
|
||||
"model-endpoint-url": "Modell-Endpunkt-URL",
|
||||
"model-type": "Modelltyp",
|
||||
"enter-your-local-model-type": "Geben Sie Ihren lokalen Modelltyp ein",
|
||||
"you-are-on-selft-host-mode": "Sie befinden sich im Self-Host-Modus",
|
||||
"you-are-using-self-hosted-mode": "Sie verwenden den Self-hosted-Modus. Um die beste Leistung dieses Produkts zu erzielen, geben Sie bitte den Google Search Key unter „MCP und Tools“ ein, um sicherzustellen, dass Eigent ordnungsgemäß funktioniert.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Der Google Search Key ist unerlässlich für die Bereitstellung genauer Suchergebnisse. Der Exa Search Key ist optional, aber für eine bessere Leistung sehr empfehlenswert.",
|
||||
"close": "Schließen",
|
||||
"enter-your-api-key": "Geben Sie Ihren API-",
|
||||
"key": "Schlüssel ein",
|
||||
"enter-your-api-host": "Geben Sie Ihren API-Host ein",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Geben Sie Ihren Modelltyp ein",
|
||||
"verifying": "Wird überprüft...",
|
||||
|
||||
"mcp-and-tools": "MCP & Tools",
|
||||
"add-mcp-server": "MCP-Server hinzufügen",
|
||||
"market": "Markt",
|
||||
"tools": "Tools",
|
||||
"added-external-servers": "Hinzugefügte externe Server",
|
||||
"loading": "Wird geladen...",
|
||||
"no-mcp-servers": "Keine MCP-Server",
|
||||
"environmental-variables-required": "Umgebungsvariablen erforderlich",
|
||||
"load-failed": "Laden fehlgeschlagen",
|
||||
"save-failed": "Speichern fehlgeschlagen",
|
||||
"invalid-json": "Ungültiges JSON",
|
||||
"coming-soon": "Demnächst verfügbar",
|
||||
"uninstall": "Deinstallieren",
|
||||
"install": "Installieren",
|
||||
"add-your-agent": "Ihren Agenten hinzufügen",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Fügen Sie einen lokalen MCP-Server hinzu, indem Sie eine gültige JSON-Konfiguration bereitstellen.",
|
||||
"learn-more": "Mehr erfahren",
|
||||
"installing": "Wird installiert...",
|
||||
"edit-mcp-config": "MCP-Konfiguration bearbeiten",
|
||||
"name": "Name",
|
||||
"description": "Beschreibung",
|
||||
"command": "Befehl",
|
||||
"args-one-per-line": "Argumente (eine pro Zeile)",
|
||||
"cancel": "Abbrechen",
|
||||
"save": "Speichern",
|
||||
"confirm-delete": "Löschung bestätigen",
|
||||
"are-you-sure-you-want-to-delete": "Sind Sie sicher, dass Sie löschen möchten",
|
||||
"deleting": "Wird gelöscht...",
|
||||
"delete": "Löschen",
|
||||
"configure {name} Toolkit": "{{name}} Toolkit konfigurieren",
|
||||
"get-it-from": "Erhalten Sie es von",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "Verbinden",
|
||||
"setting": "Einstellung",
|
||||
"all": "Alle",
|
||||
"mcp-market": "MCP-Markt",
|
||||
"no-mcp-services": "Keine MCP-Dienste",
|
||||
"loading-more": "Mehr wird geladen...",
|
||||
"no-more-mcp-servers": "Keine weiteren MCP-Server",
|
||||
"search-mcp": "MCPs durchsuchen",
|
||||
"installed": "Installiert",
|
||||
"worker-name-cannot-be-empty": "Worker-Name darf nicht leer sein",
|
||||
"worker-name-already-exists": "Worker-Name existiert bereits",
|
||||
"warning-google-search-not-configured": "Warnung: Google Search nicht konfiguriert",
|
||||
"search-functionality-may-be-limited-without-google-api": "Suchfunktionalität kann ohne Google API-Schlüssel und Search Engine ID eingeschränkt sein. Sie können diese in den MCP & Tools-Einstellungen konfigurieren.",
|
||||
"search-engine": "Suchmaschine",
|
||||
"allow-agent-to-take-screenshots": "Agent erlauben, Screenshots zu machen",
|
||||
"allow-agent-to-take-screenshots-description": "Erlauben Sie dem Agenten, Screenshots Ihres Computerbildschirms zu erfassen. Dies kann für Support, Diagnose oder Überwachungszwecke verwendet werden. Screenshots können sichtbare persönliche Informationen enthalten, daher aktivieren Sie dies mit Vorsicht.",
|
||||
"allow-agent-to-access-local-software": "Agent erlauben, auf lokale Software zuzugreifen",
|
||||
"allow-agent-to-access-local-software-description": "Gewähren Sie dem Agenten die Berechtigung, mit Software zu interagieren und sie zu nutzen, die auf Ihrem lokalen Computer installiert ist. Dies kann für Fehlerbehebung, Diagnose oder die Ausführung spezifischer Aufgaben erforderlich sein.",
|
||||
"allow-agent-to-access-your-address": "Agent erlauben, auf Ihre Adresse zuzugreifen",
|
||||
"allow-agent-to-access-your-address-description": "Autorisieren Sie den Agenten, Ihre Standort- oder Adressdetails anzuzeigen und zu verwenden. Dies kann für standortbasierte Dienste oder personalisierten Support erforderlich sein.",
|
||||
"password-storage": "Passwort-Speicherung",
|
||||
"password-storage-description": "Bestimmen Sie, wie Passwörter behandelt und gespeichert werden. Sie können wählen, Passwörter sicher auf dem Gerät oder in der Anwendung zu speichern, oder sich dafür entscheiden, sie jedes Mal manuell einzugeben. Alle gespeicherten Passwörter sind verschlüsselt.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP erfolgreich installiert",
|
||||
"failed-to-install-notion-mcp": "Fehler beim Installieren von Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar erfolgreich installiert",
|
||||
"failed-to-install-google-calendar": "Fehler beim Installieren von Google Calendar",
|
||||
"notion-workspace-integration": "Notion-Arbeitsbereich-Integration zum Lesen und Verwalten von Notion-Seiten",
|
||||
"google-calendar-integration": "Google Calendar-Integration zum Verwalten von Ereignissen und Zeitplänen",
|
||||
"mcp-server-already-exists": "MCP-Server \"{{name}}\" existiert bereits",
|
||||
"google-search": "Google Search",
|
||||
"select-default-search-engine": "Standard-Suchmaschine auswählen",
|
||||
"your-own-mcps": "Ihre eigenen MCPs",
|
||||
"get-google-search-api": "Google Search API erhalten",
|
||||
"get-exa-api": "Exa API erhalten",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Suchmaschinen-Integrationen",
|
||||
"configured": "Konfiguriert",
|
||||
"incomplete": "Unvollständig",
|
||||
"not-configured": "Nicht konfiguriert",
|
||||
"saving": "Wird gespeichert...",
|
||||
"save-changes": "Änderungen speichern",
|
||||
"enable": "Aktivieren",
|
||||
"search": "Suchen",
|
||||
"test-connection": "Verbindung testen",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Ihre API-Schlüssel werden sicher gespeichert und niemals extern geteilt.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Dieser Dienst ist öffentlich und erfordert keine Anmeldedaten.",
|
||||
"this-service-does-not-require-an-api-key": "Dieser Dienst erfordert keinen API-Schlüssel.",
|
||||
"connection-test-successful": "Verbindungstest erfolgreich!",
|
||||
"connection-test-failed": "Verbindungstest fehlgeschlagen.",
|
||||
"configuration-saved-successfully": "Konfiguration erfolgreich gespeichert!",
|
||||
"failed-to-save-configuration": "Fehler beim Speichern der Konfiguration.",
|
||||
"mcp-and-tools": "MCP & Tools",
|
||||
"add-mcp-server": "MCP-Server hinzufügen",
|
||||
"market": "Markt",
|
||||
"tools": "Tools",
|
||||
"added-external-servers": "Hinzugefügte externe Server",
|
||||
"loading": "Wird geladen...",
|
||||
"no-mcp-servers": "Keine MCP-Server",
|
||||
"environmental-variables-required": "Umgebungsvariablen erforderlich",
|
||||
"load-failed": "Laden fehlgeschlagen",
|
||||
"save-failed": "Speichern fehlgeschlagen",
|
||||
"invalid-json": "Ungültiges JSON",
|
||||
"coming-soon": "Demnächst verfügbar",
|
||||
"uninstall": "Deinstallieren",
|
||||
"install": "Installieren",
|
||||
"add-your-agent": "Ihren Agenten hinzufügen",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Fügen Sie einen lokalen MCP-Server hinzu, indem Sie eine gültige JSON-Konfiguration bereitstellen.",
|
||||
"learn-more": "Mehr erfahren",
|
||||
"installing": "Wird installiert...",
|
||||
"edit-mcp-config": "MCP-Konfiguration bearbeiten",
|
||||
"name": "Name",
|
||||
"description": "Beschreibung",
|
||||
"command": "Befehl",
|
||||
"args-one-per-line": "Argumente (eine pro Zeile)",
|
||||
"cancel": "Abbrechen",
|
||||
"save": "Speichern",
|
||||
"confirm-delete": "Löschung bestätigen",
|
||||
"are-you-sure-you-want-to-delete": "Sind Sie sicher, dass Sie löschen möchten",
|
||||
"deleting": "Wird gelöscht...",
|
||||
"delete": "Löschen",
|
||||
"configure {name} Toolkit": "{{name}} Toolkit konfigurieren",
|
||||
"get-it-from": "Erhalten Sie es von",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "Verbinden",
|
||||
"setting": "Einstellung",
|
||||
"all": "Alle",
|
||||
"mcp-market": "MCP-Markt",
|
||||
"no-mcp-services": "Keine MCP-Dienste",
|
||||
"loading-more": "Mehr wird geladen...",
|
||||
"no-more-mcp-servers": "Keine weiteren MCP-Server",
|
||||
"search-mcp": "MCPs durchsuchen",
|
||||
"installed": "Installiert",
|
||||
"worker-name-cannot-be-empty": "Worker-Name darf nicht leer sein",
|
||||
"worker-name-already-exists": "Worker-Name existiert bereits",
|
||||
"warning-google-search-not-configured": "Warnung: Google Search nicht konfiguriert",
|
||||
"search-functionality-may-be-limited-without-google-api": "Suchfunktionalität kann ohne Google API-Schlüssel und Search Engine ID eingeschränkt sein. Sie können diese in den MCP & Tools-Einstellungen konfigurieren.",
|
||||
"search-engine": "Suchmaschine",
|
||||
"allow-agent-to-take-screenshots": "Agent erlauben, Screenshots zu machen",
|
||||
"allow-agent-to-take-screenshots-description": "Erlauben Sie dem Agenten, Screenshots Ihres Computerbildschirms zu erfassen. Dies kann für Support, Diagnose oder Überwachungszwecke verwendet werden. Screenshots können sichtbare persönliche Informationen enthalten, daher aktivieren Sie dies mit Vorsicht.",
|
||||
"allow-agent-to-access-local-software": "Agent erlauben, auf lokale Software zuzugreifen",
|
||||
"allow-agent-to-access-local-software-description": "Gewähren Sie dem Agenten die Berechtigung, mit Software zu interagieren und sie zu nutzen, die auf Ihrem lokalen Computer installiert ist. Dies kann für Fehlerbehebung, Diagnose oder die Ausführung spezifischer Aufgaben erforderlich sein.",
|
||||
"allow-agent-to-access-your-address": "Agent erlauben, auf Ihre Adresse zuzugreifen",
|
||||
"allow-agent-to-access-your-address-description": "Autorisieren Sie den Agenten, Ihre Standort- oder Adressdetails anzuzeigen und zu verwenden. Dies kann für standortbasierte Dienste oder personalisierten Support erforderlich sein.",
|
||||
"password-storage": "Passwort-Speicherung",
|
||||
"password-storage-description": "Bestimmen Sie, wie Passwörter behandelt und gespeichert werden. Sie können wählen, Passwörter sicher auf dem Gerät oder in der Anwendung zu speichern, oder sich dafür entscheiden, sie jedes Mal manuell einzugeben. Alle gespeicherten Passwörter sind verschlüsselt.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP erfolgreich installiert",
|
||||
"failed-to-install-notion-mcp": "Fehler beim Installieren von Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar erfolgreich installiert",
|
||||
"failed-to-install-google-calendar": "Fehler beim Installieren von Google Calendar",
|
||||
"notion-workspace-integration": "Notion-Arbeitsbereich-Integration zum Lesen und Verwalten von Notion-Seiten",
|
||||
"google-calendar-integration": "Google Calendar-Integration zum Verwalten von Ereignissen und Zeitplänen",
|
||||
"mcp-server-already-exists": "MCP-Server \"{{name}}\" existiert bereits",
|
||||
"google-search": "Google Search",
|
||||
"select-default-search-engine": "Standard-Suchmaschine auswählen",
|
||||
"your-own-mcps": "Ihre eigenen MCPs",
|
||||
"get-google-search-api": "Google Search API erhalten",
|
||||
"get-exa-api": "Exa API erhalten",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Suchmaschinen-Integrationen",
|
||||
"configured": "Konfiguriert",
|
||||
"incomplete": "Unvollständig",
|
||||
"not-configured": "Nicht konfiguriert",
|
||||
"saving": "Wird gespeichert...",
|
||||
"save-changes": "Änderungen speichern",
|
||||
"enable": "Aktivieren",
|
||||
"search": "Suchen",
|
||||
"test-connection": "Verbindung testen",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Ihre API-Schlüssel werden sicher gespeichert und niemals extern geteilt.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Dieser Dienst ist öffentlich und erfordert keine Anmeldedaten.",
|
||||
"this-service-does-not-require-an-api-key": "Dieser Dienst erfordert keinen API-Schlüssel.",
|
||||
"connection-test-successful": "Verbindungstest erfolgreich!",
|
||||
"connection-test-failed": "Verbindungstest fehlgeschlagen.",
|
||||
"configuration-saved-successfully": "Konfiguration erfolgreich gespeichert!",
|
||||
"failed-to-save-configuration": "Fehler beim Speichern der Konfiguration.",
|
||||
"recommended": "Empfohlen",
|
||||
"reset": "Zurücksetzen",
|
||||
"reset-success": "Zurücksetzen erfolgreich!",
|
||||
"reset-failed": "Zurücksetzen fehlgeschlagen!",
|
||||
|
||||
"network-proxy": "Netzwerk-Proxy",
|
||||
"network-proxy-description": "Konfigurieren Sie einen Proxy-Server für Netzwerkanfragen. Dies ist nützlich, wenn Sie über einen Proxy auf externe APIs zugreifen müssen.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Proxy-Konfiguration gespeichert. Starten Sie die App neu, um die Änderungen anzuwenden.",
|
||||
"proxy-save-failed": "Proxy-Konfiguration konnte nicht gespeichert werden.",
|
||||
"proxy-invalid-url": "Ungültige Proxy-URL. Muss mit http://, https://, socks4:// oder socks5:// beginnen.",
|
||||
"proxy-restart-hint": "Neustart erforderlich, um Proxy-Änderungen anzuwenden."
|
||||
"network-proxy": "Netzwerk-Proxy",
|
||||
"network-proxy-description": "Konfigurieren Sie einen Proxy-Server für Netzwerkanfragen. Dies ist nützlich, wenn Sie über einen Proxy auf externe APIs zugreifen müssen.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Proxy-Konfiguration gespeichert. Starten Sie die App neu, um die Änderungen anzuwenden.",
|
||||
"proxy-save-failed": "Proxy-Konfiguration konnte nicht gespeichert werden.",
|
||||
"proxy-invalid-url": "Ungültige Proxy-URL. Muss mit http://, https://, socks4:// oder socks5:// beginnen.",
|
||||
"proxy-restart-hint": "Neustart erforderlich, um Proxy-Änderungen anzuwenden."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,198 +1,201 @@
|
|||
{
|
||||
"settings": "Settings",
|
||||
"general": "General",
|
||||
"privacy": "Privacy",
|
||||
"models": "Models",
|
||||
"mcp": "MCP & Tools",
|
||||
"settings": "Settings",
|
||||
"general": "General",
|
||||
"privacy": "Privacy",
|
||||
"models": "Models",
|
||||
"mcp": "MCP & Tools",
|
||||
|
||||
"account": "Account",
|
||||
"you-are-currently-signed-in-with": "You are currently signed in with {{email}}",
|
||||
"manage": "Manage",
|
||||
"log-out": "Log out",
|
||||
"language": "Language",
|
||||
"select-language": "Select language",
|
||||
"system-default": "System Default",
|
||||
"appearance": "Appearance",
|
||||
"light": "Light",
|
||||
"transparent": "Transparent",
|
||||
"account": "Account",
|
||||
"you-are-currently-signed-in-with": "You are currently signed in with {{email}}",
|
||||
"manage": "Manage",
|
||||
"log-out": "Log out",
|
||||
"language": "Language",
|
||||
"select-language": "Select language",
|
||||
"system-default": "System Default",
|
||||
"appearance": "Appearance",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"transparent": "Transparent",
|
||||
|
||||
"data-privacy": "Data Privacy",
|
||||
"data-privacy-description": "Eigent is built on a local-first principle to ensure your privacy. Your data remains on your device by default. Cloud features are optional and only use the minimum data necessary to function. For full details, visit our",
|
||||
"privacy-policy": "Privacy Policy",
|
||||
"how-we-handle-your-data": "How we handle your data",
|
||||
"how-we-handle-your-data-line-1": "We only use the essential data needed to run your tasks",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent may capture screenshots to analyze UI elements, read text, and determine the next action, just as you would.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent may use your mouse and keyboard to access local software and files you specify.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Only the minimum task data is sent to AI model providers or the third-party integrations you enable; we have zero data-retention",
|
||||
"how-we-handle-your-data-line-2": "Task files, outputs and screenshots remain in your designated task folder locally.",
|
||||
"how-we-handle-your-data-line-3": "Credentials are stored locally, encrypted, and used only for approved steps.",
|
||||
"how-we-handle-your-data-line-4": "Your data is never used to train our AI models without your explicit consent.",
|
||||
"how-we-handle-your-data-line-5": "We don’t sell your data to third parties.",
|
||||
"enable-privacy-permissions-settings": "Enable Privacy Permissions Settings",
|
||||
"enable-privacy-permissions-settings-description": "By turning this on, you acknowledge that you have read and agree to our Privacy Policy regarding how your task data is collected, processed, and protected.",
|
||||
"data-privacy": "Data Privacy",
|
||||
"data-privacy-description": "Eigent is built on a local-first principle to ensure your privacy. Your data remains on your device by default. Cloud features are optional and only use the minimum data necessary to function. For full details, visit our",
|
||||
"privacy-policy": "Privacy Policy",
|
||||
"how-we-handle-your-data": "How we handle your data",
|
||||
"how-we-handle-your-data-line-1": "We only use the essential data needed to run your tasks",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent may capture screenshots to analyze UI elements, read text, and determine the next action, just as you would.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent may use your mouse and keyboard to access local software and files you specify.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Only the minimum task data is sent to AI model providers or the third-party integrations you enable; we have zero data-retention",
|
||||
"how-we-handle-your-data-line-2":"Task files, outputs and screenshots remain in your designated task folder locally.",
|
||||
"how-we-handle-your-data-line-3":"Credentials are stored locally, encrypted, and used only for approved steps.",
|
||||
"how-we-handle-your-data-line-4":"Your data is never used to train our AI models without your explicit consent.",
|
||||
"how-we-handle-your-data-line-5":"We don’t sell your data to third parties.",
|
||||
"enable-privacy-permissions-settings": "Enable Privacy Permissions Settings",
|
||||
"enable-privacy-permissions-settings-description": "By turning this on, you acknowledge that you have read and agree to our Privacy Policy regarding how your task data is collected, processed, and protected.",
|
||||
|
||||
"api-key-can-not-be-empty": "API Key can not be empty!",
|
||||
"api-host-can-not-be-empty": "API Host can not be empty!",
|
||||
"model-type-can-not-be-empty": "Model Type can not be empty!",
|
||||
"validate-success": "Validate success",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "The model has been verified to support function calling, which is required to use Eigent.",
|
||||
"validate-failed": "Validate failed",
|
||||
"copy": "Copy",
|
||||
"copied-to-clipboard": "Copied to clipboard",
|
||||
"endpoint-url-can-not-be-empty": "Endpoint URL can not be empty!",
|
||||
"verification-failed-please-check-endpoint-url": "Verification failed, please check Endpoint URL",
|
||||
"eigent-cloud-version": "Eigent Cloud Version",
|
||||
"you-are-currently-subscribed-to-the": "You are currently subscribed to the",
|
||||
"discover-more-about-our": "Discover more about our",
|
||||
"pricing-options": "pricing options",
|
||||
"credits": "Credits",
|
||||
"select-model-type": "Select Model Type",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Lower cost, faster responses, but reduced output quality.",
|
||||
"gpt-4.1": "GPT-4.1: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5": "GPT-5: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5-mini": "GPT-5 mini: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5-nano": "GPT-5 nano: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"custom-model": "Custom Model",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Use your own API keys or set up a local model.",
|
||||
"verify": "Verify",
|
||||
"local-model": "Local Model",
|
||||
"model-platform": "Model Platform",
|
||||
"model-endpoint-url": "Model Endpoint URL",
|
||||
"model-type": "Model Type",
|
||||
"enter-your-local-model-type": "Enter your local model type",
|
||||
"you-are-on-selft-host-mode": "You are on Selft Host Mode",
|
||||
"you-are-using-self-hosted-mode": "You're using Self-hosted mode. To get the best performance from this product, please enter the Google Search Key in \"MCP and Tools\" to ensure Eigent works properly.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "The Google Search Key is essential for delivering accurate search results. Exa Search Key is optional but highly recommended for better performance.",
|
||||
"close": "Close",
|
||||
"enter-your-api-key": "Enter your API",
|
||||
"key": "Key",
|
||||
"enter-your-api-host": "Enter your API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Enter your Model Type",
|
||||
"verifying": "Verifying...",
|
||||
|
||||
"api-key-can-not-be-empty": "API Key can not be empty!",
|
||||
"api-host-can-not-be-empty": "API Host can not be empty!",
|
||||
"model-type-can-not-be-empty": "Model Type can not be empty!",
|
||||
"validate-success": "Validate success",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "The model has been verified to support function calling, which is required to use Eigent.",
|
||||
"validate-failed": "Validate failed",
|
||||
"copy": "Copy",
|
||||
"copied-to-clipboard": "Copied to clipboard",
|
||||
"endpoint-url-can-not-be-empty": "Endpoint URL can not be empty!",
|
||||
"verification-failed-please-check-endpoint-url": "Verification failed, please check Endpoint URL",
|
||||
"eigent-cloud-version": "Eigent Cloud Version",
|
||||
"you-are-currently-subscribed-to-the": "You are currently subscribed to the",
|
||||
"discover-more-about-our": "Discover more about our",
|
||||
"pricing-options": "pricing options",
|
||||
"credits": "Credits",
|
||||
"select-model-type": "Select Model Type",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Lower cost, faster responses, but reduced output quality.",
|
||||
"gpt-4.1": "GPT-4.1: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5": "GPT-5: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5-mini": "GPT-5 mini: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5-nano": "GPT-5 nano: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"custom-model": "Custom Model",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Use your own API keys or set up a local model.",
|
||||
"verify": "Verify",
|
||||
"local-model": "Local Model",
|
||||
"model-platform": "Model Platform",
|
||||
"model-endpoint-url": "Model Endpoint URL",
|
||||
"model-type": "Model Type",
|
||||
"enter-your-local-model-type": "Enter your local model type",
|
||||
"you-are-on-selft-host-mode": "You are on Selft Host Mode",
|
||||
"you-are-using-self-hosted-mode": "You're using Self-hosted mode. To get the best performance from this product, please enter the Google Search Key in \"MCP and Tools\" to ensure Eigent works properly.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "The Google Search Key is essential for delivering accurate search results. Exa Search Key is optional but highly recommended for better performance.",
|
||||
"close": "Close",
|
||||
"enter-your-api-key": "Enter your API",
|
||||
"key": "Key",
|
||||
"enter-your-api-host": "Enter your API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Enter your Model Type",
|
||||
"verifying": "Verifying...",
|
||||
"mcp-and-tools": "MCP & Tools",
|
||||
"add-mcp-server": "Add MCP Server",
|
||||
"market": "Market",
|
||||
"tools": "Tools",
|
||||
"added-external-servers": "Added external servers",
|
||||
"loading": "Loading...",
|
||||
"no-mcp-servers": "No MCP servers",
|
||||
"environmental-variables-required": "Environmental variables required",
|
||||
"load-failed": "Load failed",
|
||||
"save-failed": "Save failed",
|
||||
"invalid-json": "Invalid JSON",
|
||||
"coming-soon": "Coming Soon",
|
||||
"uninstall": "Uninstall",
|
||||
"install": "Install",
|
||||
"add-your-agent": "Add Your Agent",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Add a local MCP server by providing a valid JSON configuration.",
|
||||
"learn-more": "Learn more",
|
||||
"installing": "Installing...",
|
||||
"edit-mcp-config": "Edit MCP Config",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"command": "Command",
|
||||
"args-one-per-line": "Args (one per line)",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"confirm-delete": "Confirm Delete",
|
||||
"are-you-sure-you-want-to-delete": "Are you sure you want to delete",
|
||||
"deleting": "Deleting...",
|
||||
"delete": "Delete",
|
||||
"configure {name} Toolkit": "Configure {{name}} Toolkit",
|
||||
"get-it-from": "Get it from",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "Connect",
|
||||
"setting": "Setting",
|
||||
"all": "All",
|
||||
"mcp-market": "MCP Market",
|
||||
"no-mcp-services": "No MCP services",
|
||||
"loading-more": "Loading more...",
|
||||
"no-more-mcp-servers": "No more MCP servers",
|
||||
"search-mcp": "Search MCPs",
|
||||
"installed": "Installed",
|
||||
"worker-name-cannot-be-empty": "Worker name cannot be empty",
|
||||
"worker-name-already-exists": "Worker name already exists",
|
||||
"warning-google-search-not-configured": "Warning: Google Search not configured",
|
||||
"search-functionality-may-be-limited-without-google-api": "Search functionality may be limited without Google API key and Search Engine ID. You can configure these in MCP & Tools settings.",
|
||||
"search-engine": "Search Engine",
|
||||
"allow-agent-to-take-screenshots": "Allow Agent to Take Screenshots",
|
||||
"allow-agent-to-take-screenshots-description": "Permit the agent to capture screenshots of your computer screen. This can be used for support, diagnostics, or monitoring purposes. Screenshots may include visible personal information, so please enable with care.",
|
||||
"allow-agent-to-access-local-software": "Allow Agent to Access Local Software",
|
||||
"allow-agent-to-access-local-software-description": "Grant the agent permission to interact with and utilize software installed on your local machine. This may be necessary for troubleshooting, running diagnostics, or performing specific tasks.",
|
||||
"allow-agent-to-access-your-address": "Allow Agent to Access Your Address",
|
||||
"allow-agent-to-access-your-address-description": "Authorize the agent to view and use your location or address details. This may be required for location-based services or personalized support.",
|
||||
"password-storage": "Password Storage",
|
||||
"password-storage-description": "Determine how passwords are handled and stored. You can choose to store passwords securely on the device or within the application, or opt out to manually enter them each time. All stored passwords are encrypted.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP installed successfully",
|
||||
"failed-to-install-notion-mcp": "Failed to install Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar installed successfully",
|
||||
"failed-to-install-google-calendar": "Failed to install Google Calendar",
|
||||
"notion-workspace-integration": "Notion workspace integration for reading and managing Notion pages",
|
||||
"google-calendar-integration": "Google Calendar integration for managing events and schedules",
|
||||
"mcp-server-already-exists": "MCP server \"{{name}}\" already exists",
|
||||
"google-search": "Google Search",
|
||||
"select-default-search-engine": "Select default search engine",
|
||||
"your-own-mcps": "Your own MCPs",
|
||||
"get-google-search-api": "Get Google Search API",
|
||||
"get-exa-api": "Get Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Search Engine Integrations",
|
||||
"configured": "Configured",
|
||||
"incomplete": "Incomplete",
|
||||
"not-configured": "Not Configured",
|
||||
"saving": "Saving...",
|
||||
"save-changes": "Save Changes",
|
||||
"enable": "Enable",
|
||||
"search": "Search",
|
||||
"test-connection": "Test Connection",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Your API keys are stored securely and never shared externally.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "This service is public and does not require credentials.",
|
||||
"this-service-does-not-require-an-api-key": "This service does not require an API key.",
|
||||
"connection-test-successful": "Connection test successful!",
|
||||
"connection-test-failed": "Connection test failed.",
|
||||
"configuration-saved-successfully": "Configuration saved successfully!",
|
||||
"failed-to-save-configuration": "Failed to save configuration.",
|
||||
"recommended": "Recommended",
|
||||
"reset": "Reset",
|
||||
"reset-success": "Reset successfully!",
|
||||
"reset-failed": "Reset failed!",
|
||||
|
||||
"mcp-and-tools": "MCP & Tools",
|
||||
"add-mcp-server": "Add MCP Server",
|
||||
"market": "Market",
|
||||
"tools": "Tools",
|
||||
"added-external-servers": "Added external servers",
|
||||
"loading": "Loading...",
|
||||
"no-mcp-servers": "No MCP servers",
|
||||
"environmental-variables-required": "Environmental variables required",
|
||||
"load-failed": "Load failed",
|
||||
"save-failed": "Save failed",
|
||||
"invalid-json": "Invalid JSON",
|
||||
"coming-soon": "Coming Soon",
|
||||
"uninstall": "Uninstall",
|
||||
"install": "Install",
|
||||
"add-your-agent": "Add Your Agent",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Add a local MCP server by providing a valid JSON configuration.",
|
||||
"learn-more": "Learn more",
|
||||
"installing": "Installing...",
|
||||
"edit-mcp-config": "Edit MCP Config",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"command": "Command",
|
||||
"args-one-per-line": "Args (one per line)",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"confirm-delete": "Confirm Delete",
|
||||
"are-you-sure-you-want-to-delete": "Are you sure you want to delete",
|
||||
"deleting": "Deleting...",
|
||||
"delete": "Delete",
|
||||
"configure {name} Toolkit": "Configure {{name}} Toolkit",
|
||||
"get-it-from": "Get it from",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "Connect",
|
||||
"setting": "Setting",
|
||||
"all": "All",
|
||||
"mcp-market": "MCP Market",
|
||||
"no-mcp-services": "No MCP services",
|
||||
"loading-more": "Loading more...",
|
||||
"no-more-mcp-servers": "No more MCP servers",
|
||||
"search-mcp": "Search MCPs",
|
||||
"installed": "Installed",
|
||||
"worker-name-cannot-be-empty": "Worker name cannot be empty",
|
||||
"worker-name-already-exists": "Worker name already exists",
|
||||
"warning-google-search-not-configured": "Warning: Google Search not configured",
|
||||
"search-functionality-may-be-limited-without-google-api": "Search functionality may be limited without Google API key and Search Engine ID. You can configure these in MCP & Tools settings.",
|
||||
"search-engine": "Search Engine",
|
||||
"allow-agent-to-take-screenshots": "Allow Agent to Take Screenshots",
|
||||
"allow-agent-to-take-screenshots-description": "Permit the agent to capture screenshots of your computer screen. This can be used for support, diagnostics, or monitoring purposes. Screenshots may include visible personal information, so please enable with care.",
|
||||
"allow-agent-to-access-local-software": "Allow Agent to Access Local Software",
|
||||
"allow-agent-to-access-local-software-description": "Grant the agent permission to interact with and utilize software installed on your local machine. This may be necessary for troubleshooting, running diagnostics, or performing specific tasks.",
|
||||
"allow-agent-to-access-your-address": "Allow Agent to Access Your Address",
|
||||
"allow-agent-to-access-your-address-description": "Authorize the agent to view and use your location or address details. This may be required for location-based services or personalized support.",
|
||||
"password-storage": "Password Storage",
|
||||
"password-storage-description": "Determine how passwords are handled and stored. You can choose to store passwords securely on the device or within the application, or opt out to manually enter them each time. All stored passwords are encrypted.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP installed successfully",
|
||||
"failed-to-install-notion-mcp": "Failed to install Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar installed successfully",
|
||||
"failed-to-install-google-calendar": "Failed to install Google Calendar",
|
||||
"notion-workspace-integration": "Notion workspace integration for reading and managing Notion pages",
|
||||
"google-calendar-integration": "Google Calendar integration for managing events and schedules",
|
||||
"mcp-server-already-exists": "MCP server \"{{name}}\" already exists",
|
||||
"google-search": "Google Search",
|
||||
"select-default-search-engine": "Select default search engine",
|
||||
"your-own-mcps": "Your own MCPs",
|
||||
"get-google-search-api": "Get Google Search API",
|
||||
"get-exa-api": "Get Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Search Engine Integrations",
|
||||
"configured": "Configured",
|
||||
"incomplete": "Incomplete",
|
||||
"not-configured": "Not Configured",
|
||||
"saving": "Saving...",
|
||||
"save-changes": "Save Changes",
|
||||
"enable": "Enable",
|
||||
"search": "Search",
|
||||
"test-connection": "Test Connection",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Your API keys are stored securely and never shared externally.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "This service is public and does not require credentials.",
|
||||
"this-service-does-not-require-an-api-key": "This service does not require an API key.",
|
||||
"connection-test-successful": "Connection test successful!",
|
||||
"connection-test-failed": "Connection test failed.",
|
||||
"configuration-saved-successfully": "Configuration saved successfully!",
|
||||
"failed-to-save-configuration": "Failed to save configuration.",
|
||||
"recommended": "Recommended",
|
||||
"reset": "Reset",
|
||||
"reset-success": "Reset successfully!",
|
||||
"reset-failed": "Reset failed!",
|
||||
"browser-login": "Browser Login",
|
||||
"browser-login-description": "Open a Chrome browser to log in to your accounts. Your login data will be saved locally in a secure profile.",
|
||||
"open-browser-login": "Open Browser for Login",
|
||||
"opening-browser": "Opening Browser...",
|
||||
"browser-opened-successfully": "Browser opened successfully. Please log in to your accounts.",
|
||||
"failed-to-open-browser": "Failed to open browser. Please try again.",
|
||||
"restart-to-apply": "Restart to Apply",
|
||||
|
||||
"browser-login": "Browser Login",
|
||||
"browser-login-description": "Open a Chrome browser to log in to your accounts. Your login data will be saved locally in a secure profile.",
|
||||
"open-browser-login": "Open Browser for Login",
|
||||
"opening-browser": "Opening Browser...",
|
||||
"browser-opened-successfully": "Browser opened successfully. Please log in to your accounts.",
|
||||
"failed-to-open-browser": "Failed to open browser. Please try again.",
|
||||
"restart-to-apply": "Restart to Apply",
|
||||
"cookie-manager": "Cookie Manager",
|
||||
"cookie-manager-description": "Manage cookies saved from your browser sessions. Delete cookies for specific sites or all at once.",
|
||||
"refresh": "Refresh",
|
||||
"delete-all": "Delete All",
|
||||
"search-domains": "Search domains...",
|
||||
"loading-cookies": "Loading cookies...",
|
||||
"no-cookies-found": "No cookies found",
|
||||
"no-matching-domains": "No matching domains",
|
||||
"login-to-save-cookies": "Use the browser login feature above to save cookies from your accounts.",
|
||||
"cookies-count": "{{count}} cookies",
|
||||
"last-access": "Last access",
|
||||
"deleting": "Deleting...",
|
||||
"cookies-deleted-successfully": "Successfully deleted cookies for {{domain}}",
|
||||
"failed-to-load-cookies": "Failed to load cookies. Please try again.",
|
||||
"failed-to-delete-cookies": "Failed to delete cookies. Please try again.",
|
||||
"confirm-delete-all-cookies": "Are you sure you want to delete all cookies? This action cannot be undone.",
|
||||
"all-cookies-deleted": "All cookies have been deleted successfully.",
|
||||
"cookie-delete-warning": "Note: Deleting cookies will log you out of the associated websites. You may need to restart the browser to see changes take effect.",
|
||||
|
||||
"cookie-manager": "Cookie Manager",
|
||||
"cookie-manager-description": "Manage cookies saved from your browser sessions. Delete cookies for specific sites or all at once.",
|
||||
"refresh": "Refresh",
|
||||
"delete-all": "Delete All",
|
||||
"search-domains": "Search domains...",
|
||||
"loading-cookies": "Loading cookies...",
|
||||
"no-cookies-found": "No cookies found",
|
||||
"no-matching-domains": "No matching domains",
|
||||
"login-to-save-cookies": "Use the browser login feature above to save cookies from your accounts.",
|
||||
"cookies-count": "{{count}} cookies",
|
||||
"last-access": "Last access",
|
||||
"deleting": "Deleting...",
|
||||
"cookies-deleted-successfully": "Successfully deleted cookies for {{domain}}",
|
||||
"failed-to-load-cookies": "Failed to load cookies. Please try again.",
|
||||
"failed-to-delete-cookies": "Failed to delete cookies. Please try again.",
|
||||
"confirm-delete-all-cookies": "Are you sure you want to delete all cookies? This action cannot be undone.",
|
||||
"all-cookies-deleted": "All cookies have been deleted successfully.",
|
||||
"cookie-delete-warning": "Note: Deleting cookies will log you out of the associated websites. You may need to restart the browser to see changes take effect.",
|
||||
|
||||
"network-proxy": "Network Proxy",
|
||||
"network-proxy-description": "Configure a proxy server for network requests. This is useful if you need to access external APIs through a proxy.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Proxy configuration saved. Restart the app to apply changes.",
|
||||
"proxy-save-failed": "Failed to save proxy configuration.",
|
||||
"proxy-invalid-url": "Invalid proxy URL. Must start with http://, https://, socks4://, or socks5://.",
|
||||
"proxy-restart-hint": "Restart required to apply proxy changes."
|
||||
"network-proxy": "Network Proxy",
|
||||
"network-proxy-description": "Configure a proxy server for network requests. This is useful if you need to access external APIs through a proxy.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Proxy configuration saved. Restart the app to apply changes.",
|
||||
"proxy-save-failed": "Failed to save proxy configuration.",
|
||||
"proxy-invalid-url": "Invalid proxy URL. Must start with http://, https://, socks4://, or socks5://.",
|
||||
"proxy-restart-hint": "Restart required to apply proxy changes."
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,171 +1,173 @@
|
|||
{
|
||||
"settings": "Ajustes",
|
||||
"general": "Generales",
|
||||
"privacy": "Privacidad",
|
||||
"models": "Modelos",
|
||||
"mcp": "MCP & Herramientas",
|
||||
"settings": "Ajustes",
|
||||
"general": "Generales",
|
||||
"privacy": "Privacidad",
|
||||
"models": "Modelos",
|
||||
"mcp": "MCP & Herramientas",
|
||||
|
||||
"account": "Cuenta",
|
||||
"you-are-currently-signed-in-with": "Estás actualmente conectado con {{email}}",
|
||||
"manage": "Gestionar",
|
||||
"log-out": "Cerrar sesión",
|
||||
"language": "Idioma",
|
||||
"select-language": "Seleccionar idioma",
|
||||
"system-default": "Sistema predeterminado",
|
||||
"appearance": "Apariencia",
|
||||
"light": "Claro",
|
||||
"transparent": "Transparente",
|
||||
"account": "Cuenta",
|
||||
"you-are-currently-signed-in-with": "Estás actualmente conectado con {{email}}",
|
||||
"manage": "Gestionar",
|
||||
"log-out": "Cerrar sesión",
|
||||
"language": "Idioma",
|
||||
"select-language": "Seleccionar idioma",
|
||||
"system-default": "Sistema predeterminado",
|
||||
"appearance": "Apariencia",
|
||||
"dark": "Oscuro",
|
||||
"light": "Claro",
|
||||
"transparent": "Transparente",
|
||||
|
||||
"data-privacy": "Privacidad de datos",
|
||||
"data-privacy-description": "Eigent está construido sobre un principio local-first para garantizar tu privacidad. Tus datos permanecen en tu dispositivo por defecto. Las características en la nube son opcionales y solo utilizan los datos mínimos necesarios para funcionar. Para obtener más detalles, visita nuestra",
|
||||
"privacy-policy": "Política de privacidad",
|
||||
"how-we-handle-your-data": "Cómo manejamos tus datos",
|
||||
"how-we-handle-your-data-line-1": "Solo utilizamos los datos esenciales necesarios para ejecutar tus tareas",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent puede capturar capturas de pantalla para analizar elementos de la interfaz de usuario, leer texto y determinar la siguiente acción, como tú.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent puede usar tu mouse y teclado para acceder a software y archivos locales que especifiques.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Solo se envían los datos mínimos de las tareas a proveedores de modelos de IA o a las integraciones de terceros que activas; no tenemos ninguna retención de datos",
|
||||
"how-we-handle-your-data-line-2": "Los archivos de tareas, salidas y capturas de pantalla permanecen en tu carpeta de tareas designada localmente.",
|
||||
"how-we-handle-your-data-line-3": "Las credenciales se almacenan localmente, encriptadas, y solo se utilizan para pasos aprobados.",
|
||||
"how-we-handle-your-data-line-4": "Tus datos nunca se utilizan para entrenar nuestros modelos de IA sin tu consentimiento explícito.",
|
||||
"how-we-handle-your-data-line-5": "No vendemos tus datos a terceros.",
|
||||
"enable-privacy-permissions-settings": "Habilitar ajustes de permisos de privacidad",
|
||||
"enable-privacy-permissions-settings-description": "Al activar esta opción, aceptas que has leído y aceptado nuestra Política de privacidad respecto a cómo se recopilan, procesan y protegen tus datos de las tareas.",
|
||||
"data-privacy": "Privacidad de datos",
|
||||
"data-privacy-description": "Eigent está construido sobre un principio local-first para garantizar tu privacidad. Tus datos permanecen en tu dispositivo por defecto. Las características en la nube son opcionales y solo utilizan los datos mínimos necesarios para funcionar. Para obtener más detalles, visita nuestra",
|
||||
"privacy-policy": "Política de privacidad",
|
||||
"how-we-handle-your-data": "Cómo manejamos tus datos",
|
||||
"how-we-handle-your-data-line-1": "Solo utilizamos los datos esenciales necesarios para ejecutar tus tareas",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent puede capturar capturas de pantalla para analizar elementos de la interfaz de usuario, leer texto y determinar la siguiente acción, como tú.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent puede usar tu mouse y teclado para acceder a software y archivos locales que especifiques.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Solo se envían los datos mínimos de las tareas a proveedores de modelos de IA o a las integraciones de terceros que activas; no tenemos ninguna retención de datos",
|
||||
"how-we-handle-your-data-line-2":"Los archivos de tareas, salidas y capturas de pantalla permanecen en tu carpeta de tareas designada localmente.",
|
||||
"how-we-handle-your-data-line-3":"Las credenciales se almacenan localmente, encriptadas, y solo se utilizan para pasos aprobados.",
|
||||
"how-we-handle-your-data-line-4":"Tus datos nunca se utilizan para entrenar nuestros modelos de IA sin tu consentimiento explícito.",
|
||||
"how-we-handle-your-data-line-5":"No vendemos tus datos a terceros.",
|
||||
"enable-privacy-permissions-settings": "Habilitar ajustes de permisos de privacidad",
|
||||
"enable-privacy-permissions-settings-description": "Al activar esta opción, aceptas que has leído y aceptado nuestra Política de privacidad respecto a cómo se recopilan, procesan y protegen tus datos de las tareas.",
|
||||
|
||||
"api-key-can-not-be-empty": "API Key no puede estar vacío!",
|
||||
"api-host-can-not-be-empty": "API Host no puede estar vacío!",
|
||||
"model-type-can-not-be-empty": "Model Type no puede estar vacío!",
|
||||
"validate-success": "Validación exitosa",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "El modelo ha sido verificado para soportar la llamada de funciones, lo que es necesario para usar Eigent.",
|
||||
"validate-failed": "Validación fallida",
|
||||
"copy": "Copiar",
|
||||
"copied-to-clipboard": "Copiado al portapapeles",
|
||||
"endpoint-url-can-not-be-empty": "Endpoint URL no puede estar vacío!",
|
||||
"verification-failed-please-check-endpoint-url": "Verificación fallida, por favor verifique Endpoint URL",
|
||||
"eigent-cloud-version": "Eigent Cloud Version",
|
||||
"you-are-currently-subscribed-to-the": "Actualmente estás suscrito a la",
|
||||
"discover-more-about-our": "Descubre más sobre nuestra",
|
||||
"pricing-options": "opciones de precios",
|
||||
"credits": "Créditos",
|
||||
"select-model-type": "Seleccionar Model Type",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Menor costo, respuestas más rápidas, pero calidad de salida reducida.",
|
||||
"gpt-4.1": "GPT-4.1: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"gpt-5": "GPT-5: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"gpt-5-mini": "GPT-5 mini: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"gpt-5-nano": "GPT-5 nano: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"custom-model": "Modelo personalizado",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Usa tus propias API keys o configura un modelo local.",
|
||||
"verify": "Verificar",
|
||||
"local-model": "Modelo local",
|
||||
"model-platform": "Plataforma de modelo",
|
||||
"model-endpoint-url": "URL de endpoint de modelo",
|
||||
"model-type": "Tipo de modelo",
|
||||
"enter-your-local-model-type": "Ingresa tu tipo de modelo local",
|
||||
"you-are-on-selft-host-mode": "Estás en modo Selft Host",
|
||||
"you-are-using-self-hosted-mode": "Estás usando modo Self-hosted. Para obtener el mejor rendimiento de este producto, por favor ingresa la Clave de Búsqueda de Google en \"MCP y Herramientas\" para asegurar que Eigent funcione correctamente.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "La Clave de Búsqueda de Google es esencial para entregar resultados de búsqueda precisos. La Clave de Búsqueda de Exa es opcional pero altamente recomendada para mejor rendimiento.",
|
||||
"close": "Cerrar",
|
||||
"enter-your-api-key": "Ingresa tu API",
|
||||
"key": "Key",
|
||||
"enter-your-api-host": "Ingresa tu API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Ingresa tu Model Type",
|
||||
"verifying": "Verificando...",
|
||||
|
||||
"api-key-can-not-be-empty": "API Key no puede estar vacío!",
|
||||
"api-host-can-not-be-empty": "API Host no puede estar vacío!",
|
||||
"model-type-can-not-be-empty": "Model Type no puede estar vacío!",
|
||||
"validate-success": "Validación exitosa",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "El modelo ha sido verificado para soportar la llamada de funciones, lo que es necesario para usar Eigent.",
|
||||
"validate-failed": "Validación fallida",
|
||||
"copy": "Copiar",
|
||||
"copied-to-clipboard": "Copiado al portapapeles",
|
||||
"endpoint-url-can-not-be-empty": "Endpoint URL no puede estar vacío!",
|
||||
"verification-failed-please-check-endpoint-url": "Verificación fallida, por favor verifique Endpoint URL",
|
||||
"eigent-cloud-version": "Eigent Cloud Version",
|
||||
"you-are-currently-subscribed-to-the": "Actualmente estás suscrito a la",
|
||||
"discover-more-about-our": "Descubre más sobre nuestra",
|
||||
"pricing-options": "opciones de precios",
|
||||
"credits": "Créditos",
|
||||
"select-model-type": "Seleccionar Model Type",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Menor costo, respuestas más rápidas, pero calidad de salida reducida.",
|
||||
"gpt-4.1": "GPT-4.1: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"gpt-5": "GPT-5: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"gpt-5-mini": "GPT-5 mini: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"gpt-5-nano": "GPT-5 nano: Mayor costo, respuestas más lentas, pero superior calidad y razonamiento.",
|
||||
"custom-model": "Modelo personalizado",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Usa tus propias API keys o configura un modelo local.",
|
||||
"verify": "Verificar",
|
||||
"local-model": "Modelo local",
|
||||
"model-platform": "Plataforma de modelo",
|
||||
"model-endpoint-url": "URL de endpoint de modelo",
|
||||
"model-type": "Tipo de modelo",
|
||||
"enter-your-local-model-type": "Ingresa tu tipo de modelo local",
|
||||
"you-are-on-selft-host-mode": "Estás en modo Selft Host",
|
||||
"you-are-using-self-hosted-mode": "Estás usando modo Self-hosted. Para obtener el mejor rendimiento de este producto, por favor ingresa la Clave de Búsqueda de Google en \"MCP y Herramientas\" para asegurar que Eigent funcione correctamente.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "La Clave de Búsqueda de Google es esencial para entregar resultados de búsqueda precisos. La Clave de Búsqueda de Exa es opcional pero altamente recomendada para mejor rendimiento.",
|
||||
"close": "Cerrar",
|
||||
"enter-your-api-key": "Ingresa tu API",
|
||||
"key": "Key",
|
||||
"enter-your-api-host": "Ingresa tu API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Ingresa tu Model Type",
|
||||
"verifying": "Verificando...",
|
||||
|
||||
"mcp-and-tools": "MCP & Herramientas",
|
||||
"add-mcp-server": "Agregar MCP Server",
|
||||
"market": "Mercado",
|
||||
"tools": "Herramientas",
|
||||
"added-external-servers": "Servidores externos agregados",
|
||||
"loading": "Cargando...",
|
||||
"no-mcp-servers": "No MCP servers",
|
||||
"environmental-variables-required": "Variables ambientales requeridas",
|
||||
"load-failed": "Carga fallida",
|
||||
"save-failed": "Guardar fallido",
|
||||
"invalid-json": "JSON inválido",
|
||||
"coming-soon": "Próximamente",
|
||||
"uninstall": "Desinstalar",
|
||||
"install": "Instalar",
|
||||
"add-your-agent": "Añade tu Agente",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Agregar un servidor MCP local proporcionando una configuración JSON válida.",
|
||||
"learn-more": "Aprender más",
|
||||
"installing": "Instalando...",
|
||||
"edit-mcp-config": "Editar configuración MCP",
|
||||
"name": "Nombre",
|
||||
"description": "Descripción",
|
||||
"command": "Comando",
|
||||
"args-one-per-line": "Args (una por línea)",
|
||||
"cancel": "Cancelar",
|
||||
"save": "Guardar",
|
||||
"confirm-delete": "Confirmar eliminación",
|
||||
"are-you-sure-you-want-to-delete": "¿Estás seguro de que quieres eliminar",
|
||||
"deleting": "Eliminando...",
|
||||
"delete": "Eliminar",
|
||||
"configure {name} Toolkit": "Configurar {{name}} Toolkit",
|
||||
"get-it-from": "Obtenerlo de",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "Conectar",
|
||||
"setting": "Configuración",
|
||||
"all": "Todos",
|
||||
"mcp-market": "MCP Mercado",
|
||||
"no-mcp-services": "No MCP servicios",
|
||||
"loading-more": "Cargando más...",
|
||||
"no-more-mcp-servers": "No más servidores MCP",
|
||||
"search-mcp": "Buscar MCPs",
|
||||
"installed": "Instalado",
|
||||
"worker-name-cannot-be-empty": "Worker name no puede estar vacío",
|
||||
"worker-name-already-exists": "Worker name ya existe",
|
||||
"warning-google-search-not-configured": "Advertencia: Google Search no configurado",
|
||||
"search-functionality-may-be-limited-without-google-api": "La funcionalidad de búsqueda puede estar limitada sin la clave API de Google y el ID del motor de búsqueda. Puedes configurar estos en la configuración de MCP y Herramientas.",
|
||||
"search-engine": "Motor de búsqueda",
|
||||
"allow-agent-to-take-screenshots": "Permitir que el Agente tome capturas de pantalla",
|
||||
"allow-agent-to-take-screenshots-description": "Permite que el agente capture capturas de pantalla de tu pantalla de computadora. Esto puede usarse para soporte, diagnósticos o propósitos de monitoreo. Las capturas de pantalla pueden incluir información personal visible, así que habilita con cuidado.",
|
||||
"allow-agent-to-access-local-software": "Permitir que el Agente acceda al software local",
|
||||
"allow-agent-to-access-local-software-description": "Otorga al agente permiso para interactuar y utilizar software instalado en tu máquina local. Esto puede ser necesario para resolución de problemas, ejecutar diagnósticos o realizar tareas específicas.",
|
||||
"allow-agent-to-access-your-address": "Permitir que el Agente acceda a tu dirección",
|
||||
"allow-agent-to-access-your-address-description": "Autoriza al agente a ver y usar los detalles de tu ubicación o dirección. Esto puede ser requerido para servicios basados en ubicación o soporte personalizado.",
|
||||
"password-storage": "Almacenamiento de contraseñas",
|
||||
"password-storage-description": "Determina cómo se manejan y almacenan las contraseñas. Puedes elegir almacenar contraseñas de forma segura en el dispositivo o dentro de la aplicación, o optar por ingresarlas manualmente cada vez. Todas las contraseñas almacenadas están encriptadas.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP instalado exitosamente",
|
||||
"failed-to-install-notion-mcp": "Error al instalar Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar instalado exitosamente",
|
||||
"failed-to-install-google-calendar": "Error al instalar Google Calendar",
|
||||
"notion-workspace-integration": "Integración de espacio de trabajo de Notion para leer y gestionar páginas de Notion",
|
||||
"google-calendar-integration": "Integración de Google Calendar para gestionar eventos y horarios",
|
||||
"mcp-server-already-exists": "El servidor MCP \"{{name}}\" ya existe",
|
||||
"google-search": "Google Search",
|
||||
"select-default-search-engine": "Seleccionar motor de búsqueda predeterminado",
|
||||
"your-own-mcps": "Tus propios MCPs",
|
||||
"get-google-search-api": "Obtener Google Search API",
|
||||
"get-exa-api": "Obtener Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Integraciones de Motor de Búsqueda",
|
||||
"configured": "Configurado",
|
||||
"incomplete": "Incompleto",
|
||||
"not-configured": "No Configurado",
|
||||
"saving": "Guardando...",
|
||||
"save-changes": "Guardar Cambios",
|
||||
"enable": "Habilitar",
|
||||
"search": "Buscar",
|
||||
"test-connection": "Probar Conexión",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Tus claves API se almacenan de forma segura y nunca se comparten externamente.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Este servicio es público y no requiere credenciales.",
|
||||
"this-service-does-not-require-an-api-key": "Este servicio no requiere una clave API.",
|
||||
"connection-test-successful": "¡Prueba de conexión exitosa!",
|
||||
"connection-test-failed": "Prueba de conexión fallida.",
|
||||
"configuration-saved-successfully": "¡Configuración guardada exitosamente!",
|
||||
"failed-to-save-configuration": "Error al guardar la configuración.",
|
||||
"mcp-and-tools": "MCP & Herramientas",
|
||||
"add-mcp-server": "Agregar MCP Server",
|
||||
"market": "Mercado",
|
||||
"tools": "Herramientas",
|
||||
"added-external-servers": "Servidores externos agregados",
|
||||
"loading": "Cargando...",
|
||||
"no-mcp-servers": "No MCP servers",
|
||||
"environmental-variables-required": "Variables ambientales requeridas",
|
||||
"load-failed": "Carga fallida",
|
||||
"save-failed": "Guardar fallido",
|
||||
"invalid-json": "JSON inválido",
|
||||
"coming-soon": "Próximamente",
|
||||
"uninstall": "Desinstalar",
|
||||
"install": "Instalar",
|
||||
"add-your-agent": "Añade tu Agente",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Agregar un servidor MCP local proporcionando una configuración JSON válida.",
|
||||
"learn-more": "Aprender más",
|
||||
"installing": "Instalando...",
|
||||
"edit-mcp-config": "Editar configuración MCP",
|
||||
"name": "Nombre",
|
||||
"description": "Descripción",
|
||||
"command": "Comando",
|
||||
"args-one-per-line": "Args (una por línea)",
|
||||
"cancel": "Cancelar",
|
||||
"save": "Guardar",
|
||||
"confirm-delete": "Confirmar eliminación",
|
||||
"are-you-sure-you-want-to-delete": "¿Estás seguro de que quieres eliminar",
|
||||
"deleting": "Eliminando...",
|
||||
"delete": "Eliminar",
|
||||
"configure {name} Toolkit": "Configurar {{name}} Toolkit",
|
||||
"get-it-from": "Obtenerlo de",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "Conectar",
|
||||
"setting": "Configuración",
|
||||
"all": "Todos",
|
||||
"mcp-market": "MCP Mercado",
|
||||
"no-mcp-services": "No MCP servicios",
|
||||
"loading-more": "Cargando más...",
|
||||
"no-more-mcp-servers": "No más servidores MCP",
|
||||
"search-mcp": "Buscar MCPs",
|
||||
"installed": "Instalado",
|
||||
"worker-name-cannot-be-empty": "Worker name no puede estar vacío",
|
||||
"worker-name-already-exists": "Worker name ya existe",
|
||||
"warning-google-search-not-configured": "Advertencia: Google Search no configurado",
|
||||
"search-functionality-may-be-limited-without-google-api": "La funcionalidad de búsqueda puede estar limitada sin la clave API de Google y el ID del motor de búsqueda. Puedes configurar estos en la configuración de MCP y Herramientas.",
|
||||
"search-engine": "Motor de búsqueda",
|
||||
"allow-agent-to-take-screenshots": "Permitir que el Agente tome capturas de pantalla",
|
||||
"allow-agent-to-take-screenshots-description": "Permite que el agente capture capturas de pantalla de tu pantalla de computadora. Esto puede usarse para soporte, diagnósticos o propósitos de monitoreo. Las capturas de pantalla pueden incluir información personal visible, así que habilita con cuidado.",
|
||||
"allow-agent-to-access-local-software": "Permitir que el Agente acceda al software local",
|
||||
"allow-agent-to-access-local-software-description": "Otorga al agente permiso para interactuar y utilizar software instalado en tu máquina local. Esto puede ser necesario para resolución de problemas, ejecutar diagnósticos o realizar tareas específicas.",
|
||||
"allow-agent-to-access-your-address": "Permitir que el Agente acceda a tu dirección",
|
||||
"allow-agent-to-access-your-address-description": "Autoriza al agente a ver y usar los detalles de tu ubicación o dirección. Esto puede ser requerido para servicios basados en ubicación o soporte personalizado.",
|
||||
"password-storage": "Almacenamiento de contraseñas",
|
||||
"password-storage-description": "Determina cómo se manejan y almacenan las contraseñas. Puedes elegir almacenar contraseñas de forma segura en el dispositivo o dentro de la aplicación, o optar por ingresarlas manualmente cada vez. Todas las contraseñas almacenadas están encriptadas.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP instalado exitosamente",
|
||||
"failed-to-install-notion-mcp": "Error al instalar Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar instalado exitosamente",
|
||||
"failed-to-install-google-calendar": "Error al instalar Google Calendar",
|
||||
"notion-workspace-integration": "Integración de espacio de trabajo de Notion para leer y gestionar páginas de Notion",
|
||||
"google-calendar-integration": "Integración de Google Calendar para gestionar eventos y horarios",
|
||||
"mcp-server-already-exists": "El servidor MCP \"{{name}}\" ya existe",
|
||||
"google-search": "Google Search",
|
||||
"select-default-search-engine": "Seleccionar motor de búsqueda predeterminado",
|
||||
"your-own-mcps": "Tus propios MCPs",
|
||||
"get-google-search-api": "Obtener Google Search API",
|
||||
"get-exa-api": "Obtener Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Integraciones de Motor de Búsqueda",
|
||||
"configured": "Configurado",
|
||||
"incomplete": "Incompleto",
|
||||
"not-configured": "No Configurado",
|
||||
"saving": "Guardando...",
|
||||
"save-changes": "Guardar Cambios",
|
||||
"enable": "Habilitar",
|
||||
"search": "Buscar",
|
||||
"test-connection": "Probar Conexión",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Tus claves API se almacenan de forma segura y nunca se comparten externamente.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Este servicio es público y no requiere credenciales.",
|
||||
"this-service-does-not-require-an-api-key": "Este servicio no requiere una clave API.",
|
||||
"connection-test-successful": "¡Prueba de conexión exitosa!",
|
||||
"connection-test-failed": "Prueba de conexión fallida.",
|
||||
"configuration-saved-successfully": "¡Configuración guardada exitosamente!",
|
||||
"failed-to-save-configuration": "Error al guardar la configuración.",
|
||||
"recommended": "Recomendado",
|
||||
"reset": "Restablecer",
|
||||
"reset-success": "¡Restablecido correctamente!",
|
||||
"reset-failed": "¡Error al restablecer!",
|
||||
|
||||
"network-proxy": "Proxy de red",
|
||||
"network-proxy-description": "Configure un servidor proxy para las solicitudes de red. Esto es útil si necesita acceder a APIs externas a través de un proxy.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Configuración de proxy guardada. Reinicie la aplicación para aplicar los cambios.",
|
||||
"proxy-save-failed": "Error al guardar la configuración del proxy.",
|
||||
"proxy-invalid-url": "URL de proxy no válida. Debe comenzar con http://, https://, socks4:// o socks5://.",
|
||||
"proxy-restart-hint": "Es necesario reiniciar para aplicar los cambios del proxy."
|
||||
"network-proxy": "Proxy de red",
|
||||
"network-proxy-description": "Configure un servidor proxy para las solicitudes de red. Esto es útil si necesita acceder a APIs externas a través de un proxy.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Configuración de proxy guardada. Reinicie la aplicación para aplicar los cambios.",
|
||||
"proxy-save-failed": "Error al guardar la configuración del proxy.",
|
||||
"proxy-invalid-url": "URL de proxy no válida. Debe comenzar con http://, https://, socks4:// o socks5://.",
|
||||
"proxy-restart-hint": "Es necesario reiniciar para aplicar los cambios del proxy."
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,147 +1,149 @@
|
|||
{
|
||||
"settings": "Réglages",
|
||||
"general": "General",
|
||||
"privacy": "Privacy",
|
||||
"models": "Models",
|
||||
"mcp": "MCP & Tools",
|
||||
"settings": "Réglages",
|
||||
"general": "General",
|
||||
"privacy": "Privacy",
|
||||
"models": "Models",
|
||||
"mcp": "MCP & Tools",
|
||||
|
||||
"account": "Account",
|
||||
"you-are-currently-signed-in-with": "You are currently signed in with {{email}}",
|
||||
"manage": "Manage",
|
||||
"log-out": "Log out",
|
||||
"language": "Language",
|
||||
"select-language": "Select language",
|
||||
"system-default": "System Default",
|
||||
"appearance": "Appearance",
|
||||
"light": "Light",
|
||||
"transparent": "Transparent",
|
||||
"account": "Account",
|
||||
"you-are-currently-signed-in-with": "You are currently signed in with {{email}}",
|
||||
"manage": "Manage",
|
||||
"log-out": "Log out",
|
||||
"language": "Language",
|
||||
"select-language": "Select language",
|
||||
"system-default": "System Default",
|
||||
"appearance": "Appearance",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"transparent": "Transparent",
|
||||
|
||||
"data-privacy": "Data Privacy",
|
||||
"data-privacy-description": "Eigent is built on a local-first principle to ensure your privacy. Your data remains on your device by default. Cloud features are optional and only use the minimum data necessary to function. For full details, visit our",
|
||||
"privacy-policy": "Privacy Policy",
|
||||
"how-we-handle-your-data": "How we handle your data",
|
||||
"how-we-handle-your-data-line-1": "We only use the essential data needed to run your tasks",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent may capture screenshots to analyze UI elements, read text, and determine the next action, just as you would.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent may use your mouse and keyboard to access local software and files you specify.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Only the minimum task data is sent to AI model providers or the third-party integrations you enable; we have zero data-retention",
|
||||
"how-we-handle-your-data-line-2": "Task files, outputs and screenshots remain in your designated task folder locally.",
|
||||
"how-we-handle-your-data-line-3": "Credentials are stored locally, encrypted, and used only for approved steps.",
|
||||
"how-we-handle-your-data-line-4": "Your data is never used to train our AI models without your explicit consent.",
|
||||
"how-we-handle-your-data-line-5": "We don’t sell your data to third parties.",
|
||||
"enable-privacy-permissions-settings": "Enable Privacy Permissions Settings",
|
||||
"enable-privacy-permissions-settings-description": "By turning this on, you acknowledge that you have read and agree to our Privacy Policy regarding how your task data is collected, processed, and protected.",
|
||||
"data-privacy": "Data Privacy",
|
||||
"data-privacy-description": "Eigent is built on a local-first principle to ensure your privacy. Your data remains on your device by default. Cloud features are optional and only use the minimum data necessary to function. For full details, visit our",
|
||||
"privacy-policy": "Privacy Policy",
|
||||
"how-we-handle-your-data": "How we handle your data",
|
||||
"how-we-handle-your-data-line-1": "We only use the essential data needed to run your tasks",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent may capture screenshots to analyze UI elements, read text, and determine the next action, just as you would.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent may use your mouse and keyboard to access local software and files you specify.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Only the minimum task data is sent to AI model providers or the third-party integrations you enable; we have zero data-retention",
|
||||
"how-we-handle-your-data-line-2":"Task files, outputs and screenshots remain in your designated task folder locally.",
|
||||
"how-we-handle-your-data-line-3":"Credentials are stored locally, encrypted, and used only for approved steps.",
|
||||
"how-we-handle-your-data-line-4":"Your data is never used to train our AI models without your explicit consent.",
|
||||
"how-we-handle-your-data-line-5":"We don’t sell your data to third parties.",
|
||||
"enable-privacy-permissions-settings": "Enable Privacy Permissions Settings",
|
||||
"enable-privacy-permissions-settings-description": "By turning this on, you acknowledge that you have read and agree to our Privacy Policy regarding how your task data is collected, processed, and protected.",
|
||||
|
||||
"api-key-can-not-be-empty": "API Key can not be empty!",
|
||||
"api-host-can-not-be-empty": "API Host can not be empty!",
|
||||
"model-type-can-not-be-empty": "Model Type can not be empty!",
|
||||
"validate-success": "Validate success",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "The model has been verified to support function calling, which is required to use Eigent.",
|
||||
"validate-failed": "Validate failed",
|
||||
"copy": "Copy",
|
||||
"copied-to-clipboard": "Copied to clipboard",
|
||||
"endpoint-url-can-not-be-empty": "Endpoint URL can not be empty!",
|
||||
"verification-failed-please-check-endpoint-url": "Verification failed, please check Endpoint URL",
|
||||
"eigent-cloud-version": "Eigent Cloud Version",
|
||||
"you-are-currently-subscribed-to-the": "You are currently subscribed to the",
|
||||
"discover-more-about-our": "Discover more about our",
|
||||
"pricing-options": "pricing options",
|
||||
"credits": "Credits",
|
||||
"select-model-type": "Select Model Type",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Lower cost, faster responses, but reduced output quality.",
|
||||
"gpt-4.1": "GPT-4.1: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5": "GPT-5: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5-mini": "GPT-5 mini: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5-nano": "GPT-5 nano: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"custom-model": "Custom Model",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Use your own API keys or set up a local model.",
|
||||
"verify": "Verify",
|
||||
"local-model": "Local Model",
|
||||
"model-platform": "Model Platform",
|
||||
"model-endpoint-url": "Model Endpoint URL",
|
||||
"model-type": "Model Type",
|
||||
"enter-your-local-model-type": "Enter your local model type",
|
||||
"you-are-on-selft-host-mode": "You are on Selft Host Mode",
|
||||
"you-are-using-self-hosted-mode": "You're using Self-hosted mode. To get the best performance from this product, please enter the Google Search Key in \"MCP and Tools\" to ensure Eigent works properly.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "The Google Search Key is essential for delivering accurate search results. Exa Search Key is optional but highly recommended for better performance.",
|
||||
"close": "Close",
|
||||
"enter-your-api-key": "Enter your API",
|
||||
"key": "Key",
|
||||
"enter-your-api-host": "Enter your API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Enter your Model Type",
|
||||
"verifying": "Verifying...",
|
||||
|
||||
"api-key-can-not-be-empty": "API Key can not be empty!",
|
||||
"api-host-can-not-be-empty": "API Host can not be empty!",
|
||||
"model-type-can-not-be-empty": "Model Type can not be empty!",
|
||||
"validate-success": "Validate success",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "The model has been verified to support function calling, which is required to use Eigent.",
|
||||
"validate-failed": "Validate failed",
|
||||
"copy": "Copy",
|
||||
"copied-to-clipboard": "Copied to clipboard",
|
||||
"endpoint-url-can-not-be-empty": "Endpoint URL can not be empty!",
|
||||
"verification-failed-please-check-endpoint-url": "Verification failed, please check Endpoint URL",
|
||||
"eigent-cloud-version": "Eigent Cloud Version",
|
||||
"you-are-currently-subscribed-to-the": "You are currently subscribed to the",
|
||||
"discover-more-about-our": "Discover more about our",
|
||||
"pricing-options": "pricing options",
|
||||
"credits": "Credits",
|
||||
"select-model-type": "Select Model Type",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Lower cost, faster responses, but reduced output quality.",
|
||||
"gpt-4.1": "GPT-4.1: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5": "GPT-5: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5-mini": "GPT-5 mini: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"gpt-5-nano": "GPT-5 nano: Higher cost, slower responses, but superior quality and reasoning.",
|
||||
"custom-model": "Custom Model",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Use your own API keys or set up a local model.",
|
||||
"verify": "Verify",
|
||||
"local-model": "Local Model",
|
||||
"model-platform": "Model Platform",
|
||||
"model-endpoint-url": "Model Endpoint URL",
|
||||
"model-type": "Model Type",
|
||||
"enter-your-local-model-type": "Enter your local model type",
|
||||
"you-are-on-selft-host-mode": "You are on Selft Host Mode",
|
||||
"you-are-using-self-hosted-mode": "You're using Self-hosted mode. To get the best performance from this product, please enter the Google Search Key in \"MCP and Tools\" to ensure Eigent works properly.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "The Google Search Key is essential for delivering accurate search results. Exa Search Key is optional but highly recommended for better performance.",
|
||||
"close": "Close",
|
||||
"enter-your-api-key": "Enter your API",
|
||||
"key": "Key",
|
||||
"enter-your-api-host": "Enter your API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Enter your Model Type",
|
||||
"verifying": "Verifying...",
|
||||
|
||||
"mcp-and-tools": "MCP & Tools",
|
||||
"add-mcp-server": "Add MCP Server",
|
||||
"market": "Market",
|
||||
"tools": "Tools",
|
||||
"added-external-servers": "Added external servers",
|
||||
"loading": "Loading...",
|
||||
"no-mcp-servers": "No MCP servers",
|
||||
"environmental-variables-required": "Environmental variables required",
|
||||
"load-failed": "Load failed",
|
||||
"save-failed": "Save failed",
|
||||
"invalid-json": "Invalid JSON",
|
||||
"coming-soon": "Coming Soon",
|
||||
"uninstall": "Uninstall",
|
||||
"install": "Installer",
|
||||
"add-your-agent": "Ajoutez votre Agent",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Ajoutez un serveur MCP local en fournissant une configuration JSON valide.",
|
||||
"learn-more": "En savoir plus",
|
||||
"installing": "Installing...",
|
||||
"edit-mcp-config": "Edit MCP Config",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"command": "Command",
|
||||
"args-one-per-line": "Args (one per line)",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"confirm-delete": "Confirm Delete",
|
||||
"are-you-sure-you-want-to-delete": "Are you sure you want to delete",
|
||||
"deleting": "Deleting...",
|
||||
"delete": "Delete",
|
||||
"configure {name} Toolkit": "Configure {{name}} Toolkit",
|
||||
"get-it-from": "Get it from",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "Connect",
|
||||
"setting": "Setting",
|
||||
"all": "All",
|
||||
"mcp-market": "MCP Market",
|
||||
"no-mcp-services": "No MCP services",
|
||||
"loading-more": "Loading more...",
|
||||
"no-more-mcp-servers": "No more MCP servers",
|
||||
"search-mcp": "Search MCPs",
|
||||
"installed": "Installed",
|
||||
"worker-name-cannot-be-empty": "Worker name cannot be empty",
|
||||
"worker-name-already-exists": "Worker name already exists",
|
||||
"search-engine-integrations": "Intégrations de Moteur de Recherche",
|
||||
"configured": "Configuré",
|
||||
"incomplete": "Incomplet",
|
||||
"not-configured": "Non Configuré",
|
||||
"saving": "Enregistrement...",
|
||||
"save-changes": "Enregistrer les Modifications",
|
||||
"enable": "Activer",
|
||||
"search": "Rechercher",
|
||||
"test-connection": "Tester la Connexion",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Vos clés API sont stockées de manière sécurisée et ne sont jamais partagées à l'extérieur.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Ce service est public et ne nécessite pas d'identifiants.",
|
||||
"this-service-does-not-require-an-api-key": "Ce service ne nécessite pas de clé API.",
|
||||
"connection-test-successful": "Test de connexion réussi !",
|
||||
"connection-test-failed": "Test de connexion échoué.",
|
||||
"configuration-saved-successfully": "Configuration enregistrée avec succès !",
|
||||
"failed-to-save-configuration": "Échec de l'enregistrement de la configuration.",
|
||||
"mcp-and-tools": "MCP & Tools",
|
||||
"add-mcp-server": "Add MCP Server",
|
||||
"market": "Market",
|
||||
"tools": "Tools",
|
||||
"added-external-servers": "Added external servers",
|
||||
"loading": "Loading...",
|
||||
"no-mcp-servers": "No MCP servers",
|
||||
"environmental-variables-required": "Environmental variables required",
|
||||
"load-failed": "Load failed",
|
||||
"save-failed": "Save failed",
|
||||
"invalid-json": "Invalid JSON",
|
||||
"coming-soon": "Coming Soon",
|
||||
"uninstall": "Uninstall",
|
||||
"install": "Installer",
|
||||
"add-your-agent": "Ajoutez votre Agent",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Ajoutez un serveur MCP local en fournissant une configuration JSON valide.",
|
||||
"learn-more": "En savoir plus",
|
||||
"installing": "Installing...",
|
||||
"edit-mcp-config": "Edit MCP Config",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"command": "Command",
|
||||
"args-one-per-line": "Args (one per line)",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"confirm-delete": "Confirm Delete",
|
||||
"are-you-sure-you-want-to-delete": "Are you sure you want to delete",
|
||||
"deleting": "Deleting...",
|
||||
"delete": "Delete",
|
||||
"configure {name} Toolkit": "Configure {{name}} Toolkit",
|
||||
"get-it-from": "Get it from",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "Connect",
|
||||
"setting": "Setting",
|
||||
"all": "All",
|
||||
"mcp-market": "MCP Market",
|
||||
"no-mcp-services": "No MCP services",
|
||||
"loading-more": "Loading more...",
|
||||
"no-more-mcp-servers": "No more MCP servers",
|
||||
"search-mcp": "Search MCPs",
|
||||
"installed": "Installed",
|
||||
"worker-name-cannot-be-empty": "Worker name cannot be empty",
|
||||
"worker-name-already-exists": "Worker name already exists",
|
||||
"search-engine-integrations": "Intégrations de Moteur de Recherche",
|
||||
"configured": "Configuré",
|
||||
"incomplete": "Incomplet",
|
||||
"not-configured": "Non Configuré",
|
||||
"saving": "Enregistrement...",
|
||||
"save-changes": "Enregistrer les Modifications",
|
||||
"enable": "Activer",
|
||||
"search": "Rechercher",
|
||||
"test-connection": "Tester la Connexion",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Vos clés API sont stockées de manière sécurisée et ne sont jamais partagées à l'extérieur.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Ce service est public et ne nécessite pas d'identifiants.",
|
||||
"this-service-does-not-require-an-api-key": "Ce service ne nécessite pas de clé API.",
|
||||
"connection-test-successful": "Test de connexion réussi !",
|
||||
"connection-test-failed": "Test de connexion échoué.",
|
||||
"configuration-saved-successfully": "Configuration enregistrée avec succès !",
|
||||
"failed-to-save-configuration": "Échec de l'enregistrement de la configuration.",
|
||||
"recommended": "Recommandé",
|
||||
"reset": "Réinitialiser",
|
||||
"reset-success": "Réinitialisation réussie !",
|
||||
"reset-failed": "Échec de la réinitialisation !",
|
||||
|
||||
"network-proxy": "Proxy réseau",
|
||||
"network-proxy-description": "Configurez un serveur proxy pour les requêtes réseau. Utile si vous devez accéder à des API externes via un proxy.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Configuration du proxy enregistrée. Redémarrez l'application pour appliquer les modifications.",
|
||||
"proxy-save-failed": "Échec de l'enregistrement de la configuration du proxy.",
|
||||
"proxy-invalid-url": "URL de proxy invalide. Doit commencer par http://, https://, socks4:// ou socks5://.",
|
||||
"proxy-restart-hint": "Redémarrage nécessaire pour appliquer les modifications du proxy."
|
||||
"network-proxy": "Proxy réseau",
|
||||
"network-proxy-description": "Configurez un serveur proxy pour les requêtes réseau. Utile si vous devez accéder à des API externes via un proxy.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Configuration du proxy enregistrée. Redémarrez l'application pour appliquer les modifications.",
|
||||
"proxy-save-failed": "Échec de l'enregistrement de la configuration du proxy.",
|
||||
"proxy-invalid-url": "URL de proxy invalide. Doit commencer par http://, https://, socks4:// ou socks5://.",
|
||||
"proxy-restart-hint": "Redémarrage nécessaire pour appliquer les modifications du proxy."
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,171 +1,173 @@
|
|||
{
|
||||
"settings": "Impostazioni",
|
||||
"general": "Generale",
|
||||
"privacy": "Privacy",
|
||||
"models": "Modelli",
|
||||
"mcp": "MCP e Strumenti",
|
||||
"settings": "Impostazioni",
|
||||
"general": "Generale",
|
||||
"privacy": "Privacy",
|
||||
"models": "Modelli",
|
||||
"mcp": "MCP e Strumenti",
|
||||
|
||||
"account": "Account",
|
||||
"you-are-currently-signed-in-with": "Sei attualmente connesso con {{email}}",
|
||||
"manage": "Gestisci",
|
||||
"log-out": "Esci",
|
||||
"language": "Lingua",
|
||||
"select-language": "Seleziona lingua",
|
||||
"system-default": "Predefinito di sistema",
|
||||
"appearance": "Aspetto",
|
||||
"light": "Chiaro",
|
||||
"transparent": "Trasparente",
|
||||
"account": "Account",
|
||||
"you-are-currently-signed-in-with": "Sei attualmente connesso con {{email}}",
|
||||
"manage": "Gestisci",
|
||||
"log-out": "Esci",
|
||||
"language": "Lingua",
|
||||
"select-language": "Seleziona lingua",
|
||||
"system-default": "Predefinito di sistema",
|
||||
"appearance": "Aspetto",
|
||||
"dark": "Scuro",
|
||||
"light": "Chiaro",
|
||||
"transparent": "Trasparente",
|
||||
|
||||
"data-privacy": "Privacy dei dati",
|
||||
"data-privacy-description": "Eigent è costruito su un principio local-first per garantire la tua privacy. I tuoi dati rimangono sul tuo dispositivo per impostazione predefinita. Le funzionalità cloud sono opzionali e utilizzano solo i dati minimi necessari per funzionare. Per tutti i dettagli, visita la nostra",
|
||||
"privacy-policy": "Informativa sulla privacy",
|
||||
"how-we-handle-your-data": "Come gestiamo i tuoi dati",
|
||||
"how-we-handle-your-data-line-1": "Utilizziamo solo i dati essenziali necessari per eseguire le tue attività",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent può acquisire screenshot per analizzare elementi dell'interfaccia utente, leggere testo e determinare l'azione successiva, proprio come faresti tu.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent può utilizzare il tuo mouse e la tua tastiera per accedere a software e file locali da te specificati.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Solo i dati minimi necessari per l'attività vengono inviati ai fornitori di modelli AI o alle integrazioni di terze parti da te abilitate; non abbiamo alcuna conservazione dei dati",
|
||||
"how-we-handle-your-data-line-2": "I file delle attività, gli output e gli screenshot rimangono nella tua cartella attività designata localmente.",
|
||||
"how-we-handle-your-data-line-3": "Le credenziali sono archiviate localmente, crittografate e utilizzate solo per i passaggi approvati.",
|
||||
"how-we-handle-your-data-line-4": "I tuoi dati non vengono mai utilizzati per addestrare i nostri modelli AI senza il tuo esplicito consenso.",
|
||||
"how-we-handle-your-data-line-5": "Non vendiamo i tuoi dati a terzi.",
|
||||
"enable-privacy-permissions-settings": "Abilita impostazioni di autorizzazione per la privacy",
|
||||
"enable-privacy-permissions-settings-description": "Attivando questa opzione, riconosci di aver letto e accettato la nostra Informativa sulla privacy riguardo a come i tuoi dati di attività vengono raccolti, elaborati e protetti.",
|
||||
"data-privacy": "Privacy dei dati",
|
||||
"data-privacy-description": "Eigent è costruito su un principio local-first per garantire la tua privacy. I tuoi dati rimangono sul tuo dispositivo per impostazione predefinita. Le funzionalità cloud sono opzionali e utilizzano solo i dati minimi necessari per funzionare. Per tutti i dettagli, visita la nostra",
|
||||
"privacy-policy": "Informativa sulla privacy",
|
||||
"how-we-handle-your-data": "Come gestiamo i tuoi dati",
|
||||
"how-we-handle-your-data-line-1": "Utilizziamo solo i dati essenziali necessari per eseguire le tue attività",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent può acquisire screenshot per analizzare elementi dell'interfaccia utente, leggere testo e determinare l'azione successiva, proprio come faresti tu.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent può utilizzare il tuo mouse e la tua tastiera per accedere a software e file locali da te specificati.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Solo i dati minimi necessari per l'attività vengono inviati ai fornitori di modelli AI o alle integrazioni di terze parti da te abilitate; non abbiamo alcuna conservazione dei dati",
|
||||
"how-we-handle-your-data-line-2":"I file delle attività, gli output e gli screenshot rimangono nella tua cartella attività designata localmente.",
|
||||
"how-we-handle-your-data-line-3":"Le credenziali sono archiviate localmente, crittografate e utilizzate solo per i passaggi approvati.",
|
||||
"how-we-handle-your-data-line-4":"I tuoi dati non vengono mai utilizzati per addestrare i nostri modelli AI senza il tuo esplicito consenso.",
|
||||
"how-we-handle-your-data-line-5":"Non vendiamo i tuoi dati a terzi.",
|
||||
"enable-privacy-permissions-settings": "Abilita impostazioni di autorizzazione per la privacy",
|
||||
"enable-privacy-permissions-settings-description": "Attivando questa opzione, riconosci di aver letto e accettato la nostra Informativa sulla privacy riguardo a come i tuoi dati di attività vengono raccolti, elaborati e protetti.",
|
||||
|
||||
"api-key-can-not-be-empty": "La chiave API non può essere vuota!",
|
||||
"api-host-can-not-be-empty": "L'host API non può essere vuoto!",
|
||||
"model-type-can-not-be-empty": "Il tipo di modello non può essere vuoto!",
|
||||
"validate-success": "Validazione riuscita",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "Il modello è stato verificato per supportare la chiamata di funzione, necessaria per utilizzare Eigent.",
|
||||
"validate-failed": "Validazione fallita",
|
||||
"copy": "Copia",
|
||||
"copied-to-clipboard": "Copiato negli appunti",
|
||||
"endpoint-url-can-not-be-empty": "L'URL dell'endpoint non può essere vuoto!",
|
||||
"verification-failed-please-check-endpoint-url": "Verifica fallita, controlla l'URL dell'endpoint",
|
||||
"eigent-cloud-version": "Versione cloud di Eigent",
|
||||
"you-are-currently-subscribed-to-the": "Sei attualmente iscritto al",
|
||||
"discover-more-about-our": "Scopri di più sulle nostre",
|
||||
"pricing-options": "opzioni di prezzo",
|
||||
"credits": "Crediti",
|
||||
"select-model-type": "Seleziona tipo di modello",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Costo inferiore, risposte più veloci, ma qualità dell'output ridotta.",
|
||||
"gpt-4.1": "GPT-4.1: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"gpt-5": "GPT-5: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"gpt-5-mini": "GPT-5 mini: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"gpt-5-nano": "GPT-5 nano: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"custom-model": "Modello personalizzato",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Usa le tue chiavi API o imposta un modello locale.",
|
||||
"verify": "Verifica",
|
||||
"local-model": "Modello locale",
|
||||
"model-platform": "Piattaforma modello",
|
||||
"model-endpoint-url": "URL endpoint modello",
|
||||
"model-type": "Tipo modello",
|
||||
"enter-your-local-model-type": "Inserisci il tuo tipo di modello locale",
|
||||
"you-are-on-selft-host-mode": "Sei in modalità Self Host",
|
||||
"you-are-using-self-hosted-mode": "Stai usando la modalità Self-hosted. Per ottenere le migliori prestazioni da questo prodotto, inserisci la chiave di ricerca Google in \"MCP e Strumenti\" per garantire il corretto funzionamento di Eigent.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "La chiave di ricerca Google è essenziale per fornire risultati di ricerca accurati. La chiave di ricerca Exa è opzionale ma altamente raccomandata per prestazioni migliori.",
|
||||
"close": "Chiudi",
|
||||
"enter-your-api-key": "Inserisci la tua API",
|
||||
"key": "Chiave",
|
||||
"enter-your-api-host": "Inserisci il tuo host API",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Inserisci il tuo tipo di modello",
|
||||
"verifying": "Verifica in corso...",
|
||||
"api-key-can-not-be-empty": "La chiave API non può essere vuota!",
|
||||
"api-host-can-not-be-empty": "L'host API non può essere vuoto!",
|
||||
"model-type-can-not-be-empty": "Il tipo di modello non può essere vuoto!",
|
||||
"validate-success": "Validazione riuscita",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "Il modello è stato verificato per supportare la chiamata di funzione, necessaria per utilizzare Eigent.",
|
||||
"validate-failed": "Validazione fallita",
|
||||
"copy": "Copia",
|
||||
"copied-to-clipboard": "Copiato negli appunti",
|
||||
"endpoint-url-can-not-be-empty": "L'URL dell'endpoint non può essere vuoto!",
|
||||
"verification-failed-please-check-endpoint-url": "Verifica fallita, controlla l'URL dell'endpoint",
|
||||
"eigent-cloud-version": "Versione cloud di Eigent",
|
||||
"you-are-currently-subscribed-to-the": "Sei attualmente iscritto al",
|
||||
"discover-more-about-our": "Scopri di più sulle nostre",
|
||||
"pricing-options": "opzioni di prezzo",
|
||||
"credits": "Crediti",
|
||||
"select-model-type": "Seleziona tipo di modello",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Costo inferiore, risposte più veloci, ma qualità dell'output ridotta.",
|
||||
"gpt-4.1": "GPT-4.1: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"gpt-5": "GPT-5: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"gpt-5-mini": "GPT-5 mini: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"gpt-5-nano": "GPT-5 nano: Costo superiore, risposte più lente, ma qualità e ragionamento superiori.",
|
||||
"custom-model": "Modello personalizzato",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Usa le tue chiavi API o imposta un modello locale.",
|
||||
"verify": "Verifica",
|
||||
"local-model": "Modello locale",
|
||||
"model-platform": "Piattaforma modello",
|
||||
"model-endpoint-url": "URL endpoint modello",
|
||||
"model-type": "Tipo modello",
|
||||
"enter-your-local-model-type": "Inserisci il tuo tipo di modello locale",
|
||||
"you-are-on-selft-host-mode": "Sei in modalità Self Host",
|
||||
"you-are-using-self-hosted-mode": "Stai usando la modalità Self-hosted. Per ottenere le migliori prestazioni da questo prodotto, inserisci la chiave di ricerca Google in \"MCP e Strumenti\" per garantire il corretto funzionamento di Eigent.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "La chiave di ricerca Google è essenziale per fornire risultati di ricerca accurati. La chiave di ricerca Exa è opzionale ma altamente raccomandata per prestazioni migliori.",
|
||||
"close": "Chiudi",
|
||||
"enter-your-api-key": "Inserisci la tua API",
|
||||
"key": "Chiave",
|
||||
"enter-your-api-host": "Inserisci il tuo host API",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Inserisci il tuo tipo di modello",
|
||||
"verifying": "Verifica in corso...",
|
||||
|
||||
"mcp-and-tools": "MCP e Strumenti",
|
||||
"add-mcp-server": "Aggiungi server MCP",
|
||||
"market": "Mercato",
|
||||
"tools": "Strumenti",
|
||||
"added-external-servers": "Server esterni aggiunti",
|
||||
"loading": "Caricamento in corso...",
|
||||
"no-mcp-servers": "Nessun server MCP",
|
||||
"environmental-variables-required": "Variabili d'ambiente richieste",
|
||||
"load-failed": "Caricamento fallito",
|
||||
"save-failed": "Salvataggio fallito",
|
||||
"invalid-json": "JSON non valido",
|
||||
"coming-soon": "Prossimamente",
|
||||
"uninstall": "Disinstalla",
|
||||
"install": "Installa",
|
||||
"add-your-agent": "Aggiungi il tuo Agente",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Aggiungi un server MCP locale fornendo una configurazione JSON valida.",
|
||||
"learn-more": "Scopri di più",
|
||||
"installing": "Installazione in corso...",
|
||||
"edit-mcp-config": "Modifica configurazione MCP",
|
||||
"name": "Nome",
|
||||
"description": "Descrizione",
|
||||
"command": "Comando",
|
||||
"args-one-per-line": "Argomenti (uno per riga)",
|
||||
"cancel": "Annulla",
|
||||
"save": "Salva",
|
||||
"confirm-delete": "Conferma eliminazione",
|
||||
"are-you-sure-you-want-to-delete": "Sei sicuro di voler eliminare",
|
||||
"deleting": "Eliminazione in corso...",
|
||||
"delete": "Elimina",
|
||||
"configure {name} Toolkit": "Configura Toolkit {{name}}",
|
||||
"get-it-from": "Ottienilo da",
|
||||
"google-custom-search-api": "API di ricerca personalizzata Google",
|
||||
"google-cloud-console": "Console Google Cloud",
|
||||
"connect": "Connetti",
|
||||
"setting": "Impostazione",
|
||||
"all": "Tutto",
|
||||
"mcp-market": "Mercato MCP",
|
||||
"no-mcp-services": "Nessun servizio MCP",
|
||||
"loading-more": "Caricamento altro...",
|
||||
"no-more-mcp-servers": "Nessun altro server MCP",
|
||||
"search-mcp": "Cerca MCP",
|
||||
"installed": "Installato",
|
||||
"worker-name-cannot-be-empty": "Il nome del worker non può essere vuoto",
|
||||
"worker-name-already-exists": "Il nome del worker esiste già",
|
||||
"warning-google-search-not-configured": "Avviso: Google Search non configurato",
|
||||
"search-functionality-may-be-limited-without-google-api": "La funzionalità di ricerca può essere limitata senza la chiave API Google e l'ID del motore di ricerca. Puoi configurare questi nelle impostazioni MCP e Strumenti.",
|
||||
"search-engine": "Motore di ricerca",
|
||||
"allow-agent-to-take-screenshots": "Consenti all'Agente di fare screenshot",
|
||||
"allow-agent-to-take-screenshots-description": "Consenti all'agente di acquisire screenshot del tuo schermo del computer. Questo può essere utilizzato per supporto, diagnostica o scopi di monitoraggio. Gli screenshot possono includere informazioni personali visibili, quindi abilita con cautela.",
|
||||
"allow-agent-to-access-local-software": "Consenti all'Agente di accedere al software locale",
|
||||
"allow-agent-to-access-local-software-description": "Concedi all'agente il permesso di interagire e utilizzare software installato sulla tua macchina locale. Questo può essere necessario per la risoluzione dei problemi, l'esecuzione di diagnostica o l'esecuzione di attività specifiche.",
|
||||
"allow-agent-to-access-your-address": "Consenti all'Agente di accedere al tuo indirizzo",
|
||||
"allow-agent-to-access-your-address-description": "Autorizza l'agente a visualizzare e utilizzare i dettagli della tua posizione o indirizzo. Questo può essere richiesto per servizi basati sulla posizione o supporto personalizzato.",
|
||||
"password-storage": "Archiviazione password",
|
||||
"password-storage-description": "Determina come vengono gestite e archiviate le password. Puoi scegliere di archiviare le password in modo sicuro sul dispositivo o all'interno dell'applicazione, o scegliere di inserirle manualmente ogni volta. Tutte le password archiviate sono crittografate.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP installato con successo",
|
||||
"failed-to-install-notion-mcp": "Impossibile installare Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar installato con successo",
|
||||
"failed-to-install-google-calendar": "Impossibile installare Google Calendar",
|
||||
"notion-workspace-integration": "Integrazione workspace Notion per leggere e gestire le pagine Notion",
|
||||
"google-calendar-integration": "Integrazione Google Calendar per gestire eventi e programmi",
|
||||
"mcp-server-already-exists": "Il server MCP \"{{name}}\" esiste già",
|
||||
"google-search": "Google Search",
|
||||
"select-default-search-engine": "Seleziona motore di ricerca predefinito",
|
||||
"your-own-mcps": "I tuoi MCP",
|
||||
"get-google-search-api": "Ottieni Google Search API",
|
||||
"get-exa-api": "Ottieni Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Integrazioni Motore di Ricerca",
|
||||
"configured": "Configurato",
|
||||
"incomplete": "Incompleto",
|
||||
"not-configured": "Non Configurato",
|
||||
"saving": "Salvataggio...",
|
||||
"save-changes": "Salva Modifiche",
|
||||
"enable": "Abilita",
|
||||
"search": "Cerca",
|
||||
"test-connection": "Testa Connessione",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Le tue chiavi API sono memorizzate in modo sicuro e non vengono mai condivise esternamente.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Questo servizio è pubblico e non richiede credenziali.",
|
||||
"this-service-does-not-require-an-api-key": "Questo servizio non richiede una chiave API.",
|
||||
"connection-test-successful": "Test di connessione riuscito!",
|
||||
"connection-test-failed": "Test di connessione fallito.",
|
||||
"configuration-saved-successfully": "Configurazione salvata con successo!",
|
||||
"failed-to-save-configuration": "Impossibile salvare la configurazione.",
|
||||
"mcp-and-tools": "MCP e Strumenti",
|
||||
"add-mcp-server": "Aggiungi server MCP",
|
||||
"market": "Mercato",
|
||||
"tools": "Strumenti",
|
||||
"added-external-servers": "Server esterni aggiunti",
|
||||
"loading": "Caricamento in corso...",
|
||||
"no-mcp-servers": "Nessun server MCP",
|
||||
"environmental-variables-required": "Variabili d'ambiente richieste",
|
||||
"load-failed": "Caricamento fallito",
|
||||
"save-failed": "Salvataggio fallito",
|
||||
"invalid-json": "JSON non valido",
|
||||
"coming-soon": "Prossimamente",
|
||||
"uninstall": "Disinstalla",
|
||||
"install": "Installa",
|
||||
"add-your-agent": "Aggiungi il tuo Agente",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Aggiungi un server MCP locale fornendo una configurazione JSON valida.",
|
||||
"learn-more": "Scopri di più",
|
||||
"installing": "Installazione in corso...",
|
||||
"edit-mcp-config": "Modifica configurazione MCP",
|
||||
"name": "Nome",
|
||||
"description": "Descrizione",
|
||||
"command": "Comando",
|
||||
"args-one-per-line": "Argomenti (uno per riga)",
|
||||
"cancel": "Annulla",
|
||||
"save": "Salva",
|
||||
"confirm-delete": "Conferma eliminazione",
|
||||
"are-you-sure-you-want-to-delete": "Sei sicuro di voler eliminare",
|
||||
"deleting": "Eliminazione in corso...",
|
||||
"delete": "Elimina",
|
||||
"configure {name} Toolkit": "Configura Toolkit {{name}}",
|
||||
"get-it-from": "Ottienilo da",
|
||||
"google-custom-search-api": "API di ricerca personalizzata Google",
|
||||
"google-cloud-console": "Console Google Cloud",
|
||||
"connect": "Connetti",
|
||||
"setting": "Impostazione",
|
||||
"all": "Tutto",
|
||||
"mcp-market": "Mercato MCP",
|
||||
"no-mcp-services": "Nessun servizio MCP",
|
||||
"loading-more": "Caricamento altro...",
|
||||
"no-more-mcp-servers": "Nessun altro server MCP",
|
||||
"search-mcp": "Cerca MCP",
|
||||
"installed": "Installato",
|
||||
"worker-name-cannot-be-empty": "Il nome del worker non può essere vuoto",
|
||||
"worker-name-already-exists": "Il nome del worker esiste già",
|
||||
"warning-google-search-not-configured": "Avviso: Google Search non configurato",
|
||||
"search-functionality-may-be-limited-without-google-api": "La funzionalità di ricerca può essere limitata senza la chiave API Google e l'ID del motore di ricerca. Puoi configurare questi nelle impostazioni MCP e Strumenti.",
|
||||
"search-engine": "Motore di ricerca",
|
||||
"allow-agent-to-take-screenshots": "Consenti all'Agente di fare screenshot",
|
||||
"allow-agent-to-take-screenshots-description": "Consenti all'agente di acquisire screenshot del tuo schermo del computer. Questo può essere utilizzato per supporto, diagnostica o scopi di monitoraggio. Gli screenshot possono includere informazioni personali visibili, quindi abilita con cautela.",
|
||||
"allow-agent-to-access-local-software": "Consenti all'Agente di accedere al software locale",
|
||||
"allow-agent-to-access-local-software-description": "Concedi all'agente il permesso di interagire e utilizzare software installato sulla tua macchina locale. Questo può essere necessario per la risoluzione dei problemi, l'esecuzione di diagnostica o l'esecuzione di attività specifiche.",
|
||||
"allow-agent-to-access-your-address": "Consenti all'Agente di accedere al tuo indirizzo",
|
||||
"allow-agent-to-access-your-address-description": "Autorizza l'agente a visualizzare e utilizzare i dettagli della tua posizione o indirizzo. Questo può essere richiesto per servizi basati sulla posizione o supporto personalizzato.",
|
||||
"password-storage": "Archiviazione password",
|
||||
"password-storage-description": "Determina come vengono gestite e archiviate le password. Puoi scegliere di archiviare le password in modo sicuro sul dispositivo o all'interno dell'applicazione, o scegliere di inserirle manualmente ogni volta. Tutte le password archiviate sono crittografate.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP installato con successo",
|
||||
"failed-to-install-notion-mcp": "Impossibile installare Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar installato con successo",
|
||||
"failed-to-install-google-calendar": "Impossibile installare Google Calendar",
|
||||
"notion-workspace-integration": "Integrazione workspace Notion per leggere e gestire le pagine Notion",
|
||||
"google-calendar-integration": "Integrazione Google Calendar per gestire eventi e programmi",
|
||||
"mcp-server-already-exists": "Il server MCP \"{{name}}\" esiste già",
|
||||
"google-search": "Google Search",
|
||||
"select-default-search-engine": "Seleziona motore di ricerca predefinito",
|
||||
"your-own-mcps": "I tuoi MCP",
|
||||
"get-google-search-api": "Ottieni Google Search API",
|
||||
"get-exa-api": "Ottieni Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Integrazioni Motore di Ricerca",
|
||||
"configured": "Configurato",
|
||||
"incomplete": "Incompleto",
|
||||
"not-configured": "Non Configurato",
|
||||
"saving": "Salvataggio...",
|
||||
"save-changes": "Salva Modifiche",
|
||||
"enable": "Abilita",
|
||||
"search": "Cerca",
|
||||
"test-connection": "Testa Connessione",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Le tue chiavi API sono memorizzate in modo sicuro e non vengono mai condivise esternamente.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Questo servizio è pubblico e non richiede credenziali.",
|
||||
"this-service-does-not-require-an-api-key": "Questo servizio non richiede una chiave API.",
|
||||
"connection-test-successful": "Test di connessione riuscito!",
|
||||
"connection-test-failed": "Test di connessione fallito.",
|
||||
"configuration-saved-successfully": "Configurazione salvata con successo!",
|
||||
"failed-to-save-configuration": "Impossibile salvare la configurazione.",
|
||||
"recommended": "Consigliato",
|
||||
"reset": "Reimposta",
|
||||
"reset-success": "Reimpostazione riuscita!",
|
||||
"reset-failed": "Reimpostazione non riuscita!",
|
||||
|
||||
"network-proxy": "Proxy di rete",
|
||||
"network-proxy-description": "Configura un server proxy per le richieste di rete. Utile se devi accedere ad API esterne tramite un proxy.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Configurazione proxy salvata. Riavvia l'app per applicare le modifiche.",
|
||||
"proxy-save-failed": "Impossibile salvare la configurazione del proxy.",
|
||||
"proxy-invalid-url": "URL proxy non valido. Deve iniziare con http://, https://, socks4:// o socks5://.",
|
||||
"proxy-restart-hint": "Riavvio necessario per applicare le modifiche del proxy."
|
||||
"network-proxy": "Proxy di rete",
|
||||
"network-proxy-description": "Configura un server proxy per le richieste di rete. Utile se devi accedere ad API esterne tramite un proxy.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Configurazione proxy salvata. Riavvia l'app per applicare le modifiche.",
|
||||
"proxy-save-failed": "Impossibile salvare la configurazione del proxy.",
|
||||
"proxy-invalid-url": "URL proxy non valido. Deve iniziare con http://, https://, socks4:// o socks5://.",
|
||||
"proxy-restart-hint": "Riavvio necessario per applicare le modifiche del proxy."
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,171 +1,173 @@
|
|||
{
|
||||
"settings": "設定",
|
||||
"general": "一般",
|
||||
"privacy": "プライバシー",
|
||||
"models": "モデル",
|
||||
"mcp": "MCP & ツール",
|
||||
"settings": "設定",
|
||||
"general": "一般",
|
||||
"privacy": "プライバシー",
|
||||
"models": "モデル",
|
||||
"mcp": "MCP & ツール",
|
||||
|
||||
"account": "アカウント",
|
||||
"you-are-currently-signed-in-with": "{{email}}で現在サインインしています",
|
||||
"manage": "管理",
|
||||
"log-out": "ログアウト",
|
||||
"language": "言語",
|
||||
"select-language": "言語を選択",
|
||||
"system-default": "システムデフォルト",
|
||||
"appearance": "外観",
|
||||
"light": "ライト",
|
||||
"transparent": "透明",
|
||||
"account": "アカウント",
|
||||
"you-are-currently-signed-in-with": "{{email}}で現在サインインしています",
|
||||
"manage": "管理",
|
||||
"log-out": "ログアウト",
|
||||
"language": "言語",
|
||||
"select-language": "言語を選択",
|
||||
"system-default": "システムデフォルト",
|
||||
"appearance": "外観",
|
||||
"dark": "ダーク",
|
||||
"light": "ライト",
|
||||
"transparent": "透明",
|
||||
|
||||
"data-privacy": "データプライバシー",
|
||||
"data-privacy-description": "Eigentはプライバシーを確保するためにローカルファーストの原則に基づいて構築されています。デフォルトでは、データはお客様のデバイスに残ります。クラウド機能はオプションであり、機能するために必要な最小限のデータのみを使用します。詳細については、当社の",
|
||||
"privacy-policy": "プライバシーポリシー",
|
||||
"how-we-handle-your-data": "データの取り扱い方法",
|
||||
"how-we-handle-your-data-line-1": "タスクを実行するために必要な最小限のデータのみを使用します",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigentは、UI要素を分析し、テキストを読み取り、次のアクションを決定するために、お客様が行うのと同じようにスクリーンショットをキャプチャする場合があります。",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigentは、指定したローカルソフトウェアおよびファイルにアクセスするために、マウスとキーボードを使用する場合があります。",
|
||||
"how-we-handle-your-data-line-1-line-3": "有効にしたAIモデルプロバイダーまたはサードパーティ統合に送信されるのは、タスクの最小限のデータのみです。データ保持はありません。",
|
||||
"how-we-handle-your-data-line-2": "タスクファイル、出力、およびスクリーンショットは、ローカルの指定されたタスクフォルダに残ります。",
|
||||
"how-we-handle-your-data-line-3": "認証情報はローカルに保存され、暗号化され、承認されたステップにのみ使用されます。",
|
||||
"how-we-handle-your-data-line-4": "お客様の明示的な同意なしに、お客様のデータが当社のAIモデルのトレーニングに使用されることはありません。",
|
||||
"how-we-handle-your-data-line-5": "お客様のデータを第三者に販売することはありません。",
|
||||
"enable-privacy-permissions-settings": "プライバシー権限設定を有効にする",
|
||||
"enable-privacy-permissions-settings-description": "これをオンにすることで、タスクデータの収集、処理、保護方法に関するプライバシーポリシーを読み、同意したことを認めます。",
|
||||
"data-privacy": "データプライバシー",
|
||||
"data-privacy-description": "Eigentはプライバシーを確保するためにローカルファーストの原則に基づいて構築されています。デフォルトでは、データはお客様のデバイスに残ります。クラウド機能はオプションであり、機能するために必要な最小限のデータのみを使用します。詳細については、当社の",
|
||||
"privacy-policy": "プライバシーポリシー",
|
||||
"how-we-handle-your-data": "データの取り扱い方法",
|
||||
"how-we-handle-your-data-line-1": "タスクを実行するために必要な最小限のデータのみを使用します",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigentは、UI要素を分析し、テキストを読み取り、次のアクションを決定するために、お客様が行うのと同じようにスクリーンショットをキャプチャする場合があります。",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigentは、指定したローカルソフトウェアおよびファイルにアクセスするために、マウスとキーボードを使用する場合があります。",
|
||||
"how-we-handle-your-data-line-1-line-3": "有効にしたAIモデルプロバイダーまたはサードパーティ統合に送信されるのは、タスクの最小限のデータのみです。データ保持はありません。",
|
||||
"how-we-handle-your-data-line-2":"タスクファイル、出力、およびスクリーンショットは、ローカルの指定されたタスクフォルダに残ります。",
|
||||
"how-we-handle-your-data-line-3":"認証情報はローカルに保存され、暗号化され、承認されたステップにのみ使用されます。",
|
||||
"how-we-handle-your-data-line-4":"お客様の明示的な同意なしに、お客様のデータが当社のAIモデルのトレーニングに使用されることはありません。",
|
||||
"how-we-handle-your-data-line-5":"お客様のデータを第三者に販売することはありません。",
|
||||
"enable-privacy-permissions-settings": "プライバシー権限設定を有効にする",
|
||||
"enable-privacy-permissions-settings-description": "これをオンにすることで、タスクデータの収集、処理、保護方法に関するプライバシーポリシーを読み、同意したことを認めます。",
|
||||
|
||||
"api-key-can-not-be-empty": "APIキーは空にできません!",
|
||||
"api-host-can-not-be-empty": "APIホストは空にできません!",
|
||||
"model-type-can-not-be-empty": "モデルタイプは空にできません!",
|
||||
"validate-success": "検証成功",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "モデルは関数呼び出しをサポートしていることが確認されました。これはEigentの使用に必要です。",
|
||||
"validate-failed": "検証失敗",
|
||||
"copy": "コピー",
|
||||
"copied-to-clipboard": "クリップボードにコピーしました",
|
||||
"endpoint-url-can-not-be-empty": "エンドポイントURLは空にできません!",
|
||||
"verification-failed-please-check-endpoint-url": "検証に失敗しました。エンドポイントURLを確認してください",
|
||||
"eigent-cloud-version": "Eigentクラウドバージョン",
|
||||
"you-are-currently-subscribed-to-the": "現在、次のプランに登録しています",
|
||||
"discover-more-about-our": "当社の",
|
||||
"pricing-options": "料金オプション",
|
||||
"credits": "クレジット",
|
||||
"select-model-type": "モデルタイプを選択",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: 低コスト、高速な応答ですが、出力品質は低下します。",
|
||||
"gpt-4.1": "GPT-4.1: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"gpt-5": "GPT-5: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"gpt-5-mini": "GPT-5 mini: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"gpt-5-nano": "GPT-5 nano: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"custom-model": "カスタムモデル",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "独自のAPIキーを使用するか、ローカルモデルを設定します。",
|
||||
"verify": "検証",
|
||||
"local-model": "ローカルモデル",
|
||||
"model-platform": "モデルプラットフォーム",
|
||||
"model-endpoint-url": "モデルエンドポイントURL",
|
||||
"model-type": "モデルタイプ",
|
||||
"enter-your-local-model-type": "ローカルモデルタイプを入力",
|
||||
"you-are-on-selft-host-mode": "セルフホストモードです",
|
||||
"you-are-using-self-hosted-mode": "セルフホストモードを使用しています。この製品を最大限に活用するには、「MCP & ツール」にGoogle検索キーを入力して、Eigentが正しく機能するようにしてください。",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Google検索キーは、正確な検索結果を提供するために不可欠です。Exa検索キーはオプションですが、パフォーマンス向上のために強く推奨されます。",
|
||||
"close": "閉じる",
|
||||
"enter-your-api-key": "APIキーを入力",
|
||||
"key": "キー",
|
||||
"enter-your-api-host": "APIホストを入力",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "モデルタイプを入力",
|
||||
"verifying": "検証中...",
|
||||
|
||||
"api-key-can-not-be-empty": "APIキーは空にできません!",
|
||||
"api-host-can-not-be-empty": "APIホストは空にできません!",
|
||||
"model-type-can-not-be-empty": "モデルタイプは空にできません!",
|
||||
"validate-success": "検証成功",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "モデルは関数呼び出しをサポートしていることが確認されました。これはEigentの使用に必要です。",
|
||||
"validate-failed": "検証失敗",
|
||||
"copy": "コピー",
|
||||
"copied-to-clipboard": "クリップボードにコピーしました",
|
||||
"endpoint-url-can-not-be-empty": "エンドポイントURLは空にできません!",
|
||||
"verification-failed-please-check-endpoint-url": "検証に失敗しました。エンドポイントURLを確認してください",
|
||||
"eigent-cloud-version": "Eigentクラウドバージョン",
|
||||
"you-are-currently-subscribed-to-the": "現在、次のプランに登録しています",
|
||||
"discover-more-about-our": "当社の",
|
||||
"pricing-options": "料金オプション",
|
||||
"credits": "クレジット",
|
||||
"select-model-type": "モデルタイプを選択",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: 低コスト、高速な応答ですが、出力品質は低下します。",
|
||||
"gpt-4.1": "GPT-4.1: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"gpt-5": "GPT-5: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"gpt-5-mini": "GPT-5 mini: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"gpt-5-nano": "GPT-5 nano: 高コスト、低速な応答ですが、優れた品質と推論能力を備えています。",
|
||||
"custom-model": "カスタムモデル",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "独自のAPIキーを使用するか、ローカルモデルを設定します。",
|
||||
"verify": "検証",
|
||||
"local-model": "ローカルモデル",
|
||||
"model-platform": "モデルプラットフォーム",
|
||||
"model-endpoint-url": "モデルエンドポイントURL",
|
||||
"model-type": "モデルタイプ",
|
||||
"enter-your-local-model-type": "ローカルモデルタイプを入力",
|
||||
"you-are-on-selft-host-mode": "セルフホストモードです",
|
||||
"you-are-using-self-hosted-mode": "セルフホストモードを使用しています。この製品を最大限に活用するには、「MCP & ツール」にGoogle検索キーを入力して、Eigentが正しく機能するようにしてください。",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Google検索キーは、正確な検索結果を提供するために不可欠です。Exa検索キーはオプションですが、パフォーマンス向上のために強く推奨されます。",
|
||||
"close": "閉じる",
|
||||
"enter-your-api-key": "APIキーを入力",
|
||||
"key": "キー",
|
||||
"enter-your-api-host": "APIホストを入力",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "モデルタイプを入力",
|
||||
"verifying": "検証中...",
|
||||
|
||||
"mcp-and-tools": "MCP & ツール",
|
||||
"add-mcp-server": "MCPサーバーを追加",
|
||||
"market": "マーケット",
|
||||
"tools": "ツール",
|
||||
"added-external-servers": "追加された外部サーバー",
|
||||
"loading": "読み込み中...",
|
||||
"no-mcp-servers": "MCPサーバーはありません",
|
||||
"environmental-variables-required": "環境変数が必要です",
|
||||
"load-failed": "読み込みに失敗しました",
|
||||
"save-failed": "保存に失敗しました",
|
||||
"invalid-json": "無効なJSON",
|
||||
"coming-soon": "近日公開",
|
||||
"uninstall": "アンインストール",
|
||||
"install": "インストール",
|
||||
"add-your-agent": "エージェントを追加",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "有効なJSON構成を提供して、ローカルMCPサーバーを追加します。",
|
||||
"learn-more": "詳細はこちら",
|
||||
"installing": "インストール中...",
|
||||
"edit-mcp-config": "MCP構成を編集",
|
||||
"name": "名前",
|
||||
"description": "説明",
|
||||
"command": "コマンド",
|
||||
"args-one-per-line": "引数(1行に1つ)",
|
||||
"cancel": "キャンセル",
|
||||
"save": "保存",
|
||||
"confirm-delete": "削除を確認",
|
||||
"are-you-sure-you-want-to-delete": "本当に削除しますか",
|
||||
"deleting": "削除中...",
|
||||
"delete": "削除",
|
||||
"configure {name} Toolkit": "{name}ツールキットを構成",
|
||||
"get-it-from": "から入手",
|
||||
"google-custom-search-api": "Googleカスタム検索API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "接続",
|
||||
"setting": "設定",
|
||||
"all": "すべて",
|
||||
"mcp-market": "MCPマーケット",
|
||||
"no-mcp-services": "MCPサービスはありません",
|
||||
"loading-more": "さらに読み込み中...",
|
||||
"no-more-mcp-servers": "MCPサーバーはこれ以上ありません",
|
||||
"search-mcp": "MCPを検索",
|
||||
"installed": "インストール済み",
|
||||
"worker-name-cannot-be-empty": "ワーカー名は空にできません",
|
||||
"worker-name-already-exists": "ワーカー名は既に存在します",
|
||||
"warning-google-search-not-configured": "警告:Google検索が設定されていません",
|
||||
"search-functionality-may-be-limited-without-google-api": "Google APIキーと検索エンジンIDがないと、検索機能が制限される可能性があります。MCP & ツール設定でこれらを設定できます。",
|
||||
"search-engine": "検索エンジン",
|
||||
"allow-agent-to-take-screenshots": "エージェントにスクリーンショットを撮ることを許可",
|
||||
"allow-agent-to-take-screenshots-description": "エージェントがコンピューター画面のスクリーンショットをキャプチャすることを許可します。これは、サポート、診断、または監視目的で使用できます。スクリーンショットには個人情報が含まれる可能性があるため、注意して有効にしてください。",
|
||||
"allow-agent-to-access-local-software": "エージェントにローカルソフトウェアへのアクセスを許可",
|
||||
"allow-agent-to-access-local-software-description": "エージェントに、ローカルマシンにインストールされたソフトウェアと対話し、使用する権限を付与します。これは、トラブルシューティング、診断の実行、または特定のタスクの実行に必要になる場合があります。",
|
||||
"allow-agent-to-access-your-address": "エージェントにあなたの住所へのアクセスを許可",
|
||||
"allow-agent-to-access-your-address-description": "エージェントがあなたの場所や住所の詳細を表示し、使用することを許可します。これは、位置ベースのサービスやパーソナライズされたサポートに必要になる場合があります。",
|
||||
"password-storage": "パスワードストレージ",
|
||||
"password-storage-description": "パスワードの処理と保存方法を決定します。デバイス上またはアプリケーション内でパスワードを安全に保存するか、毎回手動で入力するかを選択できます。保存されたすべてのパスワードは暗号化されています。",
|
||||
"notion-mcp-installed-successfully": "Notion MCPが正常にインストールされました",
|
||||
"failed-to-install-notion-mcp": "Notion MCPのインストールに失敗しました",
|
||||
"google-calendar-installed-successfully": "Googleカレンダーが正常にインストールされました",
|
||||
"failed-to-install-google-calendar": "Googleカレンダーのインストールに失敗しました",
|
||||
"notion-workspace-integration": "Notionページの読み取りと管理のためのNotionワークスペース統合",
|
||||
"google-calendar-integration": "イベントとスケジュールの管理のためのGoogleカレンダー統合",
|
||||
"mcp-server-already-exists": "MCPサーバー「{{name}}」は既に存在します",
|
||||
"google-search": "Google検索",
|
||||
"select-default-search-engine": "デフォルトの検索エンジンを選択",
|
||||
"your-own-mcps": "あなた自身のMCP",
|
||||
"get-google-search-api": "Google検索APIを取得",
|
||||
"get-exa-api": "Exa APIを取得",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "検索エンジン統合",
|
||||
"configured": "設定済み",
|
||||
"incomplete": "不完全",
|
||||
"not-configured": "未設定",
|
||||
"saving": "保存中...",
|
||||
"save-changes": "変更を保存",
|
||||
"enable": "有効化",
|
||||
"search": "検索",
|
||||
"test-connection": "接続テスト",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "あなたのAPIキーは安全に保存され、外部と共有されることはありません。",
|
||||
"this-service-is-public-and-does-not-require-credentials": "このサービスは公開されており、認証情報は必要ありません。",
|
||||
"this-service-does-not-require-an-api-key": "このサービスはAPIキーを必要としません。",
|
||||
"connection-test-successful": "接続テストが成功しました!",
|
||||
"connection-test-failed": "接続テストが失敗しました。",
|
||||
"configuration-saved-successfully": "設定が正常に保存されました!",
|
||||
"failed-to-save-configuration": "設定の保存に失敗しました。",
|
||||
"mcp-and-tools": "MCP & ツール",
|
||||
"add-mcp-server": "MCPサーバーを追加",
|
||||
"market": "マーケット",
|
||||
"tools": "ツール",
|
||||
"added-external-servers": "追加された外部サーバー",
|
||||
"loading": "読み込み中...",
|
||||
"no-mcp-servers": "MCPサーバーはありません",
|
||||
"environmental-variables-required": "環境変数が必要です",
|
||||
"load-failed": "読み込みに失敗しました",
|
||||
"save-failed": "保存に失敗しました",
|
||||
"invalid-json": "無効なJSON",
|
||||
"coming-soon": "近日公開",
|
||||
"uninstall": "アンインストール",
|
||||
"install": "インストール",
|
||||
"add-your-agent": "エージェントを追加",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "有効なJSON構成を提供して、ローカルMCPサーバーを追加します。",
|
||||
"learn-more": "詳細はこちら",
|
||||
"installing": "インストール中...",
|
||||
"edit-mcp-config": "MCP構成を編集",
|
||||
"name": "名前",
|
||||
"description": "説明",
|
||||
"command": "コマンド",
|
||||
"args-one-per-line": "引数(1行に1つ)",
|
||||
"cancel": "キャンセル",
|
||||
"save": "保存",
|
||||
"confirm-delete": "削除を確認",
|
||||
"are-you-sure-you-want-to-delete": "本当に削除しますか",
|
||||
"deleting": "削除中...",
|
||||
"delete": "削除",
|
||||
"configure {name} Toolkit": "{name}ツールキットを構成",
|
||||
"get-it-from": "から入手",
|
||||
"google-custom-search-api": "Googleカスタム検索API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "接続",
|
||||
"setting": "設定",
|
||||
"all": "すべて",
|
||||
"mcp-market": "MCPマーケット",
|
||||
"no-mcp-services": "MCPサービスはありません",
|
||||
"loading-more": "さらに読み込み中...",
|
||||
"no-more-mcp-servers": "MCPサーバーはこれ以上ありません",
|
||||
"search-mcp": "MCPを検索",
|
||||
"installed": "インストール済み",
|
||||
"worker-name-cannot-be-empty": "ワーカー名は空にできません",
|
||||
"worker-name-already-exists": "ワーカー名は既に存在します",
|
||||
"warning-google-search-not-configured": "警告:Google検索が設定されていません",
|
||||
"search-functionality-may-be-limited-without-google-api": "Google APIキーと検索エンジンIDがないと、検索機能が制限される可能性があります。MCP & ツール設定でこれらを設定できます。",
|
||||
"search-engine": "検索エンジン",
|
||||
"allow-agent-to-take-screenshots": "エージェントにスクリーンショットを撮ることを許可",
|
||||
"allow-agent-to-take-screenshots-description": "エージェントがコンピューター画面のスクリーンショットをキャプチャすることを許可します。これは、サポート、診断、または監視目的で使用できます。スクリーンショットには個人情報が含まれる可能性があるため、注意して有効にしてください。",
|
||||
"allow-agent-to-access-local-software": "エージェントにローカルソフトウェアへのアクセスを許可",
|
||||
"allow-agent-to-access-local-software-description": "エージェントに、ローカルマシンにインストールされたソフトウェアと対話し、使用する権限を付与します。これは、トラブルシューティング、診断の実行、または特定のタスクの実行に必要になる場合があります。",
|
||||
"allow-agent-to-access-your-address": "エージェントにあなたの住所へのアクセスを許可",
|
||||
"allow-agent-to-access-your-address-description": "エージェントがあなたの場所や住所の詳細を表示し、使用することを許可します。これは、位置ベースのサービスやパーソナライズされたサポートに必要になる場合があります。",
|
||||
"password-storage": "パスワードストレージ",
|
||||
"password-storage-description": "パスワードの処理と保存方法を決定します。デバイス上またはアプリケーション内でパスワードを安全に保存するか、毎回手動で入力するかを選択できます。保存されたすべてのパスワードは暗号化されています。",
|
||||
"notion-mcp-installed-successfully": "Notion MCPが正常にインストールされました",
|
||||
"failed-to-install-notion-mcp": "Notion MCPのインストールに失敗しました",
|
||||
"google-calendar-installed-successfully": "Googleカレンダーが正常にインストールされました",
|
||||
"failed-to-install-google-calendar": "Googleカレンダーのインストールに失敗しました",
|
||||
"notion-workspace-integration": "Notionページの読み取りと管理のためのNotionワークスペース統合",
|
||||
"google-calendar-integration": "イベントとスケジュールの管理のためのGoogleカレンダー統合",
|
||||
"mcp-server-already-exists": "MCPサーバー「{{name}}」は既に存在します",
|
||||
"google-search": "Google検索",
|
||||
"select-default-search-engine": "デフォルトの検索エンジンを選択",
|
||||
"your-own-mcps": "あなた自身のMCP",
|
||||
"get-google-search-api": "Google検索APIを取得",
|
||||
"get-exa-api": "Exa APIを取得",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "検索エンジン統合",
|
||||
"configured": "設定済み",
|
||||
"incomplete": "不完全",
|
||||
"not-configured": "未設定",
|
||||
"saving": "保存中...",
|
||||
"save-changes": "変更を保存",
|
||||
"enable": "有効化",
|
||||
"search": "検索",
|
||||
"test-connection": "接続テスト",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "あなたのAPIキーは安全に保存され、外部と共有されることはありません。",
|
||||
"this-service-is-public-and-does-not-require-credentials": "このサービスは公開されており、認証情報は必要ありません。",
|
||||
"this-service-does-not-require-an-api-key": "このサービスはAPIキーを必要としません。",
|
||||
"connection-test-successful": "接続テストが成功しました!",
|
||||
"connection-test-failed": "接続テストが失敗しました。",
|
||||
"configuration-saved-successfully": "設定が正常に保存されました!",
|
||||
"failed-to-save-configuration": "設定の保存に失敗しました。",
|
||||
"recommended": "おすすめ",
|
||||
"reset": "リセット",
|
||||
"reset-success": "リセットに成功しました!",
|
||||
"reset-failed": "リセットに失敗しました!",
|
||||
|
||||
"network-proxy": "ネットワークプロキシ",
|
||||
"network-proxy-description": "ネットワークリクエスト用のプロキシサーバーを設定します。プロキシ経由で外部APIにアクセスする必要がある場合に便利です。",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "プロキシ設定が保存されました。変更を適用するにはアプリを再起動してください。",
|
||||
"proxy-save-failed": "プロキシ設定の保存に失敗しました。",
|
||||
"proxy-invalid-url": "無効なプロキシURLです。http://、https://、socks4://、またはsocks5://で始まる必要があります。",
|
||||
"proxy-restart-hint": "プロキシの変更を適用するには再起動が必要です。"
|
||||
"network-proxy": "ネットワークプロキシ",
|
||||
"network-proxy-description": "ネットワークリクエスト用のプロキシサーバーを設定します。プロキシ経由で外部APIにアクセスする必要がある場合に便利です。",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "プロキシ設定が保存されました。変更を適用するにはアプリを再起動してください。",
|
||||
"proxy-save-failed": "プロキシ設定の保存に失敗しました。",
|
||||
"proxy-invalid-url": "無効なプロキシURLです。http://、https://、socks4://、またはsocks5://で始まる必要があります。",
|
||||
"proxy-restart-hint": "プロキシの変更を適用するには再起動が必要です。"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,171 +1,172 @@
|
|||
{
|
||||
"settings": "설정",
|
||||
"general": "일반",
|
||||
"privacy": "개인정보",
|
||||
"models": "모델",
|
||||
"mcp": "MCP 및 도구",
|
||||
"settings": "설정",
|
||||
"general": "일반",
|
||||
"privacy": "개인정보",
|
||||
"models": "모델",
|
||||
"mcp": "MCP 및 도구",
|
||||
|
||||
"account": "계정",
|
||||
"you-are-currently-signed-in-with": "{{email}} 계정으로 로그인되어 있습니다.",
|
||||
"manage": "관리",
|
||||
"log-out": "로그아웃",
|
||||
"language": "언어",
|
||||
"select-language": "언어 선택",
|
||||
"system-default": "시스템 기본값",
|
||||
"appearance": "테마",
|
||||
"light": "라이트",
|
||||
"transparent": "투명",
|
||||
"account": "계정",
|
||||
"you-are-currently-signed-in-with": "{{email}} 계정으로 로그인되어 있습니다.",
|
||||
"manage": "관리",
|
||||
"log-out": "로그아웃",
|
||||
"language": "언어",
|
||||
"select-language": "언어 선택",
|
||||
"system-default": "시스템 기본값",
|
||||
"appearance": "테마",
|
||||
"dark": "다크",
|
||||
"light": "라이트",
|
||||
"transparent": "투명",
|
||||
|
||||
"data-privacy": "데이터 개인정보 보호",
|
||||
"data-privacy-description": "Eigent는 개인정보 보호를 위해 로컬 우선 원칙을 기반으로 구축되었습니다. 기본적으로 데이터는 사용자의 기기에 남아 있습니다. 클라우드 기능은 선택 사항이며 작동에 필요한 최소한의 데이터만 사용합니다. 자세한 내용은 당사의",
|
||||
"privacy-policy": "개인정보 처리방침",
|
||||
"how-we-handle-your-data": "데이터 처리 방식",
|
||||
"how-we-handle-your-data-line-1": "작업 실행에 필요한 필수 데이터만 사용합니다",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent는 UI 요소를 분석하고 텍스트를 읽고 다음 작업을 결정하기 위해 스크린샷을 캡처할 수 있습니다. 사용자가 하는 방식과 같습니다.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent는 사용자가 지정한 로컬 소프트웨어 및 파일에 액세스하기 위해 마우스와 키보드를 사용할 수 있습니다.",
|
||||
"how-we-handle-your-data-line-1-line-3": "AI 모델 제공업체 또는 사용자가 활성화한 타사 통합에 전송되는 데이터는 최소한의 작업 데이터뿐이며, 데이터 보존은 전혀 없습니다.",
|
||||
"how-we-handle-your-data-line-2": "작업 파일, 출력 및 스크린샷은 로컬의 지정된 작업 폴더에 유지됩니다.",
|
||||
"how-we-handle-your-data-line-3": "자격 증명은 로컬에 암호화되어 저장되며 승인된 단계에만 사용됩니다.",
|
||||
"how-we-handle-your-data-line-4": "귀하의 명시적인 동의 없이 귀하의 데이터는 당사 AI 모델을 학습시키는 데 사용되지 않습니다.",
|
||||
"how-we-handle-your-data-line-5": "귀하의 데이터를 제3자에게 판매하지 않습니다.",
|
||||
"enable-privacy-permissions-settings": "개인정보 보호 설정 활성화",
|
||||
"enable-privacy-permissions-settings-description": "이 설정을 켜면 작업 데이터가 수집, 처리 및 보호되는 방식에 관한 개인정보 처리방침을 읽었으며 이에 동의했음을 인정하는 것입니다.",
|
||||
"data-privacy": "데이터 개인정보 보호",
|
||||
"data-privacy-description": "Eigent는 개인정보 보호를 위해 로컬 우선 원칙을 기반으로 구축되었습니다. 기본적으로 데이터는 사용자의 기기에 남아 있습니다. 클라우드 기능은 선택 사항이며 작동에 필요한 최소한의 데이터만 사용합니다. 자세한 내용은 당사의",
|
||||
"privacy-policy": "개인정보 처리방침",
|
||||
"how-we-handle-your-data": "데이터 처리 방식",
|
||||
"how-we-handle-your-data-line-1": "작업 실행에 필요한 필수 데이터만 사용합니다",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent는 UI 요소를 분석하고 텍스트를 읽고 다음 작업을 결정하기 위해 스크린샷을 캡처할 수 있습니다. 사용자가 하는 방식과 같습니다.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent는 사용자가 지정한 로컬 소프트웨어 및 파일에 액세스하기 위해 마우스와 키보드를 사용할 수 있습니다.",
|
||||
"how-we-handle-your-data-line-1-line-3": "AI 모델 제공업체 또는 사용자가 활성화한 타사 통합에 전송되는 데이터는 최소한의 작업 데이터뿐이며, 데이터 보존은 전혀 없습니다.",
|
||||
"how-we-handle-your-data-line-2":"작업 파일, 출력 및 스크린샷은 로컬의 지정된 작업 폴더에 유지됩니다.",
|
||||
"how-we-handle-your-data-line-3":"자격 증명은 로컬에 암호화되어 저장되며 승인된 단계에만 사용됩니다.",
|
||||
"how-we-handle-your-data-line-4":"귀하의 명시적인 동의 없이 귀하의 데이터는 당사 AI 모델을 학습시키는 데 사용되지 않습니다.",
|
||||
"how-we-handle-your-data-line-5":"귀하의 데이터를 제3자에게 판매하지 않습니다.",
|
||||
"enable-privacy-permissions-settings": "개인정보 보호 설정 활성화",
|
||||
"enable-privacy-permissions-settings-description": "이 설정을 켜면 작업 데이터가 수집, 처리 및 보호되는 방식에 관한 개인정보 처리방침을 읽었으며 이에 동의했음을 인정하는 것입니다.",
|
||||
|
||||
"api-key-can-not-be-empty": "API 키는 비워둘 수 없습니다!",
|
||||
"api-host-can-not-be-empty": "API 호스트는 비워둘 수 없습니다!",
|
||||
"model-type-can-not-be-empty": "모델 유형은 비워둘 수 없습니다!",
|
||||
"validate-success": "유효성 검사 성공",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "모델이 함수 호출을 지원하는 것으로 확인되었습니다. 이는 Eigent를 사용하는 데 필요합니다.",
|
||||
"validate-failed": "유효성 검사 실패",
|
||||
"copy": "복사",
|
||||
"copied-to-clipboard": "클립보드에 복사됨",
|
||||
"endpoint-url-can-not-be-empty": "엔드포인트 URL은 비워둘 수 없습니다!",
|
||||
"verification-failed-please-check-endpoint-url": "확인 실패, 엔드포인트 URL을 확인하세요",
|
||||
"eigent-cloud-version": "Eigent 클라우드 버전",
|
||||
"you-are-currently-subscribed-to-the": "현재 다음 플랜을 구독 중입니다.",
|
||||
"discover-more-about-our": "당사의",
|
||||
"pricing-options": "가격 옵션",
|
||||
"credits": "크레딧",
|
||||
"select-model-type": "모델 유형 선택",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: 낮은 비용, 빠른 응답, 하지만 품질 감소.",
|
||||
"gpt-4.1": "GPT-4.1: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"gpt-5": "GPT-5: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"gpt-5-mini": "GPT-5 mini: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"gpt-5-nano": "GPT-5 nano: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"custom-model": "사용자 정의 모델",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "자신의 API 키를 사용하거나 로컬 모델을 설정하십시오.",
|
||||
"verify": "확인",
|
||||
"local-model": "로컬 모델",
|
||||
"model-platform": "모델 플랫폼",
|
||||
"model-endpoint-url": "모델 엔드포인트 URL",
|
||||
"model-type": "모델 유형",
|
||||
"enter-your-local-model-type": "로컬 모델 유형 입력",
|
||||
"you-are-on-selft-host-mode": "셀프 호스트 모드입니다.",
|
||||
"you-are-using-self-hosted-mode": "셀프 호스트 모드를 사용 중입니다. 이 제품의 최적 성능을 얻으려면 \"MCP 및 도구\"에 Google 검색 키를 입력하여 Eigent가 올바르게 작동하도록 하세요.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Google 검색 키는 정확한 검색 결과를 제공하는 데 필수적입니다. Exa 검색 키는 선택 사항이지만 더 나은 성능을 위해 강력히 권장됩니다.",
|
||||
"close": "닫기",
|
||||
"enter-your-api-key": "API 키 입력",
|
||||
"key": "키",
|
||||
"enter-your-api-host": "API 호스트 입력",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "모델 유형 입력",
|
||||
"verifying": "확인 중...",
|
||||
|
||||
"api-key-can-not-be-empty": "API 키는 비워둘 수 없습니다!",
|
||||
"api-host-can-not-be-empty": "API 호스트는 비워둘 수 없습니다!",
|
||||
"model-type-can-not-be-empty": "모델 유형은 비워둘 수 없습니다!",
|
||||
"validate-success": "유효성 검사 성공",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "모델이 함수 호출을 지원하는 것으로 확인되었습니다. 이는 Eigent를 사용하는 데 필요합니다.",
|
||||
"validate-failed": "유효성 검사 실패",
|
||||
"copy": "복사",
|
||||
"copied-to-clipboard": "클립보드에 복사됨",
|
||||
"endpoint-url-can-not-be-empty": "엔드포인트 URL은 비워둘 수 없습니다!",
|
||||
"verification-failed-please-check-endpoint-url": "확인 실패, 엔드포인트 URL을 확인하세요",
|
||||
"eigent-cloud-version": "Eigent 클라우드 버전",
|
||||
"you-are-currently-subscribed-to-the": "현재 다음 플랜을 구독 중입니다.",
|
||||
"discover-more-about-our": "당사의",
|
||||
"pricing-options": "가격 옵션",
|
||||
"credits": "크레딧",
|
||||
"select-model-type": "모델 유형 선택",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: 낮은 비용, 빠른 응답, 하지만 품질 감소.",
|
||||
"gpt-4.1": "GPT-4.1: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"gpt-5": "GPT-5: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"gpt-5-mini": "GPT-5 mini: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"gpt-5-nano": "GPT-5 nano: 높은 비용, 느린 응답, 하지만 우수한 품질 및 추론 능력.",
|
||||
"custom-model": "사용자 정의 모델",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "자신의 API 키를 사용하거나 로컬 모델을 설정하십시오.",
|
||||
"verify": "확인",
|
||||
"local-model": "로컬 모델",
|
||||
"model-platform": "모델 플랫폼",
|
||||
"model-endpoint-url": "모델 엔드포인트 URL",
|
||||
"model-type": "모델 유형",
|
||||
"enter-your-local-model-type": "로컬 모델 유형 입력",
|
||||
"you-are-on-selft-host-mode": "셀프 호스트 모드입니다.",
|
||||
"you-are-using-self-hosted-mode": "셀프 호스트 모드를 사용 중입니다. 이 제품의 최적 성능을 얻으려면 \"MCP 및 도구\"에 Google 검색 키를 입력하여 Eigent가 올바르게 작동하도록 하세요.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Google 검색 키는 정확한 검색 결과를 제공하는 데 필수적입니다. Exa 검색 키는 선택 사항이지만 더 나은 성능을 위해 강력히 권장됩니다.",
|
||||
"close": "닫기",
|
||||
"enter-your-api-key": "API 키 입력",
|
||||
"key": "키",
|
||||
"enter-your-api-host": "API 호스트 입력",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "모델 유형 입력",
|
||||
"verifying": "확인 중...",
|
||||
|
||||
"mcp-and-tools": "MCP 및 도구",
|
||||
"add-mcp-server": "MCP 서버 추가",
|
||||
"market": "마켓",
|
||||
"tools": "도구",
|
||||
"added-external-servers": "추가된 외부 서버",
|
||||
"loading": "로딩 중...",
|
||||
"no-mcp-servers": "MCP 서버 없음",
|
||||
"environmental-variables-required": "환경 변수 필요",
|
||||
"load-failed": "로드 실패",
|
||||
"save-failed": "저장 실패",
|
||||
"invalid-json": "잘못된 JSON",
|
||||
"coming-soon": "출시 예정",
|
||||
"uninstall": "제거",
|
||||
"install": "설치",
|
||||
"add-your-agent": "에이전트 추가",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "유효한 JSON 구성을 제공하여 로컬 MCP 서버를 추가하세요.",
|
||||
"learn-more": "더 알아보기",
|
||||
"installing": "설치 중...",
|
||||
"edit-mcp-config": "MCP 구성 편집",
|
||||
"name": "이름",
|
||||
"description": "설명",
|
||||
"command": "명령",
|
||||
"args-one-per-line": "인수 (한 줄에 하나씩)",
|
||||
"cancel": "취소",
|
||||
"save": "저장",
|
||||
"confirm-delete": "삭제 확인",
|
||||
"are-you-sure-you-want-to-delete": "정말 삭제하시겠습니까?",
|
||||
"deleting": "삭제 중...",
|
||||
"delete": "삭제",
|
||||
"configure {name} Toolkit": "{{name}} 도구 구성",
|
||||
"get-it-from": "여기서 다운로드",
|
||||
"google-custom-search-api": "Google 맞춤 검색 API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "연결",
|
||||
"setting": "설정",
|
||||
"all": "전체",
|
||||
"mcp-market": "MCP 마켓",
|
||||
"no-mcp-services": "MCP 서비스 없음",
|
||||
"loading-more": "더 로딩 중...",
|
||||
"no-more-mcp-servers": "더 이상 MCP 서버가 없습니다.",
|
||||
"search-mcp": "MCP 검색",
|
||||
"installed": "설치됨",
|
||||
"worker-name-cannot-be-empty": "작업자 이름은 비워둘 수 없습니다",
|
||||
"worker-name-already-exists": "작업자 이름이 이미 존재합니다.",
|
||||
"warning-google-search-not-configured": "경고: Google 검색이 구성되지 않음",
|
||||
"search-functionality-may-be-limited-without-google-api": "Google API 키와 검색 엔진 ID가 없으면 검색 기능이 제한될 수 있습니다. MCP 및 도구 설정에서 이를 구성할 수 있습니다.",
|
||||
"search-engine": "검색 엔진",
|
||||
"allow-agent-to-take-screenshots": "에이전트 스크린샷 촬영 허용",
|
||||
"allow-agent-to-take-screenshots-description": "에이전트가 컴퓨터 화면의 스크린샷을 캡처할 수 있도록 허용합니다. 이는 지원, 진단 또는 모니터링 목적으로 사용될 수 있습니다. 스크린샷에는 보이는 개인 정보가 포함될 수 있으므로 주의해서 활성화하세요.",
|
||||
"allow-agent-to-access-local-software": "에이전트 로컬 소프트웨어 액세스 허용",
|
||||
"allow-agent-to-access-local-software-description": "에이전트가 로컬 머신에 설치된 소프트웨어와 상호 작용하고 사용할 수 있는 권한을 부여합니다. 문제 해결, 진단 실행 또는 특정 작업 수행에 필요할 수 있습니다.",
|
||||
"allow-agent-to-access-your-address": "에이전트 주소 액세스 허용",
|
||||
"allow-agent-to-access-your-address-description": "에이전트가 위치 또는 주소 세부 정보를 보고 사용할 수 있도록 승인합니다. 위치 기반 서비스 또는 개인화된 지원에 필요할 수 있습니다.",
|
||||
"password-storage": "비밀번호 저장",
|
||||
"password-storage-description": "비밀번호가 처리되고 저장되는 방식을 결정합니다. 장치나 애플리케이션 내에서 비밀번호를 안전하게 저장하거나 매번 수동으로 입력하도록 선택할 수 있습니다. 모든 저장된 비밀번호는 암호화됩니다.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP가 성공적으로 설치되었습니다",
|
||||
"failed-to-install-notion-mcp": "Notion MCP 설치에 실패했습니다",
|
||||
"google-calendar-installed-successfully": "Google Calendar가 성공적으로 설치되었습니다",
|
||||
"failed-to-install-google-calendar": "Google Calendar 설치에 실패했습니다",
|
||||
"notion-workspace-integration": "Notion 페이지 읽기 및 관리를 위한 Notion 작업 공간 통합",
|
||||
"google-calendar-integration": "이벤트 및 일정 관리를 위한 Google Calendar 통합",
|
||||
"mcp-server-already-exists": "MCP 서버 \"{{name}}\"이(가) 이미 존재합니다",
|
||||
"google-search": "Google 검색",
|
||||
"select-default-search-engine": "기본 검색 엔진 선택",
|
||||
"your-own-mcps": "자신의 MCP",
|
||||
"get-google-search-api": "Google 검색 API 가져오기",
|
||||
"get-exa-api": "Exa API 가져오기",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "검색 엔진 통합",
|
||||
"configured": "구성됨",
|
||||
"incomplete": "불완전",
|
||||
"not-configured": "구성되지 않음",
|
||||
"saving": "저장 중...",
|
||||
"save-changes": "변경사항 저장",
|
||||
"enable": "활성화",
|
||||
"search": "검색",
|
||||
"test-connection": "연결 테스트",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "API 키는 안전하게 저장되며 외부와 공유되지 않습니다.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "이 서비스는 공개되어 있으며 자격 증명이 필요하지 않습니다.",
|
||||
"this-service-does-not-require-an-api-key": "이 서비스는 API 키가 필요하지 않습니다.",
|
||||
"connection-test-successful": "연결 테스트 성공!",
|
||||
"connection-test-failed": "연결 테스트 실패.",
|
||||
"configuration-saved-successfully": "구성이 성공적으로 저장되었습니다!",
|
||||
"failed-to-save-configuration": "구성 저장에 실패했습니다.",
|
||||
"mcp-and-tools": "MCP 및 도구",
|
||||
"add-mcp-server": "MCP 서버 추가",
|
||||
"market": "마켓",
|
||||
"tools": "도구",
|
||||
"added-external-servers": "추가된 외부 서버",
|
||||
"loading": "로딩 중...",
|
||||
"no-mcp-servers": "MCP 서버 없음",
|
||||
"environmental-variables-required": "환경 변수 필요",
|
||||
"load-failed": "로드 실패",
|
||||
"save-failed": "저장 실패",
|
||||
"invalid-json": "잘못된 JSON",
|
||||
"coming-soon": "출시 예정",
|
||||
"uninstall": "제거",
|
||||
"install": "설치",
|
||||
"add-your-agent": "에이전트 추가",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "유효한 JSON 구성을 제공하여 로컬 MCP 서버를 추가하세요.",
|
||||
"learn-more": "더 알아보기",
|
||||
"installing": "설치 중...",
|
||||
"edit-mcp-config": "MCP 구성 편집",
|
||||
"name": "이름",
|
||||
"description": "설명",
|
||||
"command": "명령",
|
||||
"args-one-per-line": "인수 (한 줄에 하나씩)",
|
||||
"cancel": "취소",
|
||||
"save": "저장",
|
||||
"confirm-delete": "삭제 확인",
|
||||
"are-you-sure-you-want-to-delete": "정말 삭제하시겠습니까?",
|
||||
"deleting": "삭제 중...",
|
||||
"delete": "삭제",
|
||||
"configure {name} Toolkit": "{{name}} 도구 구성",
|
||||
"get-it-from": "여기서 다운로드",
|
||||
"google-custom-search-api": "Google 맞춤 검색 API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "연결",
|
||||
"setting": "설정",
|
||||
"all": "전체",
|
||||
"mcp-market": "MCP 마켓",
|
||||
"no-mcp-services": "MCP 서비스 없음",
|
||||
"loading-more": "더 로딩 중...",
|
||||
"no-more-mcp-servers": "더 이상 MCP 서버가 없습니다.",
|
||||
"search-mcp": "MCP 검색",
|
||||
"installed": "설치됨",
|
||||
"worker-name-cannot-be-empty": "작업자 이름은 비워둘 수 없습니다",
|
||||
"worker-name-already-exists": "작업자 이름이 이미 존재합니다.",
|
||||
"warning-google-search-not-configured": "경고: Google 검색이 구성되지 않음",
|
||||
"search-functionality-may-be-limited-without-google-api": "Google API 키와 검색 엔진 ID가 없으면 검색 기능이 제한될 수 있습니다. MCP 및 도구 설정에서 이를 구성할 수 있습니다.",
|
||||
"search-engine": "검색 엔진",
|
||||
"allow-agent-to-take-screenshots": "에이전트 스크린샷 촬영 허용",
|
||||
"allow-agent-to-take-screenshots-description": "에이전트가 컴퓨터 화면의 스크린샷을 캡처할 수 있도록 허용합니다. 이는 지원, 진단 또는 모니터링 목적으로 사용될 수 있습니다. 스크린샷에는 보이는 개인 정보가 포함될 수 있으므로 주의해서 활성화하세요.",
|
||||
"allow-agent-to-access-local-software": "에이전트 로컬 소프트웨어 액세스 허용",
|
||||
"allow-agent-to-access-local-software-description": "에이전트가 로컬 머신에 설치된 소프트웨어와 상호 작용하고 사용할 수 있는 권한을 부여합니다. 문제 해결, 진단 실행 또는 특정 작업 수행에 필요할 수 있습니다.",
|
||||
"allow-agent-to-access-your-address": "에이전트 주소 액세스 허용",
|
||||
"allow-agent-to-access-your-address-description": "에이전트가 위치 또는 주소 세부 정보를 보고 사용할 수 있도록 승인합니다. 위치 기반 서비스 또는 개인화된 지원에 필요할 수 있습니다.",
|
||||
"password-storage": "비밀번호 저장",
|
||||
"password-storage-description": "비밀번호가 처리되고 저장되는 방식을 결정합니다. 장치나 애플리케이션 내에서 비밀번호를 안전하게 저장하거나 매번 수동으로 입력하도록 선택할 수 있습니다. 모든 저장된 비밀번호는 암호화됩니다.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP가 성공적으로 설치되었습니다",
|
||||
"failed-to-install-notion-mcp": "Notion MCP 설치에 실패했습니다",
|
||||
"google-calendar-installed-successfully": "Google Calendar가 성공적으로 설치되었습니다",
|
||||
"failed-to-install-google-calendar": "Google Calendar 설치에 실패했습니다",
|
||||
"notion-workspace-integration": "Notion 페이지 읽기 및 관리를 위한 Notion 작업 공간 통합",
|
||||
"google-calendar-integration": "이벤트 및 일정 관리를 위한 Google Calendar 통합",
|
||||
"mcp-server-already-exists": "MCP 서버 \"{{name}}\"이(가) 이미 존재합니다",
|
||||
"google-search": "Google 검색",
|
||||
"select-default-search-engine": "기본 검색 엔진 선택",
|
||||
"your-own-mcps": "자신의 MCP",
|
||||
"get-google-search-api": "Google 검색 API 가져오기",
|
||||
"get-exa-api": "Exa API 가져오기",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "검색 엔진 통합",
|
||||
"configured": "구성됨",
|
||||
"incomplete": "불완전",
|
||||
"not-configured": "구성되지 않음",
|
||||
"saving": "저장 중...",
|
||||
"save-changes": "변경사항 저장",
|
||||
"enable": "활성화",
|
||||
"search": "검색",
|
||||
"test-connection": "연결 테스트",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "API 키는 안전하게 저장되며 외부와 공유되지 않습니다.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "이 서비스는 공개되어 있으며 자격 증명이 필요하지 않습니다.",
|
||||
"this-service-does-not-require-an-api-key": "이 서비스는 API 키가 필요하지 않습니다.",
|
||||
"connection-test-successful": "연결 테스트 성공!",
|
||||
"connection-test-failed": "연결 테스트 실패.",
|
||||
"configuration-saved-successfully": "구성이 성공적으로 저장되었습니다!",
|
||||
"failed-to-save-configuration": "구성 저장에 실패했습니다.",
|
||||
"recommended": "추천",
|
||||
"reset": "초기화",
|
||||
"reset-success": "초기화 완료!",
|
||||
"reset-failed": "초기화 실패!",
|
||||
|
||||
"network-proxy": "네트워크 프록시",
|
||||
"network-proxy-description": "네트워크 요청을 위한 프록시 서버를 구성합니다. 프록시를 통해 외부 API에 접근해야 하는 경우 유용합니다.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "프록시 설정이 저장되었습니다. 변경 사항을 적용하려면 앱을 다시 시작하세요.",
|
||||
"proxy-save-failed": "프록시 설정 저장에 실패했습니다.",
|
||||
"proxy-invalid-url": "잘못된 프록시 URL입니다. http://, https://, socks4://, 또는 socks5://로 시작해야 합니다.",
|
||||
"proxy-restart-hint": "프록시 변경 사항을 적용하려면 다시 시작해야 합니다."
|
||||
"network-proxy": "네트워크 프록시",
|
||||
"network-proxy-description": "네트워크 요청을 위한 프록시 서버를 구성합니다. 프록시를 통해 외부 API에 접근해야 하는 경우 유용합니다.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "프록시 설정이 저장되었습니다. 변경 사항을 적용하려면 앱을 다시 시작하세요.",
|
||||
"proxy-save-failed": "프록시 설정 저장에 실패했습니다.",
|
||||
"proxy-invalid-url": "잘못된 프록시 URL입니다. http://, https://, socks4://, 또는 socks5://로 시작해야 합니다.",
|
||||
"proxy-restart-hint": "프록시 변경 사항을 적용하려면 다시 시작해야 합니다."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,171 +1,173 @@
|
|||
{
|
||||
"settings": "настройки",
|
||||
"general": "Общие",
|
||||
"privacy": "Конфиденциальность",
|
||||
"models": "Модели",
|
||||
"mcp": "MCP и Инструменты",
|
||||
"settings": "настройки",
|
||||
"general": "Общие",
|
||||
"privacy": "Конфиденциальность",
|
||||
"models": "Модели",
|
||||
"mcp": "MCP и Инструменты",
|
||||
|
||||
"account": "Аккаунт",
|
||||
"you-are-currently-signed-in-with": "Вы вошли с аккаунтом {{email}}",
|
||||
"manage": "Управление",
|
||||
"log-out": "Выйти",
|
||||
"language": "Язык",
|
||||
"select-language": "Выберите язык",
|
||||
"system-default": "По умолчанию в системе",
|
||||
"appearance": "Внешний вид",
|
||||
"light": "Светлый",
|
||||
"transparent": "Прозрачный",
|
||||
"account": "Аккаунт",
|
||||
"you-are-currently-signed-in-with": "Вы вошли с аккаунтом {{email}}",
|
||||
"manage": "Управление",
|
||||
"log-out": "Выйти",
|
||||
"language": "Язык",
|
||||
"select-language": "Выберите язык",
|
||||
"system-default": "По умолчанию в системе",
|
||||
"appearance": "Внешний вид",
|
||||
"dark": "Тёмный",
|
||||
"light": "Светлый",
|
||||
"transparent": "Прозрачный",
|
||||
|
||||
"data-privacy": "Конфиденциальность данных",
|
||||
"data-privacy-description": "Eigent построен по принципу \"сначала локально\", чтобы обеспечить вашу конфиденциальность. Ваши данные по умолчанию остаются на вашем устройстве. Облачные функции являются необязательными и используют только минимальные необходимые данные для функционирования. Полные сведения см. в нашей",
|
||||
"privacy-policy": "Политике конфиденциальности",
|
||||
"how-we-handle-your-data": "Как мы обрабатываем ваши данные",
|
||||
"how-we-handle-your-data-line-1": "Мы используем только необходимые данные для выполнения ваших задач",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent может делать снимки экрана для анализа элементов пользовательского интерфейса, чтения текста и определения следующего действия, точно так же, как это сделали бы вы.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent может использовать вашу мышь и клавиатуру для доступа к локальному программному обеспечению и файлам, которые вы указываете.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Только минимальные данные задачи отправляются поставщикам моделей ИИ или сторонним интеграциям, которые вы включаете; мы не храним данные.",
|
||||
"how-we-handle-your-data-line-2": "Файлы задач, результаты и снимки экрана остаются в вашей целевой папке задач локально.",
|
||||
"how-we-handle-your-data-line-3": "Учетные данные хранятся локально, зашифрованы и используются только для одобренных шагов.",
|
||||
"how-we-handle-your-data-line-4": "Ваши данные никогда не используются для обучения наших моделей ИИ без вашего явного согласия.",
|
||||
"how-we-handle-your-data-line-5": "Мы не продаем ваши данные третьим лицам.",
|
||||
"enable-privacy-permissions-settings": "Включить настройки разрешений конфиденциальности",
|
||||
"enable-privacy-permissions-settings-description": "Включив эту опцию, вы подтверждаете, что прочитали и согласны с нашей Политикой конфиденциальности в отношении того, как ваши данные задачи собираются, обрабатываются и защищаются.",
|
||||
"data-privacy": "Конфиденциальность данных",
|
||||
"data-privacy-description": "Eigent построен по принципу \"сначала локально\", чтобы обеспечить вашу конфиденциальность. Ваши данные по умолчанию остаются на вашем устройстве. Облачные функции являются необязательными и используют только минимальные необходимые данные для функционирования. Полные сведения см. в нашей",
|
||||
"privacy-policy": "Политике конфиденциальности",
|
||||
"how-we-handle-your-data": "Как мы обрабатываем ваши данные",
|
||||
"how-we-handle-your-data-line-1": "Мы используем только необходимые данные для выполнения ваших задач",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent может делать снимки экрана для анализа элементов пользовательского интерфейса, чтения текста и определения следующего действия, точно так же, как это сделали бы вы.",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent может использовать вашу мышь и клавиатуру для доступа к локальному программному обеспечению и файлам, которые вы указываете.",
|
||||
"how-we-handle-your-data-line-1-line-3": "Только минимальные данные задачи отправляются поставщикам моделей ИИ или сторонним интеграциям, которые вы включаете; мы не храним данные.",
|
||||
"how-we-handle-your-data-line-2":"Файлы задач, результаты и снимки экрана остаются в вашей целевой папке задач локально.",
|
||||
"how-we-handle-your-data-line-3":"Учетные данные хранятся локально, зашифрованы и используются только для одобренных шагов.",
|
||||
"how-we-handle-your-data-line-4":"Ваши данные никогда не используются для обучения наших моделей ИИ без вашего явного согласия.",
|
||||
"how-we-handle-your-data-line-5":"Мы не продаем ваши данные третьим лицам.",
|
||||
"enable-privacy-permissions-settings": "Включить настройки разрешений конфиденциальности",
|
||||
"enable-privacy-permissions-settings-description": "Включив эту опцию, вы подтверждаете, что прочитали и согласны с нашей Политикой конфиденциальности в отношении того, как ваши данные задачи собираются, обрабатываются и защищаются.",
|
||||
|
||||
"api-key-can-not-be-empty": "API-ключ не может быть пустым!",
|
||||
"api-host-can-not-be-empty": "API-хост не может быть пустым!",
|
||||
"model-type-can-not-be-empty": "Тип модели не может быть пустым!",
|
||||
"validate-success": "Проверка прошла успешно",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "Модель была проверена на поддержку вызовов функций, что требуется для использования Eigent.",
|
||||
"validate-failed": "Проверка не удалась",
|
||||
"copy": "Копировать",
|
||||
"copied-to-clipboard": "Скопировано в буфер обмена",
|
||||
"endpoint-url-can-not-be-empty": "URL конечной точки не может быть пустым!",
|
||||
"verification-failed-please-check-endpoint-url": "Проверка не удалась, проверьте URL конечной точки",
|
||||
"eigent-cloud-version": "Eigent Cloud Версия",
|
||||
"you-are-currently-subscribed-to-the": "В настоящее время вы подписаны на",
|
||||
"discover-more-about-our": "Узнайте больше о наших",
|
||||
"pricing-options": "вариантах ценообразования",
|
||||
"credits": "Кредиты",
|
||||
"select-model-type": "Выберите тип модели",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Ниже стоимость, быстрее ответы, но сниженное качество вывода.",
|
||||
"gpt-4.1": "GPT-4.1: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"gpt-5": "GPT-5: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"gpt-5-mini": "GPT-5 mini: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"gpt-5-nano": "GPT-5 nano: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"custom-model": "Пользовательская модель",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Используйте свои собственные API-ключи или настройте локальную модель.",
|
||||
"verify": "Проверить",
|
||||
"local-model": "Локальная модель",
|
||||
"model-platform": "Платформа модели",
|
||||
"model-endpoint-url": "URL конечной точки модели",
|
||||
"model-type": "Тип модели",
|
||||
"enter-your-local-model-type": "Введите тип вашей локальной модели",
|
||||
"you-are-on-selft-host-mode": "Вы находитесь в режиме самостоятельного хостинга",
|
||||
"you-are-using-self-hosted-mode": "Вы используете режим самостоятельного хостинга. Для достижения наилучшей производительности этого продукта введите ключ поиска Google в разделе \"MCP и Инструменты\", чтобы Eigent работал должным образом.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Ключ поиска Google необходим для предоставления точных результатов поиска. Ключ Exa Search является необязательным, но настоятельно рекомендуется для лучшей производительности.",
|
||||
"close": "Закрыть",
|
||||
"enter-your-api-key": "Введите ваш API",
|
||||
"key": "ключ",
|
||||
"enter-your-api-host": "Введите ваш API-хост",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Введите ваш тип модели",
|
||||
"verifying": "Проверка...",
|
||||
|
||||
"api-key-can-not-be-empty": "API-ключ не может быть пустым!",
|
||||
"api-host-can-not-be-empty": "API-хост не может быть пустым!",
|
||||
"model-type-can-not-be-empty": "Тип модели не может быть пустым!",
|
||||
"validate-success": "Проверка прошла успешно",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "Модель была проверена на поддержку вызовов функций, что требуется для использования Eigent.",
|
||||
"validate-failed": "Проверка не удалась",
|
||||
"copy": "Копировать",
|
||||
"copied-to-clipboard": "Скопировано в буфер обмена",
|
||||
"endpoint-url-can-not-be-empty": "URL конечной точки не может быть пустым!",
|
||||
"verification-failed-please-check-endpoint-url": "Проверка не удалась, проверьте URL конечной точки",
|
||||
"eigent-cloud-version": "Eigent Cloud Версия",
|
||||
"you-are-currently-subscribed-to-the": "В настоящее время вы подписаны на",
|
||||
"discover-more-about-our": "Узнайте больше о наших",
|
||||
"pricing-options": "вариантах ценообразования",
|
||||
"credits": "Кредиты",
|
||||
"select-model-type": "Выберите тип модели",
|
||||
"gpt-4.1-mini": "GPT-4.1 Mini: Ниже стоимость, быстрее ответы, но сниженное качество вывода.",
|
||||
"gpt-4.1": "GPT-4.1: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"gpt-5": "GPT-5: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"gpt-5-mini": "GPT-5 mini: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"gpt-5-nano": "GPT-5 nano: Выше стоимость, медленнее ответы, но превосходное качество и рассуждения.",
|
||||
"custom-model": "Пользовательская модель",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "Используйте свои собственные API-ключи или настройте локальную модель.",
|
||||
"verify": "Проверить",
|
||||
"local-model": "Локальная модель",
|
||||
"model-platform": "Платформа модели",
|
||||
"model-endpoint-url": "URL конечной точки модели",
|
||||
"model-type": "Тип модели",
|
||||
"enter-your-local-model-type": "Введите тип вашей локальной модели",
|
||||
"you-are-on-selft-host-mode": "Вы находитесь в режиме самостоятельного хостинга",
|
||||
"you-are-using-self-hosted-mode": "Вы используете режим самостоятельного хостинга. Для достижения наилучшей производительности этого продукта введите ключ поиска Google в разделе \"MCP и Инструменты\", чтобы Eigent работал должным образом.",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Ключ поиска Google необходим для предоставления точных результатов поиска. Ключ Exa Search является необязательным, но настоятельно рекомендуется для лучшей производительности.",
|
||||
"close": "Закрыть",
|
||||
"enter-your-api-key": "Введите ваш API",
|
||||
"key": "ключ",
|
||||
"enter-your-api-host": "Введите ваш API-хост",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "Введите ваш тип модели",
|
||||
"verifying": "Проверка...",
|
||||
|
||||
"mcp-and-tools": "MCP и Инструменты",
|
||||
"add-mcp-server": "Добавить MCP-сервер",
|
||||
"market": "Рынок",
|
||||
"tools": "Инструменты",
|
||||
"added-external-servers": "Добавленные внешние серверы",
|
||||
"loading": "Загрузка...",
|
||||
"no-mcp-servers": "Нет MCP-серверов",
|
||||
"environmental-variables-required": "Требуются переменные окружения",
|
||||
"load-failed": "Не удалось загрузить",
|
||||
"save-failed": "Не удалось сохранить",
|
||||
"invalid-json": "Недопустимый JSON",
|
||||
"coming-soon": "Скоро",
|
||||
"uninstall": "Удалить",
|
||||
"install": "Установить",
|
||||
"add-your-agent": "Добавьте своего агента",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Добавьте локальный MCP-сервер, предоставив допустимую JSON-конфигурацию.",
|
||||
"learn-more": "Подробнее",
|
||||
"installing": "Установка...",
|
||||
"edit-mcp-config": "Редактировать конфигурацию MCP",
|
||||
"name": "Имя",
|
||||
"description": "Описание",
|
||||
"command": "Команда",
|
||||
"args-one-per-line": "Аргументы (по одному на строку)",
|
||||
"cancel": "Отмена",
|
||||
"save": "Сохранить",
|
||||
"confirm-delete": "Подтвердить удаление",
|
||||
"are-you-sure-you-want-to-delete": "Вы уверены, что хотите удалить",
|
||||
"deleting": "Удаление...",
|
||||
"delete": "Удалить",
|
||||
"configure {name} Toolkit": "Настроить набор инструментов {{name}}",
|
||||
"get-it-from": "Получить из",
|
||||
"google-custom-search-api": "API поиска Google",
|
||||
"google-cloud-console": "Консоль Google Cloud",
|
||||
"connect": "Подключить",
|
||||
"setting": "Настройка",
|
||||
"all": "Все",
|
||||
"mcp-market": "MCP Маркет",
|
||||
"no-mcp-services": "Нет MCP-сервисов",
|
||||
"loading-more": "Загрузка еще...",
|
||||
"no-more-mcp-servers": "Больше MCP-серверов нет",
|
||||
"search-mcp": "Поиск MCP",
|
||||
"installed": "Установлено",
|
||||
"worker-name-cannot-be-empty": "Имя работника не может быть пустым",
|
||||
"worker-name-already-exists": "Имя работника уже существует",
|
||||
"warning-google-search-not-configured": "Предупреждение: Google поиск не настроен",
|
||||
"search-functionality-may-be-limited-without-google-api": "Функциональность поиска может быть ограничена без ключа Google API и ID поисковой системы. Вы можете настроить их в настройках MCP и инструментов.",
|
||||
"search-engine": "Поисковая система",
|
||||
"allow-agent-to-take-screenshots": "Разрешить агенту делать скриншоты",
|
||||
"allow-agent-to-take-screenshots-description": "Разрешить агенту захватывать скриншоты экрана вашего компьютера. Это может использоваться для поддержки, диагностики или мониторинга. Скриншоты могут содержать видимую личную информацию, поэтому включайте с осторожностью.",
|
||||
"allow-agent-to-access-local-software": "Разрешить агенту доступ к локальному программному обеспечению",
|
||||
"allow-agent-to-access-local-software-description": "Предоставить агенту разрешение на взаимодействие с программным обеспечением, установленным на вашей локальной машине. Это может быть необходимо для устранения неполадок, запуска диагностики или выполнения определенных задач.",
|
||||
"allow-agent-to-access-your-address": "Разрешить агенту доступ к вашему адресу",
|
||||
"allow-agent-to-access-your-address-description": "Авторизовать агента для просмотра и использования ваших данных о местоположении или адресе. Это может потребоваться для услуг на основе местоположения или персонализированной поддержки.",
|
||||
"password-storage": "Хранение паролей",
|
||||
"password-storage-description": "Определить, как обрабатываются и хранятся пароли. Вы можете выбрать безопасное хранение паролей на устройстве или в приложении, или отказаться от этого, чтобы вводить их вручную каждый раз. Все сохраненные пароли зашифрованы.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP успешно установлен",
|
||||
"failed-to-install-notion-mcp": "Не удалось установить Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar успешно установлен",
|
||||
"failed-to-install-google-calendar": "Не удалось установить Google Calendar",
|
||||
"notion-workspace-integration": "Интеграция рабочего пространства Notion для чтения и управления страницами Notion",
|
||||
"google-calendar-integration": "Интеграция Google Calendar для управления событиями и расписанием",
|
||||
"mcp-server-already-exists": "MCP-сервер \"{{name}}\" уже существует",
|
||||
"google-search": "Google поиск",
|
||||
"select-default-search-engine": "Выберите поисковую систему по умолчанию",
|
||||
"your-own-mcps": "Ваши собственные MCP",
|
||||
"get-google-search-api": "Получить Google Search API",
|
||||
"get-exa-api": "Получить Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Интеграции поисковых систем",
|
||||
"configured": "Настроено",
|
||||
"incomplete": "Неполное",
|
||||
"not-configured": "Не настроено",
|
||||
"saving": "Сохранение...",
|
||||
"save-changes": "Сохранить изменения",
|
||||
"enable": "Включить",
|
||||
"search": "Поиск",
|
||||
"test-connection": "Тест соединения",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Ваши API-ключи хранятся безопасно и никогда не передаются внешним сторонам.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Эта служба является общедоступной и не требует учетных данных.",
|
||||
"this-service-does-not-require-an-api-key": "Эта служба не требует API-ключа.",
|
||||
"connection-test-successful": "Тест соединения успешен!",
|
||||
"connection-test-failed": "Тест соединения не удался.",
|
||||
"configuration-saved-successfully": "Конфигурация успешно сохранена!",
|
||||
"failed-to-save-configuration": "Не удалось сохранить конфигурацию.",
|
||||
"mcp-and-tools": "MCP и Инструменты",
|
||||
"add-mcp-server": "Добавить MCP-сервер",
|
||||
"market": "Рынок",
|
||||
"tools": "Инструменты",
|
||||
"added-external-servers": "Добавленные внешние серверы",
|
||||
"loading": "Загрузка...",
|
||||
"no-mcp-servers": "Нет MCP-серверов",
|
||||
"environmental-variables-required": "Требуются переменные окружения",
|
||||
"load-failed": "Не удалось загрузить",
|
||||
"save-failed": "Не удалось сохранить",
|
||||
"invalid-json": "Недопустимый JSON",
|
||||
"coming-soon": "Скоро",
|
||||
"uninstall": "Удалить",
|
||||
"install": "Установить",
|
||||
"add-your-agent": "Добавьте своего агента",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "Добавьте локальный MCP-сервер, предоставив допустимую JSON-конфигурацию.",
|
||||
"learn-more": "Подробнее",
|
||||
"installing": "Установка...",
|
||||
"edit-mcp-config": "Редактировать конфигурацию MCP",
|
||||
"name": "Имя",
|
||||
"description": "Описание",
|
||||
"command": "Команда",
|
||||
"args-one-per-line": "Аргументы (по одному на строку)",
|
||||
"cancel": "Отмена",
|
||||
"save": "Сохранить",
|
||||
"confirm-delete": "Подтвердить удаление",
|
||||
"are-you-sure-you-want-to-delete": "Вы уверены, что хотите удалить",
|
||||
"deleting": "Удаление...",
|
||||
"delete": "Удалить",
|
||||
"configure {name} Toolkit": "Настроить набор инструментов {{name}}",
|
||||
"get-it-from": "Получить из",
|
||||
"google-custom-search-api": "API поиска Google",
|
||||
"google-cloud-console": "Консоль Google Cloud",
|
||||
"connect": "Подключить",
|
||||
"setting": "Настройка",
|
||||
"all": "Все",
|
||||
"mcp-market": "MCP Маркет",
|
||||
"no-mcp-services": "Нет MCP-сервисов",
|
||||
"loading-more": "Загрузка еще...",
|
||||
"no-more-mcp-servers": "Больше MCP-серверов нет",
|
||||
"search-mcp": "Поиск MCP",
|
||||
"installed": "Установлено",
|
||||
"worker-name-cannot-be-empty": "Имя работника не может быть пустым",
|
||||
"worker-name-already-exists": "Имя работника уже существует",
|
||||
"warning-google-search-not-configured": "Предупреждение: Google поиск не настроен",
|
||||
"search-functionality-may-be-limited-without-google-api": "Функциональность поиска может быть ограничена без ключа Google API и ID поисковой системы. Вы можете настроить их в настройках MCP и инструментов.",
|
||||
"search-engine": "Поисковая система",
|
||||
"allow-agent-to-take-screenshots": "Разрешить агенту делать скриншоты",
|
||||
"allow-agent-to-take-screenshots-description": "Разрешить агенту захватывать скриншоты экрана вашего компьютера. Это может использоваться для поддержки, диагностики или мониторинга. Скриншоты могут содержать видимую личную информацию, поэтому включайте с осторожностью.",
|
||||
"allow-agent-to-access-local-software": "Разрешить агенту доступ к локальному программному обеспечению",
|
||||
"allow-agent-to-access-local-software-description": "Предоставить агенту разрешение на взаимодействие с программным обеспечением, установленным на вашей локальной машине. Это может быть необходимо для устранения неполадок, запуска диагностики или выполнения определенных задач.",
|
||||
"allow-agent-to-access-your-address": "Разрешить агенту доступ к вашему адресу",
|
||||
"allow-agent-to-access-your-address-description": "Авторизовать агента для просмотра и использования ваших данных о местоположении или адресе. Это может потребоваться для услуг на основе местоположения или персонализированной поддержки.",
|
||||
"password-storage": "Хранение паролей",
|
||||
"password-storage-description": "Определить, как обрабатываются и хранятся пароли. Вы можете выбрать безопасное хранение паролей на устройстве или в приложении, или отказаться от этого, чтобы вводить их вручную каждый раз. Все сохраненные пароли зашифрованы.",
|
||||
"notion-mcp-installed-successfully": "Notion MCP успешно установлен",
|
||||
"failed-to-install-notion-mcp": "Не удалось установить Notion MCP",
|
||||
"google-calendar-installed-successfully": "Google Calendar успешно установлен",
|
||||
"failed-to-install-google-calendar": "Не удалось установить Google Calendar",
|
||||
"notion-workspace-integration": "Интеграция рабочего пространства Notion для чтения и управления страницами Notion",
|
||||
"google-calendar-integration": "Интеграция Google Calendar для управления событиями и расписанием",
|
||||
"mcp-server-already-exists": "MCP-сервер \"{{name}}\" уже существует",
|
||||
"google-search": "Google поиск",
|
||||
"select-default-search-engine": "Выберите поисковую систему по умолчанию",
|
||||
"your-own-mcps": "Ваши собственные MCP",
|
||||
"get-google-search-api": "Получить Google Search API",
|
||||
"get-exa-api": "Получить Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "Интеграции поисковых систем",
|
||||
"configured": "Настроено",
|
||||
"incomplete": "Неполное",
|
||||
"not-configured": "Не настроено",
|
||||
"saving": "Сохранение...",
|
||||
"save-changes": "Сохранить изменения",
|
||||
"enable": "Включить",
|
||||
"search": "Поиск",
|
||||
"test-connection": "Тест соединения",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "Ваши API-ключи хранятся безопасно и никогда не передаются внешним сторонам.",
|
||||
"this-service-is-public-and-does-not-require-credentials": "Эта служба является общедоступной и не требует учетных данных.",
|
||||
"this-service-does-not-require-an-api-key": "Эта служба не требует API-ключа.",
|
||||
"connection-test-successful": "Тест соединения успешен!",
|
||||
"connection-test-failed": "Тест соединения не удался.",
|
||||
"configuration-saved-successfully": "Конфигурация успешно сохранена!",
|
||||
"failed-to-save-configuration": "Не удалось сохранить конфигурацию.",
|
||||
"recommended": "Рекомендуется",
|
||||
"reset": "Сброс",
|
||||
"reset-success": "Сброс выполнен успешно!",
|
||||
"reset-failed": "Сбой сброса!",
|
||||
|
||||
"network-proxy": "Сетевой прокси",
|
||||
"network-proxy-description": "Настройте прокси-сервер для сетевых запросов. Это полезно, если вам нужен доступ к внешним API через прокси.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Конфигурация прокси сохранена. Перезапустите приложение для применения изменений.",
|
||||
"proxy-save-failed": "Не удалось сохранить конфигурацию прокси.",
|
||||
"proxy-invalid-url": "Недопустимый URL прокси. Должен начинаться с http://, https://, socks4:// или socks5://.",
|
||||
"proxy-restart-hint": "Для применения изменений прокси требуется перезапуск."
|
||||
"network-proxy": "Сетевой прокси",
|
||||
"network-proxy-description": "Настройте прокси-сервер для сетевых запросов. Это полезно, если вам нужен доступ к внешним API через прокси.",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "Конфигурация прокси сохранена. Перезапустите приложение для применения изменений.",
|
||||
"proxy-save-failed": "Не удалось сохранить конфигурацию прокси.",
|
||||
"proxy-invalid-url": "Недопустимый URL прокси. Должен начинаться с http://, https://, socks4:// или socks5://.",
|
||||
"proxy-restart-hint": "Для применения изменений прокси требуется перезапуск."
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,198 +1,199 @@
|
|||
{
|
||||
"settings": "设置",
|
||||
"general": "通用",
|
||||
"privacy": "隐私",
|
||||
"models": "模型",
|
||||
"mcp": "MCP & 工具",
|
||||
"settings": "设置",
|
||||
"general": "通用",
|
||||
"privacy": "隐私",
|
||||
"models": "模型",
|
||||
"mcp": "MCP & 工具",
|
||||
|
||||
"account": "账户",
|
||||
"you-are-currently-signed-in-with": "你当前使用的是 {{email}} 账户",
|
||||
"manage": "管理",
|
||||
"log-out": "退出",
|
||||
"language": "语言",
|
||||
"select-language": "选择语言",
|
||||
"system-default": "系统默认",
|
||||
"appearance": "外观",
|
||||
"light": "亮色",
|
||||
"transparent": "透明",
|
||||
"account": "账户",
|
||||
"you-are-currently-signed-in-with": "你当前使用的是 {{email}} 账户",
|
||||
"manage": "管理",
|
||||
"log-out": "退出",
|
||||
"language": "语言",
|
||||
"select-language": "选择语言",
|
||||
"system-default": "系统默认",
|
||||
"appearance": "外观",
|
||||
"dark": "深色",
|
||||
"light": "亮色",
|
||||
"transparent": "透明",
|
||||
|
||||
"data-privacy": "数据隐私",
|
||||
"data-privacy-description": "Eigent 基于本地优先原则确保您的隐私。您的数据默认存储在您的设备上。云功能是可选的,仅使用最小的数据来实现功能。详细信息请访问我们的",
|
||||
"privacy-policy": "隐私政策",
|
||||
"how-we-handle-your-data": "我们如何处理您的数据",
|
||||
"we-only-use-the-essential-data-needed-to-run-your-tasks": "我们仅使用运行您的任务所需的关键数据",
|
||||
"how-we-handle-your-data-line-1": "我们仅使用运行您的任务所需的关键数据",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent 可能捕获您的屏幕截图以分析 UI 元素、读取文本并确定下一步操作,就像您一样。",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent 可能使用您的鼠标和键盘访问您指定的本地软件和文件。",
|
||||
"how-we-handle-your-data-line-1-line-3": "只有最少的任务数据被发送到 AI 模型提供商或您启用的第三方集成;我们与这些提供商没有数据保留协议",
|
||||
"how-we-handle-your-data-line-2": "任务文件、输出和截图保留在您的指定任务文件夹中。",
|
||||
"how-we-handle-your-data-line-3": "凭据存储在本地,加密,仅用于批准的步骤。",
|
||||
"how-we-handle-your-data-line-4": "您的数据永远不会用于训练我们的 AI 模型,除非您明确同意。",
|
||||
"how-we-handle-your-data-line-5": "我们不会出售您的数据给第三方。",
|
||||
"enable-privacy-permissions-settings": "启用隐私权限设置",
|
||||
"enable-privacy-permissions-settings-description": "通过打开此功能,您承认已阅读并同意我们的隐私政策,关于您的任务数据如何被收集、处理和保护。",
|
||||
"data-privacy": "数据隐私",
|
||||
"data-privacy-description": "Eigent 基于本地优先原则确保您的隐私。您的数据默认存储在您的设备上。云功能是可选的,仅使用最小的数据来实现功能。详细信息请访问我们的",
|
||||
"privacy-policy": "隐私政策",
|
||||
"how-we-handle-your-data": "我们如何处理您的数据",
|
||||
"we-only-use-the-essential-data-needed-to-run-your-tasks": "我们仅使用运行您的任务所需的关键数据",
|
||||
"how-we-handle-your-data-line-1": "我们仅使用运行您的任务所需的关键数据",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent 可能捕获您的屏幕截图以分析 UI 元素、读取文本并确定下一步操作,就像您一样。",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent 可能使用您的鼠标和键盘访问您指定的本地软件和文件。",
|
||||
"how-we-handle-your-data-line-1-line-3": "只有最少的任务数据被发送到 AI 模型提供商或您启用的第三方集成;我们与这些提供商没有数据保留协议",
|
||||
"how-we-handle-your-data-line-2": "任务文件、输出和截图保留在您的指定任务文件夹中。",
|
||||
"how-we-handle-your-data-line-3": "凭据存储在本地,加密,仅用于批准的步骤。",
|
||||
"how-we-handle-your-data-line-4": "您的数据永远不会用于训练我们的 AI 模型,除非您明确同意。",
|
||||
"how-we-handle-your-data-line-5": "我们不会出售您的数据给第三方。",
|
||||
"enable-privacy-permissions-settings": "启用隐私权限设置",
|
||||
"enable-privacy-permissions-settings-description": "通过打开此功能,您承认已阅读并同意我们的隐私政策,关于您的任务数据如何被收集、处理和保护。",
|
||||
|
||||
"api-key-can-not-be-empty": "API Key 不能为空!",
|
||||
"api-host-can-not-be-empty": "API Host 不能为空!",
|
||||
"model-type-can-not-be-empty": "Model Type 不能为空!",
|
||||
"validate-success": "验证成功",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "该模型已验证支持函数调用,这是使用 Eigent 所必需的。",
|
||||
"validate-failed": "验证失败",
|
||||
"copy": "复制",
|
||||
"copied-to-clipboard": "复制到剪贴板",
|
||||
"endpoint-url-can-not-be-empty": "Endpoint URL 不能为空!",
|
||||
"verification-failed-please-check-endpoint-url": "验证失败,请检查 Endpoint URL",
|
||||
"eigent-cloud-version": "Eigent 云端版本",
|
||||
"you-are-currently-subscribed-to-the": "你当前订阅的是",
|
||||
"discover-more-about-our": "发现更多关于我们的",
|
||||
"pricing-options": "定价选项",
|
||||
"credits": "积分",
|
||||
"select-model-type": "选择模型类型",
|
||||
"gpt-4.1-mini": "GPT-4.1 mini: 更低成本,更快响应,但输出质量降低。",
|
||||
"gpt-4.1": "GPT-4.1: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"gpt-5": "GPT-5: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"gpt-5-mini": "GPT-5 mini: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"gpt-5-nano": "GPT-5 nano: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"custom-model": "自定义模型",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "使用您自己的 API 密钥或设置本地模型。",
|
||||
"verify": "验证",
|
||||
"local-model": "本地模型",
|
||||
"model-platform": "模型平台",
|
||||
"model-endpoint-url": "模型端点 URL",
|
||||
"model-type": "模型类型",
|
||||
"enter-your-local-model-type": "输入您的本地模型类型",
|
||||
"you-are-on-selft-host-mode": "你正在自托管模式",
|
||||
"you-are-using-self-hosted-mode": "你正在使用自托管模式。为了获得最佳性能,请在 \"MCP and Tools\" 中输入 Google Search Key 以确保 Eigent 正常工作。",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Google Search Key 是确保准确搜索结果的关键。Exa Search Key 是可选的,但强烈建议使用以获得更好的性能。",
|
||||
"close": "关闭",
|
||||
"enter-your-api-key": "输入您的 API",
|
||||
"key": "密钥",
|
||||
"enter-your-api-host": "输入您的 API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "输入您的模型类型",
|
||||
"verifying": "验证中...",
|
||||
"mcp-and-tools": "MCP & 工具",
|
||||
"add-mcp-server": "添加 MCP 服务器",
|
||||
"market": "市场",
|
||||
"tools": "工具",
|
||||
"added-external-servers": "添加的外部服务器",
|
||||
"loading": "加载中...",
|
||||
"no-mcp-servers": "没有 MCP 服务器",
|
||||
"environmental-variables-required": "环境变量要求",
|
||||
"load-failed": "加载失败",
|
||||
"save-failed": "保存失败",
|
||||
"invalid-json": "无效的 JSON",
|
||||
"coming-soon": "即将推出",
|
||||
"uninstall": "卸载",
|
||||
"install": "安装",
|
||||
"add-your-agent": "添加您的智能体",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "通过提供有效的 JSON 配置添加本地 MCP 服务器。",
|
||||
"learn-more": "了解更多",
|
||||
"installing": "安装中...",
|
||||
"edit-mcp-config": "编辑 MCP 配置",
|
||||
"name": "名称",
|
||||
"description": "描述",
|
||||
"command": "命令",
|
||||
"args-one-per-line": "参数 (每行一个)",
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"confirm-delete": "确认删除",
|
||||
"are-you-sure-you-want-to-delete": "你确定要删除",
|
||||
"deleting": "删除中...",
|
||||
"delete": "删除",
|
||||
"configure {name} Toolkit": "配置 {{name}} 工具包",
|
||||
"get-it-from": "获取它来自",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "连接",
|
||||
"setting": "设置",
|
||||
"all": "全部",
|
||||
"mcp-market": "MCP 市场",
|
||||
"no-mcp-services": "没有 MCP 服务",
|
||||
"loading-more": "加载更多...",
|
||||
"no-more-mcp-servers": "没有更多 MCP 服务器",
|
||||
"search-mcp": "搜索 MCP",
|
||||
"installed": "已安装",
|
||||
"worker-name-cannot-be-empty": "Worker 名称不能为空",
|
||||
"worker-name-already-exists": "Worker 名称已存在",
|
||||
"warning-google-search-not-configured": "警告:Google 搜索未配置",
|
||||
"search-functionality-may-be-limited-without-google-api": "没有 Google API 密钥和搜索引擎 ID,搜索功能可能受限。您可以在 MCP & 工具设置中配置这些。",
|
||||
"search-engine": "搜索引擎",
|
||||
"allow-agent-to-take-screenshots": "允许智能体截屏",
|
||||
"allow-agent-to-take-screenshots-description": "允许智能体捕获您计算机屏幕的截图。这可用于支持、诊断或监控目的。截图可能包含可见的个人信息,请谨慎启用。",
|
||||
"allow-agent-to-access-local-software": "允许智能体访问本地软件",
|
||||
"allow-agent-to-access-local-software-description": "授予智能体与您本地机器上安装的软件交互和使用的权限。这对于故障排除、运行诊断或执行特定任务可能是必要的。",
|
||||
"allow-agent-to-access-your-address": "允许智能体访问您的地址",
|
||||
"allow-agent-to-access-your-address-description": "授权智能体查看和使用您的位置或地址详细信息。这对于基于位置的服务或个性化支持可能是必需的。",
|
||||
"password-storage": "密码存储",
|
||||
"password-storage-description": "确定如何处理和存储密码。您可以选择在设备或应用程序中安全存储密码,或选择每次手动输入。所有存储的密码都经过加密。",
|
||||
"notion-mcp-installed-successfully": "Notion MCP 安装成功",
|
||||
"failed-to-install-notion-mcp": "Notion MCP 安装失败",
|
||||
"google-calendar-installed-successfully": "Google Calendar 安装成功",
|
||||
"failed-to-install-google-calendar": "Google Calendar 安装失败",
|
||||
"notion-workspace-integration": "Notion 工作区集成,用于读取和管理 Notion 页面",
|
||||
"google-calendar-integration": "Google Calendar 集成,用于管理事件和日程安排",
|
||||
"mcp-server-already-exists": "MCP 服务器 \"{{name}}\" 已存在",
|
||||
"google-search": "Google 搜索",
|
||||
"select-default-search-engine": "选择默认搜索引擎",
|
||||
"your-own-mcps": "您自己的 MCP",
|
||||
"get-google-search-api": "获取 Google 搜索 API",
|
||||
"get-exa-api": "获取 Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "搜索引擎集成",
|
||||
"configured": "已配置",
|
||||
"incomplete": "不完整",
|
||||
"not-configured": "未配置",
|
||||
"saving": "保存中...",
|
||||
"save-changes": "保存更改",
|
||||
"enable": "启用",
|
||||
"search": "搜索",
|
||||
"test-connection": "测试连接",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "您的API密钥安全存储,从不与外部共享。",
|
||||
"this-service-is-public-and-does-not-require-credentials": "此服务是公开的,不需要凭据。",
|
||||
"this-service-does-not-require-an-api-key": "此服务不需要API密钥。",
|
||||
"connection-test-successful": "连接测试成功!",
|
||||
"connection-test-failed": "连接测试失败。",
|
||||
"configuration-saved-successfully": "配置保存成功!",
|
||||
"failed-to-save-configuration": "保存配置失败。",
|
||||
"recommended": "推荐",
|
||||
"reset": "重置",
|
||||
"reset-success": "重置成功!",
|
||||
"reset-failed": "重置失败!",
|
||||
"api-key-can-not-be-empty": "API Key 不能为空!",
|
||||
"api-host-can-not-be-empty": "API Host 不能为空!",
|
||||
"model-type-can-not-be-empty": "Model Type 不能为空!",
|
||||
"validate-success": "验证成功",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "该模型已验证支持函数调用,这是使用 Eigent 所必需的。",
|
||||
"validate-failed": "验证失败",
|
||||
"copy": "复制",
|
||||
"copied-to-clipboard": "复制到剪贴板",
|
||||
"endpoint-url-can-not-be-empty": "Endpoint URL 不能为空!",
|
||||
"verification-failed-please-check-endpoint-url": "验证失败,请检查 Endpoint URL",
|
||||
"eigent-cloud-version": "Eigent 云端版本",
|
||||
"you-are-currently-subscribed-to-the": "你当前订阅的是",
|
||||
"discover-more-about-our": "发现更多关于我们的",
|
||||
"pricing-options": "定价选项",
|
||||
"credits": "积分",
|
||||
"select-model-type": "选择模型类型",
|
||||
"gpt-4.1-mini": "GPT-4.1 mini: 更低成本,更快响应,但输出质量降低。",
|
||||
"gpt-4.1": "GPT-4.1: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"gpt-5": "GPT-5: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"gpt-5-mini": "GPT-5 mini: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"gpt-5-nano": "GPT-5 nano: 更高成本,更慢响应,但更高质量和推理。",
|
||||
"custom-model": "自定义模型",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "使用您自己的 API 密钥或设置本地模型。",
|
||||
"verify": "验证",
|
||||
"local-model": "本地模型",
|
||||
"model-platform": "模型平台",
|
||||
"model-endpoint-url": "模型端点 URL",
|
||||
"model-type": "模型类型",
|
||||
"enter-your-local-model-type": "输入您的本地模型类型",
|
||||
"you-are-on-selft-host-mode": "你正在自托管模式",
|
||||
"you-are-using-self-hosted-mode": "你正在使用自托管模式。为了获得最佳性能,请在 \"MCP and Tools\" 中输入 Google Search Key 以确保 Eigent 正常工作。",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Google Search Key 是确保准确搜索结果的关键。Exa Search Key 是可选的,但强烈建议使用以获得更好的性能。",
|
||||
"close": "关闭",
|
||||
"enter-your-api-key": "输入您的 API",
|
||||
"key": "密钥",
|
||||
"enter-your-api-host": "输入您的 API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "输入您的模型类型",
|
||||
"verifying": "验证中...",
|
||||
"mcp-and-tools": "MCP & 工具",
|
||||
"add-mcp-server": "添加 MCP 服务器",
|
||||
"market": "市场",
|
||||
"tools": "工具",
|
||||
"added-external-servers": "添加的外部服务器",
|
||||
"loading": "加载中...",
|
||||
"no-mcp-servers": "没有 MCP 服务器",
|
||||
"environmental-variables-required": "环境变量要求",
|
||||
"load-failed": "加载失败",
|
||||
"save-failed": "保存失败",
|
||||
"invalid-json": "无效的 JSON",
|
||||
"coming-soon": "即将推出",
|
||||
"uninstall": "卸载",
|
||||
"install": "安装",
|
||||
"add-your-agent": "添加您的智能体",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "通过提供有效的 JSON 配置添加本地 MCP 服务器。",
|
||||
"learn-more": "了解更多",
|
||||
"installing": "安装中...",
|
||||
"edit-mcp-config": "编辑 MCP 配置",
|
||||
"name": "名称",
|
||||
"description": "描述",
|
||||
"command": "命令",
|
||||
"args-one-per-line": "参数 (每行一个)",
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"confirm-delete": "确认删除",
|
||||
"are-you-sure-you-want-to-delete": "你确定要删除",
|
||||
"deleting": "删除中...",
|
||||
"delete": "删除",
|
||||
"configure {name} Toolkit": "配置 {{name}} 工具包",
|
||||
"get-it-from": "获取它来自",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "连接",
|
||||
"setting": "设置",
|
||||
"all": "全部",
|
||||
"mcp-market": "MCP 市场",
|
||||
"no-mcp-services": "没有 MCP 服务",
|
||||
"loading-more": "加载更多...",
|
||||
"no-more-mcp-servers": "没有更多 MCP 服务器",
|
||||
"search-mcp": "搜索 MCP",
|
||||
"installed": "已安装",
|
||||
"worker-name-cannot-be-empty": "Worker 名称不能为空",
|
||||
"worker-name-already-exists": "Worker 名称已存在",
|
||||
"warning-google-search-not-configured": "警告:Google 搜索未配置",
|
||||
"search-functionality-may-be-limited-without-google-api": "没有 Google API 密钥和搜索引擎 ID,搜索功能可能受限。您可以在 MCP & 工具设置中配置这些。",
|
||||
"search-engine": "搜索引擎",
|
||||
"allow-agent-to-take-screenshots": "允许智能体截屏",
|
||||
"allow-agent-to-take-screenshots-description": "允许智能体捕获您计算机屏幕的截图。这可用于支持、诊断或监控目的。截图可能包含可见的个人信息,请谨慎启用。",
|
||||
"allow-agent-to-access-local-software": "允许智能体访问本地软件",
|
||||
"allow-agent-to-access-local-software-description": "授予智能体与您本地机器上安装的软件交互和使用的权限。这对于故障排除、运行诊断或执行特定任务可能是必要的。",
|
||||
"allow-agent-to-access-your-address": "允许智能体访问您的地址",
|
||||
"allow-agent-to-access-your-address-description": "授权智能体查看和使用您的位置或地址详细信息。这对于基于位置的服务或个性化支持可能是必需的。",
|
||||
"password-storage": "密码存储",
|
||||
"password-storage-description": "确定如何处理和存储密码。您可以选择在设备或应用程序中安全存储密码,或选择每次手动输入。所有存储的密码都经过加密。",
|
||||
"notion-mcp-installed-successfully": "Notion MCP 安装成功",
|
||||
"failed-to-install-notion-mcp": "Notion MCP 安装失败",
|
||||
"google-calendar-installed-successfully": "Google Calendar 安装成功",
|
||||
"failed-to-install-google-calendar": "Google Calendar 安装失败",
|
||||
"notion-workspace-integration": "Notion 工作区集成,用于读取和管理 Notion 页面",
|
||||
"google-calendar-integration": "Google Calendar 集成,用于管理事件和日程安排",
|
||||
"mcp-server-already-exists": "MCP 服务器 \"{{name}}\" 已存在",
|
||||
"google-search": "Google 搜索",
|
||||
"select-default-search-engine": "选择默认搜索引擎",
|
||||
"your-own-mcps": "您自己的 MCP",
|
||||
"get-google-search-api": "获取 Google 搜索 API",
|
||||
"get-exa-api": "获取 Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "搜索引擎集成",
|
||||
"configured": "已配置",
|
||||
"incomplete": "不完整",
|
||||
"not-configured": "未配置",
|
||||
"saving": "保存中...",
|
||||
"save-changes": "保存更改",
|
||||
"enable": "启用",
|
||||
"search": "搜索",
|
||||
"test-connection": "测试连接",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "您的API密钥安全存储,从不与外部共享。",
|
||||
"this-service-is-public-and-does-not-require-credentials": "此服务是公开的,不需要凭据。",
|
||||
"this-service-does-not-require-an-api-key": "此服务不需要API密钥。",
|
||||
"connection-test-successful": "连接测试成功!",
|
||||
"connection-test-failed": "连接测试失败。",
|
||||
"configuration-saved-successfully": "配置保存成功!",
|
||||
"failed-to-save-configuration": "保存配置失败。",
|
||||
"recommended": "推荐",
|
||||
"reset": "重置",
|
||||
"reset-success": "重置成功!",
|
||||
"reset-failed": "重置失败!",
|
||||
|
||||
"browser-login": "浏览器登录",
|
||||
"browser-login-description": "打开 Chrome 浏览器以登录您的账户。您的登录数据将安全地保存在本地配置文件中。",
|
||||
"open-browser-login": "打开浏览器登录",
|
||||
"opening-browser": "正在打开浏览器...",
|
||||
"browser-opened-successfully": "浏览器已成功打开。请登录您的账户。",
|
||||
"failed-to-open-browser": "打开浏览器失败,请重试。",
|
||||
"restart-to-apply": "重启应用",
|
||||
"browser-login": "浏览器登录",
|
||||
"browser-login-description": "打开 Chrome 浏览器以登录您的账户。您的登录数据将安全地保存在本地配置文件中。",
|
||||
"open-browser-login": "打开浏览器登录",
|
||||
"opening-browser": "正在打开浏览器...",
|
||||
"browser-opened-successfully": "浏览器已成功打开。请登录您的账户。",
|
||||
"failed-to-open-browser": "打开浏览器失败,请重试。",
|
||||
"restart-to-apply": "重启应用",
|
||||
|
||||
"cookie-manager": "Cookie 管理器",
|
||||
"cookie-manager-description": "管理从浏览器会话中保存的 Cookie。可以删除特定网站或全部 Cookie。",
|
||||
"refresh": "刷新",
|
||||
"delete-all": "全部删除",
|
||||
"search-domains": "搜索域名...",
|
||||
"loading-cookies": "正在加载 Cookie...",
|
||||
"no-cookies-found": "未找到 Cookie",
|
||||
"no-matching-domains": "没有匹配的域名",
|
||||
"login-to-save-cookies": "使用上面的浏览器登录功能来保存您账户的 Cookie。",
|
||||
"cookies-count": "{{count}} 个 Cookie",
|
||||
"last-access": "最后访问",
|
||||
"deleting": "删除中...",
|
||||
"cookies-deleted-successfully": "已成功删除 {{domain}} 的 Cookie",
|
||||
"failed-to-load-cookies": "加载 Cookie 失败,请重试。",
|
||||
"failed-to-delete-cookies": "删除 Cookie 失败,请重试。",
|
||||
"confirm-delete-all-cookies": "确定要删除所有 Cookie 吗?此操作无法撤销。",
|
||||
"all-cookies-deleted": "所有 Cookie 已成功删除。",
|
||||
"cookie-delete-warning": "注意:删除 Cookie 会使您从相关网站登出。您可能需要重启浏览器才能看到更改生效。",
|
||||
"cookie-manager": "Cookie 管理器",
|
||||
"cookie-manager-description": "管理从浏览器会话中保存的 Cookie。可以删除特定网站或全部 Cookie。",
|
||||
"refresh": "刷新",
|
||||
"delete-all": "全部删除",
|
||||
"search-domains": "搜索域名...",
|
||||
"loading-cookies": "正在加载 Cookie...",
|
||||
"no-cookies-found": "未找到 Cookie",
|
||||
"no-matching-domains": "没有匹配的域名",
|
||||
"login-to-save-cookies": "使用上面的浏览器登录功能来保存您账户的 Cookie。",
|
||||
"cookies-count": "{{count}} 个 Cookie",
|
||||
"last-access": "最后访问",
|
||||
"deleting": "删除中...",
|
||||
"cookies-deleted-successfully": "已成功删除 {{domain}} 的 Cookie",
|
||||
"failed-to-load-cookies": "加载 Cookie 失败,请重试。",
|
||||
"failed-to-delete-cookies": "删除 Cookie 失败,请重试。",
|
||||
"confirm-delete-all-cookies": "确定要删除所有 Cookie 吗?此操作无法撤销。",
|
||||
"all-cookies-deleted": "所有 Cookie 已成功删除。",
|
||||
"cookie-delete-warning": "注意:删除 Cookie 会使您从相关网站登出。您可能需要重启浏览器才能看到更改生效。",
|
||||
|
||||
"network-proxy": "网络代理",
|
||||
"network-proxy-description": "配置网络请求的代理服务器。如果您需要通过代理访问外部 API,这将非常有用。",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "代理配置已保存。请重启应用以应用更改。",
|
||||
"proxy-save-failed": "保存代理配置失败。",
|
||||
"proxy-invalid-url": "无效的代理 URL。必须以 http://、https://、socks4:// 或 socks5:// 开头。",
|
||||
"proxy-restart-hint": "需要重启应用以应用代理更改。"
|
||||
"network-proxy": "网络代理",
|
||||
"network-proxy-description": "配置网络请求的代理服务器。如果您需要通过代理访问外部 API,这将非常有用。",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "代理配置已保存。请重启应用以应用更改。",
|
||||
"proxy-save-failed": "保存代理配置失败。",
|
||||
"proxy-invalid-url": "无效的代理 URL。必须以 http://、https://、socks4:// 或 socks5:// 开头。",
|
||||
"proxy-restart-hint": "需要重启应用以应用代理更改。"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,171 +1,172 @@
|
|||
{
|
||||
"settings": "設定",
|
||||
"general": "通用",
|
||||
"privacy": "隱私",
|
||||
"models": "模型",
|
||||
"mcp": "MCP & 工具",
|
||||
"settings": "設定",
|
||||
"general": "通用",
|
||||
"privacy": "隱私",
|
||||
"models": "模型",
|
||||
"mcp": "MCP & 工具",
|
||||
|
||||
"account": "帳戶",
|
||||
"you-are-currently-signed-in-with": "您目前使用的是 {{email}} 帳戶",
|
||||
"manage": "管理",
|
||||
"log-out": "登出",
|
||||
"language": "語言",
|
||||
"select-language": "選擇語言",
|
||||
"system-default": "系統預設",
|
||||
"appearance": "外觀",
|
||||
"light": "淺色",
|
||||
"transparent": "透明",
|
||||
"account": "帳戶",
|
||||
"you-are-currently-signed-in-with": "您目前使用的是 {{email}} 帳戶",
|
||||
"manage": "管理",
|
||||
"log-out": "登出",
|
||||
"language": "語言",
|
||||
"select-language": "選擇語言",
|
||||
"system-default": "系統預設",
|
||||
"appearance": "外觀",
|
||||
"dark": "深色",
|
||||
"light": "淺色",
|
||||
"transparent": "透明",
|
||||
|
||||
"data-privacy": "數據隱私",
|
||||
"data-privacy-description": "Eigent 以本地優先原則確保您的隱私。您的數據預設儲存在您的設備上。雲端功能是可選的,僅使用最少量的數據來實現功能。詳細資訊請參閱我們的",
|
||||
"privacy-policy": "隱私政策",
|
||||
"how-we-handle-your-data": "我們如何處理您的數據",
|
||||
"we-only-use-the-essential-data-needed-to-run-your-tasks": "我們僅使用運行您的任務所需的關鍵數據",
|
||||
"how-we-handle-your-data-line-1": "我們僅使用運行您的任務所需的關鍵數據",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent 可能會擷取您的螢幕截圖以分析 UI 元素、讀取文字並判斷下一步操作,就像您一樣。",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent 可能會使用您的滑鼠和鍵盤存取您指定的本地軟體和檔案。",
|
||||
"how-we-handle-your-data-line-1-line-3": "僅有最少量的任務數據會傳送至 AI 模型供應商或您啟用的第三方整合;我們與這些供應商沒有數據保留協議。",
|
||||
"how-we-handle-your-data-line-2": "任務檔案、輸出和螢幕截圖會保留在您指定的任務資料夾中。",
|
||||
"how-we-handle-your-data-line-3": "憑證儲存在本地,經過加密,僅用於核准的步驟。",
|
||||
"how-we-handle-your-data-line-4": "您的數據永遠不會用於訓練我們的 AI 模型,除非您明確同意。",
|
||||
"how-we-handle-your-data-line-5": "我們不會將您的數據出售給第三方。",
|
||||
"enable-privacy-permissions-settings": "啟用隱私權限設定",
|
||||
"enable-privacy-permissions-settings-description": "透過啟用此功能,您即表示已閱讀並同意我們的隱私權政策,關於您的任務數據如何被收集、處理和保護。",
|
||||
"data-privacy": "數據隱私",
|
||||
"data-privacy-description": "Eigent 以本地優先原則確保您的隱私。您的數據預設儲存在您的設備上。雲端功能是可選的,僅使用最少量的數據來實現功能。詳細資訊請參閱我們的",
|
||||
"privacy-policy": "隱私政策",
|
||||
"how-we-handle-your-data": "我們如何處理您的數據",
|
||||
"we-only-use-the-essential-data-needed-to-run-your-tasks": "我們僅使用運行您的任務所需的關鍵數據",
|
||||
"how-we-handle-your-data-line-1": "我們僅使用運行您的任務所需的關鍵數據",
|
||||
"how-we-handle-your-data-line-1-line-1": "Eigent 可能會擷取您的螢幕截圖以分析 UI 元素、讀取文字並判斷下一步操作,就像您一樣。",
|
||||
"how-we-handle-your-data-line-1-line-2": "Eigent 可能會使用您的滑鼠和鍵盤存取您指定的本地軟體和檔案。",
|
||||
"how-we-handle-your-data-line-1-line-3": "僅有最少量的任務數據會傳送至 AI 模型供應商或您啟用的第三方整合;我們與這些供應商沒有數據保留協議。",
|
||||
"how-we-handle-your-data-line-2": "任務檔案、輸出和螢幕截圖會保留在您指定的任務資料夾中。",
|
||||
"how-we-handle-your-data-line-3": "憑證儲存在本地,經過加密,僅用於核准的步驟。",
|
||||
"how-we-handle-your-data-line-4": "您的數據永遠不會用於訓練我們的 AI 模型,除非您明確同意。",
|
||||
"how-we-handle-your-data-line-5": "我們不會將您的數據出售給第三方。",
|
||||
"enable-privacy-permissions-settings": "啟用隱私權限設定",
|
||||
"enable-privacy-permissions-settings-description": "透過啟用此功能,您即表示已閱讀並同意我們的隱私權政策,關於您的任務數據如何被收集、處理和保護。",
|
||||
|
||||
"api-key-can-not-be-empty": "API 金鑰不可為空!",
|
||||
"api-host-can-not-be-empty": "API Host 不可為空!",
|
||||
"model-type-can-not-be-empty": "模型類型不可為空!",
|
||||
"validate-success": "驗證成功",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "該模型已驗證支援函數呼叫,這是使用 Eigent 所必需的。",
|
||||
"validate-failed": "驗證失敗",
|
||||
"copy": "複製",
|
||||
"copied-to-clipboard": "已複製到剪貼板",
|
||||
"endpoint-url-can-not-be-empty": "端點 URL 不可為空!",
|
||||
"verification-failed-please-check-endpoint-url": "驗證失敗,請檢查端點 URL",
|
||||
"eigent-cloud-version": "Eigent 雲端版本",
|
||||
"you-are-currently-subscribed-to-the": "您目前訂閱的是",
|
||||
"discover-more-about-our": "了解更多關於我們的",
|
||||
"pricing-options": "定價選項",
|
||||
"credits": "點數",
|
||||
"select-model-type": "選擇模型類型",
|
||||
"gpt-4.1-mini": "GPT-4.1 mini: 更低成本,更快回應,但輸出品質降低。",
|
||||
"gpt-4.1": "GPT-4.1: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"gpt-5": "GPT-5: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"gpt-5-mini": "GPT-5 mini: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"gpt-5-nano": "GPT-5 nano: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"custom-model": "自訂模型",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "使用您自己的 API 金鑰或設定本地模型。",
|
||||
"verify": "驗證",
|
||||
"local-model": "本地模型",
|
||||
"model-platform": "模型平台",
|
||||
"model-endpoint-url": "模型端點 URL",
|
||||
"model-type": "模型類型",
|
||||
"enter-your-local-model-type": "輸入您的本地模型類型",
|
||||
"you-are-on-selft-host-mode": "您正在自託管模式",
|
||||
"you-are-using-self-hosted-mode": "您正在使用自託管模式。為了獲得最佳效能,請在 \"MCP and Tools\" 中輸入 Google Search Key 以確保 Eigent 正常運作。",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Google Search Key 對提供準確的搜尋結果至關重要。Exa Search Key 是可選的,但強烈建議使用以獲得更好的效能。",
|
||||
"close": "關閉",
|
||||
"enter-your-api-key": "輸入您的 API",
|
||||
"key": "金鑰",
|
||||
"enter-your-api-host": "輸入您的 API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "輸入您的模型類型",
|
||||
"verifying": "驗證中...",
|
||||
"mcp-and-tools": "MCP & 工具",
|
||||
"add-mcp-server": "新增 MCP 伺服器",
|
||||
"market": "市場",
|
||||
"tools": "工具",
|
||||
"added-external-servers": "已新增外部伺服器",
|
||||
"loading": "載入中...",
|
||||
"no-mcp-servers": "沒有 MCP 伺服器",
|
||||
"environmental-variables-required": "環境變數要求",
|
||||
"load-failed": "載入失敗",
|
||||
"save-failed": "儲存失敗",
|
||||
"invalid-json": "無效的 JSON",
|
||||
"coming-soon": "即將推出",
|
||||
"uninstall": "解除安裝",
|
||||
"install": "安裝",
|
||||
"add-your-agent": "新增您的智能體",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "透過提供有效的 JSON 設定新增本地 MCP 伺服器。",
|
||||
"learn-more": "了解更多",
|
||||
"installing": "安裝中...",
|
||||
"edit-mcp-config": "編輯 MCP 設定",
|
||||
"name": "名稱",
|
||||
"description": "描述",
|
||||
"command": "指令",
|
||||
"args-one-per-line": "參數 (每行一個)",
|
||||
"cancel": "取消",
|
||||
"save": "儲存",
|
||||
"confirm-delete": "確認刪除",
|
||||
"are-you-sure-you-want-to-delete": "您確定要刪除",
|
||||
"deleting": "刪除中...",
|
||||
"delete": "刪除",
|
||||
"configure {name} Toolkit": "設定 {{name}} 工具組",
|
||||
"get-it-from": "從此處取得",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "連接",
|
||||
"setting": "設定",
|
||||
"all": "全部",
|
||||
"mcp-market": "MCP 市場",
|
||||
"no-mcp-services": "沒有 MCP 服務",
|
||||
"loading-more": "載入更多...",
|
||||
"no-more-mcp-servers": "沒有更多 MCP 伺服器",
|
||||
"search-mcp": "搜尋 MCP",
|
||||
"installed": "已安裝",
|
||||
"worker-name-cannot-be-empty": "Worker 名稱不可為空",
|
||||
"worker-name-already-exists": "Worker 名稱已存在",
|
||||
"warning-google-search-not-configured": "警告:Google 搜尋未設定",
|
||||
"search-functionality-may-be-limited-without-google-api": "沒有 Google API 金鑰和搜尋引擎 ID,搜尋功能可能受限。您可以在 MCP & 工具設定中設定這些。",
|
||||
"search-engine": "搜尋引擎",
|
||||
"allow-agent-to-take-screenshots": "允許智能體截圖",
|
||||
"allow-agent-to-take-screenshots-description": "允許智能體擷取您電腦螢幕的截圖。這可用於支援、診斷或監控目的。截圖可能包含可見的個人資訊,請謹慎啟用。",
|
||||
"allow-agent-to-access-local-software": "允許智能體存取本地軟體",
|
||||
"allow-agent-to-access-local-software-description": "授予智能體與您本地機器上安裝的軟體互動和使用的權限。這對於故障排除、執行診斷或執行特定任務可能是必要的。",
|
||||
"allow-agent-to-access-your-address": "允許智能體存取您的地址",
|
||||
"allow-agent-to-access-your-address-description": "授權智能體檢視和使用您的位置或地址詳細資訊。這對於基於位置的服務或個人化支援可能是必需的。",
|
||||
"password-storage": "密碼儲存",
|
||||
"password-storage-description": "確定如何處理和儲存密碼。您可以選擇在裝置或應用程式中安全儲存密碼,或選擇每次手動輸入。所有儲存的密碼都經過加密。",
|
||||
"notion-mcp-installed-successfully": "Notion MCP 安裝成功",
|
||||
"failed-to-install-notion-mcp": "Notion MCP 安裝失敗",
|
||||
"google-calendar-installed-successfully": "Google Calendar 安裝成功",
|
||||
"failed-to-install-google-calendar": "Google Calendar 安裝失敗",
|
||||
"notion-workspace-integration": "Notion 工作區整合,用於讀取和管理 Notion 頁面",
|
||||
"google-calendar-integration": "Google Calendar 整合,用於管理事件和行程安排",
|
||||
"mcp-server-already-exists": "MCP 伺服器 \"{{name}}\" 已存在",
|
||||
"google-search": "Google 搜尋",
|
||||
"select-default-search-engine": "選擇預設搜尋引擎",
|
||||
"your-own-mcps": "您自己的 MCP",
|
||||
"get-google-search-api": "取得 Google 搜尋 API",
|
||||
"get-exa-api": "取得 Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "搜尋引擎整合",
|
||||
"configured": "已設定",
|
||||
"incomplete": "不完整",
|
||||
"not-configured": "未設定",
|
||||
"saving": "儲存中...",
|
||||
"save-changes": "儲存變更",
|
||||
"enable": "啟用",
|
||||
"search": "搜尋",
|
||||
"test-connection": "測試連線",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "您的API金鑰安全儲存,從不與外部共享。",
|
||||
"this-service-is-public-and-does-not-require-credentials": "此服務是公開的,不需要憑證。",
|
||||
"this-service-does-not-require-an-api-key": "此服務不需要API金鑰。",
|
||||
"connection-test-successful": "連線測試成功!",
|
||||
"connection-test-failed": "連線測試失敗。",
|
||||
"configuration-saved-successfully": "設定儲存成功!",
|
||||
"failed-to-save-configuration": "儲存設定失敗。",
|
||||
"api-key-can-not-be-empty": "API 金鑰不可為空!",
|
||||
"api-host-can-not-be-empty": "API Host 不可為空!",
|
||||
"model-type-can-not-be-empty": "模型類型不可為空!",
|
||||
"validate-success": "驗證成功",
|
||||
"the-model-has-been-verified-to-support-function-calling-which-is-required-to-use-eigent": "該模型已驗證支援函數呼叫,這是使用 Eigent 所必需的。",
|
||||
"validate-failed": "驗證失敗",
|
||||
"copy": "複製",
|
||||
"copied-to-clipboard": "已複製到剪貼板",
|
||||
"endpoint-url-can-not-be-empty": "端點 URL 不可為空!",
|
||||
"verification-failed-please-check-endpoint-url": "驗證失敗,請檢查端點 URL",
|
||||
"eigent-cloud-version": "Eigent 雲端版本",
|
||||
"you-are-currently-subscribed-to-the": "您目前訂閱的是",
|
||||
"discover-more-about-our": "了解更多關於我們的",
|
||||
"pricing-options": "定價選項",
|
||||
"credits": "點數",
|
||||
"select-model-type": "選擇模型類型",
|
||||
"gpt-4.1-mini": "GPT-4.1 mini: 更低成本,更快回應,但輸出品質降低。",
|
||||
"gpt-4.1": "GPT-4.1: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"claude-opus-4.1": "Claude Opus 4.1: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"gpt-5": "GPT-5: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"gpt-5-mini": "GPT-5 mini: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"gpt-5-nano": "GPT-5 nano: 更高成本,更慢回應,但更高品質和推理。",
|
||||
"custom-model": "自訂模型",
|
||||
"use-your-own-api-keys-or-set-up-a-local-model": "使用您自己的 API 金鑰或設定本地模型。",
|
||||
"verify": "驗證",
|
||||
"local-model": "本地模型",
|
||||
"model-platform": "模型平台",
|
||||
"model-endpoint-url": "模型端點 URL",
|
||||
"model-type": "模型類型",
|
||||
"enter-your-local-model-type": "輸入您的本地模型類型",
|
||||
"you-are-on-selft-host-mode": "您正在自託管模式",
|
||||
"you-are-using-self-hosted-mode": "您正在使用自託管模式。為了獲得最佳效能,請在 \"MCP and Tools\" 中輸入 Google Search Key 以確保 Eigent 正常運作。",
|
||||
"the-google-search-key-is-essential-for-delivering-accurate-search-results": "Google Search Key 對提供準確的搜尋結果至關重要。Exa Search Key 是可選的,但強烈建議使用以獲得更好的效能。",
|
||||
"close": "關閉",
|
||||
"enter-your-api-key": "輸入您的 API",
|
||||
"key": "金鑰",
|
||||
"enter-your-api-host": "輸入您的 API Host",
|
||||
"url": "URL",
|
||||
"enter-your-model-type": "輸入您的模型類型",
|
||||
"verifying": "驗證中...",
|
||||
"mcp-and-tools": "MCP & 工具",
|
||||
"add-mcp-server": "新增 MCP 伺服器",
|
||||
"market": "市場",
|
||||
"tools": "工具",
|
||||
"added-external-servers": "已新增外部伺服器",
|
||||
"loading": "載入中...",
|
||||
"no-mcp-servers": "沒有 MCP 伺服器",
|
||||
"environmental-variables-required": "環境變數要求",
|
||||
"load-failed": "載入失敗",
|
||||
"save-failed": "儲存失敗",
|
||||
"invalid-json": "無效的 JSON",
|
||||
"coming-soon": "即將推出",
|
||||
"uninstall": "解除安裝",
|
||||
"install": "安裝",
|
||||
"add-your-agent": "新增您的智能體",
|
||||
"add-a-local-mcp-server-by-providing-a-valid-json-configuration": "透過提供有效的 JSON 設定新增本地 MCP 伺服器。",
|
||||
"learn-more": "了解更多",
|
||||
"installing": "安裝中...",
|
||||
"edit-mcp-config": "編輯 MCP 設定",
|
||||
"name": "名稱",
|
||||
"description": "描述",
|
||||
"command": "指令",
|
||||
"args-one-per-line": "參數 (每行一個)",
|
||||
"cancel": "取消",
|
||||
"save": "儲存",
|
||||
"confirm-delete": "確認刪除",
|
||||
"are-you-sure-you-want-to-delete": "您確定要刪除",
|
||||
"deleting": "刪除中...",
|
||||
"delete": "刪除",
|
||||
"configure {name} Toolkit": "設定 {{name}} 工具組",
|
||||
"get-it-from": "從此處取得",
|
||||
"google-custom-search-api": "Google Custom Search API",
|
||||
"google-cloud-console": "Google Cloud Console",
|
||||
"connect": "連接",
|
||||
"setting": "設定",
|
||||
"all": "全部",
|
||||
"mcp-market": "MCP 市場",
|
||||
"no-mcp-services": "沒有 MCP 服務",
|
||||
"loading-more": "載入更多...",
|
||||
"no-more-mcp-servers": "沒有更多 MCP 伺服器",
|
||||
"search-mcp": "搜尋 MCP",
|
||||
"installed": "已安裝",
|
||||
"worker-name-cannot-be-empty": "Worker 名稱不可為空",
|
||||
"worker-name-already-exists": "Worker 名稱已存在",
|
||||
"warning-google-search-not-configured": "警告:Google 搜尋未設定",
|
||||
"search-functionality-may-be-limited-without-google-api": "沒有 Google API 金鑰和搜尋引擎 ID,搜尋功能可能受限。您可以在 MCP & 工具設定中設定這些。",
|
||||
"search-engine": "搜尋引擎",
|
||||
"allow-agent-to-take-screenshots": "允許智能體截圖",
|
||||
"allow-agent-to-take-screenshots-description": "允許智能體擷取您電腦螢幕的截圖。這可用於支援、診斷或監控目的。截圖可能包含可見的個人資訊,請謹慎啟用。",
|
||||
"allow-agent-to-access-local-software": "允許智能體存取本地軟體",
|
||||
"allow-agent-to-access-local-software-description": "授予智能體與您本地機器上安裝的軟體互動和使用的權限。這對於故障排除、執行診斷或執行特定任務可能是必要的。",
|
||||
"allow-agent-to-access-your-address": "允許智能體存取您的地址",
|
||||
"allow-agent-to-access-your-address-description": "授權智能體檢視和使用您的位置或地址詳細資訊。這對於基於位置的服務或個人化支援可能是必需的。",
|
||||
"password-storage": "密碼儲存",
|
||||
"password-storage-description": "確定如何處理和儲存密碼。您可以選擇在裝置或應用程式中安全儲存密碼,或選擇每次手動輸入。所有儲存的密碼都經過加密。",
|
||||
"notion-mcp-installed-successfully": "Notion MCP 安裝成功",
|
||||
"failed-to-install-notion-mcp": "Notion MCP 安裝失敗",
|
||||
"google-calendar-installed-successfully": "Google Calendar 安裝成功",
|
||||
"failed-to-install-google-calendar": "Google Calendar 安裝失敗",
|
||||
"notion-workspace-integration": "Notion 工作區整合,用於讀取和管理 Notion 頁面",
|
||||
"google-calendar-integration": "Google Calendar 整合,用於管理事件和行程安排",
|
||||
"mcp-server-already-exists": "MCP 伺服器 \"{{name}}\" 已存在",
|
||||
"google-search": "Google 搜尋",
|
||||
"select-default-search-engine": "選擇預設搜尋引擎",
|
||||
"your-own-mcps": "您自己的 MCP",
|
||||
"get-google-search-api": "取得 Google 搜尋 API",
|
||||
"get-exa-api": "取得 Exa API",
|
||||
"exa-ai": "Exa AI",
|
||||
"search-engine-integrations": "搜尋引擎整合",
|
||||
"configured": "已設定",
|
||||
"incomplete": "不完整",
|
||||
"not-configured": "未設定",
|
||||
"saving": "儲存中...",
|
||||
"save-changes": "儲存變更",
|
||||
"enable": "啟用",
|
||||
"search": "搜尋",
|
||||
"test-connection": "測試連線",
|
||||
"your-api-keys-are-stored-securely-and-never-shared-externally": "您的API金鑰安全儲存,從不與外部共享。",
|
||||
"this-service-is-public-and-does-not-require-credentials": "此服務是公開的,不需要憑證。",
|
||||
"this-service-does-not-require-an-api-key": "此服務不需要API金鑰。",
|
||||
"connection-test-successful": "連線測試成功!",
|
||||
"connection-test-failed": "連線測試失敗。",
|
||||
"configuration-saved-successfully": "設定儲存成功!",
|
||||
"failed-to-save-configuration": "儲存設定失敗。",
|
||||
"recommended": "推薦",
|
||||
"reset": "重設",
|
||||
"reset-success": "重設成功!",
|
||||
"reset-failed": "重設失敗!",
|
||||
|
||||
"network-proxy": "網路代理",
|
||||
"network-proxy-description": "設定網路請求的代理伺服器。如果您需要透過代理存取外部 API,這將非常有用。",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "代理設定已儲存。請重新啟動應用程式以套用變更。",
|
||||
"proxy-save-failed": "儲存代理設定失敗。",
|
||||
"proxy-invalid-url": "無效的代理 URL。必須以 http://、https://、socks4:// 或 socks5:// 開頭。",
|
||||
"proxy-restart-hint": "需要重新啟動應用程式以套用代理變更。"
|
||||
"network-proxy": "網路代理",
|
||||
"network-proxy-description": "設定網路請求的代理伺服器。如果您需要透過代理存取外部 API,這將非常有用。",
|
||||
"proxy-placeholder": "http://127.0.0.1:7890",
|
||||
"proxy-saved-restart-required": "代理設定已儲存。請重新啟動應用程式以套用變更。",
|
||||
"proxy-save-failed": "儲存代理設定失敗。",
|
||||
"proxy-invalid-url": "無效的代理 URL。必須以 http://、https://、socks4:// 或 socks5:// 開頭。",
|
||||
"proxy-restart-hint": "需要重新啟動應用程式以套用代理變更。"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,186 +12,142 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { Compass } from '@/components/animate-ui/icons/compass';
|
||||
import { Hammer } from '@/components/animate-ui/icons/hammer';
|
||||
import { Settings } from '@/components/animate-ui/icons/settings';
|
||||
import { Sparkle } from '@/components/animate-ui/icons/sparkle';
|
||||
import {
|
||||
MenuToggleGroup,
|
||||
MenuToggleItem,
|
||||
} from '@/components/MenuButton/MenuButton';
|
||||
import AlertDialog from '@/components/ui/alertDialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import WordCarousel from '@/components/ui/WordCarousel';
|
||||
import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
|
||||
import Project from '@/pages/Dashboard/Project';
|
||||
import Setting from '@/pages/Setting';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import Browser from './Dashboard/Browser';
|
||||
import MCP from './Setting/MCP';
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
import { MenuToggleGroup, MenuToggleItem } from "@/components/MenuButton/MenuButton";
|
||||
import Project from "@/pages/Dashboard/Project";
|
||||
import AlertDialog from "@/components/ui/alertDialog";
|
||||
import { Settings } from "@/components/animate-ui/icons/settings";
|
||||
import { Compass } from "@/components/animate-ui/icons/compass";
|
||||
import Setting from "@/pages/Setting";
|
||||
import { Hammer } from "@/components/animate-ui/icons/hammer";
|
||||
import MCP from "./Setting/MCP";
|
||||
import Browser from "./Dashboard/Browser";
|
||||
import WordCarousel from "@/components/ui/WordCarousel";
|
||||
import { Sparkle } from "@/components/animate-ui/icons/sparkle";
|
||||
|
||||
const VALID_TABS = [
|
||||
'projects',
|
||||
'workers',
|
||||
'trigger',
|
||||
'settings',
|
||||
'mcp_tools',
|
||||
'browser',
|
||||
'projects',
|
||||
'workers',
|
||||
'trigger',
|
||||
'settings',
|
||||
'mcp_tools',
|
||||
'browser',
|
||||
] as const;
|
||||
|
||||
type TabType = (typeof VALID_TABS)[number];
|
||||
|
||||
export default function Home() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { chatStore, projectStore } = useChatStoreAdapter();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { chatStore, projectStore } = useChatStoreAdapter();
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const { username, email } = useAuthStore();
|
||||
const displayName = username ?? email ?? "";
|
||||
|
||||
// Compute activeTab from URL, fallback to 'projects' if not in URL or invalid
|
||||
const activeTab = useMemo(() => {
|
||||
const tabFromUrl = searchParams.get('tab');
|
||||
if (tabFromUrl && VALID_TABS.includes(tabFromUrl as TabType)) {
|
||||
return tabFromUrl as TabType;
|
||||
}
|
||||
return 'projects' as TabType;
|
||||
}, [searchParams]);
|
||||
// Compute activeTab from URL, fallback to 'projects' if not in URL or invalid
|
||||
const activeTab = useMemo(() => {
|
||||
const tabFromUrl = searchParams.get('tab');
|
||||
if (tabFromUrl && VALID_TABS.includes(tabFromUrl as TabType)) {
|
||||
return tabFromUrl as TabType;
|
||||
}
|
||||
return 'projects' as TabType;
|
||||
}, [searchParams]);
|
||||
|
||||
const handleTabChange = (value: string) => {
|
||||
if (value) {
|
||||
navigate(`?tab=${value}`, { replace: true });
|
||||
}
|
||||
};
|
||||
const handleTabChange = (value: string) => {
|
||||
if (value) {
|
||||
navigate(`?tab=${value}`, { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const { username, email } = useAuthStore();
|
||||
const displayName = username ?? email ?? '';
|
||||
const formatWelcomeName = (raw: string): string => {
|
||||
if (!raw) return "";
|
||||
if (/^[^@]+@gmail\.com$/i.test(raw)) {
|
||||
const local = raw.split("@")[0];
|
||||
const pretty = local.replace(/[._-]+/g, " ").trim();
|
||||
return pretty
|
||||
.split(/\s+/)
|
||||
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join(" ");
|
||||
}
|
||||
return raw;
|
||||
};
|
||||
|
||||
if (!chatStore || !projectStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
const welcomeName = formatWelcomeName(displayName);
|
||||
|
||||
const formatWelcomeName = (raw: string): string => {
|
||||
if (!raw) return '';
|
||||
if (/^[^@]+@gmail\.com$/i.test(raw)) {
|
||||
const local = raw.split('@')[0];
|
||||
const pretty = local.replace(/[._-]+/g, ' ').trim();
|
||||
return pretty
|
||||
.split(/\s+/)
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
return raw;
|
||||
};
|
||||
const confirmDelete = () => {
|
||||
setDeleteModalOpen(false);
|
||||
};
|
||||
|
||||
const welcomeName = formatWelcomeName(displayName);
|
||||
// create task
|
||||
const createChat = () => {
|
||||
//Handles refocusing id & non duplicate logic internally
|
||||
projectStore?.createProject("new project");
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
const _handleAnimationComplete = () => {
|
||||
console.log('All letters have animated!');
|
||||
};
|
||||
if (!chatStore || !projectStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const confirmDelete = () => {
|
||||
setDeleteModalOpen(false);
|
||||
};
|
||||
|
||||
// create task
|
||||
const createChat = () => {
|
||||
//Handles refocusing id & non duplicate logic internally
|
||||
projectStore.createProject('new project');
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className="scrollbar-hide mx-auto h-full overflow-y-auto"
|
||||
>
|
||||
{/* alert dialog */}
|
||||
<AlertDialog
|
||||
isOpen={deleteModalOpen}
|
||||
onClose={() => setDeleteModalOpen(false)}
|
||||
onConfirm={confirmDelete}
|
||||
title={t('layout.delete-task')}
|
||||
message={t('layout.delete-task-confirmation')}
|
||||
confirmText={t('layout.delete')}
|
||||
cancelText={t('layout.cancel')}
|
||||
/>
|
||||
{/* welcome text */}
|
||||
<div className="flex w-full flex-row bg-gradient-to-b from-transparent to-[#F9F8F6] px-20 pt-16">
|
||||
<WordCarousel
|
||||
words={[`${t('layout.welcome')}, ${welcomeName} !`]}
|
||||
className="text-heading-xl font-bold tracking-tight"
|
||||
rotateIntervalMs={100}
|
||||
sweepDurationMs={2000}
|
||||
sweepOnce
|
||||
gradient={`linear-gradient(in oklch 90deg,
|
||||
return (
|
||||
<div ref={scrollContainerRef} className="h-full overflow-y-auto scrollbar-hide mx-auto">
|
||||
{/* alert dialog */}
|
||||
<AlertDialog
|
||||
isOpen={deleteModalOpen}
|
||||
onClose={() => setDeleteModalOpen(false)}
|
||||
onConfirm={confirmDelete}
|
||||
title={t("layout.delete-task")}
|
||||
message={t("layout.delete-task-confirmation")}
|
||||
confirmText={t("layout.delete")}
|
||||
cancelText={t("layout.cancel")}
|
||||
/>
|
||||
{/* welcome text */}
|
||||
<div className="flex flex-row w-full pt-16 px-20 bg-gradient-to-b from-transparent to-background">
|
||||
<WordCarousel
|
||||
words={[`${t("layout.welcome")}, ${welcomeName} !`]}
|
||||
className="text-heading-xl font-bold tracking-tight"
|
||||
rotateIntervalMs={100}
|
||||
sweepDurationMs={2000}
|
||||
sweepOnce
|
||||
gradient={`linear-gradient(in oklch 90deg,
|
||||
#f9f8f6 0%, var(--colors-blue-300) 30%,
|
||||
var(--colors-emerald-default) 50%,
|
||||
var(--colors-emerald-default) 50%,
|
||||
var(--colors-green-500) 70%,
|
||||
var(--colors-orange-300) 100%)`}
|
||||
ariaLabel="rotating headline"
|
||||
/>
|
||||
</div>
|
||||
{/* Navbar */}
|
||||
<div
|
||||
className={`sticky top-0 z-20 flex flex-col items-center justify-between border-x-0 border-t-0 border-solid border-border-disabled bg-[#F9F8F6] px-20 pb-4 pt-10`}
|
||||
>
|
||||
<div className="mx-auto flex w-full flex-row items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<MenuToggleGroup
|
||||
type="single"
|
||||
value={activeTab}
|
||||
orientation="horizontal"
|
||||
onValueChange={handleTabChange}
|
||||
>
|
||||
<MenuToggleItem
|
||||
size="xs"
|
||||
value="projects"
|
||||
iconAnimateOnHover="wiggle"
|
||||
icon={<Sparkle />}
|
||||
>
|
||||
{t('layout.projects')}
|
||||
</MenuToggleItem>
|
||||
<MenuToggleItem
|
||||
size="xs"
|
||||
value="mcp_tools"
|
||||
iconAnimateOnHover="default"
|
||||
icon={<Hammer />}
|
||||
>
|
||||
{t('layout.mcp-tools')}
|
||||
</MenuToggleItem>
|
||||
<MenuToggleItem
|
||||
size="xs"
|
||||
value="browser"
|
||||
iconAnimateOnHover="default"
|
||||
icon={<Compass />}
|
||||
>
|
||||
{t('layout.browser')}
|
||||
</MenuToggleItem>
|
||||
<MenuToggleItem
|
||||
size="xs"
|
||||
value="settings"
|
||||
iconAnimateOnHover="default"
|
||||
icon={<Settings />}
|
||||
>
|
||||
{t('layout.settings')}
|
||||
</MenuToggleItem>
|
||||
</MenuToggleGroup>
|
||||
</div>
|
||||
<Button variant="primary" size="sm" onClick={createChat}>
|
||||
<Plus />
|
||||
{t('layout.new-project')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === 'projects' && <Project />}
|
||||
{activeTab === 'mcp_tools' && <MCP />}
|
||||
{activeTab === 'browser' && <Browser />}
|
||||
{activeTab === 'settings' && <Setting />}
|
||||
</div>
|
||||
);
|
||||
ariaLabel="rotating headline"
|
||||
/>
|
||||
</div>
|
||||
{/* Navbar */}
|
||||
<div
|
||||
className={`sticky top-0 z-20 flex flex-col justify-between items-center bg-background px-20 pt-10 pb-4 border-border-disabled border-x-0 border-t-0 border-solid`}
|
||||
>
|
||||
<div className="flex flex-row justify-between items-center w-full mx-auto">
|
||||
<div className="flex items-center gap-2">
|
||||
<MenuToggleGroup type="single" value={activeTab} orientation="horizontal" onValueChange={handleTabChange}>
|
||||
<MenuToggleItem size="xs" value="projects" iconAnimateOnHover="wiggle" icon={<Sparkle/>}>{t("layout.projects")}</MenuToggleItem>
|
||||
<MenuToggleItem size="xs" value="mcp_tools" iconAnimateOnHover="default" icon={<Hammer/>}>{t("layout.mcp-tools")}</MenuToggleItem>
|
||||
<MenuToggleItem size="xs" value="browser" iconAnimateOnHover="default" icon={<Compass/>}>{t("layout.browser")}</MenuToggleItem>
|
||||
<MenuToggleItem size="xs" value="settings" iconAnimateOnHover="default" icon={<Settings/>}>{t("layout.settings")}</MenuToggleItem>
|
||||
</MenuToggleGroup>
|
||||
</div>
|
||||
<Button variant="primary" size="sm" onClick={createChat}>
|
||||
<Plus />
|
||||
{t("layout.new-project")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === "projects" && <Project />}
|
||||
{activeTab === "mcp_tools" && <MCP />}
|
||||
{activeTab === "browser" && <Browser />}
|
||||
{activeTab === "settings" && <Setting />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,307 +12,302 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import BottomBar from '@/components/BottomBar';
|
||||
import BrowserAgentWorkSpace from '@/components/BrowserAgentWorkSpace';
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import Folder from '@/components/Folder';
|
||||
import TerminalAgentWrokSpace from '@/components/TerminalAgentWrokSpace';
|
||||
import ChatBox from "@/components/ChatBox";
|
||||
import Workflow from "@/components/WorkFlow";
|
||||
import Folder from "@/components/Folder";
|
||||
import Terminal from "@/components/Terminal";
|
||||
import useChatStoreAdapter from "@/hooks/useChatStoreAdapter";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { ReactFlowProvider } from "@xyflow/react";
|
||||
import BottomBar from "@/components/BottomBar";
|
||||
import BrowserAgentWorkSpace from "@/components/BrowserAgentWorkSpace";
|
||||
import TerminalAgentWrokSpace from "@/components/TerminalAgentWrokSpace";
|
||||
import UpdateElectron from "@/components/update";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@/components/ui/resizable';
|
||||
import UpdateElectron from '@/components/update';
|
||||
import Workflow from '@/components/WorkFlow';
|
||||
import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
|
||||
import { ReactFlowProvider } from '@xyflow/react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable"
|
||||
|
||||
export default function Home() {
|
||||
//Get Chatstore for the active project's task
|
||||
const { chatStore, projectStore } = useChatStoreAdapter();
|
||||
//Get Chatstore for the active project's task
|
||||
const { chatStore, projectStore } = useChatStoreAdapter();
|
||||
|
||||
const [_activeWebviewId, setActiveWebviewId] = useState<string | null>(null);
|
||||
const [_activeWebviewId, setActiveWebviewId] = useState<string | null>(null);
|
||||
|
||||
// Add webview-show listener in useEffect with cleanup
|
||||
useEffect(() => {
|
||||
const handleWebviewShow = (_event: any, id: string) => {
|
||||
setActiveWebviewId(id);
|
||||
};
|
||||
// Add webview-show listener in useEffect with cleanup
|
||||
useEffect(() => {
|
||||
const handleWebviewShow = (_event: any, id: string) => {
|
||||
setActiveWebviewId(id);
|
||||
};
|
||||
|
||||
window.ipcRenderer?.on('webview-show', handleWebviewShow);
|
||||
window.ipcRenderer?.on("webview-show", handleWebviewShow);
|
||||
|
||||
// Cleanup: remove listener on unmount
|
||||
return () => {
|
||||
window.ipcRenderer?.off('webview-show', handleWebviewShow);
|
||||
};
|
||||
}, []); // Empty dependency array means this only runs once
|
||||
// Cleanup: remove listener on unmount
|
||||
return () => {
|
||||
window.ipcRenderer?.off("webview-show", handleWebviewShow);
|
||||
};
|
||||
}, []); // Empty dependency array means this only runs once
|
||||
|
||||
// Extract complex dependency to a variable
|
||||
const taskAssigning =
|
||||
chatStore?.tasks[chatStore?.activeTaskId as string]?.taskAssigning;
|
||||
// Extract complex dependency to a variable
|
||||
const taskAssigning =
|
||||
chatStore?.tasks[chatStore?.activeTaskId as string]?.taskAssigning;
|
||||
|
||||
const getSize = useCallback(() => {
|
||||
const webviewContainer = document.getElementById('webview-container');
|
||||
if (webviewContainer) {
|
||||
const rect = webviewContainer.getBoundingClientRect();
|
||||
window.electronAPI.setSize({
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
});
|
||||
console.log('setSize', rect);
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!chatStore) return;
|
||||
|
||||
useEffect(() => {
|
||||
if (!chatStore) return;
|
||||
let taskAssigningArray = [
|
||||
...(taskAssigning || []),
|
||||
];
|
||||
let webviews: { id: string; agent_id: string; index: number }[] = [];
|
||||
taskAssigningArray.map((item) => {
|
||||
if (item.type === "browser_agent") {
|
||||
item.activeWebviewIds?.map((webview, index) => {
|
||||
webviews.push({ ...webview, agent_id: item.agent_id, index });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let taskAssigningArray = [...(taskAssigning || [])];
|
||||
let webviews: { id: string; agent_id: string; index: number }[] = [];
|
||||
taskAssigningArray.map((item) => {
|
||||
if (item.type === 'browser_agent') {
|
||||
item.activeWebviewIds?.map((webview, index) => {
|
||||
webviews.push({ ...webview, agent_id: item.agent_id, index });
|
||||
});
|
||||
}
|
||||
});
|
||||
if (taskAssigningArray.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (taskAssigningArray.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (webviews.length === 0) {
|
||||
const browserAgent = taskAssigningArray.find(agent => agent.type === 'browser_agent');
|
||||
if (browserAgent && browserAgent.activeWebviewIds && browserAgent.activeWebviewIds.length > 0) {
|
||||
browserAgent.activeWebviewIds.forEach((webview, index) => {
|
||||
webviews.push({ ...webview, agent_id: browserAgent.agent_id, index });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (webviews.length === 0) {
|
||||
const browserAgent = taskAssigningArray.find(
|
||||
(agent) => agent.type === 'browser_agent'
|
||||
);
|
||||
if (
|
||||
browserAgent &&
|
||||
browserAgent.activeWebviewIds &&
|
||||
browserAgent.activeWebviewIds.length > 0
|
||||
) {
|
||||
browserAgent.activeWebviewIds.forEach((webview, index) => {
|
||||
webviews.push({ ...webview, agent_id: browserAgent.agent_id, index });
|
||||
});
|
||||
}
|
||||
}
|
||||
if (webviews.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (webviews.length === 0) {
|
||||
return;
|
||||
}
|
||||
// capture webview
|
||||
const captureWebview = async () => {
|
||||
const activeTask = chatStore.tasks[chatStore.activeTaskId as string];
|
||||
if (!activeTask || activeTask.status === "finished") {
|
||||
return;
|
||||
}
|
||||
webviews.map((webview) => {
|
||||
window.ipcRenderer
|
||||
.invoke("capture-webview", webview.id)
|
||||
.then((base64: string) => {
|
||||
const currentTask = chatStore.tasks[chatStore.activeTaskId as string];
|
||||
if (!currentTask || currentTask.type) return;
|
||||
let taskAssigning = [
|
||||
...currentTask.taskAssigning,
|
||||
];
|
||||
const browserAgentIndex = taskAssigning.findIndex(
|
||||
(agent) => agent.agent_id === webview.agent_id
|
||||
);
|
||||
|
||||
// capture webview
|
||||
const captureWebview = async () => {
|
||||
const activeTask = chatStore.tasks[chatStore.activeTaskId as string];
|
||||
if (!activeTask || activeTask.status === 'finished') {
|
||||
return;
|
||||
}
|
||||
webviews.map((webview) => {
|
||||
window.ipcRenderer
|
||||
.invoke('capture-webview', webview.id)
|
||||
.then((base64: string) => {
|
||||
const currentTask =
|
||||
chatStore.tasks[chatStore.activeTaskId as string];
|
||||
if (!currentTask || currentTask.type) return;
|
||||
let taskAssigning = [...currentTask.taskAssigning];
|
||||
const browserAgentIndex = taskAssigning.findIndex(
|
||||
(agent) => agent.agent_id === webview.agent_id
|
||||
);
|
||||
if (
|
||||
browserAgentIndex !== -1 &&
|
||||
base64 !== "data:image/jpeg;base64,"
|
||||
) {
|
||||
taskAssigning[browserAgentIndex].activeWebviewIds![
|
||||
webview.index
|
||||
].img = base64;
|
||||
chatStore.setTaskAssigning(
|
||||
chatStore.activeTaskId as string,
|
||||
taskAssigning
|
||||
);
|
||||
const { processTaskId, url } =
|
||||
taskAssigning[browserAgentIndex].activeWebviewIds![
|
||||
webview.index
|
||||
];
|
||||
chatStore.setSnapshotsTemp(chatStore.activeTaskId as string, {
|
||||
api_task_id: chatStore.activeTaskId,
|
||||
camel_task_id: processTaskId,
|
||||
browser_url: url,
|
||||
image_base64: base64,
|
||||
});
|
||||
|
||||
if (
|
||||
browserAgentIndex !== -1 &&
|
||||
base64 !== 'data:image/jpeg;base64,'
|
||||
) {
|
||||
taskAssigning[browserAgentIndex].activeWebviewIds![
|
||||
webview.index
|
||||
].img = base64;
|
||||
chatStore.setTaskAssigning(
|
||||
chatStore.activeTaskId as string,
|
||||
taskAssigning
|
||||
);
|
||||
const { processTaskId, url } =
|
||||
taskAssigning[browserAgentIndex].activeWebviewIds![
|
||||
webview.index
|
||||
];
|
||||
chatStore.setSnapshotsTemp(chatStore.activeTaskId as string, {
|
||||
api_task_id: chatStore.activeTaskId,
|
||||
camel_task_id: processTaskId,
|
||||
browser_url: url,
|
||||
image_base64: base64,
|
||||
});
|
||||
}
|
||||
// let list: any = [];
|
||||
// taskAssigning.forEach((item: any) => {
|
||||
// item.activeWebviewIds.forEach((item2: any) => {
|
||||
// if (item2.img && item2.url && item2.processTaskId) {
|
||||
// list.push({
|
||||
// api_task_id: chatStore.activeTaskId,
|
||||
// camel_task_id: item2.processTaskId,
|
||||
// browser_url: item2.url,
|
||||
// image_base64: item2.img,
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// chatStore.setSnapshots(chatStore.activeTaskId as string, list);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error('capture webview error:', error);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
// let list: any = [];
|
||||
// taskAssigning.forEach((item: any) => {
|
||||
// item.activeWebviewIds.forEach((item2: any) => {
|
||||
// if (item2.img && item2.url && item2.processTaskId) {
|
||||
// list.push({
|
||||
// api_task_id: chatStore.activeTaskId,
|
||||
// camel_task_id: item2.processTaskId,
|
||||
// browser_url: item2.url,
|
||||
// image_base64: item2.img,
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// chatStore.setSnapshots(chatStore.activeTaskId as string, list);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("capture webview error:", error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let intervalTimer: NodeJS.Timeout | null = null;
|
||||
let intervalTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
const initialTimer = setTimeout(() => {
|
||||
captureWebview();
|
||||
intervalTimer = setInterval(captureWebview, 2000);
|
||||
}, 2000);
|
||||
const initialTimer = setTimeout(() => {
|
||||
captureWebview();
|
||||
intervalTimer = setInterval(captureWebview, 2000);
|
||||
}, 2000);
|
||||
|
||||
// cleanup function
|
||||
return () => {
|
||||
clearTimeout(initialTimer);
|
||||
if (intervalTimer) {
|
||||
clearInterval(intervalTimer);
|
||||
}
|
||||
};
|
||||
}, [chatStore, taskAssigning]);
|
||||
// cleanup function
|
||||
return () => {
|
||||
clearTimeout(initialTimer);
|
||||
if (intervalTimer) {
|
||||
clearInterval(intervalTimer);
|
||||
}
|
||||
};
|
||||
}, [chatStore, taskAssigning]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chatStore) return;
|
||||
const getSize = useCallback(() => {
|
||||
const webviewContainer = document.getElementById("webview-container");
|
||||
if (webviewContainer) {
|
||||
const rect = webviewContainer.getBoundingClientRect();
|
||||
window.electronAPI.setSize({
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
});
|
||||
console.log("setSize", rect);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!chatStore.activeTaskId) {
|
||||
projectStore.createProject('new project');
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!chatStore) return;
|
||||
|
||||
const webviewContainer = document.getElementById('webview-container');
|
||||
if (webviewContainer) {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
getSize();
|
||||
});
|
||||
resizeObserver.observe(webviewContainer);
|
||||
if (!chatStore.activeTaskId) {
|
||||
projectStore?.createProject("new project");
|
||||
}
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}
|
||||
}, [chatStore, projectStore, getSize]);
|
||||
const webviewContainer = document.getElementById("webview-container");
|
||||
if (webviewContainer) {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
getSize();
|
||||
});
|
||||
resizeObserver.observe(webviewContainer);
|
||||
|
||||
if (!chatStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}
|
||||
}, [chatStore, projectStore, getSize]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-row overflow-hidden px-2 pb-2 pt-10">
|
||||
<ReactFlowProvider>
|
||||
<div className="relative flex min-h-0 min-w-0 flex-1 items-center justify-center gap-2 overflow-hidden rounded-2xl border-solid border-border-tertiary bg-surface-secondary">
|
||||
<ResizablePanelGroup direction="horizontal">
|
||||
<ResizablePanel defaultSize={30} minSize={20}>
|
||||
<ChatBox />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle
|
||||
withHandle={true}
|
||||
className="custom-resizable-handle"
|
||||
/>
|
||||
<ResizablePanel>
|
||||
{chatStore.tasks[chatStore.activeTaskId as string]
|
||||
?.activeWorkSpace && (
|
||||
<div className="flex h-full w-full flex-1 flex-col pr-2 duration-300 animate-in fade-in-0 slide-in-from-right-2">
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
)?.type === 'browser_agent' && (
|
||||
<div className="flex h-[calc(100vh-104px)] w-full flex-1 duration-300 animate-in fade-in-0 slide-in-from-right-2">
|
||||
<BrowserAgentWorkSpace />
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[chatStore.activeTaskId as string]
|
||||
?.activeWorkSpace === 'workflow' && (
|
||||
<div className="flex h-full w-full flex-1 items-center justify-center duration-300 animate-in fade-in-0 slide-in-from-right-2">
|
||||
<div className="relative flex h-full w-full flex-col rounded-2xl border border-solid border-transparent p-2">
|
||||
{/*filter blur */}
|
||||
<div className="pointer-events-none absolute inset-0 rounded-xl bg-transparent"></div>
|
||||
<div className="relative z-10 h-full w-full">
|
||||
<Workflow
|
||||
taskAssigning={
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
?.taskAssigning || []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
)?.type === 'developer_agent' && (
|
||||
<div className="flex h-[calc(100vh-104px)] w-full flex-1 duration-300 animate-in fade-in-0 slide-in-from-right-2">
|
||||
<TerminalAgentWrokSpace></TerminalAgentWrokSpace>
|
||||
{/* <Terminal content={[]} /> */}
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace === 'documentWorkSpace' && (
|
||||
<div className="flex h-[calc(100vh-104px)] w-full flex-1 items-center justify-center duration-300 animate-in fade-in-0 slide-in-from-right-2">
|
||||
<div className="relative flex h-[calc(100vh-104px)] w-full flex-col rounded-2xl border border-solid border-zinc-300">
|
||||
{/*filter blur */}
|
||||
<div className="blur-bg bg-white-50 pointer-events-none absolute inset-0 rounded-xl"></div>
|
||||
<div className="relative z-10 h-full w-full">
|
||||
<Folder />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
)?.type === 'document_agent' && (
|
||||
<div className="flex h-[calc(100vh-104px)] w-full flex-1 items-center justify-center duration-300 animate-in fade-in-0 slide-in-from-right-2">
|
||||
<div className="relative flex h-[calc(100vh-104px)] w-full flex-col rounded-2xl border border-solid border-zinc-300">
|
||||
{/*filter blur */}
|
||||
<div className="blur-bg bg-white-50 pointer-events-none absolute inset-0 rounded-xl"></div>
|
||||
<div className="relative z-10 h-full w-full">
|
||||
<Folder
|
||||
data={chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
].activeWorkSpace
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<BottomBar />
|
||||
</div>
|
||||
)}
|
||||
</ResizablePanel>
|
||||
{/* Fixed sidebar on the right
|
||||
if (!chatStore) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full min-h-0 flex flex-row overflow-hidden pt-10 px-2 pb-2">
|
||||
<ReactFlowProvider>
|
||||
<div className="flex-1 min-w-0 min-h-0 flex items-center justify-center bg-surface-secondary border-solid border-border-tertiary rounded-2xl gap-2 relative overflow-hidden">
|
||||
<ResizablePanelGroup direction="horizontal">
|
||||
<ResizablePanel defaultSize={30} minSize={20}>
|
||||
<ChatBox />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle={true} className="custom-resizable-handle" />
|
||||
<ResizablePanel>
|
||||
{chatStore.tasks[chatStore.activeTaskId as string]
|
||||
?.activeWorkSpace && (
|
||||
<div className="w-full h-full flex-1 flex flex-col animate-in fade-in-0 pr-2 slide-in-from-right-2 duration-300">
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
)?.type === "browser_agent" && (
|
||||
<div className="w-full h-[calc(100vh-104px)] flex-1 flex animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<BrowserAgentWorkSpace />
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[chatStore.activeTaskId as string]
|
||||
?.activeWorkSpace === "workflow" && (
|
||||
<div className="w-full h-full flex-1 flex items-center justify-center animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<div className="w-full h-full flex flex-col rounded-2xl border border-transparent border-solid p-2 relative">
|
||||
{/*filter blur */}
|
||||
<div className="absolute inset-0 pointer-events-none bg-transparent rounded-xl"></div>
|
||||
<div className="w-full h-full relative z-10">
|
||||
<Workflow
|
||||
taskAssigning={
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
?.taskAssigning || []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
)?.type === "developer_agent" && (
|
||||
<div className="w-full h-[calc(100vh-104px)] flex-1 flex animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<TerminalAgentWrokSpace></TerminalAgentWrokSpace>
|
||||
{/* <Terminal content={[]} /> */}
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace === "documentWorkSpace" && (
|
||||
<div className="w-full h-[calc(100vh-104px)] flex-1 flex items-center justify-center animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<div className="w-full h-[calc(100vh-104px)] flex flex-col rounded-2xl border border-border-subtle-strong border-solid relative">
|
||||
{/*filter blur */}
|
||||
<div className="absolute inset-0 blur-bg pointer-events-none bg-white-50 rounded-xl"></div>
|
||||
<div className="w-full h-full relative z-10">
|
||||
<Folder />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
)?.type === "document_agent" && (
|
||||
<div className="w-full h-[calc(100vh-104px)] flex-1 flex items-center justify-center animate-in fade-in-0 slide-in-from-right-2 duration-300">
|
||||
<div className="w-full h-[calc(100vh-104px)] flex flex-col rounded-2xl border border-border-subtle-strong border-solid relative">
|
||||
{/*filter blur */}
|
||||
<div className="absolute inset-0 blur-bg pointer-events-none bg-white-50 rounded-xl"></div>
|
||||
<div className="w-full h-full relative z-10">
|
||||
<Folder
|
||||
data={chatStore.tasks[
|
||||
chatStore.activeTaskId as string
|
||||
]?.taskAssigning?.find(
|
||||
(agent) =>
|
||||
agent.agent_id ===
|
||||
chatStore.tasks[chatStore.activeTaskId as string]
|
||||
.activeWorkSpace
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<BottomBar />
|
||||
</div>
|
||||
)}
|
||||
</ResizablePanel>
|
||||
{/* Fixed sidebar on the right
|
||||
<div className="h-full z-30">
|
||||
<SideBar />
|
||||
</div>*/}
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
</ReactFlowProvider>
|
||||
<UpdateElectron />
|
||||
</div>
|
||||
);
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
</ReactFlowProvider>
|
||||
<UpdateElectron />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,109 +12,114 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import vsersionLogo from '@/assets/version-logo.png';
|
||||
import VerticalNavigation, {
|
||||
type VerticalNavItem,
|
||||
} from '@/components/Navigation';
|
||||
import useAppVersion from '@/hooks/use-app-version';
|
||||
import General from '@/pages/Setting/General';
|
||||
import Models from '@/pages/Setting/Models';
|
||||
import Privacy from '@/pages/Setting/Privacy';
|
||||
import { CircleCheck, Fingerprint, Settings, TextSelect } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import VerticalNavigation, { type VerticalNavItem } from "@/components/Navigation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useAppVersion from "@/hooks/use-app-version";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
import logoBlack from '@/assets/logo/logo_black.png';
|
||||
import logoWhite from '@/assets/logo/logo_white.png';
|
||||
import General from "@/pages/Setting/General";
|
||||
import Privacy from "@/pages/Setting/Privacy";
|
||||
import Models from "@/pages/Setting/Models";
|
||||
import MCP from "@/pages/Setting/MCP";
|
||||
import {
|
||||
X,
|
||||
CircleCheck,
|
||||
Settings,
|
||||
Fingerprint,
|
||||
TextSelect,
|
||||
Server,
|
||||
} from "lucide-react";
|
||||
|
||||
export default function Setting() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const version = useAppVersion();
|
||||
const { t } = useTranslation();
|
||||
// Setting menu configuration
|
||||
const settingMenus = [
|
||||
{
|
||||
id: 'general',
|
||||
name: t('setting.general'),
|
||||
icon: Settings,
|
||||
path: '/setting/general',
|
||||
},
|
||||
{
|
||||
id: 'privacy',
|
||||
name: t('setting.privacy'),
|
||||
icon: Fingerprint,
|
||||
path: '/setting/privacy',
|
||||
},
|
||||
{
|
||||
id: 'models',
|
||||
name: t('setting.models'),
|
||||
icon: TextSelect,
|
||||
path: '/setting/models',
|
||||
},
|
||||
];
|
||||
// Initialize tab from URL once, then manage locally without routing
|
||||
const getCurrentTab = () => {
|
||||
const path = location.pathname;
|
||||
const tabFromUrl = path.split('/setting/')[1] || 'general';
|
||||
return settingMenus.find((menu) => menu.id === tabFromUrl)?.id || 'general';
|
||||
};
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const version = useAppVersion();
|
||||
const { appearance } = useAuthStore();
|
||||
const { t } = useTranslation();
|
||||
const logoSrc = appearance === 'dark' ? logoWhite : logoBlack;
|
||||
// Setting menu configuration
|
||||
const settingMenus = [
|
||||
{
|
||||
id: "general",
|
||||
name: t("setting.general"),
|
||||
icon: Settings,
|
||||
path: "/setting/general",
|
||||
},
|
||||
{
|
||||
id: "privacy",
|
||||
name: t("setting.privacy"),
|
||||
icon: Fingerprint,
|
||||
path: "/setting/privacy",
|
||||
},
|
||||
{
|
||||
id: "models",
|
||||
name: t("setting.models"),
|
||||
icon: TextSelect,
|
||||
path: "/setting/models",
|
||||
},
|
||||
];
|
||||
// Initialize tab from URL once, then manage locally without routing
|
||||
const getCurrentTab = () => {
|
||||
const path = location.pathname;
|
||||
const tabFromUrl = path.split("/setting/")[1] || "general";
|
||||
return settingMenus.find((menu) => menu.id === tabFromUrl)?.id || "general";
|
||||
};
|
||||
|
||||
const [activeTab, setActiveTab] = useState(getCurrentTab);
|
||||
const [activeTab, setActiveTab] = useState(getCurrentTab);
|
||||
|
||||
// Switch tabs locally (no navigation)
|
||||
const handleTabChange = (tabId: string) => {
|
||||
setActiveTab(tabId);
|
||||
};
|
||||
// Switch tabs locally (no navigation)
|
||||
const handleTabChange = (tabId: string) => {
|
||||
setActiveTab(tabId);
|
||||
};
|
||||
|
||||
// Close settings page
|
||||
const _handleClose = () => {
|
||||
navigate('/');
|
||||
};
|
||||
// Close settings page
|
||||
const handleClose = () => {
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="m-auto flex h-auto max-w-[900px] flex-col px-4 py-4">
|
||||
<div className="flex h-auto w-full px-3">
|
||||
<div className="sticky top-20 flex !w-[222px] flex-shrink-0 flex-grow-0 flex-col self-start pr-4 pt-md">
|
||||
<VerticalNavigation
|
||||
items={
|
||||
settingMenus.map((menu) => {
|
||||
const _Icon = menu.icon;
|
||||
return {
|
||||
value: menu.id,
|
||||
label: (
|
||||
<span className="text-sm font-bold leading-13">
|
||||
{menu.name}
|
||||
</span>
|
||||
),
|
||||
};
|
||||
}) as VerticalNavItem[]
|
||||
}
|
||||
value={activeTab}
|
||||
onValueChange={handleTabChange}
|
||||
className="h-full min-h-0 w-full flex-1 gap-0"
|
||||
listClassName="w-full h-full overflow-y-auto"
|
||||
contentClassName="hidden"
|
||||
/>
|
||||
<div className="mt-8 flex w-full flex-shrink-0 flex-grow-0 items-center justify-center border-[0px] border-t border-solid border-white-80% pb-2 pt-4">
|
||||
<div className="flex items-center gap-1 leading-9">
|
||||
<img src={vsersionLogo} alt="version-logo" className="h-6" />
|
||||
</div>
|
||||
<div className="bg-bg-surface-tertiary flex items-center justify-center gap-1 rounded-full px-sm py-0.5">
|
||||
<CircleCheck className="text-bg-fill-success-primary h-4 w-4" />
|
||||
<div className="text-primary text-xs font-bold leading-17">
|
||||
{version}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className="max-w-[900px] h-auto px-4 m-auto flex flex-col py-4">
|
||||
<div className="w-full h-auto flex px-3">
|
||||
<div className="!w-[222px] flex-shrink-0 flex-grow-0 pt-md pr-4 flex flex-col sticky top-20 self-start">
|
||||
<VerticalNavigation
|
||||
items={settingMenus.map((menu) => {
|
||||
const Icon = menu.icon;
|
||||
return {
|
||||
value: menu.id,
|
||||
label: <span className="text-sm font-bold leading-13 text-text-primary">{menu.name}</span>,
|
||||
};
|
||||
}) as VerticalNavItem[]}
|
||||
value={activeTab}
|
||||
onValueChange={handleTabChange}
|
||||
className="w-full h-full flex-1 min-h-0 gap-0"
|
||||
listClassName="w-full h-full overflow-y-auto"
|
||||
contentClassName="hidden"
|
||||
/>
|
||||
<div className="w-full mt-8 pt-4 pb-2 flex items-center justify-center border-[0px] border-t border-solid border-white-80% flex-shrink-0 flex-grow-0">
|
||||
<div className="flex items-center gap-1 leading-9">
|
||||
<img src={logoSrc} alt="version-logo" className="h-6" />
|
||||
</div>
|
||||
<div className="px-sm py-0.5 bg-bg-surface-tertiary rounded-full gap-1 flex items-center justify-center">
|
||||
<CircleCheck className="w-4 h-4 text-bg-fill-success-primary" />
|
||||
<div className="text-primary text-xs font-bold leading-17">
|
||||
{version}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex h-auto w-full flex-1 flex-col">
|
||||
<div className="flex flex-col gap-4 py-md pb-md">
|
||||
{activeTab === 'general' && <General />}
|
||||
{activeTab === 'privacy' && <Privacy />}
|
||||
{activeTab === 'models' && <Models />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="flex-1 flex flex-col w-full h-auto">
|
||||
<div className="flex flex-col gap-4 pb-md py-md">
|
||||
{activeTab === "general" && <General />}
|
||||
{activeTab === "privacy" && <Privacy />}
|
||||
{activeTab === "models" && <Models />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,131 +12,113 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { proxyFetchGet, proxyFetchPost } from '@/api/http';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Circle } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Circle } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { proxyFetchGet, proxyFetchPost } from "@/api/http";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface ConfigItem {
|
||||
name: string;
|
||||
env_vars: string[];
|
||||
name: string;
|
||||
env_vars: string[];
|
||||
}
|
||||
|
||||
export default function SettingAPI() {
|
||||
const { t } = useTranslation();
|
||||
const [items, setItems] = useState<ConfigItem[]>([]);
|
||||
const [envValues, setEnvValues] = useState<Record<string, string>>({});
|
||||
const [loading, setLoading] = useState<Record<string, boolean>>({});
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const { t } = useTranslation();
|
||||
const [items, setItems] = useState<ConfigItem[]>([]);
|
||||
const [envValues, setEnvValues] = useState<Record<string, string>>({});
|
||||
const [loading, setLoading] = useState<Record<string, boolean>>({});
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
useEffect(() => {
|
||||
proxyFetchGet('/api/config/info').then((res) => {
|
||||
const configs = Object.entries(res || {})
|
||||
.map(([name, v]: [string, any]) => ({ name, env_vars: v.env_vars }))
|
||||
.filter(
|
||||
(item) => Array.isArray(item.env_vars) && item.env_vars.length > 0
|
||||
);
|
||||
setItems(configs);
|
||||
});
|
||||
proxyFetchGet('/api/configs').then((res) => {
|
||||
if (Array.isArray(res)) {
|
||||
const envMap: Record<string, string> = {};
|
||||
res.forEach((item: any) => {
|
||||
if (item.config_name && item.config_value) {
|
||||
envMap[item.config_name] = item.config_value;
|
||||
}
|
||||
});
|
||||
setEnvValues(envMap);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
proxyFetchGet("/api/config/info").then((res) => {
|
||||
const configs = Object.entries(res || {})
|
||||
.map(([name, v]: [string, any]) => ({ name, env_vars: v.env_vars }))
|
||||
.filter((item) => Array.isArray(item.env_vars) && item.env_vars.length > 0);
|
||||
setItems(configs);
|
||||
});
|
||||
proxyFetchGet("/api/configs").then((res) => {
|
||||
if (Array.isArray(res)) {
|
||||
const envMap: Record<string, string> = {};
|
||||
res.forEach((item: any) => {
|
||||
if (item.config_name && item.config_value) {
|
||||
envMap[item.config_name] = item.config_value;
|
||||
}
|
||||
});
|
||||
setEnvValues(envMap);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleInputChange = (env: string, value: string) => {
|
||||
setEnvValues((prev) => ({ ...prev, [env]: value }));
|
||||
};
|
||||
const handleInputChange = (env: string, value: string) => {
|
||||
setEnvValues((prev) => ({ ...prev, [env]: value }));
|
||||
};
|
||||
|
||||
const handleVerify = async (configGroup: string, env: string) => {
|
||||
const value = envValues[env] || '';
|
||||
if (!value.trim()) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[env]: t('layout.env-should-not-empty'),
|
||||
}));
|
||||
return;
|
||||
} else {
|
||||
setErrors((prev) => ({ ...prev, [env]: '' }));
|
||||
}
|
||||
setLoading((prev) => ({ ...prev, [env]: true }));
|
||||
try {
|
||||
await proxyFetchPost('/api/configs', {
|
||||
config_name: env,
|
||||
config_value: value,
|
||||
config_group: configGroup,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setLoading((prev) => ({ ...prev, [env]: false }));
|
||||
}
|
||||
};
|
||||
const handleVerify = async (configGroup: string, env: string) => {
|
||||
const value = envValues[env] || "";
|
||||
if (!value.trim()) {
|
||||
setErrors((prev) => ({ ...prev, [env]: t("layout.env-should-not-empty") }));
|
||||
return;
|
||||
} else {
|
||||
setErrors((prev) => ({ ...prev, [env]: "" }));
|
||||
}
|
||||
setLoading((prev) => ({ ...prev, [env]: true }));
|
||||
try {
|
||||
await proxyFetchPost("/api/configs", {
|
||||
config_name: env,
|
||||
config_value: value,
|
||||
config_group: configGroup,
|
||||
});
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading((prev) => ({ ...prev, [env]: false }));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className="bg-bg-surface-tertiary rounded-2xl px-6 py-4"
|
||||
>
|
||||
<div>
|
||||
<div className="text-text-primary text-base font-bold leading-12">
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-md">
|
||||
<div>
|
||||
{item.env_vars.map((env) => (
|
||||
<div key={env} className="mt-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
id={env}
|
||||
placeholder={env}
|
||||
className="w-full"
|
||||
value={envValues[env] || ''}
|
||||
onChange={(e) => {
|
||||
handleInputChange(env, e.target.value);
|
||||
if (errors[env])
|
||||
setErrors((prev) => ({ ...prev, [env]: '' }));
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className="bg-bg-fill-disabled px-sm py-xs shadow-none"
|
||||
onClick={() => handleVerify(item.name, env)}
|
||||
disabled={loading[env]}
|
||||
>
|
||||
<span className="text-text-inverse-primary text-sm leading-13">
|
||||
{loading[env]
|
||||
? t('layout.loading')
|
||||
: t('layout.verify')}
|
||||
</span>
|
||||
<Circle className="text-icon-inverse-primary h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-text-secondary mt-1.5 text-xs leading-17">
|
||||
{env}
|
||||
</div>
|
||||
{errors[env] && (
|
||||
<span className="mt-1 text-xs text-red-500">
|
||||
{errors[env]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{items.map((item) => (
|
||||
<div key={item.name} className="px-6 py-4 bg-bg-surface-tertiary rounded-2xl">
|
||||
<div>
|
||||
<div className="text-base font-bold leading-12 text-text-primary">{item.name}</div>
|
||||
</div>
|
||||
<div className="mt-md">
|
||||
<div>
|
||||
{item.env_vars.map((env) => (
|
||||
<div key={env} className="mt-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
id={env}
|
||||
placeholder={env}
|
||||
className="w-full"
|
||||
value={envValues[env] || ""}
|
||||
onChange={(e) => {
|
||||
handleInputChange(env, e.target.value);
|
||||
if (errors[env]) setErrors((prev) => ({ ...prev, [env]: "" }));
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className="shadow-none px-sm py-xs bg-bg-fill-disabled"
|
||||
onClick={() => handleVerify(item.name, env)}
|
||||
disabled={loading[env]}
|
||||
>
|
||||
<span className="text-sm leading-13 text-text-inverse-primary">
|
||||
{loading[env] ? t("layout.loading") : t("layout.verify")}
|
||||
</span>
|
||||
<Circle className="w-4 h-4 text-icon-inverse-primary" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs leading-17 text-text-secondary mt-1.5">{env}</div>
|
||||
{errors[env] && (
|
||||
<span className="text-xs text-text-error mt-1">{errors[env]}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import dark from '@/assets/dark.png';
|
||||
import light from '@/assets/light.png';
|
||||
import transparent from '@/assets/transparent.png';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -56,37 +57,59 @@ export default function SettingGeneral() {
|
|||
//Get Chatstore for the active project's task
|
||||
const { chatStore } = useChatStoreAdapter();
|
||||
|
||||
const themeList = useMemo(() => {
|
||||
const platform = window.electronAPI.getPlatform();
|
||||
if (platform === 'darwin') {
|
||||
return [
|
||||
{
|
||||
img: light,
|
||||
label: 'setting.light',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
img: transparent,
|
||||
label: 'setting.transparent',
|
||||
value: 'transparent',
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
img: light,
|
||||
label: 'setting.light',
|
||||
value: 'light',
|
||||
},
|
||||
];
|
||||
}
|
||||
}, []);
|
||||
const [themeList, setThemeList] = useState<any>([
|
||||
{
|
||||
img: dark,
|
||||
label: 'setting.dark',
|
||||
value: 'dark',
|
||||
},
|
||||
{
|
||||
img: light,
|
||||
label: 'setting.light',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
img: transparent,
|
||||
label: 'setting.transparent',
|
||||
value: 'transparent',
|
||||
},
|
||||
]);
|
||||
|
||||
// Proxy configuration state
|
||||
const [proxyUrl, setProxyUrl] = useState('');
|
||||
const [isProxySaving, setIsProxySaving] = useState(false);
|
||||
const [proxyNeedsRestart, setProxyNeedsRestart] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const platform = window.electronAPI.getPlatform();
|
||||
console.log(platform);
|
||||
const baseThemes = [
|
||||
{
|
||||
img: dark,
|
||||
label: 'setting.dark',
|
||||
value: 'dark',
|
||||
},
|
||||
{
|
||||
img: light,
|
||||
label: 'setting.light',
|
||||
value: 'light',
|
||||
},
|
||||
];
|
||||
|
||||
if (platform === 'darwin') {
|
||||
setThemeList([
|
||||
...baseThemes,
|
||||
{
|
||||
img: transparent,
|
||||
label: 'setting.transparent',
|
||||
value: 'transparent',
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
setThemeList(baseThemes);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const languageList = [
|
||||
{
|
||||
key: LocaleEnum.English,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -12,413 +12,415 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { proxyFetchDelete, proxyFetchGet, proxyFetchPost } from '@/api/http';
|
||||
import githubIcon from '@/assets/github.svg';
|
||||
import SearchInput from '@/components/SearchInput';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||
import { TooltipSimple } from '@/components/ui/tooltip';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { ChevronLeft, CircleAlert, Store } from 'lucide-react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MCPEnvDialog } from './components/MCPEnvDialog';
|
||||
import { useEffect, useState, useRef, useCallback } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipSimple,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { MCPEnvDialog } from "./components/MCPEnvDialog";
|
||||
import { Plus, Store, CircleAlert, ArrowLeft, ChevronLeft } from "lucide-react";
|
||||
import { proxyFetchDelete, proxyFetchGet, proxyFetchPost } from "@/api/http";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import githubIcon from "@/assets/github.svg";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
import SearchInput from "@/components/SearchInput";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
interface MCPItem {
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
description: string;
|
||||
status: number | string;
|
||||
category?: { name: string };
|
||||
home_page?: string;
|
||||
install_command?: {
|
||||
command: string;
|
||||
args: string[];
|
||||
env?: Record<string, string>;
|
||||
};
|
||||
homepage?: string;
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
description: string;
|
||||
status: number | string;
|
||||
category?: { name: string };
|
||||
home_page?: string;
|
||||
install_command?: {
|
||||
command: string;
|
||||
args: string[];
|
||||
env?: Record<string, string>;
|
||||
};
|
||||
homepage?: string;
|
||||
}
|
||||
interface _EnvValue {
|
||||
value: string;
|
||||
required: boolean;
|
||||
tip: string;
|
||||
interface EnvValue {
|
||||
value: string;
|
||||
required: boolean;
|
||||
tip: string;
|
||||
}
|
||||
|
||||
const _PAGE_SIZE = 10;
|
||||
const _STICKY_Z = 20;
|
||||
const PAGE_SIZE = 10;
|
||||
const STICKY_Z = 20;
|
||||
|
||||
function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debounced, setDebounced] = useState(value);
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => setDebounced(value), delay);
|
||||
return () => clearTimeout(handler);
|
||||
}, [value, delay]);
|
||||
return debounced;
|
||||
const [debounced, setDebounced] = useState(value);
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => setDebounced(value), delay);
|
||||
return () => clearTimeout(handler);
|
||||
}, [value, delay]);
|
||||
return debounced;
|
||||
}
|
||||
|
||||
// map category name to svg file name
|
||||
const categoryIconMap: Record<string, string> = {
|
||||
anthropic: 'Anthropic',
|
||||
community: 'Community',
|
||||
official: 'Official',
|
||||
camel: 'Camel',
|
||||
anthropic: "Anthropic",
|
||||
community: "Community",
|
||||
official: "Official",
|
||||
camel: "Camel",
|
||||
};
|
||||
|
||||
// load all svg files dynamically
|
||||
const svgIcons = import.meta.glob('@/assets/mcp/*.svg', {
|
||||
eager: true,
|
||||
query: '?url',
|
||||
import: 'default',
|
||||
const svgIcons = import.meta.glob("@/assets/mcp/*.svg", {
|
||||
eager: true,
|
||||
query: "?url",
|
||||
import: "default",
|
||||
});
|
||||
|
||||
type MCPMarketProps = {
|
||||
onBack?: () => void;
|
||||
keyword?: string;
|
||||
onBack?: () => void;
|
||||
keyword?: string;
|
||||
};
|
||||
|
||||
export default function MCPMarket({
|
||||
onBack,
|
||||
keyword: externalKeyword,
|
||||
}: MCPMarketProps) {
|
||||
const { t } = useTranslation();
|
||||
const { checkAgentTool } = useAuthStore();
|
||||
const [items, setItems] = useState<MCPItem[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const effectiveKeyword =
|
||||
externalKeyword !== undefined ? externalKeyword : keyword;
|
||||
const debouncedKeyword = useDebounce(effectiveKeyword, 400);
|
||||
const loader = useRef<HTMLDivElement | null>(null);
|
||||
const [installing, setInstalling] = useState<{ [id: number]: boolean }>({});
|
||||
const [installed, setInstalled] = useState<{ [id: number]: boolean }>({});
|
||||
const [installedIds, setInstalledIds] = useState<number[]>([]);
|
||||
const [mcpCategory, setMcpCategory] = useState<
|
||||
{ id: number; name: string }[]
|
||||
>([]);
|
||||
export default function MCPMarket({ onBack, keyword: externalKeyword }: MCPMarketProps) {
|
||||
const { t } = useTranslation();
|
||||
const { checkAgentTool } = useAuthStore();
|
||||
const [items, setItems] = useState<MCPItem[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [page, setPage] = useState(1);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const effectiveKeyword = externalKeyword !== undefined ? externalKeyword : keyword;
|
||||
const debouncedKeyword = useDebounce(effectiveKeyword, 400);
|
||||
const loader = useRef<HTMLDivElement | null>(null);
|
||||
const [installing, setInstalling] = useState<{ [id: number]: boolean }>({});
|
||||
const [installed, setInstalled] = useState<{ [id: number]: boolean }>({});
|
||||
const [installedIds, setInstalledIds] = useState<number[]>([]);
|
||||
const [mcpCategory, setMcpCategory] = useState<
|
||||
{ id: number; name: string }[]
|
||||
>([]);
|
||||
|
||||
// environment variable configuration
|
||||
const [showEnvConfig, setShowEnvConfig] = useState(false);
|
||||
const [activeMcp, setActiveMcp] = useState<MCPItem | null>(null);
|
||||
// environment variable configuration
|
||||
const [showEnvConfig, setShowEnvConfig] = useState(false);
|
||||
const [activeMcp, setActiveMcp] = useState<MCPItem | null>(null);
|
||||
|
||||
const [categoryId, setCategoryId] = useState<number | undefined>(undefined);
|
||||
const effectiveCategoryId = categoryId;
|
||||
const [userInstallMcp, setUserInstallMcp] = useState<any | undefined>([]);
|
||||
// get installed MCP list
|
||||
useEffect(() => {
|
||||
proxyFetchGet('/api/mcp/users').then((res) => {
|
||||
let ids: number[] = [];
|
||||
if (Array.isArray(res)) {
|
||||
setUserInstallMcp(res);
|
||||
ids = res.map((item: any) => item.mcp_id);
|
||||
} else if (Array.isArray(res.items)) {
|
||||
setUserInstallMcp(res.items);
|
||||
ids = res.items.map((item: any) => item.mcp_id);
|
||||
}
|
||||
setInstalledIds(ids);
|
||||
});
|
||||
}, []);
|
||||
const [categoryId, setCategoryId] = useState<number | undefined>(undefined);
|
||||
const effectiveCategoryId = categoryId;
|
||||
const [userInstallMcp, setUserInstallMcp] = useState<any | undefined>([]);
|
||||
// get installed MCP list
|
||||
useEffect(() => {
|
||||
proxyFetchGet("/api/mcp/users").then((res) => {
|
||||
let ids: number[] = [];
|
||||
if (Array.isArray(res)) {
|
||||
setUserInstallMcp(res);
|
||||
ids = res.map((item: any) => item.mcp_id);
|
||||
} else if (Array.isArray(res.items)) {
|
||||
setUserInstallMcp(res.items);
|
||||
ids = res.items.map((item: any) => item.mcp_id);
|
||||
}
|
||||
setInstalledIds(ids);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// get MCP categories
|
||||
useEffect(() => {
|
||||
proxyFetchGet('/api/mcp/categories').then((res) => {
|
||||
if (Array.isArray(res)) {
|
||||
setMcpCategory(res);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
// get MCP categories
|
||||
useEffect(() => {
|
||||
proxyFetchGet("/api/mcp/categories").then((res) => {
|
||||
if (Array.isArray(res)) {
|
||||
setMcpCategory(res);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
// load data
|
||||
const loadData = useCallback(
|
||||
async (pageNum: number, kw: string, catId?: number, pageSize = 20) => {
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const params: any = { page: pageNum, size: pageSize, keyword: kw };
|
||||
if (catId) params.category_id = catId;
|
||||
const res = await proxyFetchGet('/api/mcps', params);
|
||||
if (res && Array.isArray(res.items)) {
|
||||
// frontend deduplication
|
||||
const all: MCPItem[] =
|
||||
pageNum === 1 ? res.items : [...items, ...res.items];
|
||||
const unique: MCPItem[] = Array.from(
|
||||
new Map(all.map((i: MCPItem) => [i.id, i])).values()
|
||||
);
|
||||
setItems(unique);
|
||||
setHasMore(res.items.length === pageSize);
|
||||
} else {
|
||||
if (pageNum === 1) setItems([]);
|
||||
setHasMore(false);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err?.message || 'Load failed');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[items]
|
||||
);
|
||||
// load data
|
||||
const loadData = useCallback(
|
||||
async (pageNum: number, kw: string, catId?: number, pageSize = 20) => {
|
||||
setIsLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const params: any = { page: pageNum, size: pageSize, keyword: kw };
|
||||
if (catId) params.category_id = catId;
|
||||
const res = await proxyFetchGet("/api/mcps", params);
|
||||
if (res && Array.isArray(res.items)) {
|
||||
// frontend deduplication
|
||||
const all: MCPItem[] =
|
||||
pageNum === 1 ? res.items : [...items, ...res.items];
|
||||
const unique: MCPItem[] = Array.from(
|
||||
new Map(all.map((i: MCPItem) => [i.id, i])).values()
|
||||
);
|
||||
setItems(unique);
|
||||
setHasMore(res.items.length === pageSize);
|
||||
} else {
|
||||
if (pageNum === 1) setItems([]);
|
||||
setHasMore(false);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err?.message || "Load failed");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[items]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
loadData(1, debouncedKeyword, effectiveCategoryId);
|
||||
// eslint-disable-next-line
|
||||
}, [debouncedKeyword, effectiveCategoryId]);
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
loadData(1, debouncedKeyword, effectiveCategoryId);
|
||||
// eslint-disable-next-line
|
||||
}, [debouncedKeyword, effectiveCategoryId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (page > 1) loadData(page, debouncedKeyword, effectiveCategoryId);
|
||||
// eslint-disable-next-line
|
||||
}, [page]);
|
||||
useEffect(() => {
|
||||
if (page > 1) loadData(page, debouncedKeyword, effectiveCategoryId);
|
||||
// eslint-disable-next-line
|
||||
}, [page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasMore || isLoading) return;
|
||||
const node = loader.current;
|
||||
if (!node) return;
|
||||
const observer = new window.IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
setPage((p) => (isLoading || !hasMore ? p : p + 1));
|
||||
}
|
||||
},
|
||||
{ root: null, rootMargin: '0px', threshold: 0.1 }
|
||||
);
|
||||
observer.observe(node);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [hasMore, isLoading]);
|
||||
useEffect(() => {
|
||||
if (!hasMore || isLoading) return;
|
||||
const node = loader.current;
|
||||
if (!node) return;
|
||||
const observer = new window.IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
setPage((p) => (isLoading || !hasMore ? p : p + 1));
|
||||
}
|
||||
},
|
||||
{ root: null, rootMargin: "0px", threshold: 0.1 }
|
||||
);
|
||||
observer.observe(node);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [hasMore, isLoading]);
|
||||
|
||||
const checkEnv = (id: number) => {
|
||||
const mcp = items.find((mcp) => mcp.id === id);
|
||||
if (mcp && Object.keys(mcp?.install_command?.env || {}).length > 0) {
|
||||
setActiveMcp(mcp);
|
||||
setShowEnvConfig(true);
|
||||
} else {
|
||||
installMcp(id);
|
||||
}
|
||||
};
|
||||
const onConnect = (mcp: MCPItem) => {
|
||||
console.log(mcp);
|
||||
setItems((prev) =>
|
||||
prev.map((item) => (item.id === mcp.id ? { ...item, ...mcp } : item))
|
||||
);
|
||||
installMcp(mcp.id);
|
||||
onClose();
|
||||
};
|
||||
const onClose = () => {
|
||||
setShowEnvConfig(false);
|
||||
setActiveMcp(null);
|
||||
};
|
||||
const installMcp = async (id: number) => {
|
||||
setInstalling((prev) => ({ ...prev, [id]: true }));
|
||||
try {
|
||||
const mcpItem = items.find((item) => item.id === id);
|
||||
const res = await proxyFetchPost('/api/mcp/install?mcp_id=' + id);
|
||||
if (res) {
|
||||
console.log(res);
|
||||
setUserInstallMcp((prev: any) => [...prev, res]);
|
||||
}
|
||||
setInstalled((prev) => ({ ...prev, [id]: true }));
|
||||
setInstalledIds((prev) => [...prev, id]);
|
||||
// notify main process
|
||||
if (window.ipcRenderer && mcpItem) {
|
||||
await window.ipcRenderer.invoke(
|
||||
'mcp-install',
|
||||
mcpItem.key,
|
||||
mcpItem.install_command
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setInstalling((prev) => ({ ...prev, [id]: false }));
|
||||
}
|
||||
};
|
||||
const checkEnv = (id: number) => {
|
||||
const mcp = items.find((mcp) => mcp.id === id);
|
||||
if (mcp && Object.keys(mcp?.install_command?.env || {}).length > 0) {
|
||||
setActiveMcp(mcp);
|
||||
setShowEnvConfig(true);
|
||||
} else {
|
||||
installMcp(id);
|
||||
}
|
||||
};
|
||||
const onConnect = (mcp: MCPItem) => {
|
||||
console.log(mcp);
|
||||
setItems((prev) =>
|
||||
prev.map((item) => (item.id === mcp.id ? { ...item, ...mcp } : item))
|
||||
);
|
||||
installMcp(mcp.id);
|
||||
onClose();
|
||||
};
|
||||
const onClose = () => {
|
||||
setShowEnvConfig(false);
|
||||
setActiveMcp(null);
|
||||
};
|
||||
const installMcp = async (id: number) => {
|
||||
setInstalling((prev) => ({ ...prev, [id]: true }));
|
||||
try {
|
||||
const mcpItem = items.find((item) => item.id === id);
|
||||
const res = await proxyFetchPost("/api/mcp/install?mcp_id=" + id);
|
||||
if (res) {
|
||||
console.log(res);
|
||||
setUserInstallMcp((prev: any) => [...prev, res]);
|
||||
}
|
||||
setInstalled((prev) => ({ ...prev, [id]: true }));
|
||||
setInstalledIds((prev) => [...prev, id]);
|
||||
// notify main process
|
||||
if (window.ipcRenderer && mcpItem) {
|
||||
await window.ipcRenderer.invoke(
|
||||
"mcp-install",
|
||||
mcpItem.key,
|
||||
mcpItem.install_command
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setInstalling((prev) => ({ ...prev, [id]: false }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (onBack) onBack();
|
||||
else window.history.back();
|
||||
};
|
||||
const handleBack = () => {
|
||||
if (onBack) onBack();
|
||||
else window.history.back();
|
||||
};
|
||||
|
||||
const handleDelete = async (deleteTarget: MCPItem) => {
|
||||
if (!deleteTarget) return;
|
||||
try {
|
||||
checkAgentTool(deleteTarget.name);
|
||||
console.log(userInstallMcp, deleteTarget);
|
||||
const id = userInstallMcp.find(
|
||||
(item: any) => item.mcp_id === deleteTarget.id
|
||||
)?.id;
|
||||
console.log('deleteTarget', deleteTarget);
|
||||
await proxyFetchDelete(`/api/mcp/users/${id}`);
|
||||
// notify main process
|
||||
if (window.ipcRenderer) {
|
||||
await window.ipcRenderer.invoke('mcp-remove', deleteTarget.key);
|
||||
}
|
||||
setInstalledIds((prev) =>
|
||||
prev.filter((item) => item !== deleteTarget.id)
|
||||
);
|
||||
setInstalled((prev) => ({ ...prev, [deleteTarget.id]: false }));
|
||||
loadData(1, debouncedKeyword, categoryId, page * 20);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center">
|
||||
{externalKeyword === undefined && (
|
||||
<>
|
||||
<div className="text-body sticky top-0 z-[20] mb-0 flex w-full max-w-4xl items-center justify-between py-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleBack}
|
||||
className="mr-2"
|
||||
>
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
</Button>
|
||||
<span className="text-text-primary text-base font-bold leading-12">
|
||||
{t('setting.mcp-market')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-40 max-w-4xl">
|
||||
<SearchInput
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
const handleDelete = async (deleteTarget: MCPItem) => {
|
||||
if (!deleteTarget) return;
|
||||
try {
|
||||
checkAgentTool(deleteTarget.name);
|
||||
console.log(userInstallMcp, deleteTarget);
|
||||
const id = userInstallMcp.find(
|
||||
(item: any) => item.mcp_id === deleteTarget.id
|
||||
)?.id;
|
||||
console.log("deleteTarget", deleteTarget);
|
||||
await proxyFetchDelete(`/api/mcp/users/${id}`);
|
||||
// notify main process
|
||||
if (window.ipcRenderer) {
|
||||
await window.ipcRenderer.invoke("mcp-remove", deleteTarget.key);
|
||||
}
|
||||
setInstalledIds((prev) =>
|
||||
prev.filter((item) => item !== deleteTarget.id)
|
||||
);
|
||||
setInstalled((prev) => ({ ...prev, [deleteTarget.id]: false }));
|
||||
loadData(1, debouncedKeyword, categoryId, page * 20);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center ">
|
||||
{externalKeyword === undefined && (
|
||||
<>
|
||||
<div className="text-body flex items-center justify-between sticky top-0 z-[20] py-2 mb-0 w-full max-w-4xl">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleBack}
|
||||
className="mr-2"
|
||||
>
|
||||
<ChevronLeft className="w-6 h-6" />
|
||||
</Button>
|
||||
<span className="text-base font-bold leading-12 text-text-primary">
|
||||
{t("setting.mcp-market")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-40 max-w-4xl">
|
||||
<SearchInput
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Category toggle row */}
|
||||
<div className="flex w-full py-2">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={categoryId ? String(categoryId) : 'all'}
|
||||
onValueChange={(val) =>
|
||||
setCategoryId(!val || val === 'all' ? undefined : Number(val))
|
||||
}
|
||||
className="flex flex-wrap"
|
||||
>
|
||||
<ToggleGroupItem value="all">{t('setting.all')}</ToggleGroupItem>
|
||||
{mcpCategory.map((cat) => (
|
||||
<ToggleGroupItem key={cat.id} value={String(cat.id)}>
|
||||
{cat.name}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
{/* Category toggle row */}
|
||||
<div className="w-full flex py-2">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={categoryId ? String(categoryId) : "all"}
|
||||
onValueChange={(val) => setCategoryId(!val || val === "all" ? undefined : Number(val))}
|
||||
className="flex flex-wrap"
|
||||
>
|
||||
<ToggleGroupItem value="all">
|
||||
{t("setting.all")}
|
||||
</ToggleGroupItem>
|
||||
{mcpCategory.map((cat) => (
|
||||
<ToggleGroupItem key={cat.id} value={String(cat.id)}>
|
||||
{cat.name}
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
|
||||
{/* list */}
|
||||
<MCPEnvDialog
|
||||
showEnvConfig={showEnvConfig}
|
||||
onClose={onClose}
|
||||
onConnect={onConnect}
|
||||
activeMcp={activeMcp}
|
||||
></MCPEnvDialog>
|
||||
<div className="flex w-full flex-col gap-4 pt-4">
|
||||
{isLoading && items.length === 0 && (
|
||||
<div className="py-8 text-center text-gray-400">
|
||||
{t('setting.loading')}
|
||||
</div>
|
||||
)}
|
||||
{error && <div className="py-8 text-center text-red-500">{error}</div>}
|
||||
{!isLoading && !error && items.length === 0 && (
|
||||
<div className="py-8 text-center text-gray-400">
|
||||
{t('setting.no-mcp-services')}
|
||||
</div>
|
||||
)}
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="flex items-center rounded-2xl bg-surface-secondary p-4"
|
||||
>
|
||||
{/* Left: Icon */}
|
||||
<div className="mr-4 flex items-center">
|
||||
{(() => {
|
||||
const catName = item.category?.name;
|
||||
const iconKey = catName ? categoryIconMap[catName] : undefined;
|
||||
const iconUrl = iconKey
|
||||
? (svgIcons[`/src/assets/mcp/${iconKey}.svg`] as string)
|
||||
: undefined;
|
||||
return iconUrl ? (
|
||||
<img src={iconUrl} alt={catName} className="h-11 w-9" />
|
||||
) : (
|
||||
<Store className="h-11 w-9 text-icon-primary" />
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col justify-center">
|
||||
<div className="flex w-full items-center gap-xs pb-1">
|
||||
<div className="flex flex-1 items-center gap-xs">
|
||||
<span className="text-text-primary truncate text-base font-bold leading-9">
|
||||
{item.name}
|
||||
</span>
|
||||
<TooltipSimple content={item.description}>
|
||||
<CircleAlert className="h-4 w-4 text-icon-secondary" />
|
||||
</TooltipSimple>
|
||||
</div>
|
||||
<Button
|
||||
variant={
|
||||
!installedIds.includes(item.id) ? 'primary' : 'secondary'
|
||||
}
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
installedIds.includes(item.id)
|
||||
? handleDelete(item)
|
||||
: checkEnv(item.id)
|
||||
}
|
||||
>
|
||||
{installedIds.includes(item.id)
|
||||
? t('setting.uninstall')
|
||||
: installing[item.id]
|
||||
? t('setting.installing')
|
||||
: installed[item.id]
|
||||
? t('setting.uninstall')
|
||||
: t('setting.install')}
|
||||
</Button>
|
||||
</div>
|
||||
{item.home_page &&
|
||||
item.home_page.startsWith('https://github.com/') && (
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={githubIcon}
|
||||
alt="github"
|
||||
style={{
|
||||
width: 14.7,
|
||||
height: 14.7,
|
||||
marginRight: 4,
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
/>
|
||||
<span className="items-center justify-center self-stretch text-xs font-medium leading-3">
|
||||
{(() => {
|
||||
const parts = item.home_page.split('/');
|
||||
return parts.length > 4 ? parts[4] : item.home_page;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-1 whitespace-pre-line break-words text-sm text-gray-500">
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div ref={loader} />
|
||||
{isLoading && items.length > 0 && (
|
||||
<div className="py-4 text-center text-gray-400">
|
||||
{t('setting.loading-more')}
|
||||
</div>
|
||||
)}
|
||||
{!hasMore && items.length > 0 && (
|
||||
<div className="py-4 text-center text-gray-400">
|
||||
{t('setting.no-more-mcp-servers')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
{/* list */}
|
||||
<MCPEnvDialog
|
||||
showEnvConfig={showEnvConfig}
|
||||
onClose={onClose}
|
||||
onConnect={onConnect}
|
||||
activeMcp={activeMcp}
|
||||
></MCPEnvDialog>
|
||||
<div className="flex flex-col gap-4 w-full pt-4">
|
||||
{isLoading && items.length === 0 && (
|
||||
<div className="text-center py-8 text-text-muted">{t("setting.loading")}</div>
|
||||
)}
|
||||
{error && <div className="text-center py-8 text-text-error">{error}</div>}
|
||||
{!isLoading && !error && items.length === 0 && (
|
||||
<div className="text-center py-8 text-text-muted">{t("setting.no-mcp-services")}</div>
|
||||
)}
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="p-4 bg-surface-secondary rounded-2xl flex items-center"
|
||||
>
|
||||
{/* Left: Icon */}
|
||||
<div className="flex items-center mr-4">
|
||||
{(() => {
|
||||
const catName = item.category?.name;
|
||||
const iconKey = catName ? categoryIconMap[catName] : undefined;
|
||||
const iconUrl = iconKey
|
||||
? (svgIcons[`/src/assets/mcp/${iconKey}.svg`] as string)
|
||||
: undefined;
|
||||
return iconUrl ? (
|
||||
<img src={iconUrl} alt={catName} className="w-9 h-11" />
|
||||
) : (
|
||||
<Store className="w-9 h-11 text-icon-primary" />
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 flex flex-col justify-center">
|
||||
<div className="flex items-center gap-xs w-full pb-1">
|
||||
<div className="flex items-center gap-xs flex-1">
|
||||
<span className="text-base leading-9 font-bold text-text-primary truncate ">
|
||||
{item.name}
|
||||
</span>
|
||||
<TooltipSimple content={item.description}>
|
||||
<CircleAlert className="w-4 h-4 text-icon-secondary" />
|
||||
</TooltipSimple>
|
||||
</div>
|
||||
<Button
|
||||
variant={
|
||||
!installedIds.includes(item.id) ? "primary" : "secondary"
|
||||
}
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
installedIds.includes(item.id)
|
||||
? handleDelete(item)
|
||||
: checkEnv(item.id)
|
||||
}
|
||||
>
|
||||
{installedIds.includes(item.id)
|
||||
? t("setting.uninstall")
|
||||
: installing[item.id]
|
||||
? t("setting.installing")
|
||||
: installed[item.id]
|
||||
? t("setting.uninstall")
|
||||
: t("setting.install")}
|
||||
</Button>
|
||||
</div>
|
||||
{item.home_page &&
|
||||
item.home_page.startsWith("https://github.com/") && (
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={githubIcon}
|
||||
alt="github"
|
||||
style={{
|
||||
width: 14.7,
|
||||
height: 14.7,
|
||||
marginRight: 4,
|
||||
display: "inline-block",
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
/>
|
||||
<span className="self-stretch items-center justify-center text-xs font-medium leading-3">
|
||||
{(() => {
|
||||
const parts = item.home_page.split("/");
|
||||
return parts.length > 4 ? parts[4] : item.home_page;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm text-text-muted mt-1 break-words whitespace-pre-line">
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div ref={loader} />
|
||||
{isLoading && items.length > 0 && (
|
||||
<div className="text-center py-4 text-text-muted">{t("setting.loading-more")}</div>
|
||||
)}
|
||||
{!hasMore && items.length > 0 && (
|
||||
<div className="text-center py-4 text-text-muted">
|
||||
{t("setting.no-more-mcp-servers")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -12,183 +12,179 @@
|
|||
// limitations under the License.
|
||||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { proxyFetchGet, proxyFetchPut } from '@/api/http';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const API_FIELDS = [
|
||||
'take_screenshot',
|
||||
'access_local_software',
|
||||
'access_your_address',
|
||||
'password_storage',
|
||||
] as const;
|
||||
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { useState, useEffect } from "react";
|
||||
import { proxyFetchGet, proxyFetchPut } from "@/api/http";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { FolderSearch, ChevronDown } from "lucide-react";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function SettingPrivacy() {
|
||||
const { email } = useAuthStore();
|
||||
const [_privacy, setPrivacy] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [settings, setSettings] = useState([
|
||||
{
|
||||
title: t('setting.allow-agent-to-take-screenshots'),
|
||||
description: t('setting.allow-agent-to-take-screenshots-description'),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t('setting.allow-agent-to-access-local-software'),
|
||||
description: t(
|
||||
'setting.allow-agent-to-access-local-software-description'
|
||||
),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t('setting.allow-agent-to-access-your-address'),
|
||||
description: t('setting.allow-agent-to-access-your-address-description'),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t('setting.password-storage'),
|
||||
description: t('setting.password-storage-description'),
|
||||
checked: false,
|
||||
},
|
||||
]);
|
||||
useEffect(() => {
|
||||
proxyFetchGet('/api/user/privacy')
|
||||
.then((res) => {
|
||||
let hasFalse = false;
|
||||
setSettings((prev) =>
|
||||
prev.map((item, index) => {
|
||||
if (!res[API_FIELDS[index]]) {
|
||||
hasFalse = true;
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
checked: res[API_FIELDS[index]] || false,
|
||||
};
|
||||
})
|
||||
);
|
||||
setPrivacy(!hasFalse);
|
||||
})
|
||||
.catch((err) => console.error('Failed to fetch settings:', err));
|
||||
}, []);
|
||||
const { email } = useAuthStore();
|
||||
const [_privacy, setPrivacy] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const API_FIELDS = [
|
||||
"take_screenshot",
|
||||
"access_local_software",
|
||||
"access_your_address",
|
||||
"password_storage",
|
||||
];
|
||||
const [settings, setSettings] = useState([
|
||||
{
|
||||
title: t("setting.allow-agent-to-take-screenshots"),
|
||||
description: t("setting.allow-agent-to-take-screenshots-description"),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t("setting.allow-agent-to-access-local-software"),
|
||||
description: t("setting.allow-agent-to-access-local-software-description"),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t("setting.allow-agent-to-access-your-address"),
|
||||
description: t("setting.allow-agent-to-access-your-address-description"),
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
title: t("setting.password-storage"),
|
||||
description: t("setting.password-storage-description"),
|
||||
checked: false,
|
||||
},
|
||||
]);
|
||||
useEffect(() => {
|
||||
proxyFetchGet("/api/user/privacy")
|
||||
.then((res) => {
|
||||
let hasFalse = false;
|
||||
setSettings((prev) =>
|
||||
prev.map((item, index) => {
|
||||
if (!res[API_FIELDS[index]]) {
|
||||
hasFalse = true;
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
checked: res[API_FIELDS[index]] || false,
|
||||
};
|
||||
})
|
||||
);
|
||||
setPrivacy(!hasFalse);
|
||||
})
|
||||
.catch((err) => console.error("Failed to fetch settings:", err));
|
||||
}, []);
|
||||
|
||||
const handleTurnOnAll = (type: boolean) => {
|
||||
const newSettings = settings.map((item) => ({
|
||||
...item,
|
||||
checked: type,
|
||||
}));
|
||||
setSettings(newSettings);
|
||||
setPrivacy(type);
|
||||
const requestData = {
|
||||
[API_FIELDS[0]]: type,
|
||||
[API_FIELDS[1]]: type,
|
||||
[API_FIELDS[2]]: type,
|
||||
[API_FIELDS[3]]: type,
|
||||
};
|
||||
const handleTurnOnAll = (type: boolean) => {
|
||||
const newSettings = settings.map((item) => ({
|
||||
...item,
|
||||
checked: type,
|
||||
}));
|
||||
setSettings(newSettings);
|
||||
setPrivacy(type);
|
||||
const requestData = {
|
||||
[API_FIELDS[0]]: type,
|
||||
[API_FIELDS[1]]: type,
|
||||
[API_FIELDS[2]]: type,
|
||||
[API_FIELDS[3]]: type,
|
||||
};
|
||||
|
||||
proxyFetchPut('/api/user/privacy', requestData);
|
||||
};
|
||||
proxyFetchPut("/api/user/privacy", requestData);
|
||||
};
|
||||
|
||||
const _handleToggle = (index: number) => {
|
||||
setSettings((prev) => {
|
||||
const newSettings = [...prev];
|
||||
newSettings[index] = {
|
||||
...newSettings[index],
|
||||
checked: !newSettings[index].checked,
|
||||
};
|
||||
return newSettings;
|
||||
});
|
||||
const handleToggle = (index: number) => {
|
||||
setSettings((prev) => {
|
||||
const newSettings = [...prev];
|
||||
newSettings[index] = {
|
||||
...newSettings[index],
|
||||
checked: !newSettings[index].checked,
|
||||
};
|
||||
return newSettings;
|
||||
});
|
||||
|
||||
const requestData = {
|
||||
[API_FIELDS[0]]: settings[0].checked,
|
||||
[API_FIELDS[1]]: settings[1].checked,
|
||||
[API_FIELDS[2]]: settings[2].checked,
|
||||
[API_FIELDS[3]]: settings[3].checked,
|
||||
};
|
||||
const requestData = {
|
||||
[API_FIELDS[0]]: settings[0].checked,
|
||||
[API_FIELDS[1]]: settings[1].checked,
|
||||
[API_FIELDS[2]]: settings[2].checked,
|
||||
[API_FIELDS[3]]: settings[3].checked,
|
||||
};
|
||||
|
||||
requestData[API_FIELDS[index]] = !settings[index].checked;
|
||||
requestData[API_FIELDS[index]] = !settings[index].checked;
|
||||
|
||||
proxyFetchPut('/api/user/privacy', requestData).catch((err) =>
|
||||
console.error('Failed to update settings:', err)
|
||||
);
|
||||
};
|
||||
proxyFetchPut("/api/user/privacy", requestData).catch((err) =>
|
||||
console.error("Failed to update settings:", err)
|
||||
);
|
||||
};
|
||||
|
||||
const [logFolder, setLogFolder] = useState('');
|
||||
const [isHowWeHandleOpen, setIsHowWeHandleOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
window.ipcRenderer
|
||||
.invoke('get-log-folder', email)
|
||||
.then((logFolder: string) => {
|
||||
setLogFolder(logFolder);
|
||||
});
|
||||
}, [email]);
|
||||
const [logFolder, setLogFolder] = useState("");
|
||||
const [isHowWeHandleOpen, setIsHowWeHandleOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
window.ipcRenderer.invoke("get-log-folder", email).then((logFolder) => {
|
||||
setLogFolder(logFolder);
|
||||
});
|
||||
}, [email]);
|
||||
|
||||
const _handleOpenFolder = () => {
|
||||
if (logFolder) {
|
||||
window.ipcRenderer.invoke('reveal-in-folder', logFolder + '/');
|
||||
}
|
||||
};
|
||||
const handleOpenFolder = () => {
|
||||
if (logFolder) {
|
||||
window.ipcRenderer.invoke("reveal-in-folder", logFolder + "/");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 pb-40">
|
||||
<div className="flex flex-col gap-2 rounded-2xl bg-surface-secondary px-6 py-4">
|
||||
<div className="text-body-lg font-bold text-text-heading">
|
||||
{t('setting.data-privacy')}
|
||||
</div>
|
||||
<span className="text-body-sm font-normal text-text-body">
|
||||
{t('setting.data-privacy-description')}{' '}
|
||||
<a
|
||||
className="text-blue-500 no-underline"
|
||||
href="https://www.eigent.ai/privacy-policy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t('setting.privacy-policy')}
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="-ml-2"
|
||||
onClick={() => setIsHowWeHandleOpen((prev) => !prev)}
|
||||
aria-expanded={isHowWeHandleOpen}
|
||||
aria-controls="how-we-handle-your-data"
|
||||
>
|
||||
<span>{t('setting.how-we-handle-your-data')}</span>
|
||||
<ChevronDown
|
||||
className={`h-4 w-4 transition-transform ${isHowWeHandleOpen ? 'rotate-0' : '-rotate-90'}`}
|
||||
/>
|
||||
</Button>
|
||||
{isHowWeHandleOpen && (
|
||||
<ol
|
||||
id="how-we-handle-your-data"
|
||||
className="mt-2 pl-5 text-body-sm font-normal text-text-body"
|
||||
>
|
||||
<li>
|
||||
{t(
|
||||
'setting.we-only-use-the-essential-data-needed-to-run-your-tasks'
|
||||
)}
|
||||
:
|
||||
</li>
|
||||
<ul className="mb-2 pl-4">
|
||||
<li>{t('setting.how-we-handle-your-data-line-1-line-1')}</li>
|
||||
<li>{t('setting.how-we-handle-your-data-line-1-line-2')}</li>
|
||||
<li>{t('setting.how-we-handle-your-data-line-1-line-3')}</li>
|
||||
</ul>
|
||||
<li>{t('setting.how-we-handle-your-data-line-2')}</li>
|
||||
<li>{t('setting.how-we-handle-your-data-line-3')}</li>
|
||||
<li>{t('setting.how-we-handle-your-data-line-4')}</li>
|
||||
<li>{t('setting.how-we-handle-your-data-line-5')}</li>
|
||||
</ol>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<div className="flex flex-col gap-4 pb-40 ">
|
||||
<div className="px-6 py-4 bg-surface-secondary rounded-2xl flex flex-col gap-2">
|
||||
<div className="text-body-lg font-bold text-text-heading">{t("setting.data-privacy")}</div>
|
||||
<span className="text-body-sm font-normal text-text-body">
|
||||
{t("setting.data-privacy-description")}
|
||||
{" "}
|
||||
<a
|
||||
className="text-text-link no-underline"
|
||||
href="https://www.eigent.ai/privacy-policy"
|
||||
target="_blank"
|
||||
>
|
||||
{t("setting.privacy-policy")}
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="-ml-2"
|
||||
onClick={() => setIsHowWeHandleOpen((prev) => !prev)}
|
||||
aria-expanded={isHowWeHandleOpen}
|
||||
aria-controls="how-we-handle-your-data"
|
||||
>
|
||||
<span>{t("setting.how-we-handle-your-data")}</span>
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 transition-transform ${isHowWeHandleOpen ? "rotate-0" : "-rotate-90"}`}
|
||||
/>
|
||||
</Button>
|
||||
{isHowWeHandleOpen && (
|
||||
<ol id="how-we-handle-your-data" className="pl-5 text-body-sm text-text-body font-normal mt-2">
|
||||
<li>{t("setting.we-only-use-the-essential-data-needed-to-run-your-tasks")}:</li>
|
||||
<ul className="pl-4 mb-2">
|
||||
<li>
|
||||
{t("setting.how-we-handle-your-data-line-1-line-1")}
|
||||
</li>
|
||||
<li>
|
||||
{t("setting.how-we-handle-your-data-line-1-line-2")}
|
||||
</li>
|
||||
<li>
|
||||
{t("setting.how-we-handle-your-data-line-1-line-3")}
|
||||
</li>
|
||||
</ul>
|
||||
<li>
|
||||
{t("setting.how-we-handle-your-data-line-2")}
|
||||
</li>
|
||||
<li>
|
||||
{t("setting.how-we-handle-your-data-line-3")}
|
||||
</li>
|
||||
<li>
|
||||
{t("setting.how-we-handle-your-data-line-4")}
|
||||
</li>
|
||||
<li>{t("setting.how-we-handle-your-data-line-5")}</li>
|
||||
</ol>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Privacy controls */}
|
||||
{/* <h2 className="mb-2">Privacy controls</h2>
|
||||
{/* Privacy controls */}
|
||||
{/* <h2 className="mb-2">Privacy controls</h2>
|
||||
<div className="flex gap-2 h-[32px]">
|
||||
<div className="font-bold leading-4">Task Directory</div>
|
||||
<div className="flex-1 text-sm text-text-secondary bg-white-100% text-gray-400 h-[32px] flex items-center px-2 cursor-pointer">
|
||||
|
|
@ -201,24 +197,24 @@ export default function SettingPrivacy() {
|
|||
Open Folder
|
||||
</Button>
|
||||
</div> */}
|
||||
<div className="mt-4 rounded-2xl bg-surface-secondary px-6 py-4">
|
||||
<div className="flex items-center justify-between gap-md">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-body-md font-bold text-text-heading">
|
||||
{t('setting.enable-privacy-permissions-settings')}
|
||||
</div>
|
||||
<div className="text-body-sm font-normal text-text-body">
|
||||
{t('setting.enable-privacy-permissions-settings-description')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<Switch
|
||||
checked={_privacy}
|
||||
onCheckedChange={() => handleTurnOnAll(!_privacy)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="px-6 py-4 bg-surface-secondary rounded-2xl mt-4">
|
||||
<div className="flex gap-md items-center justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-body-md font-bold text-text-heading">
|
||||
{t("setting.enable-privacy-permissions-settings")}
|
||||
</div>
|
||||
<div className="text-body-sm text-text-body font-normal">
|
||||
{t("setting.enable-privacy-permissions-settings-description")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<Switch
|
||||
checked={_privacy}
|
||||
onCheckedChange={() => handleTurnOnAll(!_privacy)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,74 @@ body {
|
|||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
color: var(--text-body);
|
||||
}
|
||||
|
||||
p,
|
||||
span,
|
||||
li,
|
||||
label {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
color: var(--text-primary);
|
||||
background-color: var(--surface-primary);
|
||||
border: 1px solid var(--border-secondary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
color: var(--text-tertiary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.lucide {
|
||||
color: var(--icon-secondary);
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
button .lucide,
|
||||
a .lucide,
|
||||
.lucide[data-state="active"] {
|
||||
color: var(--icon-primary);
|
||||
}
|
||||
|
||||
:is(.bg-white, .bg-white-100, .bg-white-50, [class*="bg-white-100%"], [class*="bg-white-50"]) {
|
||||
color: var(--text-primary);
|
||||
background-color: var(--surface-card) !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[class*="bg-white-50"] {
|
||||
background-color: var(--surface-tertiary) !important;
|
||||
}
|
||||
|
||||
.theme-image-invert-dark {
|
||||
filter: none;
|
||||
transition: filter 150ms ease;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .theme-image-invert-dark {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
[data-theme="transparent"] .theme-image-invert-dark {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
#root,
|
||||
.App {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -176,17 +176,17 @@
|
|||
|
||||
/* Component Tokens */
|
||||
/* Input Component */
|
||||
--input-bg-input: var(--fill-default);
|
||||
--input-bg-input: var(--surface-secondary);
|
||||
--input-bg-spliting: var(--surface-information);
|
||||
--input-bg-confirm: var(--surface-success);
|
||||
--input-bg-default: var(--surface-primary);
|
||||
--input-bg-hover: var(--fill-default);
|
||||
--input-border-default: var(--border-action);
|
||||
--input-bg-hover: var(--surface-secondary);
|
||||
--input-border-default: var(--border-secondary);
|
||||
--input-border-hover: var(--border-action-hover);
|
||||
--input-border-focus: var(--border-information);
|
||||
--input-text-default: var(--text-disabled);
|
||||
--input-text-focus: var(--text-action);
|
||||
--input-label-default: var(--text-label);
|
||||
--input-text-default: var(--text-primary);
|
||||
--input-text-focus: var(--text-primary);
|
||||
--input-label-default: var(--text-secondary);
|
||||
--input-border-success: var(--border-success);
|
||||
--input-border-cuation: var(--border-cuation);
|
||||
--input-border-warning: var(--border-warning);
|
||||
|
|
@ -393,6 +393,10 @@
|
|||
--task-border-focus-success: var(--border-success);
|
||||
--task-border-focus-warning: var(--border-warning);
|
||||
--task-border-focus-error: var(--border-cuation);
|
||||
--task-fill-running: var(--surface-secondary);
|
||||
|
||||
/* Log Component */
|
||||
--log-default: #F5F5F5;
|
||||
|
||||
/* Worker Component */
|
||||
--worker-surface-primary: var(--surface-tertiary);
|
||||
|
|
@ -407,21 +411,50 @@
|
|||
/* Code Component */
|
||||
--code-bg: var(--bg-dark-default);
|
||||
--code-foreground: var(--text-on-action);
|
||||
--code-surface: #f4f4f5;
|
||||
/* zinc-100 equivalent */
|
||||
|
||||
/* Surface Variants */
|
||||
--surface-error-subtle: #fee2e2;
|
||||
/* red-100 equivalent */
|
||||
--surface-hover-subtle: #f3f4f6;
|
||||
/* gray-100 equivalent */
|
||||
--surface-success-subtle: #d1fae5;
|
||||
/* emerald-100 equivalent */
|
||||
--surface-tertiary-subtle: #f9fafb;
|
||||
/* gray-50 equivalent */
|
||||
|
||||
/* Text Variants */
|
||||
--text-muted: var(--colors-primary-5);
|
||||
--text-muted-strong: var(--colors-primary-7);
|
||||
--text-link: var(--colors-blue-500);
|
||||
--text-link-hover: var(--colors-blue-700);
|
||||
--text-error: var(--colors-red-500);
|
||||
|
||||
--border-subtle: var(--colors-primary-2);
|
||||
--border-subtle-strong: var(--colors-primary-3);
|
||||
|
||||
/* Shadow Tokens */
|
||||
|
||||
/* Perfect Shadow */
|
||||
--shadow-perfect:
|
||||
0 8px 20px -2px #1d21291a, 0 32px 48px -12px #1d21291f,
|
||||
0 96px 120px -12px #414a5c0f, 0 108px 72px -16px #414a5c14,
|
||||
0 32px 64px -8px #7199bd1f, 0 8px 10px 0 #7199bd1f;
|
||||
0 8px 20px -2px #1d21291a,
|
||||
0 32px 48px -12px #1d21291f,
|
||||
0 96px 120px -12px #414a5c0f,
|
||||
0 108px 72px -16px #414a5c14,
|
||||
0 32px 64px -8px #7199bd1f,
|
||||
0 8px 10px 0 #7199bd1f;
|
||||
|
||||
/* Button Shadow */
|
||||
--shadow-button:
|
||||
inset 0 1px 0 0 #ffffff54, 0 3px 4px -1px #00000040, 0 0 0 1px #d4d4d440;
|
||||
inset 0 1px 0 0 #ffffff54,
|
||||
0 3px 4px -1px #00000040,
|
||||
0 0 0 1px #d4d4d440;
|
||||
|
||||
}
|
||||
|
||||
.root,
|
||||
[data-theme='transparent'] {
|
||||
[data-theme="transparent"] {
|
||||
--text-heading: var(--colors-primary-10);
|
||||
--text-body: var(--colors-primary-default);
|
||||
--text-label: var(--colors-primary-7);
|
||||
|
|
@ -440,7 +473,15 @@
|
|||
--text-developer: var(--colors-emerald-default);
|
||||
--text-multimodal: var(--colors-fuchsia-default);
|
||||
--text-on-hover: var(--colors-primary-2);
|
||||
--surface-primary: var(--colors-off-white-50);
|
||||
--text-primary: var(--text-body);
|
||||
--text-secondary: var(--text-label);
|
||||
--text-tertiary: var(--text-disabled);
|
||||
--text-inverse-primary: var(--text-on-action);
|
||||
--text-success-primary: var(--text-success);
|
||||
--text-success-default: var(--text-success);
|
||||
--text-caution: var(--text-cuation);
|
||||
--text-cuation-default: var(--text-cuation);
|
||||
--surface-primary: var(--colors-off-white-80);
|
||||
--surface-secondary: var(--colors-off-white-50);
|
||||
--surface-success: var(--colors-green-50);
|
||||
--surface-information: var(--colors-blue-50);
|
||||
|
|
@ -539,7 +580,7 @@
|
|||
}
|
||||
|
||||
.root,
|
||||
[data-theme='light'] {
|
||||
[data-theme="light"] {
|
||||
--text-heading: var(--colors-primary-10);
|
||||
--text-body: var(--colors-primary-default);
|
||||
--text-label: var(--colors-primary-7);
|
||||
|
|
@ -655,4 +696,145 @@
|
|||
--bg-dark-default: var(--colors-off-black-100);
|
||||
--bg-page-default: var(--colors-off-white-100);
|
||||
}
|
||||
}
|
||||
|
||||
.root,
|
||||
[data-theme="dark"] {
|
||||
--text-heading: #e8ecff;
|
||||
--text-body: #f4f6ff;
|
||||
--text-label: #b0bdd8;
|
||||
--text-action: #f8fafc;
|
||||
--text-action-hover: #d9e2ff;
|
||||
--text-disabled: rgba(148, 163, 184, 0.35);
|
||||
--text-information: #7ab3ff;
|
||||
--text-success: #4ade80;
|
||||
--text-warning: #facc15;
|
||||
--text-cuation: #f87171;
|
||||
--text-on-action: #0b1020;
|
||||
--text-on-disabled: rgba(15, 23, 42, 0.7);
|
||||
--text-document: #ffd479;
|
||||
--text-socialmedia: #d8b4fe;
|
||||
--text-browser: #7dd3fc;
|
||||
--text-developer: #6ee7b7;
|
||||
--text-multimodal: #f5a8ff;
|
||||
--text-on-hover: #ffffff;
|
||||
--text-primary: #f4f6ff;
|
||||
--text-secondary: var(--text-label);
|
||||
--text-tertiary: var(--text-disabled);
|
||||
--text-inverse-primary: var(--text-on-action);
|
||||
--text-success-primary: var(--text-success);
|
||||
--text-success-default: var(--text-success);
|
||||
--text-caution: var(--text-cuation);
|
||||
--text-cuation-default: var(--text-cuation);
|
||||
--surface-primary: #131b2b;
|
||||
--surface-secondary: #1b2435;
|
||||
--surface-success: rgba(15, 118, 110, 0.25);
|
||||
--surface-information: rgba(30, 64, 175, 0.22);
|
||||
--surface-warning: rgba(161, 98, 7, 0.26);
|
||||
--surface-cuation: rgba(153, 27, 27, 0.26);
|
||||
--surface-action: #2c3a55;
|
||||
--surface-action-hover: #35476a;
|
||||
--surface-disabled: rgba(148, 163, 184, 0.14);
|
||||
--surface-tertiary: #222d41;
|
||||
--surface-tertiary-hover: #2c3950;
|
||||
--surface-card: #161f30;
|
||||
--surface-card-hover: #1f2a40;
|
||||
--surface-card-focus: #2a3a55;
|
||||
--surface-card-default: 1.25rem;
|
||||
--border-primary: rgba(148, 163, 184, 0.24);
|
||||
--border-secondary: rgba(148, 163, 184, 0.12);
|
||||
--border-tertiary: rgba(148, 163, 184, 0.08);
|
||||
--border-information: rgba(125, 179, 255, 0.65);
|
||||
--border-success: rgba(74, 222, 128, 0.6);
|
||||
--border-cuation: rgba(248, 113, 113, 0.6);
|
||||
--border-warning: rgba(250, 204, 21, 0.6);
|
||||
--border-focus: rgba(226, 232, 240, 0.4);
|
||||
--border-action: rgba(148, 163, 184, 0.24);
|
||||
--border-action-hover: rgba(226, 232, 240, 0.38);
|
||||
--border-disabled: rgba(148, 163, 184, 0.08);
|
||||
--border-developer: rgba(110, 231, 183, 0.6);
|
||||
--border-browser: rgba(125, 211, 252, 0.6);
|
||||
--border-socialmedia: rgba(216, 180, 254, 0.6);
|
||||
--border-multimodal: rgba(245, 168, 255, 0.6);
|
||||
--border-document: rgba(255, 212, 121, 0.6);
|
||||
--border-transparent: var(--colors-black-0);
|
||||
--icon-primary: #d0dcff;
|
||||
--icon-action: #f1f5ff;
|
||||
--icon-disabled: rgba(148, 163, 184, 0.4);
|
||||
--icon-information: #7ab3ff;
|
||||
--icon-success: #4ade80;
|
||||
--icon-warning: #facc15;
|
||||
--icon-cuation: #f87171;
|
||||
--icon-action-hover: #ffffff;
|
||||
--icon-multimodal: #f5a8ff;
|
||||
--icon-socialmedia: #d8b4fe;
|
||||
--icon-document: #ffd479;
|
||||
--icon-browser: #7dd3fc;
|
||||
--icon-developer: #6ee7b7;
|
||||
--icon-on-disabled: rgba(15, 23, 42, 0.7);
|
||||
--icon-on-hover: #ffffff;
|
||||
--icon-on-action: #0b1020;
|
||||
--icon-secondary: rgba(226, 232, 240, 0.55);
|
||||
--developer: #34d399;
|
||||
--browser: #38bdf8;
|
||||
--document: #fbbf24;
|
||||
--multimodal: #f472b6;
|
||||
--socialmedia: #c084fc;
|
||||
--fill-default: #131b2b;
|
||||
--fill-fill-primary: #e2e8ff;
|
||||
--fill-fill-primary-hover: #ffffff;
|
||||
--fill-fill-primary-active: #ffffff;
|
||||
--fill-fill-primary-disabled: rgba(209, 213, 219, 0.4);
|
||||
--fill-fill-tertiary: #1f2937;
|
||||
--fill-fill-transparent: rgba(15, 23, 42, 0.4);
|
||||
--fill-fill-transparent-hover: rgba(59, 74, 99, 0.6);
|
||||
--fill-fill-tertiary-hover: #2c3a55;
|
||||
--fill-fill-tertiary-active: #35476a;
|
||||
--fill-fill-tertiary-disabled: rgba(31, 41, 55, 0.6);
|
||||
--fill-fill-transparent-active: rgba(59, 74, 99, 0.75);
|
||||
--fill-fill-transparent-disabled: rgba(15, 23, 42, 0.2);
|
||||
--fill-fill-secondary-disabled: rgba(148, 163, 184, 0.25);
|
||||
--fill-fill-secondary-active: #f1f5ff;
|
||||
--fill-fill-secondary-hover: rgba(226, 232, 240, 0.85);
|
||||
--fill-fill-secondary: rgba(226, 232, 240, 0.65);
|
||||
--fill-fill-success: #22c55e;
|
||||
--fill-fill-success-hover: #4ade80;
|
||||
--fill-fill-success-active: #a7f3d0;
|
||||
--fill-fill-success-disable: rgba(15, 118, 110, 0.45);
|
||||
--fill-fill-warning: #facc15;
|
||||
--fill-fill-cuation: #f87171;
|
||||
--fill-socialmedia: rgba(134, 94, 189, 0.45);
|
||||
--fill-document: rgba(180, 128, 41, 0.45);
|
||||
--fill-browser: rgba(40, 94, 151, 0.45);
|
||||
--fill-multimodal: rgba(161, 60, 190, 0.45);
|
||||
--fill-developer: rgba(23, 121, 96, 0.45);
|
||||
--fill-scrollbar-dark: rgba(148, 163, 184, 0.2);
|
||||
--fill-scrollbar-light: rgba(209, 213, 219, 0.35);
|
||||
--fill-skeloten-default: rgba(148, 163, 184, 0.18);
|
||||
--fill-fill-information: #7ab3ff;
|
||||
--bg-page: #0d1424;
|
||||
--bg-primary: #121a2a;
|
||||
--bg-secondary: #161f30;
|
||||
--bg-tertiary: #1c2640;
|
||||
--bg-dark: #131b2b;
|
||||
--bg-dark-primary: #1b2435;
|
||||
--bg-dark-secondary: #222d41;
|
||||
--bg-dark-tertiary: #2c3950;
|
||||
--bg-dark-default: #0d1424;
|
||||
--bg-page-default: #0d1424;
|
||||
--task-fill-running: #1f2937;
|
||||
--log-default: #1f2937;
|
||||
--code-surface: #27272a;
|
||||
--surface-error-subtle: rgba(153, 27, 27, 0.3);
|
||||
--surface-hover-subtle: rgba(55, 65, 81, 0.5);
|
||||
--surface-success-subtle: rgba(6, 95, 70, 0.35);
|
||||
--surface-tertiary-subtle: rgba(31, 41, 55, 0.5);
|
||||
--text-muted: var(--colors-primary-4);
|
||||
--text-muted-strong: var(--colors-primary-3);
|
||||
--text-link: var(--colors-blue-400);
|
||||
--text-link-hover: var(--colors-blue-300);
|
||||
--text-error: var(--colors-red-400);
|
||||
--border-subtle: var(--colors-primary-7);
|
||||
--border-subtle-strong: var(--colors-primary-6);
|
||||
}
|
||||
|
||||
}
|
||||
1431
tailwind.config.js
1431
tailwind.config.js
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue