mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
Frontend audit: Bug fixes and refinements (#2112)
* (fix:attachements) sep id for redux ops * (fix:ui) popups, toast, share modal * (feat:agentsPreview) stable preview, ui fixes * (fix:ui) light theme icon, sleek scroll * (chore:i18n) missin keys * (chore:i18n) missing keys * (feat:preferrenceSlice) autoclear invalid source from storage * (fix:general) delete all conv close btn * (fix:tts) play one at a time * (fix:tts) gracefully unmount * (feat:tts) audio LRU cache * (feat:tts) pointer on hovered area * (feat:tts) clean text for speach --------- Co-authored-by: GH Action - Upstream Sync <action@github.com>
This commit is contained in:
@@ -130,11 +130,15 @@ class TextToSpeech(Resource):
|
||||
@api.expect(tts_model)
|
||||
@api.doc(description="Synthesize audio speech from text")
|
||||
def post(self):
|
||||
from application.utils import clean_text_for_tts
|
||||
|
||||
data = request.get_json()
|
||||
text = data["text"]
|
||||
cleaned_text = clean_text_for_tts(text)
|
||||
|
||||
try:
|
||||
tts_instance = TTSCreator.create_tts(settings.TTS_PROVIDER)
|
||||
audio_base64, detected_language = tts_instance.text_to_speech(text)
|
||||
audio_base64, detected_language = tts_instance.text_to_speech(cleaned_text)
|
||||
return make_response(
|
||||
jsonify(
|
||||
{
|
||||
|
||||
@@ -187,3 +187,44 @@ def generate_image_url(image_path):
|
||||
else:
|
||||
base_url = getattr(settings, "API_URL", "http://localhost:7091")
|
||||
return f"{base_url}/api/images/{image_path}"
|
||||
|
||||
|
||||
def clean_text_for_tts(text: str) -> str:
|
||||
"""
|
||||
clean text for Text-to-Speech processing.
|
||||
"""
|
||||
# Handle code blocks and links
|
||||
text = re.sub(r'```mermaid[\s\S]*?```', ' flowchart, ', text) ## ```mermaid...```
|
||||
text = re.sub(r'```[\s\S]*?```', ' code block, ', text) ## ```code```
|
||||
text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text) ## [text](url)
|
||||
text = re.sub(r'!\[([^\]]*)\]\([^\)]+\)', '', text) ## 
|
||||
|
||||
# Remove markdown formatting
|
||||
text = re.sub(r'`([^`]+)`', r'\1', text) ## `code`
|
||||
text = re.sub(r'\{([^}]*)\}', r' \1 ', text) ## {text}
|
||||
text = re.sub(r'[{}]', ' ', text) ## unmatched {}
|
||||
text = re.sub(r'\[([^\]]+)\]', r' \1 ', text) ## [text]
|
||||
text = re.sub(r'[\[\]]', ' ', text) ## unmatched []
|
||||
text = re.sub(r'(\*\*|__)(.*?)\1', r'\2', text) ## **bold** __bold__
|
||||
text = re.sub(r'(\*|_)(.*?)\1', r'\2', text) ## *italic* _italic_
|
||||
text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE) ## # headers
|
||||
text = re.sub(r'^>\s+', '', text, flags=re.MULTILINE) ## > blockquotes
|
||||
text = re.sub(r'^[\s]*[-\*\+]\s+', '', text, flags=re.MULTILINE) ## - * + lists
|
||||
text = re.sub(r'^[\s]*\d+\.\s+', '', text, flags=re.MULTILINE) ## 1. numbered lists
|
||||
text = re.sub(r'^[\*\-_]{3,}\s*$', '', text, flags=re.MULTILINE) ## --- *** ___ rules
|
||||
text = re.sub(r'<[^>]*>', '', text) ## <html> tags
|
||||
|
||||
#Remove non-ASCII (emojis, special Unicode)
|
||||
text = re.sub(r'[^\x20-\x7E\n\r\t]', '', text)
|
||||
|
||||
#Replace special sequences
|
||||
text = re.sub(r'-->', ', ', text) ## -->
|
||||
text = re.sub(r'<--', ', ', text) ## <--
|
||||
text = re.sub(r'=>', ', ', text) ## =>
|
||||
text = re.sub(r'::', ' ', text) ## ::
|
||||
|
||||
#Normalize whitespace
|
||||
text = re.sub(r'\s+', ' ', text)
|
||||
text = text.strip()
|
||||
|
||||
return text
|
||||
|
||||
@@ -411,7 +411,9 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
{recentAgents?.length > 0 ? (
|
||||
<div>
|
||||
<div className="mx-4 my-auto mt-2 flex h-6 items-center">
|
||||
<p className="mt-1 ml-4 text-sm font-semibold">Agents</p>
|
||||
<p className="mt-1 ml-4 text-sm font-semibold">
|
||||
{t('navigation.agents')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="agents-container">
|
||||
<div>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function PageNotFound() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="dark:bg-raisin-black grid min-h-screen">
|
||||
<p className="text-jet dark:bg-outer-space mx-auto my-auto mt-20 flex w-full max-w-6xl flex-col place-items-center gap-6 rounded-3xl bg-gray-100 p-6 lg:p-10 xl:p-16 dark:text-gray-100">
|
||||
<h1>404</h1>
|
||||
<p>The page you are looking for does not exist.</p>
|
||||
<h1>{t('pageNotFound.title')}</h1>
|
||||
<p>{t('pageNotFound.message')}</p>
|
||||
<button className="pointer-cursor bg-blue-1000 hover:bg-blue-3000 mr-4 flex cursor-pointer items-center justify-center rounded-full px-4 py-2 text-white transition-colors duration-100">
|
||||
<Link to="/">Go Back Home</Link>
|
||||
<Link to="/">{t('pageNotFound.goHome')}</Link>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
@@ -11,6 +12,7 @@ import Logs from '../settings/Logs';
|
||||
import { Agent } from './types';
|
||||
|
||||
export default function AgentLogs() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { agentId } = useParams();
|
||||
const token = useSelector(selectToken);
|
||||
@@ -45,12 +47,12 @@ export default function AgentLogs() {
|
||||
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
|
||||
</button>
|
||||
<p className="text-eerie-black dark:text-bright-gray mt-px text-sm font-semibold">
|
||||
Back to all agents
|
||||
{t('agents.backToAll')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-5 flex w-full flex-wrap items-center justify-between gap-2 px-4">
|
||||
<h1 className="text-eerie-black m-0 text-[32px] font-bold md:text-[40px] dark:text-white">
|
||||
Agent Logs
|
||||
{t('agents.logs.title')}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col gap-3 px-4">
|
||||
@@ -59,9 +61,10 @@ export default function AgentLogs() {
|
||||
<p className="text-[#28292E] dark:text-[#E0E0E0]">{agent.name}</p>
|
||||
<p className="text-xs text-[#28292E] dark:text-[#E0E0E0]/40">
|
||||
{agent.last_used_at
|
||||
? 'Last used at ' +
|
||||
? t('agents.logs.lastUsedAt') +
|
||||
' ' +
|
||||
new Date(agent.last_used_at).toLocaleString()
|
||||
: 'No usage history'}
|
||||
: t('agents.logs.noUsageHistory')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -79,7 +82,9 @@ export default function AgentLogs() {
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
agent && <Logs agentId={agent.id} tableHeader="Agent endpoint logs" />
|
||||
agent && (
|
||||
<Logs agentId={agent.id} tableHeader={t('agents.logs.tableHeader')} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import MessageInput from '../components/MessageInput';
|
||||
@@ -17,6 +18,7 @@ import { selectSelectedAgent } from '../preferences/preferenceSlice';
|
||||
import { AppDispatch } from '../store';
|
||||
|
||||
export default function AgentPreview() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
const queries = useSelector(selectPreviewQueries);
|
||||
@@ -130,8 +132,7 @@ export default function AgentPreview() {
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-4000 dark:text-sonic-silver w-full bg-transparent text-center text-xs md:inline">
|
||||
This is a preview of the agent. You can publish it to start using it
|
||||
in conversations.
|
||||
{t('agents.preview.testMessage')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -17,6 +18,7 @@ import { agentSectionsConfig } from './agents.config';
|
||||
import { Agent } from './types';
|
||||
|
||||
export default function AgentsList() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const token = useSelector(selectToken);
|
||||
const selectedAgent = useSelector(selectSelectedAgent);
|
||||
@@ -33,11 +35,10 @@ export default function AgentsList() {
|
||||
return (
|
||||
<div className="p-4 md:p-12">
|
||||
<h1 className="text-eerie-black mb-0 text-[32px] font-bold lg:text-[40px] dark:text-[#E0E0E0]">
|
||||
Agents
|
||||
{t('agents.title')}
|
||||
</h1>
|
||||
<p className="dark:text-gray-4000 mt-5 text-[15px] text-[#71717A]">
|
||||
Discover and create custom versions of DocsGPT that combine
|
||||
instructions, extra knowledge, and any combination of skills
|
||||
{t('agents.description')}
|
||||
</p>
|
||||
{agentSectionsConfig.map((sectionConfig) => (
|
||||
<AgentSection key={sectionConfig.id} config={sectionConfig} />
|
||||
@@ -51,6 +52,7 @@ function AgentSection({
|
||||
}: {
|
||||
config: (typeof agentSectionsConfig)[number];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const token = useSelector(selectToken);
|
||||
@@ -85,16 +87,18 @@ function AgentSection({
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h2 className="text-[18px] font-semibold text-[#18181B] dark:text-[#E0E0E0]">
|
||||
{config.title}
|
||||
{t(`agents.sections.${config.id}.title`)}
|
||||
</h2>
|
||||
<p className="text-[13px] text-[#71717A]">{config.description}</p>
|
||||
<p className="text-[13px] text-[#71717A]">
|
||||
{t(`agents.sections.${config.id}.description`)}
|
||||
</p>
|
||||
</div>
|
||||
{config.showNewAgentButton && (
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue rounded-full px-4 py-2 text-sm text-white"
|
||||
onClick={() => navigate('/agents/new')}
|
||||
>
|
||||
New Agent
|
||||
{t('agents.newAgent')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -117,13 +121,13 @@ function AgentSection({
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-72 w-full flex-col items-center justify-center gap-3 text-base text-[#18181B] dark:text-[#E0E0E0]">
|
||||
<p>{config.emptyStateDescription}</p>
|
||||
<p>{t(`agents.sections.${config.id}.emptyState`)}</p>
|
||||
{config.showNewAgentButton && (
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue ml-2 rounded-full px-4 py-2 text-sm text-white"
|
||||
onClick={() => navigate('/agents/new')}
|
||||
>
|
||||
New Agent
|
||||
{t('agents.newAgent')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
@@ -30,6 +31,7 @@ const embeddingsName =
|
||||
'huggingface_sentence-transformers/all-mpnet-base-v2';
|
||||
|
||||
export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { agentId } = useParams();
|
||||
@@ -87,8 +89,8 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
|
||||
const modeConfig = {
|
||||
new: {
|
||||
heading: 'New Agent',
|
||||
buttonText: 'Publish',
|
||||
heading: t('agents.form.headings.new'),
|
||||
buttonText: t('agents.form.buttons.publish'),
|
||||
showDelete: false,
|
||||
showSaveDraft: true,
|
||||
showLogs: false,
|
||||
@@ -96,8 +98,8 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
trackChanges: false,
|
||||
},
|
||||
edit: {
|
||||
heading: 'Edit Agent',
|
||||
buttonText: 'Save',
|
||||
heading: t('agents.form.headings.edit'),
|
||||
buttonText: t('agents.form.buttons.save'),
|
||||
showDelete: true,
|
||||
showSaveDraft: false,
|
||||
showLogs: true,
|
||||
@@ -105,8 +107,8 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
trackChanges: true,
|
||||
},
|
||||
draft: {
|
||||
heading: 'New Agent (Draft)',
|
||||
buttonText: 'Publish',
|
||||
heading: t('agents.form.headings.draft'),
|
||||
buttonText: t('agents.form.buttons.publish'),
|
||||
showDelete: true,
|
||||
showSaveDraft: true,
|
||||
showLogs: false,
|
||||
@@ -116,8 +118,8 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
};
|
||||
const chunks = ['0', '2', '4', '6', '8', '10'];
|
||||
const agentTypes = [
|
||||
{ label: 'Classic', value: 'classic' },
|
||||
{ label: 'ReAct', value: 'react' },
|
||||
{ label: t('agents.form.agentTypes.classic'), value: 'classic' },
|
||||
{ label: t('agents.form.agentTypes.react'), value: 'react' },
|
||||
];
|
||||
|
||||
const isPublishable = () => {
|
||||
@@ -543,7 +545,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
|
||||
</button>
|
||||
<p className="text-eerie-black dark:text-bright-gray mt-px text-sm font-semibold">
|
||||
Back to all agents
|
||||
{t('agents.backToAll')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-5 flex w-full flex-wrap items-center justify-between gap-2 px-4">
|
||||
@@ -555,7 +557,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
className="text-purple-30 dark:text-light-gray mr-4 rounded-3xl py-2 text-sm font-medium dark:bg-transparent"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Cancel
|
||||
{t('agents.form.buttons.cancel')}
|
||||
</button>
|
||||
{modeConfig[effectiveMode].showDelete && agent.id && (
|
||||
<button
|
||||
@@ -563,7 +565,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
onClick={() => setDeleteConfirmation('ACTIVE')}
|
||||
>
|
||||
<span className="block h-4 w-4 bg-[url('/src/assets/red-trash.svg')] bg-contain bg-center bg-no-repeat transition-all group-hover:bg-[url('/src/assets/white-trash.svg')]" />
|
||||
Delete
|
||||
{t('agents.form.buttons.delete')}
|
||||
</button>
|
||||
)}
|
||||
{modeConfig[effectiveMode].showSaveDraft && (
|
||||
@@ -578,7 +580,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
{draftLoading ? (
|
||||
<Spinner size="small" color="#976af3" />
|
||||
) : (
|
||||
'Save Draft'
|
||||
t('agents.form.buttons.saveDraft')
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
@@ -589,7 +591,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
onClick={() => navigate(`/agents/logs/${agent.id}`)}
|
||||
>
|
||||
<span className="block h-5 w-5 bg-[url('/src/assets/monitoring-purple.svg')] bg-contain bg-center bg-no-repeat transition-all group-hover:bg-[url('/src/assets/monitoring-white.svg')]" />
|
||||
Logs
|
||||
{t('agents.form.buttons.logs')}
|
||||
</button>
|
||||
)}
|
||||
{modeConfig[effectiveMode].showAccessDetails && (
|
||||
@@ -597,7 +599,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
className="hover:bg-vi</button>olets-are-blue border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
||||
onClick={() => setAgentDetails('ACTIVE')}
|
||||
>
|
||||
Access Details
|
||||
{t('agents.form.buttons.accessDetails')}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
@@ -618,17 +620,19 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
<div className="mt-3 flex w-full flex-1 grid-cols-5 flex-col gap-10 rounded-[30px] bg-[#F6F6F6] p-5 max-[1179px]:overflow-visible min-[1180px]:grid min-[1180px]:gap-5 min-[1180px]:overflow-hidden dark:bg-[#383838]">
|
||||
<div className="scrollbar-thin col-span-2 flex flex-col gap-5 max-[1179px]:overflow-visible min-[1180px]:max-h-full min-[1180px]:overflow-y-auto min-[1180px]:pr-3">
|
||||
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
|
||||
<h2 className="text-lg font-semibold">Meta</h2>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t('agents.form.sections.meta')}
|
||||
</h2>
|
||||
<input
|
||||
className="border-silver text-jet dark:bg-raisin-black dark:text-bright-gray dark:placeholder:text-silver mt-3 w-full rounded-3xl border bg-white px-5 py-3 text-sm outline-hidden placeholder:text-gray-400 dark:border-[#7E7E7E]"
|
||||
type="text"
|
||||
value={agent.name}
|
||||
placeholder="Agent name"
|
||||
placeholder={t('agents.form.placeholders.agentName')}
|
||||
onChange={(e) => setAgent({ ...agent, name: e.target.value })}
|
||||
/>
|
||||
<textarea
|
||||
className="border-silver text-jet dark:bg-raisin-black dark:text-bright-gray dark:placeholder:text-silver mt-3 h-32 w-full rounded-xl border bg-white px-5 py-4 text-sm outline-hidden placeholder:text-gray-400 dark:border-[#7E7E7E]"
|
||||
placeholder="Describe your agent"
|
||||
placeholder={t('agents.form.placeholders.describeAgent')}
|
||||
value={agent.description}
|
||||
onChange={(e) =>
|
||||
setAgent({ ...agent, description: e.target.value })
|
||||
@@ -641,9 +645,12 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
onUpload={handleUpload}
|
||||
onRemove={() => setImageFile(null)}
|
||||
uploadText={[
|
||||
{ text: 'Click to upload', colorClass: 'text-[#7D54D1]' },
|
||||
{
|
||||
text: ' or drag and drop',
|
||||
text: t('agents.form.upload.clickToUpload'),
|
||||
colorClass: 'text-[#7D54D1]',
|
||||
},
|
||||
{
|
||||
text: t('agents.form.upload.dragAndDrop'),
|
||||
colorClass: 'text-[#525252]',
|
||||
},
|
||||
]}
|
||||
@@ -651,7 +658,9 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
|
||||
<h2 className="text-lg font-semibold">Source</h2>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t('agents.form.sections.source')}
|
||||
</h2>
|
||||
<div className="mt-3">
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
<button
|
||||
@@ -672,11 +681,13 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
source.name === id ||
|
||||
source.retriever === id,
|
||||
);
|
||||
return matchedDoc?.name || `External KB`;
|
||||
return (
|
||||
matchedDoc?.name || t('agents.form.externalKb')
|
||||
);
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(', ')
|
||||
: 'Select sources'}
|
||||
: t('agents.form.placeholders.selectSources')}
|
||||
</button>
|
||||
<MultiSelectPopup
|
||||
isOpen={isSourcePopupOpen}
|
||||
@@ -720,9 +731,13 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
setSelectedSourceIds(newSelectedIds);
|
||||
}
|
||||
}}
|
||||
title="Select Sources"
|
||||
searchPlaceholder="Search sources..."
|
||||
noOptionsMessage="No sources available"
|
||||
title={t('agents.form.sourcePopup.title')}
|
||||
searchPlaceholder={t(
|
||||
'agents.form.sourcePopup.searchPlaceholder',
|
||||
)}
|
||||
noOptionsMessage={t(
|
||||
'agents.form.sourcePopup.noOptionsMessage',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
@@ -737,7 +752,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
border="border"
|
||||
buttonClassName="bg-white dark:bg-[#222327] border-silver dark:border-[#7E7E7E]"
|
||||
optionsClassName="bg-white dark:bg-[#383838] border-silver dark:border-[#7E7E7E]"
|
||||
placeholder="Chunks per query"
|
||||
placeholder={t('agents.form.placeholders.chunksPerQuery')}
|
||||
placeholderClassName="text-gray-400 dark:text-silver"
|
||||
contentSize="text-sm"
|
||||
/>
|
||||
@@ -757,7 +772,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
setAgent({ ...agent, prompt_id: id })
|
||||
}
|
||||
setPrompts={setPrompts}
|
||||
title="Prompt"
|
||||
title={t('agents.form.sections.prompt')}
|
||||
titleClassName="text-lg font-semibold dark:text-[#E0E0E0]"
|
||||
showAddButton={false}
|
||||
dropdownProps={{
|
||||
@@ -777,12 +792,14 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
className="border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue w-20 shrink-0 basis-full rounded-3xl border-2 border-solid px-5 py-[11px] text-sm transition-colors hover:text-white sm:basis-auto"
|
||||
onClick={() => setAddPromptModal('ACTIVE')}
|
||||
>
|
||||
Add
|
||||
{t('agents.form.buttons.add')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
|
||||
<h2 className="text-lg font-semibold">Tools</h2>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t('agents.form.sections.tools')}
|
||||
</h2>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-1">
|
||||
<button
|
||||
ref={toolAnchorButtonRef}
|
||||
@@ -798,7 +815,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
.map((tool) => tool.display_name || tool.name)
|
||||
.filter(Boolean)
|
||||
.join(', ')
|
||||
: 'Select tools'}
|
||||
: t('agents.form.placeholders.selectTools')}
|
||||
</button>
|
||||
<MultiSelectPopup
|
||||
isOpen={isToolsPopupOpen}
|
||||
@@ -817,14 +834,18 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
})),
|
||||
)
|
||||
}
|
||||
title="Select Tools"
|
||||
searchPlaceholder="Search tools..."
|
||||
noOptionsMessage="No tools available"
|
||||
title={t('agents.form.toolsPopup.title')}
|
||||
searchPlaceholder={t(
|
||||
'agents.form.toolsPopup.searchPlaceholder',
|
||||
)}
|
||||
noOptionsMessage={t('agents.form.toolsPopup.noOptionsMessage')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dark:bg-raisin-black rounded-[30px] bg-white px-6 py-3 dark:text-[#E0E0E0]">
|
||||
<h2 className="text-lg font-semibold">Agent type</h2>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t('agents.form.sections.agentType')}
|
||||
</h2>
|
||||
<div className="mt-3">
|
||||
<Dropdown
|
||||
options={agentTypes}
|
||||
@@ -842,7 +863,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
border="border"
|
||||
buttonClassName="bg-white dark:bg-[#222327] border-silver dark:border-[#7E7E7E]"
|
||||
optionsClassName="bg-white dark:bg-[#383838] border-silver dark:border-[#7E7E7E]"
|
||||
placeholder="Select type"
|
||||
placeholder={t('agents.form.placeholders.selectType')}
|
||||
placeholderClassName="text-gray-400 dark:text-silver"
|
||||
contentSize="text-sm"
|
||||
/>
|
||||
@@ -856,7 +877,9 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
className="flex w-full items-center justify-between text-left focus:outline-none"
|
||||
>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Advanced</h2>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t('agents.form.sections.advanced')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="ml-4 flex items-center">
|
||||
<svg
|
||||
@@ -879,9 +902,11 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
{isAdvancedSectionExpanded && (
|
||||
<div className="mt-3">
|
||||
<div>
|
||||
<h2 className="text-sm font-medium">JSON response schema</h2>
|
||||
<h2 className="text-sm font-medium">
|
||||
{t('agents.form.advanced.jsonSchema')}
|
||||
</h2>
|
||||
<p className="mt-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
Define a JSON schema to enforce structured output format
|
||||
{t('agents.form.advanced.jsonSchemaDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<textarea
|
||||
@@ -915,17 +940,19 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
}`}
|
||||
/>
|
||||
{jsonSchemaValid
|
||||
? 'Valid JSON'
|
||||
: 'Invalid JSON - fix to enable saving'}
|
||||
? t('agents.form.advanced.validJson')
|
||||
: t('agents.form.advanced.invalidJson')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-sm font-medium">Token limiting</h2>
|
||||
<h2 className="text-sm font-medium">
|
||||
{t('agents.form.advanced.tokenLimiting')}
|
||||
</h2>
|
||||
<p className="mt-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
Limit daily total tokens that can be used by this agent
|
||||
{t('agents.form.advanced.tokenLimitingDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -965,7 +992,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
})
|
||||
}
|
||||
disabled={!agent.limited_token_mode}
|
||||
placeholder="Enter token limit"
|
||||
placeholder={t('agents.form.placeholders.enterTokenLimit')}
|
||||
className={`border-silver text-jet dark:bg-raisin-black dark:text-bright-gray dark:placeholder:text-silver mt-2 w-full rounded-3xl border bg-white px-5 py-3 text-sm outline-hidden placeholder:text-gray-400 dark:border-[#7E7E7E] ${
|
||||
!agent.limited_token_mode
|
||||
? 'cursor-not-allowed opacity-50'
|
||||
@@ -977,10 +1004,11 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-sm font-medium">Request limiting</h2>
|
||||
<h2 className="text-sm font-medium">
|
||||
{t('agents.form.advanced.requestLimiting')}
|
||||
</h2>
|
||||
<p className="mt-1 text-xs text-gray-600 dark:text-gray-400">
|
||||
Limit daily total requests that can be made to this
|
||||
agent
|
||||
{t('agents.form.advanced.requestLimitingDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -1020,7 +1048,9 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
})
|
||||
}
|
||||
disabled={!agent.limited_request_mode}
|
||||
placeholder="Enter request limit"
|
||||
placeholder={t(
|
||||
'agents.form.placeholders.enterRequestLimit',
|
||||
)}
|
||||
className={`border-silver text-jet dark:bg-raisin-black dark:text-bright-gray dark:placeholder:text-silver mt-2 w-full rounded-3xl border bg-white px-5 py-3 text-sm outline-hidden placeholder:text-gray-400 dark:border-[#7E7E7E] ${
|
||||
!agent.limited_request_mode
|
||||
? 'cursor-not-allowed opacity-50'
|
||||
@@ -1033,22 +1063,24 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-3 flex flex-col gap-2 max-[1179px]:h-auto max-[1179px]:px-0 max-[1179px]:py-0 min-[1180px]:h-full min-[1180px]:py-2 dark:text-[#E0E0E0]">
|
||||
<h2 className="text-lg font-semibold">Preview</h2>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t('agents.form.sections.preview')}
|
||||
</h2>
|
||||
<div className="flex-1 max-[1179px]:overflow-visible min-[1180px]:min-h-0 min-[1180px]:overflow-hidden">
|
||||
<AgentPreviewArea />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
message="Are you sure you want to delete this agent?"
|
||||
message={t('agents.deleteConfirmation')}
|
||||
modalState={deleteConfirmation}
|
||||
setModalState={setDeleteConfirmation}
|
||||
submitLabel="Delete"
|
||||
submitLabel={t('agents.form.buttons.delete')}
|
||||
handleSubmit={() => {
|
||||
handleDelete(agent.id || '');
|
||||
setDeleteConfirmation('INACTIVE');
|
||||
}}
|
||||
cancelLabel="Cancel"
|
||||
cancelLabel={t('agents.form.buttons.cancel')}
|
||||
variant="danger"
|
||||
/>
|
||||
<AgentDetailsModal
|
||||
@@ -1071,6 +1103,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
}
|
||||
|
||||
function AgentPreviewArea() {
|
||||
const { t } = useTranslation();
|
||||
const selectedAgent = useSelector(selectSelectedAgent);
|
||||
return (
|
||||
<div className="dark:bg-raisin-black w-full rounded-[30px] border border-[#F6F6F6] bg-white max-[1179px]:h-[600px] min-[1180px]:h-full dark:border-[#7E7E7E]">
|
||||
@@ -1082,7 +1115,7 @@ function AgentPreviewArea() {
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-2">
|
||||
<span className="block h-12 w-12 bg-[url('/src/assets/science-spark.svg')] bg-contain bg-center bg-no-repeat transition-all dark:bg-[url('/src/assets/science-spark-dark.svg')]" />{' '}
|
||||
<p className="dark:text-gray-4000 text-xs text-[#18181B]">
|
||||
Published agents can be previewed here
|
||||
{t('agents.form.preview.publishedPreview')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -144,7 +144,7 @@ export default function SharedAgent() {
|
||||
className="mx-auto mb-6 h-32 w-32"
|
||||
/>
|
||||
<p className="dark:text-gray-4000 text-center text-lg text-[#71717A]">
|
||||
No agent found. Please ensure the agent is shared.
|
||||
{t('agents.shared.notFound')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function ActionButtons({
|
||||
<div className={`flex items-center gap-2 sm:gap-4 ${className}`}>
|
||||
{showNewChat && (
|
||||
<button
|
||||
title="Open New Chat"
|
||||
title={t('actionButtons.openNewChat')}
|
||||
onClick={newChat}
|
||||
className="hover:bg-bright-gray flex items-center gap-1 rounded-full p-2 lg:hidden dark:hover:bg-[#28292E]"
|
||||
>
|
||||
@@ -62,7 +62,7 @@ export default function ActionButtons({
|
||||
{showShare && conversationId && (
|
||||
<>
|
||||
<button
|
||||
title="Share"
|
||||
title={t('actionButtons.share')}
|
||||
onClick={() => setShareModalState(true)}
|
||||
className="hover:bg-bright-gray rounded-full p-2 dark:hover:bg-[#28292E]"
|
||||
>
|
||||
|
||||
@@ -38,7 +38,7 @@ interface DirectoryStructure {
|
||||
[key: string]: FileNode;
|
||||
}
|
||||
|
||||
interface ConnectorTreeComponentProps {
|
||||
interface ConnectorTreeProps {
|
||||
docId: string;
|
||||
sourceName: string;
|
||||
onBackToDocuments: () => void;
|
||||
@@ -50,7 +50,7 @@ interface SearchResult {
|
||||
isFile: boolean;
|
||||
}
|
||||
|
||||
const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
const ConnectorTree: React.FC<ConnectorTreeProps> = ({
|
||||
docId,
|
||||
sourceName,
|
||||
onBackToDocuments,
|
||||
@@ -744,4 +744,4 @@ const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectorTreeComponent;
|
||||
export default ConnectorTree;
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { formatBytes } from '../utils/stringUtils';
|
||||
import { formatDate } from '../utils/dateTimeUtils';
|
||||
import {
|
||||
@@ -66,6 +67,7 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [files, setFiles] = useState<CloudFile[]>([]);
|
||||
const [selectedFiles, setSelectedFiles] =
|
||||
useState<string[]>(initialSelectedFiles);
|
||||
@@ -417,7 +419,7 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
<div className="mb-3 max-w-md">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search files and folders..."
|
||||
placeholder={t('filePicker.searchPlaceholder')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearchChange(e.target.value)}
|
||||
colorVariant="silver"
|
||||
@@ -431,7 +433,9 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
|
||||
{/* Selected Files Message */}
|
||||
<div className="pb-3 text-sm text-gray-600 dark:text-gray-400">
|
||||
{selectedFiles.length + selectedFolders.length} selected
|
||||
{t('filePicker.itemsSelected', {
|
||||
count: selectedFiles.length + selectedFolders.length,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -448,9 +452,15 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeader width="40px"></TableHeader>
|
||||
<TableHeader width="60%">Name</TableHeader>
|
||||
<TableHeader width="20%">Last Modified</TableHeader>
|
||||
<TableHeader width="20%">Size</TableHeader>
|
||||
<TableHeader width="60%">
|
||||
{t('filePicker.name')}
|
||||
</TableHeader>
|
||||
<TableHeader width="20%">
|
||||
{t('filePicker.lastModified')}
|
||||
</TableHeader>
|
||||
<TableHeader width="20%">
|
||||
{t('filePicker.size')}
|
||||
</TableHeader>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
||||
@@ -36,7 +36,7 @@ interface DirectoryStructure {
|
||||
[key: string]: FileNode;
|
||||
}
|
||||
|
||||
interface FileTreeComponentProps {
|
||||
interface FileTreeProps {
|
||||
docId: string;
|
||||
sourceName: string;
|
||||
onBackToDocuments: () => void;
|
||||
@@ -48,7 +48,7 @@ interface SearchResult {
|
||||
isFile: boolean;
|
||||
}
|
||||
|
||||
const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
||||
const FileTree: React.FC<FileTreeProps> = ({
|
||||
docId,
|
||||
sourceName,
|
||||
onBackToDocuments,
|
||||
@@ -871,4 +871,4 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default FileTreeComponent;
|
||||
export default FileTree;
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
@@ -44,13 +45,14 @@ export const FileUpload = ({
|
||||
activeClassName = 'border-blue-500 bg-blue-50',
|
||||
acceptClassName = 'border-green-500 dark:border-green-500 bg-green-50 dark:bg-green-50/10',
|
||||
rejectClassName = 'border-red-500 bg-red-50 dark:bg-red-500/10 dark:border-red-500',
|
||||
uploadText = 'Click to upload or drag and drop',
|
||||
dragActiveText = 'Drop the files here',
|
||||
fileTypeText = 'PNG, JPG, JPEG up to',
|
||||
sizeLimitText = 'MB',
|
||||
uploadText,
|
||||
dragActiveText,
|
||||
fileTypeText,
|
||||
sizeLimitText,
|
||||
disabled = false,
|
||||
validator,
|
||||
}: FileUploadProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [errors, setErrors] = useState<string[]>([]);
|
||||
const [preview, setPreview] = useState<string | null>(null);
|
||||
const [currentFile, setCurrentFile] = useState<File | null>(null);
|
||||
@@ -71,7 +73,9 @@ export const FileUpload = ({
|
||||
if (file.size > maxSize) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: `File exceeds ${maxSize / 1024 / 1024}MB limit`,
|
||||
error: t('components.fileUpload.fileSizeError', {
|
||||
size: maxSize / 1024 / 1024,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,7 +182,11 @@ export const FileUpload = ({
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return <p className="text-sm font-semibold">{uploadText}</p>;
|
||||
return (
|
||||
<p className="text-sm font-semibold">
|
||||
{uploadText || t('components.fileUpload.clickToUpload')}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const defaultContent = (
|
||||
@@ -196,14 +204,17 @@ export const FileUpload = ({
|
||||
<div className="text-center">
|
||||
<div className="text-sm font-medium">
|
||||
{isDragActive ? (
|
||||
<p className="text-sm font-semibold">{dragActiveText}</p>
|
||||
<p className="text-sm font-semibold">
|
||||
{dragActiveText || t('components.fileUpload.dropFiles')}
|
||||
</p>
|
||||
) : (
|
||||
renderUploadText()
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-[#A3A3A3]">
|
||||
{fileTypeText} {maxSize / 1024 / 1024}
|
||||
{sizeLimitText}
|
||||
{fileTypeText || t('components.fileUpload.fileTypes')}{' '}
|
||||
{maxSize / 1024 / 1024}
|
||||
{sizeLimitText || t('components.fileUpload.sizeLimitUnit')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import mermaid from 'mermaid';
|
||||
import CopyButton from './CopyButton';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
@@ -15,6 +16,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
code,
|
||||
isLoading,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [isDarkTheme] = useDarkTheme();
|
||||
const diagramId = useRef(
|
||||
`mermaid-${Date.now()}-${Math.random().toString(36).substring(2)}`,
|
||||
@@ -273,7 +275,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
<button
|
||||
onClick={() => setShowDownloadMenu(!showDownloadMenu)}
|
||||
className="flex h-full items-center rounded-sm bg-gray-100 px-2 py-1 text-xs dark:bg-gray-700"
|
||||
title="Download options"
|
||||
title={t('mermaid.downloadOptions')}
|
||||
>
|
||||
Download <span className="ml-1">▼</span>
|
||||
</button>
|
||||
@@ -307,7 +309,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
? 'bg-blue-200 dark:bg-blue-800'
|
||||
: 'bg-gray-100 dark:bg-gray-700'
|
||||
}`}
|
||||
title="View Code"
|
||||
title={t('mermaid.viewCode')}
|
||||
>
|
||||
Code
|
||||
</button>
|
||||
@@ -353,7 +355,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
setZoomFactor((prev) => Math.max(1, prev - 0.5))
|
||||
}
|
||||
className="rounded px-1 hover:bg-gray-600"
|
||||
title="Decrease zoom"
|
||||
title={t('mermaid.decreaseZoom')}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
@@ -362,7 +364,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
onClick={() => {
|
||||
setZoomFactor(2);
|
||||
}}
|
||||
title="Reset zoom"
|
||||
title={t('mermaid.resetZoom')}
|
||||
>
|
||||
{zoomFactor.toFixed(1)}x
|
||||
</span>
|
||||
@@ -371,7 +373,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
setZoomFactor((prev) => Math.min(6, prev + 0.5))
|
||||
}
|
||||
className="rounded px-1 hover:bg-gray-600"
|
||||
title="Increase zoom"
|
||||
title={t('mermaid.increaseZoom')}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import close from '../assets/cross.svg';
|
||||
import rightArrow from '../assets/arrow-full-right.svg';
|
||||
import bg from '../assets/notification-bg.jpg';
|
||||
@@ -13,13 +14,14 @@ export default function Notification({
|
||||
notificationLink,
|
||||
handleCloseNotification,
|
||||
}: NotificationProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<a
|
||||
className="absolute right-2 bottom-6 z-20 flex w-3/4 items-center justify-center gap-2 rounded-lg bg-cover bg-center bg-no-repeat px-2 py-4 sm:right-4 md:w-2/5 lg:w-1/3 xl:w-1/4 2xl:w-1/5"
|
||||
style={{ backgroundImage: `url(${bg})` }}
|
||||
href={notificationLink}
|
||||
target="_blank"
|
||||
aria-label="Notification"
|
||||
aria-label={t('notification.ariaLabel')}
|
||||
rel="noreferrer"
|
||||
>
|
||||
<p className="text-white-3000 text-xs leading-6 font-semibold xl:text-sm xl:leading-7">
|
||||
@@ -31,7 +33,7 @@ export default function Notification({
|
||||
|
||||
<button
|
||||
className="absolute top-2 right-2 z-30 h-4 w-4 hover:opacity-70"
|
||||
aria-label="Close notification"
|
||||
aria-label={t('notification.closeAriaLabel')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@@ -24,6 +24,7 @@ interface SettingsBarProps {
|
||||
}
|
||||
|
||||
const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [hiddenGradient, setHiddenGradient] =
|
||||
useState<HiddenGradientType>('left');
|
||||
const containerRef = useRef<null | HTMLDivElement>(null);
|
||||
@@ -60,7 +61,7 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
<button
|
||||
onClick={() => scrollTabs(-1)}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
aria-label="Scroll tabs left"
|
||||
aria-label={t('settings.scrollTabsLeft')}
|
||||
>
|
||||
<img src={ArrowLeft} alt="left-arrow" className="h-3" />
|
||||
</button>
|
||||
@@ -69,7 +70,7 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
ref={containerRef}
|
||||
className="no-scrollbar flex snap-x flex-nowrap overflow-x-auto scroll-smooth md:space-x-4"
|
||||
role="tablist"
|
||||
aria-label="Settings tabs"
|
||||
aria-label={t('settings.tabsAriaLabel')}
|
||||
>
|
||||
{tabs.map((tab, index) => (
|
||||
<button
|
||||
@@ -93,7 +94,7 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
<button
|
||||
onClick={() => scrollTabs(1)}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
aria-label="Scroll tabs right"
|
||||
aria-label={t('settings.scrollTabsRight')}
|
||||
>
|
||||
<img src={ArrowRight} alt="right-arrow" className="h-3" />
|
||||
</button>
|
||||
|
||||
@@ -172,11 +172,7 @@ export default function SourcesPopup({
|
||||
: doc.date !== option.date,
|
||||
)
|
||||
: [];
|
||||
dispatch(
|
||||
setSelectedDocs(
|
||||
updatedDocs.length > 0 ? updatedDocs : null,
|
||||
),
|
||||
);
|
||||
dispatch(setSelectedDocs(updatedDocs));
|
||||
handlePostDocumentSelect(
|
||||
updatedDocs.length > 0 ? updatedDocs : null,
|
||||
);
|
||||
|
||||
@@ -1,94 +1,202 @@
|
||||
import { useState, useRef } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import Speaker from '../assets/speaker.svg?react';
|
||||
import Stopspeech from '../assets/stopspeech.svg?react';
|
||||
import LoadingIcon from '../assets/Loading.svg?react'; // Add a loading icon SVG here
|
||||
|
||||
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
|
||||
|
||||
export default function SpeakButton({
|
||||
text,
|
||||
colorLight,
|
||||
colorDark,
|
||||
}: {
|
||||
text: string;
|
||||
colorLight?: string;
|
||||
colorDark?: string;
|
||||
}) {
|
||||
let currentlyPlayingAudio: {
|
||||
audio: HTMLAudioElement;
|
||||
stopCallback: () => void;
|
||||
} | null = null;
|
||||
|
||||
let currentLoadingRequest: {
|
||||
abortController: AbortController;
|
||||
stopLoadingCallback: () => void;
|
||||
} | null = null;
|
||||
|
||||
// LRU Cache for audio
|
||||
const audioCache = new Map<string, string>();
|
||||
const MAX_CACHE_SIZE = 10;
|
||||
|
||||
function getCachedAudio(text: string): string | undefined {
|
||||
const cached = audioCache.get(text);
|
||||
if (cached) {
|
||||
audioCache.delete(text);
|
||||
audioCache.set(text, cached);
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
function setCachedAudio(text: string, audioBase64: string) {
|
||||
if (audioCache.has(text)) {
|
||||
audioCache.delete(text);
|
||||
}
|
||||
if (audioCache.size >= MAX_CACHE_SIZE) {
|
||||
const firstKey = audioCache.keys().next().value;
|
||||
if (firstKey !== undefined) {
|
||||
audioCache.delete(firstKey);
|
||||
}
|
||||
}
|
||||
|
||||
audioCache.set(text, audioBase64);
|
||||
}
|
||||
|
||||
export default function SpeakButton({ text }: { text: string }) {
|
||||
const [isSpeaking, setIsSpeaking] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSpeakHovered, setIsSpeakHovered] = useState(false);
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Abort any pending fetch request
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
abortControllerRef.current = null;
|
||||
}
|
||||
|
||||
// Stop any playing audio
|
||||
if (audioRef.current) {
|
||||
audioRef.current.pause();
|
||||
if (currentlyPlayingAudio?.audio === audioRef.current) {
|
||||
currentlyPlayingAudio = null;
|
||||
}
|
||||
audioRef.current = null;
|
||||
}
|
||||
|
||||
// Clear global loading request if it's this component's
|
||||
if (currentLoadingRequest) {
|
||||
currentLoadingRequest = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleSpeakClick = async () => {
|
||||
if (isSpeaking) {
|
||||
// Stop audio if it's currently playing
|
||||
audioRef.current?.pause();
|
||||
audioRef.current = null;
|
||||
currentlyPlayingAudio = null;
|
||||
setIsSpeaking(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any currently playing audio
|
||||
if (currentlyPlayingAudio) {
|
||||
currentlyPlayingAudio.audio.pause();
|
||||
currentlyPlayingAudio.stopCallback();
|
||||
currentlyPlayingAudio = null;
|
||||
}
|
||||
|
||||
// Abort any pending loading request
|
||||
if (currentLoadingRequest) {
|
||||
currentLoadingRequest.abortController.abort();
|
||||
currentLoadingRequest.stopLoadingCallback();
|
||||
currentLoadingRequest = null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Set loading state and initiate TTS request
|
||||
setIsLoading(true);
|
||||
const cachedAudio = getCachedAudio(text);
|
||||
let audioBase64: string;
|
||||
|
||||
const response = await fetch(apiHost + '/api/tts', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.audio_base64) {
|
||||
// Create and play the audio
|
||||
const audio = new Audio(`data:audio/mp3;base64,${data.audio_base64}`);
|
||||
audioRef.current = audio;
|
||||
|
||||
audio.play().then(() => {
|
||||
setIsSpeaking(true);
|
||||
setIsLoading(false);
|
||||
|
||||
// Reset when audio ends
|
||||
audio.onended = () => {
|
||||
setIsSpeaking(false);
|
||||
audioRef.current = null;
|
||||
};
|
||||
});
|
||||
} else {
|
||||
console.error('Failed to retrieve audio.');
|
||||
if (cachedAudio) {
|
||||
audioBase64 = cachedAudio;
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
const abortController = new AbortController();
|
||||
abortControllerRef.current = abortController;
|
||||
|
||||
currentLoadingRequest = {
|
||||
abortController,
|
||||
stopLoadingCallback: () => {
|
||||
setIsLoading(false);
|
||||
},
|
||||
};
|
||||
|
||||
const response = await fetch(apiHost + '/api/tts', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text }),
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
abortControllerRef.current = null;
|
||||
currentLoadingRequest = null;
|
||||
|
||||
if (data.success && data.audio_base64) {
|
||||
audioBase64 = data.audio_base64;
|
||||
// Store in cache
|
||||
setCachedAudio(text, audioBase64);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
console.error('Failed to retrieve audio.');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const audio = new Audio(`data:audio/mp3;base64,${audioBase64}`);
|
||||
audioRef.current = audio;
|
||||
|
||||
currentlyPlayingAudio = {
|
||||
audio,
|
||||
stopCallback: () => {
|
||||
setIsSpeaking(false);
|
||||
audioRef.current = null;
|
||||
},
|
||||
};
|
||||
|
||||
audio.play().then(() => {
|
||||
setIsSpeaking(true);
|
||||
setIsLoading(false);
|
||||
|
||||
audio.onended = () => {
|
||||
setIsSpeaking(false);
|
||||
audioRef.current = null;
|
||||
if (currentlyPlayingAudio?.audio === audio) {
|
||||
currentlyPlayingAudio = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
} catch (error: any) {
|
||||
abortControllerRef.current = null;
|
||||
currentLoadingRequest = null;
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching audio from TTS endpoint', error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-center rounded-full p-2 ${
|
||||
isSpeakHovered
|
||||
? `dark:bg-purple-taupe bg-[#EEEEEE]`
|
||||
: `bg-[${colorLight ? colorLight : '#FFFFFF'}] dark:bg-[${colorDark ? colorDark : 'transparent'}]`
|
||||
<button
|
||||
type="button"
|
||||
className={`flex cursor-pointer items-center justify-center rounded-full p-2 ${
|
||||
isSpeaking || isLoading
|
||||
? 'dark:bg-purple-taupe bg-[#EEEEEE]'
|
||||
: 'bg-white-3000 dark:hover:bg-purple-taupe hover:bg-[#EEEEEE] dark:bg-transparent'
|
||||
}`}
|
||||
onClick={handleSpeakClick}
|
||||
aria-label={
|
||||
isLoading
|
||||
? 'Loading audio'
|
||||
: isSpeaking
|
||||
? 'Stop speaking'
|
||||
: 'Speak text'
|
||||
}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<LoadingIcon className="animate-spin" />
|
||||
) : isSpeaking ? (
|
||||
<Stopspeech
|
||||
className="cursor-pointer fill-none"
|
||||
onClick={handleSpeakClick}
|
||||
onMouseEnter={() => setIsSpeakHovered(true)}
|
||||
onMouseLeave={() => setIsSpeakHovered(false)}
|
||||
/>
|
||||
<Stopspeech className="fill-none" />
|
||||
) : (
|
||||
<Speaker
|
||||
className="cursor-pointer fill-none"
|
||||
onClick={handleSpeakClick}
|
||||
onMouseEnter={() => setIsSpeakHovered(true)}
|
||||
onMouseLeave={() => setIsSpeakHovered(false)}
|
||||
/>
|
||||
<Speaker className="fill-none" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -560,37 +560,47 @@ const ConversationBubble = forwardRef<
|
||||
{handleFeedback && (
|
||||
<>
|
||||
<div className="relative mr-2 flex items-center justify-center">
|
||||
<div>
|
||||
<div className="bg-white-3000 dark:hover:bg-purple-taupe flex items-center justify-center rounded-full p-2 hover:bg-[#EEEEEE] dark:bg-transparent">
|
||||
<Like
|
||||
className={`${feedback === 'LIKE' ? 'fill-white-3000 stroke-purple-30 dark:fill-transparent' : 'stroke-gray-4000 fill-none'} cursor-pointer`}
|
||||
onClick={() => {
|
||||
if (feedback === 'LIKE') {
|
||||
handleFeedback?.(null);
|
||||
} else {
|
||||
handleFeedback?.('LIKE');
|
||||
}
|
||||
}}
|
||||
></Like>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white-3000 dark:hover:bg-purple-taupe flex cursor-pointer items-center justify-center rounded-full p-2 hover:bg-[#EEEEEE] dark:bg-transparent"
|
||||
onClick={() => {
|
||||
if (feedback === 'LIKE') {
|
||||
handleFeedback?.(null);
|
||||
} else {
|
||||
handleFeedback?.('LIKE');
|
||||
}
|
||||
}}
|
||||
aria-label={
|
||||
feedback === 'LIKE' ? 'Remove like' : 'Like'
|
||||
}
|
||||
>
|
||||
<Like
|
||||
className={`${feedback === 'LIKE' ? 'fill-white-3000 stroke-purple-30 dark:fill-transparent' : 'stroke-gray-4000 fill-none'}`}
|
||||
></Like>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative mr-2 flex items-center justify-center">
|
||||
<div>
|
||||
<div className="bg-white-3000 dark:hover:bg-purple-taupe flex items-center justify-center rounded-full p-2 hover:bg-[#EEEEEE] dark:bg-transparent">
|
||||
<Dislike
|
||||
className={`${feedback === 'DISLIKE' ? 'fill-white-3000 stroke-red-2000 dark:fill-transparent' : 'stroke-gray-4000 fill-none'} cursor-pointer`}
|
||||
onClick={() => {
|
||||
if (feedback === 'DISLIKE') {
|
||||
handleFeedback?.(null);
|
||||
} else {
|
||||
handleFeedback?.('DISLIKE');
|
||||
}
|
||||
}}
|
||||
></Dislike>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white-3000 dark:hover:bg-purple-taupe flex cursor-pointer items-center justify-center rounded-full p-2 hover:bg-[#EEEEEE] dark:bg-transparent"
|
||||
onClick={() => {
|
||||
if (feedback === 'DISLIKE') {
|
||||
handleFeedback?.(null);
|
||||
} else {
|
||||
handleFeedback?.('DISLIKE');
|
||||
}
|
||||
}}
|
||||
aria-label={
|
||||
feedback === 'DISLIKE'
|
||||
? 'Remove dislike'
|
||||
: 'Dislike'
|
||||
}
|
||||
>
|
||||
<Dislike
|
||||
className={`${feedback === 'DISLIKE' ? 'fill-white-3000 stroke-red-2000 dark:fill-transparent' : 'stroke-gray-4000 fill-none'}`}
|
||||
></Dislike>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -793,6 +803,7 @@ function Thought({
|
||||
thought: string;
|
||||
preprocessLaTeX: (content: string) => string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [isDarkTheme] = useDarkTheme();
|
||||
const [isThoughtOpen, setIsThoughtOpen] = useState(true);
|
||||
|
||||
@@ -813,7 +824,9 @@ function Thought({
|
||||
className="flex flex-row items-center gap-2"
|
||||
onClick={() => setIsThoughtOpen(!isThoughtOpen)}
|
||||
>
|
||||
<p className="text-base font-semibold">Reasoning</p>
|
||||
<p className="text-base font-semibold">
|
||||
{t('conversation.reasoning')}
|
||||
</p>
|
||||
<img
|
||||
src={ChevronDown}
|
||||
alt="ChevronDown"
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import ArrowDown from '../assets/arrow-down.svg';
|
||||
import RetryIcon from '../components/RetryIcon';
|
||||
@@ -14,6 +15,7 @@ import Hero from '../Hero';
|
||||
import { useDarkTheme } from '../hooks';
|
||||
import ConversationBubble from './ConversationBubble';
|
||||
import { FEEDBACK, Query, Status } from './conversationModels';
|
||||
import { selectConversationId } from '../preferences/preferenceSlice';
|
||||
|
||||
const SCROLL_THRESHOLD = 10;
|
||||
const LAST_BUBBLE_MARGIN = 'mb-32';
|
||||
@@ -50,6 +52,7 @@ export default function ConversationMessages({
|
||||
}: ConversationMessagesProps) {
|
||||
const [isDarkTheme] = useDarkTheme();
|
||||
const { t } = useTranslation();
|
||||
const conversationId = useSelector(selectConversationId);
|
||||
|
||||
const conversationRef = useRef<HTMLDivElement>(null);
|
||||
const [hasScrolledToLast, setHasScrolledToLast] = useState(true);
|
||||
@@ -137,7 +140,7 @@ export default function ConversationMessages({
|
||||
return (
|
||||
<ConversationBubble
|
||||
className={bubbleMargin}
|
||||
key={`${index}-ANSWER`}
|
||||
key={`${conversationId}-${index}-ANSWER`}
|
||||
message={query.response}
|
||||
type={'ANSWER'}
|
||||
thought={query.thought}
|
||||
@@ -175,7 +178,7 @@ export default function ConversationMessages({
|
||||
return (
|
||||
<ConversationBubble
|
||||
className={bubbleMargin}
|
||||
key={`${index}-ERROR`}
|
||||
key={`${conversationId}-${index}-ERROR`}
|
||||
message={query.error}
|
||||
type="ERROR"
|
||||
retryBtn={retryButton}
|
||||
@@ -214,10 +217,10 @@ export default function ConversationMessages({
|
||||
|
||||
{queries.length > 0 ? (
|
||||
queries.map((query, index) => (
|
||||
<Fragment key={`${index}-query-fragment`}>
|
||||
<Fragment key={`${conversationId}-${index}-query-fragment`}>
|
||||
<ConversationBubble
|
||||
className={index === 0 ? FIRST_QUESTION_BUBBLE_MARGIN_TOP : ''}
|
||||
key={`${index}-QUESTION`}
|
||||
key={`${conversationId}-${index}-QUESTION`}
|
||||
message={query.prompt}
|
||||
type="QUESTION"
|
||||
handleUpdatedQuestionSubmission={handleQuestionSubmission}
|
||||
|
||||
@@ -201,6 +201,15 @@
|
||||
"noAuth": "No Authentication",
|
||||
"oauthInProgress": "Waiting for OAuth completion...",
|
||||
"oauthCompleted": "OAuth completed successfully",
|
||||
"authType": "Authentication Type",
|
||||
"defaultServerName": "My MCP Server",
|
||||
"authTypes": {
|
||||
"none": "No Authentication",
|
||||
"apiKey": "API Key",
|
||||
"bearer": "Bearer Token",
|
||||
"oauth": "OAuth",
|
||||
"basic": "Basic Authentication"
|
||||
},
|
||||
"placeholders": {
|
||||
"serverUrl": "https://api.example.com",
|
||||
"apiKey": "Your secret API key",
|
||||
@@ -220,10 +229,14 @@
|
||||
"testFailed": "Connection test failed",
|
||||
"saveFailed": "Failed to save MCP server",
|
||||
"oauthFailed": "OAuth process failed or was cancelled",
|
||||
"oauthTimeout": "OAuth process timed out, please try again"
|
||||
"oauthTimeout": "OAuth process timed out, please try again",
|
||||
"timeoutRange": "Timeout must be between 1 and 300 seconds"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrollTabsLeft": "Scroll tabs left",
|
||||
"tabsAriaLabel": "Settings tabs",
|
||||
"scrollTabsRight": "Scroll tabs right"
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
@@ -343,7 +356,8 @@
|
||||
"disclaimer": "This is the only time your key will be shown.",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
"confirm": "I saved the Key"
|
||||
"confirm": "I saved the Key",
|
||||
"apiKeyLabel": "API Key"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "Are you sure you want to delete all the conversations?",
|
||||
@@ -361,7 +375,8 @@
|
||||
"apiKeyLabel": "API Key / OAuth",
|
||||
"apiKeyPlaceholder": "Enter API Key / OAuth",
|
||||
"addButton": "Add Tool",
|
||||
"closeButton": "Close"
|
||||
"closeButton": "Close",
|
||||
"customNamePlaceholder": "Enter custom name (optional)"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "Add Prompt",
|
||||
@@ -386,6 +401,22 @@
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"deleteConfirmation": "Are you sure you want to delete this chunk?"
|
||||
},
|
||||
"addAction": {
|
||||
"title": "New Action",
|
||||
"actionNamePlaceholder": "Action Name",
|
||||
"invalidFormat": "Invalid function name format. Use only letters, numbers, underscores, and hyphens.",
|
||||
"formatHelp": "Use only letters, numbers, underscores, and hyphens (e.g., `get_data`, `send_report`, etc.)",
|
||||
"addButton": "Add"
|
||||
},
|
||||
"agentDetails": {
|
||||
"title": "Access Details",
|
||||
"publicLink": "Public Link",
|
||||
"apiKey": "API Key",
|
||||
"webhookUrl": "Webhook URL",
|
||||
"generate": "Generate",
|
||||
"test": "Test",
|
||||
"learnMore": "Learn more"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -428,6 +459,153 @@
|
||||
"attach": "Attach",
|
||||
"remove": "Remove attachment"
|
||||
},
|
||||
"retry": "Retry"
|
||||
"retry": "Retry",
|
||||
"reasoning": "Reasoning"
|
||||
},
|
||||
"agents": {
|
||||
"title": "Agents",
|
||||
"description": "Discover and create custom versions of DocsGPT that combine instructions, extra knowledge, and any combination of skills",
|
||||
"newAgent": "New Agent",
|
||||
"backToAll": "Back to all agents",
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "By DocsGPT",
|
||||
"description": "Agents provided by DocsGPT",
|
||||
"emptyState": "No template agents found."
|
||||
},
|
||||
"user": {
|
||||
"title": "By me",
|
||||
"description": "Agents created or published by you",
|
||||
"emptyState": "You don't have any created agents yet."
|
||||
},
|
||||
"shared": {
|
||||
"title": "Shared with me",
|
||||
"description": "Agents imported by using a public link",
|
||||
"emptyState": "No shared agents found."
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"headings": {
|
||||
"new": "New Agent",
|
||||
"edit": "Edit Agent",
|
||||
"draft": "New Agent (Draft)"
|
||||
},
|
||||
"buttons": {
|
||||
"publish": "Publish",
|
||||
"save": "Save",
|
||||
"saveDraft": "Save Draft",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"logs": "Logs",
|
||||
"accessDetails": "Access Details",
|
||||
"add": "Add"
|
||||
},
|
||||
"sections": {
|
||||
"meta": "Meta",
|
||||
"source": "Source",
|
||||
"prompt": "Prompt",
|
||||
"tools": "Tools",
|
||||
"agentType": "Agent type",
|
||||
"advanced": "Advanced",
|
||||
"preview": "Preview"
|
||||
},
|
||||
"placeholders": {
|
||||
"agentName": "Agent name",
|
||||
"describeAgent": "Describe your agent",
|
||||
"selectSources": "Select sources",
|
||||
"chunksPerQuery": "Chunks per query",
|
||||
"selectType": "Select type",
|
||||
"selectTools": "Select tools",
|
||||
"enterTokenLimit": "Enter token limit",
|
||||
"enterRequestLimit": "Enter request limit"
|
||||
},
|
||||
"sourcePopup": {
|
||||
"title": "Select Sources",
|
||||
"searchPlaceholder": "Search sources...",
|
||||
"noOptionsMessage": "No sources available"
|
||||
},
|
||||
"toolsPopup": {
|
||||
"title": "Select Tools",
|
||||
"searchPlaceholder": "Search tools...",
|
||||
"noOptionsMessage": "No tools available"
|
||||
},
|
||||
"upload": {
|
||||
"clickToUpload": "Click to upload",
|
||||
"dragAndDrop": " or drag and drop"
|
||||
},
|
||||
"agentTypes": {
|
||||
"classic": "Classic",
|
||||
"react": "ReAct"
|
||||
},
|
||||
"advanced": {
|
||||
"jsonSchema": "JSON response schema",
|
||||
"jsonSchemaDescription": "Define a JSON schema to enforce structured output format",
|
||||
"validJson": "Valid JSON",
|
||||
"invalidJson": "Invalid JSON - fix to enable saving",
|
||||
"tokenLimiting": "Token limiting",
|
||||
"tokenLimitingDescription": "Limit daily total tokens that can be used by this agent",
|
||||
"requestLimiting": "Request limiting",
|
||||
"requestLimitingDescription": "Limit daily total requests that can be made to this agent"
|
||||
},
|
||||
"preview": {
|
||||
"publishedPreview": "Published agents can be previewed here"
|
||||
},
|
||||
"externalKb": "External KB"
|
||||
},
|
||||
"logs": {
|
||||
"title": "Agent Logs",
|
||||
"lastUsedAt": "Last used at",
|
||||
"noUsageHistory": "No usage history",
|
||||
"tableHeader": "Agent endpoint logs"
|
||||
},
|
||||
"shared": {
|
||||
"notFound": "No agent found. Please ensure the agent is shared."
|
||||
},
|
||||
"preview": {
|
||||
"testMessage": "Test your agent here. Published agents can be used in conversations."
|
||||
},
|
||||
"deleteConfirmation": "Are you sure you want to delete this agent?"
|
||||
},
|
||||
"components": {
|
||||
"fileUpload": {
|
||||
"clickToUpload": "Click to upload or drag and drop",
|
||||
"dropFiles": "Drop the files here",
|
||||
"fileTypes": "PNG, JPG, JPEG up to",
|
||||
"sizeLimitUnit": "MB",
|
||||
"fileSizeError": "File exceeds {{size}}MB limit"
|
||||
}
|
||||
},
|
||||
"pageNotFound": {
|
||||
"title": "404",
|
||||
"message": "The page you are looking for does not exist.",
|
||||
"goHome": "Go Back Home"
|
||||
},
|
||||
"filePicker": {
|
||||
"searchPlaceholder": "Search files and folders...",
|
||||
"itemsSelected": "{{count}} selected",
|
||||
"name": "Name",
|
||||
"lastModified": "Last Modified",
|
||||
"size": "Size"
|
||||
},
|
||||
"actionButtons": {
|
||||
"openNewChat": "Open New Chat",
|
||||
"share": "Share"
|
||||
},
|
||||
"mermaid": {
|
||||
"downloadOptions": "Download options",
|
||||
"viewCode": "View Code",
|
||||
"decreaseZoom": "Decrease zoom",
|
||||
"resetZoom": "Reset zoom",
|
||||
"increaseZoom": "Increase zoom"
|
||||
},
|
||||
"navigation": {
|
||||
"agents": "Agents"
|
||||
},
|
||||
"notification": {
|
||||
"ariaLabel": "Notification",
|
||||
"closeAriaLabel": "Close notification"
|
||||
},
|
||||
"prompts": {
|
||||
"textAriaLabel": "Prompt Text"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +185,58 @@
|
||||
"fieldDescription": "Descripción del campo",
|
||||
"add": "Añadir",
|
||||
"cancel": "Cancelar",
|
||||
"addNew": "Añadir Nuevo"
|
||||
}
|
||||
"addNew": "Añadir Nuevo",
|
||||
"mcp": {
|
||||
"addServer": "Add MCP Server",
|
||||
"editServer": "Edit Server",
|
||||
"serverName": "Server Name",
|
||||
"serverUrl": "Server URL",
|
||||
"headerName": "Header Name",
|
||||
"timeout": "Timeout (seconds)",
|
||||
"testConnection": "Test Connection",
|
||||
"testing": "Testing",
|
||||
"saving": "Saving",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"noAuth": "No Authentication",
|
||||
"oauthInProgress": "Waiting for OAuth completion...",
|
||||
"oauthCompleted": "OAuth completed successfully",
|
||||
"authType": "Authentication Type",
|
||||
"defaultServerName": "My MCP Server",
|
||||
"authTypes": {
|
||||
"none": "No Authentication",
|
||||
"apiKey": "API Key",
|
||||
"bearer": "Bearer Token",
|
||||
"oauth": "OAuth",
|
||||
"basic": "Basic Authentication"
|
||||
},
|
||||
"placeholders": {
|
||||
"serverUrl": "https://api.example.com",
|
||||
"apiKey": "Your secret API key",
|
||||
"bearerToken": "Your secret token",
|
||||
"username": "Your username",
|
||||
"password": "Your password",
|
||||
"oauthScopes": "OAuth scopes (comma separated)"
|
||||
},
|
||||
"errors": {
|
||||
"nameRequired": "Server name is required",
|
||||
"urlRequired": "Server URL is required",
|
||||
"invalidUrl": "Please enter a valid URL",
|
||||
"apiKeyRequired": "API key is required",
|
||||
"tokenRequired": "Bearer token is required",
|
||||
"usernameRequired": "Username is required",
|
||||
"passwordRequired": "Password is required",
|
||||
"testFailed": "Connection test failed",
|
||||
"saveFailed": "Failed to save MCP server",
|
||||
"oauthFailed": "OAuth process failed or was cancelled",
|
||||
"oauthTimeout": "OAuth process timed out, please try again",
|
||||
"timeoutRange": "Timeout must be between 1 and 300 seconds"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrollTabsLeft": "Desplazar pestañas a la izquierda",
|
||||
"tabsAriaLabel": "Pestañas de configuración",
|
||||
"scrollTabsRight": "Desplazar pestañas a la derecha"
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
@@ -306,7 +356,8 @@
|
||||
"disclaimer": "Esta es la única vez que se mostrará tu clave.",
|
||||
"copy": "Copiar",
|
||||
"copied": "Copiado",
|
||||
"confirm": "He guardado la Clave"
|
||||
"confirm": "He guardado la Clave",
|
||||
"apiKeyLabel": "API Key"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "¿Estás seguro de que deseas eliminar todas las conversaciones?",
|
||||
@@ -324,7 +375,8 @@
|
||||
"apiKeyLabel": "Clave API / OAuth",
|
||||
"apiKeyPlaceholder": "Ingrese la Clave API / OAuth",
|
||||
"addButton": "Agregar Herramienta",
|
||||
"closeButton": "Cerrar"
|
||||
"closeButton": "Cerrar",
|
||||
"customNamePlaceholder": "Enter custom name (optional)"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "Agregar Prompt",
|
||||
@@ -349,6 +401,22 @@
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar",
|
||||
"deleteConfirmation": "¿Estás seguro de que deseas eliminar este fragmento?"
|
||||
},
|
||||
"addAction": {
|
||||
"title": "New Action",
|
||||
"actionNamePlaceholder": "Action Name",
|
||||
"invalidFormat": "Invalid function name format. Use only letters, numbers, underscores, and hyphens.",
|
||||
"formatHelp": "Use only letters, numbers, underscores, and hyphens (e.g., `get_data`, `send_report`, etc.)",
|
||||
"addButton": "Add"
|
||||
},
|
||||
"agentDetails": {
|
||||
"title": "Access Details",
|
||||
"publicLink": "Public Link",
|
||||
"apiKey": "API Key",
|
||||
"webhookUrl": "Webhook URL",
|
||||
"generate": "Generate",
|
||||
"test": "Test",
|
||||
"learnMore": "Learn more"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -391,6 +459,153 @@
|
||||
"attach": "Adjuntar",
|
||||
"remove": "Eliminar adjunto"
|
||||
},
|
||||
"retry": "Reintentar"
|
||||
"retry": "Reintentar",
|
||||
"reasoning": "Razonamiento"
|
||||
},
|
||||
"agents": {
|
||||
"title": "Agentes",
|
||||
"description": "Descubre y crea versiones personalizadas de DocsGPT que combinan instrucciones, conocimiento adicional y cualquier combinación de habilidades",
|
||||
"newAgent": "Nuevo Agente",
|
||||
"backToAll": "Volver a todos los agentes",
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "Por DocsGPT",
|
||||
"description": "Agentes proporcionados por DocsGPT",
|
||||
"emptyState": "No se encontraron agentes de plantilla."
|
||||
},
|
||||
"user": {
|
||||
"title": "Por mí",
|
||||
"description": "Agentes creados o publicados por ti",
|
||||
"emptyState": "Aún no tienes agentes creados."
|
||||
},
|
||||
"shared": {
|
||||
"title": "Compartidos conmigo",
|
||||
"description": "Agentes importados mediante un enlace público",
|
||||
"emptyState": "No se encontraron agentes compartidos."
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"headings": {
|
||||
"new": "Nuevo Agente",
|
||||
"edit": "Editar Agente",
|
||||
"draft": "Nuevo Agente (Borrador)"
|
||||
},
|
||||
"buttons": {
|
||||
"publish": "Publicar",
|
||||
"save": "Guardar",
|
||||
"saveDraft": "Guardar Borrador",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Eliminar",
|
||||
"logs": "Registros",
|
||||
"accessDetails": "Detalles de Acceso",
|
||||
"add": "Agregar"
|
||||
},
|
||||
"sections": {
|
||||
"meta": "Meta",
|
||||
"source": "Fuente",
|
||||
"prompt": "Prompt",
|
||||
"tools": "Herramientas",
|
||||
"agentType": "Tipo de agente",
|
||||
"advanced": "Avanzado",
|
||||
"preview": "Vista previa"
|
||||
},
|
||||
"placeholders": {
|
||||
"agentName": "Nombre del agente",
|
||||
"describeAgent": "Describe tu agente",
|
||||
"selectSources": "Seleccionar fuentes",
|
||||
"chunksPerQuery": "Fragmentos por consulta",
|
||||
"selectType": "Seleccionar tipo",
|
||||
"selectTools": "Seleccionar herramientas",
|
||||
"enterTokenLimit": "Ingresar límite de tokens",
|
||||
"enterRequestLimit": "Ingresar límite de solicitudes"
|
||||
},
|
||||
"sourcePopup": {
|
||||
"title": "Seleccionar Fuentes",
|
||||
"searchPlaceholder": "Buscar fuentes...",
|
||||
"noOptionsMessage": "No hay fuentes disponibles"
|
||||
},
|
||||
"toolsPopup": {
|
||||
"title": "Seleccionar Herramientas",
|
||||
"searchPlaceholder": "Buscar herramientas...",
|
||||
"noOptionsMessage": "No hay herramientas disponibles"
|
||||
},
|
||||
"upload": {
|
||||
"clickToUpload": "Haz clic para subir",
|
||||
"dragAndDrop": " o arrastra y suelta"
|
||||
},
|
||||
"agentTypes": {
|
||||
"classic": "Clásico",
|
||||
"react": "ReAct"
|
||||
},
|
||||
"advanced": {
|
||||
"jsonSchema": "Esquema de respuesta JSON",
|
||||
"jsonSchemaDescription": "Define un esquema JSON para aplicar formato de salida estructurado",
|
||||
"validJson": "JSON válido",
|
||||
"invalidJson": "JSON inválido - corrige para habilitar el guardado",
|
||||
"tokenLimiting": "Límite de tokens",
|
||||
"tokenLimitingDescription": "Limita el total diario de tokens que puede usar este agente",
|
||||
"requestLimiting": "Límite de solicitudes",
|
||||
"requestLimitingDescription": "Limita el total diario de solicitudes que se pueden hacer a este agente"
|
||||
},
|
||||
"preview": {
|
||||
"publishedPreview": "Los agentes publicados se pueden previsualizar aquí"
|
||||
},
|
||||
"externalKb": "KB Externa"
|
||||
},
|
||||
"logs": {
|
||||
"title": "Registros del Agente",
|
||||
"lastUsedAt": "Último uso",
|
||||
"noUsageHistory": "Sin historial de uso",
|
||||
"tableHeader": "Registros del endpoint del agente"
|
||||
},
|
||||
"shared": {
|
||||
"notFound": "No se encontró el agente. Asegúrate de que el agente esté compartido."
|
||||
},
|
||||
"preview": {
|
||||
"testMessage": "Prueba tu agente aquí. Los agentes publicados se pueden usar en conversaciones."
|
||||
},
|
||||
"deleteConfirmation": "¿Estás seguro de que quieres eliminar este agente?"
|
||||
},
|
||||
"components": {
|
||||
"fileUpload": {
|
||||
"clickToUpload": "Click to upload or drag and drop",
|
||||
"dropFiles": "Drop the files here",
|
||||
"fileTypes": "PNG, JPG, JPEG up to",
|
||||
"sizeLimitUnit": "MB",
|
||||
"fileSizeError": "File exceeds {{size}}MB limit"
|
||||
}
|
||||
},
|
||||
"pageNotFound": {
|
||||
"title": "404",
|
||||
"message": "The page you are looking for does not exist.",
|
||||
"goHome": "Go Back Home"
|
||||
},
|
||||
"filePicker": {
|
||||
"searchPlaceholder": "Buscar archivos y carpetas...",
|
||||
"itemsSelected": "{{count}} seleccionados",
|
||||
"name": "Nombre",
|
||||
"lastModified": "Última modificación",
|
||||
"size": "Tamaño"
|
||||
},
|
||||
"actionButtons": {
|
||||
"openNewChat": "Abrir nuevo chat",
|
||||
"share": "Compartir"
|
||||
},
|
||||
"mermaid": {
|
||||
"downloadOptions": "Opciones de descarga",
|
||||
"viewCode": "Ver código",
|
||||
"decreaseZoom": "Reducir zoom",
|
||||
"resetZoom": "Restablecer zoom",
|
||||
"increaseZoom": "Aumentar zoom"
|
||||
},
|
||||
"navigation": {
|
||||
"agents": "Agentes"
|
||||
},
|
||||
"notification": {
|
||||
"ariaLabel": "Notificación",
|
||||
"closeAriaLabel": "Cerrar notificación"
|
||||
},
|
||||
"prompts": {
|
||||
"textAriaLabel": "Texto del prompt"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +185,58 @@
|
||||
"cancel": "キャンセル",
|
||||
"addNew": "新規追加",
|
||||
"name": "名前",
|
||||
"type": "タイプ"
|
||||
}
|
||||
"type": "タイプ",
|
||||
"mcp": {
|
||||
"addServer": "Add MCP Server",
|
||||
"editServer": "Edit Server",
|
||||
"serverName": "Server Name",
|
||||
"serverUrl": "Server URL",
|
||||
"headerName": "Header Name",
|
||||
"timeout": "Timeout (seconds)",
|
||||
"testConnection": "Test Connection",
|
||||
"testing": "Testing",
|
||||
"saving": "Saving",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"noAuth": "No Authentication",
|
||||
"oauthInProgress": "Waiting for OAuth completion...",
|
||||
"oauthCompleted": "OAuth completed successfully",
|
||||
"authType": "Authentication Type",
|
||||
"defaultServerName": "My MCP Server",
|
||||
"authTypes": {
|
||||
"none": "No Authentication",
|
||||
"apiKey": "API Key",
|
||||
"bearer": "Bearer Token",
|
||||
"oauth": "OAuth",
|
||||
"basic": "Basic Authentication"
|
||||
},
|
||||
"placeholders": {
|
||||
"serverUrl": "https://api.example.com",
|
||||
"apiKey": "Your secret API key",
|
||||
"bearerToken": "Your secret token",
|
||||
"username": "Your username",
|
||||
"password": "Your password",
|
||||
"oauthScopes": "OAuth scopes (comma separated)"
|
||||
},
|
||||
"errors": {
|
||||
"nameRequired": "Server name is required",
|
||||
"urlRequired": "Server URL is required",
|
||||
"invalidUrl": "Please enter a valid URL",
|
||||
"apiKeyRequired": "API key is required",
|
||||
"tokenRequired": "Bearer token is required",
|
||||
"usernameRequired": "Username is required",
|
||||
"passwordRequired": "Password is required",
|
||||
"testFailed": "Connection test failed",
|
||||
"saveFailed": "Failed to save MCP server",
|
||||
"oauthFailed": "OAuth process failed or was cancelled",
|
||||
"oauthTimeout": "OAuth process timed out, please try again",
|
||||
"timeoutRange": "Timeout must be between 1 and 300 seconds"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrollTabsLeft": "タブを左にスクロール",
|
||||
"tabsAriaLabel": "設定タブ",
|
||||
"scrollTabsRight": "タブを右にスクロール"
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
@@ -306,7 +356,8 @@
|
||||
"disclaimer": "キーが表示されるのはこのときだけです。",
|
||||
"copy": "コピー",
|
||||
"copied": "コピーしました",
|
||||
"confirm": "キーを保存しました"
|
||||
"confirm": "キーを保存しました",
|
||||
"apiKeyLabel": "API Key"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "すべての会話を削除してもよろしいですか?",
|
||||
@@ -324,7 +375,8 @@
|
||||
"apiKeyLabel": "APIキー / OAuth",
|
||||
"apiKeyPlaceholder": "APIキー / OAuthを入力してください",
|
||||
"addButton": "ツールを追加",
|
||||
"closeButton": "閉じる"
|
||||
"closeButton": "閉じる",
|
||||
"customNamePlaceholder": "Enter custom name (optional)"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "プロンプトを追加",
|
||||
@@ -349,6 +401,22 @@
|
||||
"cancel": "キャンセル",
|
||||
"delete": "削除",
|
||||
"deleteConfirmation": "このチャンクを削除してもよろしいですか?"
|
||||
},
|
||||
"addAction": {
|
||||
"title": "New Action",
|
||||
"actionNamePlaceholder": "Action Name",
|
||||
"invalidFormat": "Invalid function name format. Use only letters, numbers, underscores, and hyphens.",
|
||||
"formatHelp": "Use only letters, numbers, underscores, and hyphens (e.g., `get_data`, `send_report`, etc.)",
|
||||
"addButton": "Add"
|
||||
},
|
||||
"agentDetails": {
|
||||
"title": "Access Details",
|
||||
"publicLink": "Public Link",
|
||||
"apiKey": "API Key",
|
||||
"webhookUrl": "Webhook URL",
|
||||
"generate": "Generate",
|
||||
"test": "Test",
|
||||
"learnMore": "Learn more"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -391,6 +459,153 @@
|
||||
"attach": "添付",
|
||||
"remove": "添付ファイルを削除"
|
||||
},
|
||||
"retry": "再試行"
|
||||
"retry": "再試行",
|
||||
"reasoning": "推論"
|
||||
},
|
||||
"agents": {
|
||||
"title": "エージェント",
|
||||
"description": "指示、追加知識、スキルの組み合わせを含むDocsGPTのカスタムバージョンを発見して作成します",
|
||||
"newAgent": "新しいエージェント",
|
||||
"backToAll": "すべてのエージェントに戻る",
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "DocsGPT提供",
|
||||
"description": "DocsGPTが提供するエージェント",
|
||||
"emptyState": "テンプレートエージェントが見つかりません。"
|
||||
},
|
||||
"user": {
|
||||
"title": "自分のエージェント",
|
||||
"description": "あなたが作成または公開したエージェント",
|
||||
"emptyState": "まだ作成されたエージェントがありません。"
|
||||
},
|
||||
"shared": {
|
||||
"title": "共有されたエージェント",
|
||||
"description": "公開リンクを使用してインポートされたエージェント",
|
||||
"emptyState": "共有エージェントが見つかりません。"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"headings": {
|
||||
"new": "新しいエージェント",
|
||||
"edit": "エージェントを編集",
|
||||
"draft": "新しいエージェント(下書き)"
|
||||
},
|
||||
"buttons": {
|
||||
"publish": "公開",
|
||||
"save": "保存",
|
||||
"saveDraft": "下書きを保存",
|
||||
"cancel": "キャンセル",
|
||||
"delete": "削除",
|
||||
"logs": "ログ",
|
||||
"accessDetails": "アクセス詳細",
|
||||
"add": "追加"
|
||||
},
|
||||
"sections": {
|
||||
"meta": "メタ",
|
||||
"source": "ソース",
|
||||
"prompt": "プロンプト",
|
||||
"tools": "ツール",
|
||||
"agentType": "エージェントタイプ",
|
||||
"advanced": "詳細設定",
|
||||
"preview": "プレビュー"
|
||||
},
|
||||
"placeholders": {
|
||||
"agentName": "エージェント名",
|
||||
"describeAgent": "エージェントを説明してください",
|
||||
"selectSources": "ソースを選択",
|
||||
"chunksPerQuery": "クエリごとのチャンク数",
|
||||
"selectType": "タイプを選択",
|
||||
"selectTools": "ツールを選択",
|
||||
"enterTokenLimit": "トークン制限を入力",
|
||||
"enterRequestLimit": "リクエスト制限を入力"
|
||||
},
|
||||
"sourcePopup": {
|
||||
"title": "ソースを選択",
|
||||
"searchPlaceholder": "ソースを検索...",
|
||||
"noOptionsMessage": "利用可能なソースがありません"
|
||||
},
|
||||
"toolsPopup": {
|
||||
"title": "ツールを選択",
|
||||
"searchPlaceholder": "ツールを検索...",
|
||||
"noOptionsMessage": "利用可能なツールがありません"
|
||||
},
|
||||
"upload": {
|
||||
"clickToUpload": "クリックしてアップロード",
|
||||
"dragAndDrop": " またはドラッグ&ドロップ"
|
||||
},
|
||||
"agentTypes": {
|
||||
"classic": "クラシック",
|
||||
"react": "ReAct"
|
||||
},
|
||||
"advanced": {
|
||||
"jsonSchema": "JSON応答スキーマ",
|
||||
"jsonSchemaDescription": "構造化された出力形式を適用するためのJSONスキーマを定義します",
|
||||
"validJson": "有効なJSON",
|
||||
"invalidJson": "無効なJSON - 保存を有効にするには修正してください",
|
||||
"tokenLimiting": "トークン制限",
|
||||
"tokenLimitingDescription": "このエージェントが使用できる1日の合計トークン数を制限します",
|
||||
"requestLimiting": "リクエスト制限",
|
||||
"requestLimitingDescription": "このエージェントに対して行える1日の合計リクエスト数を制限します"
|
||||
},
|
||||
"preview": {
|
||||
"publishedPreview": "公開されたエージェントはここでプレビューできます"
|
||||
},
|
||||
"externalKb": "外部KB"
|
||||
},
|
||||
"logs": {
|
||||
"title": "エージェントログ",
|
||||
"lastUsedAt": "最終使用日時",
|
||||
"noUsageHistory": "使用履歴がありません",
|
||||
"tableHeader": "エージェントエンドポイントログ"
|
||||
},
|
||||
"shared": {
|
||||
"notFound": "エージェントが見つかりません。エージェントが共有されていることを確認してください。"
|
||||
},
|
||||
"preview": {
|
||||
"testMessage": "ここでエージェントをテストできます。公開されたエージェントは会話で使用できます。"
|
||||
},
|
||||
"deleteConfirmation": "このエージェントを削除してもよろしいですか?"
|
||||
},
|
||||
"components": {
|
||||
"fileUpload": {
|
||||
"clickToUpload": "Click to upload or drag and drop",
|
||||
"dropFiles": "Drop the files here",
|
||||
"fileTypes": "PNG, JPG, JPEG up to",
|
||||
"sizeLimitUnit": "MB",
|
||||
"fileSizeError": "File exceeds {{size}}MB limit"
|
||||
}
|
||||
},
|
||||
"pageNotFound": {
|
||||
"title": "404",
|
||||
"message": "The page you are looking for does not exist.",
|
||||
"goHome": "Go Back Home"
|
||||
},
|
||||
"filePicker": {
|
||||
"searchPlaceholder": "ファイルとフォルダを検索...",
|
||||
"itemsSelected": "{{count}} 件選択済み",
|
||||
"name": "名前",
|
||||
"lastModified": "最終更新日",
|
||||
"size": "サイズ"
|
||||
},
|
||||
"actionButtons": {
|
||||
"openNewChat": "新しいチャットを開く",
|
||||
"share": "共有"
|
||||
},
|
||||
"mermaid": {
|
||||
"downloadOptions": "ダウンロードオプション",
|
||||
"viewCode": "コードを表示",
|
||||
"decreaseZoom": "ズームアウト",
|
||||
"resetZoom": "ズームをリセット",
|
||||
"increaseZoom": "ズームイン"
|
||||
},
|
||||
"navigation": {
|
||||
"agents": "エージェント"
|
||||
},
|
||||
"notification": {
|
||||
"ariaLabel": "通知",
|
||||
"closeAriaLabel": "通知を閉じる"
|
||||
},
|
||||
"prompts": {
|
||||
"textAriaLabel": "プロンプトテキスト"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +185,58 @@
|
||||
"cancel": "Отмена",
|
||||
"addNew": "Добавить новое",
|
||||
"name": "Имя",
|
||||
"type": "Тип"
|
||||
}
|
||||
"type": "Тип",
|
||||
"mcp": {
|
||||
"addServer": "Add MCP Server",
|
||||
"editServer": "Edit Server",
|
||||
"serverName": "Server Name",
|
||||
"serverUrl": "Server URL",
|
||||
"headerName": "Header Name",
|
||||
"timeout": "Timeout (seconds)",
|
||||
"testConnection": "Test Connection",
|
||||
"testing": "Testing",
|
||||
"saving": "Saving",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"noAuth": "No Authentication",
|
||||
"oauthInProgress": "Waiting for OAuth completion...",
|
||||
"oauthCompleted": "OAuth completed successfully",
|
||||
"authType": "Authentication Type",
|
||||
"defaultServerName": "My MCP Server",
|
||||
"authTypes": {
|
||||
"none": "No Authentication",
|
||||
"apiKey": "API Key",
|
||||
"bearer": "Bearer Token",
|
||||
"oauth": "OAuth",
|
||||
"basic": "Basic Authentication"
|
||||
},
|
||||
"placeholders": {
|
||||
"serverUrl": "https://api.example.com",
|
||||
"apiKey": "Your secret API key",
|
||||
"bearerToken": "Your secret token",
|
||||
"username": "Your username",
|
||||
"password": "Your password",
|
||||
"oauthScopes": "OAuth scopes (comma separated)"
|
||||
},
|
||||
"errors": {
|
||||
"nameRequired": "Server name is required",
|
||||
"urlRequired": "Server URL is required",
|
||||
"invalidUrl": "Please enter a valid URL",
|
||||
"apiKeyRequired": "API key is required",
|
||||
"tokenRequired": "Bearer token is required",
|
||||
"usernameRequired": "Username is required",
|
||||
"passwordRequired": "Password is required",
|
||||
"testFailed": "Connection test failed",
|
||||
"saveFailed": "Failed to save MCP server",
|
||||
"oauthFailed": "OAuth process failed or was cancelled",
|
||||
"oauthTimeout": "OAuth process timed out, please try again",
|
||||
"timeoutRange": "Timeout must be between 1 and 300 seconds"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrollTabsLeft": "Прокрутить вкладки влево",
|
||||
"tabsAriaLabel": "Вкладки настроек",
|
||||
"scrollTabsRight": "Прокрутить вкладки вправо"
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
@@ -306,7 +356,8 @@
|
||||
"disclaimer": "Ваш ключ будет показан только один раз.",
|
||||
"copy": "Копировать",
|
||||
"copied": "Скопировано",
|
||||
"confirm": "Я сохранил ключ"
|
||||
"confirm": "Я сохранил ключ",
|
||||
"apiKeyLabel": "API Key"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "Вы уверены, что хотите удалить все разговоры?",
|
||||
@@ -324,7 +375,8 @@
|
||||
"apiKeyLabel": "API ключ / OAuth",
|
||||
"apiKeyPlaceholder": "Введите API ключ / OAuth",
|
||||
"addButton": "Добавить инструмент",
|
||||
"closeButton": "Закрыть"
|
||||
"closeButton": "Закрыть",
|
||||
"customNamePlaceholder": "Enter custom name (optional)"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "Добавить подсказку",
|
||||
@@ -349,6 +401,22 @@
|
||||
"cancel": "Отмена",
|
||||
"delete": "Удалить",
|
||||
"deleteConfirmation": "Вы уверены, что хотите удалить этот фрагмент?"
|
||||
},
|
||||
"addAction": {
|
||||
"title": "New Action",
|
||||
"actionNamePlaceholder": "Action Name",
|
||||
"invalidFormat": "Invalid function name format. Use only letters, numbers, underscores, and hyphens.",
|
||||
"formatHelp": "Use only letters, numbers, underscores, and hyphens (e.g., `get_data`, `send_report`, etc.)",
|
||||
"addButton": "Add"
|
||||
},
|
||||
"agentDetails": {
|
||||
"title": "Access Details",
|
||||
"publicLink": "Public Link",
|
||||
"apiKey": "API Key",
|
||||
"webhookUrl": "Webhook URL",
|
||||
"generate": "Generate",
|
||||
"test": "Test",
|
||||
"learnMore": "Learn more"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -391,6 +459,153 @@
|
||||
"attach": "Прикрепить",
|
||||
"remove": "Удалить вложение"
|
||||
},
|
||||
"retry": "Повторить"
|
||||
"retry": "Повторить",
|
||||
"reasoning": "Рассуждение"
|
||||
},
|
||||
"agents": {
|
||||
"title": "Агенты",
|
||||
"description": "Откройте и создайте пользовательские версии DocsGPT, которые объединяют инструкции, дополнительные знания и любую комбинацию навыков",
|
||||
"newAgent": "Новый Агент",
|
||||
"backToAll": "Вернуться ко всем агентам",
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "От DocsGPT",
|
||||
"description": "Агенты, предоставленные DocsGPT",
|
||||
"emptyState": "Шаблонные агенты не найдены."
|
||||
},
|
||||
"user": {
|
||||
"title": "Мои",
|
||||
"description": "Агенты, созданные или опубликованные вами",
|
||||
"emptyState": "У вас пока нет созданных агентов."
|
||||
},
|
||||
"shared": {
|
||||
"title": "Поделились со мной",
|
||||
"description": "Агенты, импортированные по публичной ссылке",
|
||||
"emptyState": "Общие агенты не найдены."
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"headings": {
|
||||
"new": "Новый Агент",
|
||||
"edit": "Редактировать Агента",
|
||||
"draft": "Новый Агент (Черновик)"
|
||||
},
|
||||
"buttons": {
|
||||
"publish": "Опубликовать",
|
||||
"save": "Сохранить",
|
||||
"saveDraft": "Сохранить Черновик",
|
||||
"cancel": "Отмена",
|
||||
"delete": "Удалить",
|
||||
"logs": "Логи",
|
||||
"accessDetails": "Детали Доступа",
|
||||
"add": "Добавить"
|
||||
},
|
||||
"sections": {
|
||||
"meta": "Мета",
|
||||
"source": "Источник",
|
||||
"prompt": "Промпт",
|
||||
"tools": "Инструменты",
|
||||
"agentType": "Тип агента",
|
||||
"advanced": "Расширенные",
|
||||
"preview": "Предпросмотр"
|
||||
},
|
||||
"placeholders": {
|
||||
"agentName": "Имя агента",
|
||||
"describeAgent": "Опишите вашего агента",
|
||||
"selectSources": "Выберите источники",
|
||||
"chunksPerQuery": "Фрагментов на запрос",
|
||||
"selectType": "Выберите тип",
|
||||
"selectTools": "Выберите инструменты",
|
||||
"enterTokenLimit": "Введите лимит токенов",
|
||||
"enterRequestLimit": "Введите лимит запросов"
|
||||
},
|
||||
"sourcePopup": {
|
||||
"title": "Выберите Источники",
|
||||
"searchPlaceholder": "Поиск источников...",
|
||||
"noOptionsMessage": "Нет доступных источников"
|
||||
},
|
||||
"toolsPopup": {
|
||||
"title": "Выберите Инструменты",
|
||||
"searchPlaceholder": "Поиск инструментов...",
|
||||
"noOptionsMessage": "Нет доступных инструментов"
|
||||
},
|
||||
"upload": {
|
||||
"clickToUpload": "Нажмите для загрузки",
|
||||
"dragAndDrop": " или перетащите"
|
||||
},
|
||||
"agentTypes": {
|
||||
"classic": "Классический",
|
||||
"react": "ReAct"
|
||||
},
|
||||
"advanced": {
|
||||
"jsonSchema": "Схема ответа JSON",
|
||||
"jsonSchemaDescription": "Определите схему JSON для применения структурированного формата вывода",
|
||||
"validJson": "Валидный JSON",
|
||||
"invalidJson": "Невалидный JSON - исправьте для сохранения",
|
||||
"tokenLimiting": "Лимит токенов",
|
||||
"tokenLimitingDescription": "Ограничить ежедневное общее количество токенов, которые может использовать этот агент",
|
||||
"requestLimiting": "Лимит запросов",
|
||||
"requestLimitingDescription": "Ограничить ежедневное общее количество запросов, которые можно сделать к этому агенту"
|
||||
},
|
||||
"preview": {
|
||||
"publishedPreview": "Опубликованные агенты можно просмотреть здесь"
|
||||
},
|
||||
"externalKb": "Внешняя БЗ"
|
||||
},
|
||||
"logs": {
|
||||
"title": "Логи Агента",
|
||||
"lastUsedAt": "Последнее использование",
|
||||
"noUsageHistory": "Нет истории использования",
|
||||
"tableHeader": "Логи конечной точки агента"
|
||||
},
|
||||
"shared": {
|
||||
"notFound": "Агент не найден. Убедитесь, что агент является общим."
|
||||
},
|
||||
"preview": {
|
||||
"testMessage": "Протестируйте своего агента здесь. Опубликованные агенты можно использовать в разговорах."
|
||||
},
|
||||
"deleteConfirmation": "Вы уверены, что хотите удалить этого агента?"
|
||||
},
|
||||
"components": {
|
||||
"fileUpload": {
|
||||
"clickToUpload": "Click to upload or drag and drop",
|
||||
"dropFiles": "Drop the files here",
|
||||
"fileTypes": "PNG, JPG, JPEG up to",
|
||||
"sizeLimitUnit": "MB",
|
||||
"fileSizeError": "File exceeds {{size}}MB limit"
|
||||
}
|
||||
},
|
||||
"pageNotFound": {
|
||||
"title": "404",
|
||||
"message": "The page you are looking for does not exist.",
|
||||
"goHome": "Go Back Home"
|
||||
},
|
||||
"filePicker": {
|
||||
"searchPlaceholder": "Поиск файлов и папок...",
|
||||
"itemsSelected": "{{count}} выбрано",
|
||||
"name": "Имя",
|
||||
"lastModified": "Последнее изменение",
|
||||
"size": "Размер"
|
||||
},
|
||||
"actionButtons": {
|
||||
"openNewChat": "Открыть новый чат",
|
||||
"share": "Поделиться"
|
||||
},
|
||||
"mermaid": {
|
||||
"downloadOptions": "Параметры загрузки",
|
||||
"viewCode": "Просмотр кода",
|
||||
"decreaseZoom": "Уменьшить масштаб",
|
||||
"resetZoom": "Сбросить масштаб",
|
||||
"increaseZoom": "Увеличить масштаб"
|
||||
},
|
||||
"navigation": {
|
||||
"agents": "Агенты"
|
||||
},
|
||||
"notification": {
|
||||
"ariaLabel": "Уведомление",
|
||||
"closeAriaLabel": "Закрыть уведомление"
|
||||
},
|
||||
"prompts": {
|
||||
"textAriaLabel": "Текст подсказки"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +185,58 @@
|
||||
"cancel": "取消",
|
||||
"addNew": "新增",
|
||||
"name": "名稱",
|
||||
"type": "類型"
|
||||
}
|
||||
"type": "類型",
|
||||
"mcp": {
|
||||
"addServer": "Add MCP Server",
|
||||
"editServer": "Edit Server",
|
||||
"serverName": "Server Name",
|
||||
"serverUrl": "Server URL",
|
||||
"headerName": "Header Name",
|
||||
"timeout": "Timeout (seconds)",
|
||||
"testConnection": "Test Connection",
|
||||
"testing": "Testing",
|
||||
"saving": "Saving",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"noAuth": "No Authentication",
|
||||
"oauthInProgress": "Waiting for OAuth completion...",
|
||||
"oauthCompleted": "OAuth completed successfully",
|
||||
"authType": "Authentication Type",
|
||||
"defaultServerName": "My MCP Server",
|
||||
"authTypes": {
|
||||
"none": "No Authentication",
|
||||
"apiKey": "API Key",
|
||||
"bearer": "Bearer Token",
|
||||
"oauth": "OAuth",
|
||||
"basic": "Basic Authentication"
|
||||
},
|
||||
"placeholders": {
|
||||
"serverUrl": "https://api.example.com",
|
||||
"apiKey": "Your secret API key",
|
||||
"bearerToken": "Your secret token",
|
||||
"username": "Your username",
|
||||
"password": "Your password",
|
||||
"oauthScopes": "OAuth scopes (comma separated)"
|
||||
},
|
||||
"errors": {
|
||||
"nameRequired": "Server name is required",
|
||||
"urlRequired": "Server URL is required",
|
||||
"invalidUrl": "Please enter a valid URL",
|
||||
"apiKeyRequired": "API key is required",
|
||||
"tokenRequired": "Bearer token is required",
|
||||
"usernameRequired": "Username is required",
|
||||
"passwordRequired": "Password is required",
|
||||
"testFailed": "Connection test failed",
|
||||
"saveFailed": "Failed to save MCP server",
|
||||
"oauthFailed": "OAuth process failed or was cancelled",
|
||||
"oauthTimeout": "OAuth process timed out, please try again",
|
||||
"timeoutRange": "Timeout must be between 1 and 300 seconds"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrollTabsLeft": "向左捲動標籤",
|
||||
"tabsAriaLabel": "設定標籤",
|
||||
"scrollTabsRight": "向右捲動標籤"
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
@@ -306,7 +356,8 @@
|
||||
"disclaimer": "這是唯一一次顯示您的金鑰。",
|
||||
"copy": "複製",
|
||||
"copied": "已複製",
|
||||
"confirm": "我已儲存金鑰"
|
||||
"confirm": "我已儲存金鑰",
|
||||
"apiKeyLabel": "API Key"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "您確定要刪除所有對話嗎?",
|
||||
@@ -324,7 +375,8 @@
|
||||
"apiKeyLabel": "API 金鑰 / OAuth",
|
||||
"apiKeyPlaceholder": "輸入 API 金鑰 / OAuth",
|
||||
"addButton": "新增工具",
|
||||
"closeButton": "關閉"
|
||||
"closeButton": "關閉",
|
||||
"customNamePlaceholder": "Enter custom name (optional)"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "新增提示",
|
||||
@@ -349,6 +401,22 @@
|
||||
"cancel": "取消",
|
||||
"delete": "刪除",
|
||||
"deleteConfirmation": "您確定要刪除此區塊嗎?"
|
||||
},
|
||||
"addAction": {
|
||||
"title": "New Action",
|
||||
"actionNamePlaceholder": "Action Name",
|
||||
"invalidFormat": "Invalid function name format. Use only letters, numbers, underscores, and hyphens.",
|
||||
"formatHelp": "Use only letters, numbers, underscores, and hyphens (e.g., `get_data`, `send_report`, etc.)",
|
||||
"addButton": "Add"
|
||||
},
|
||||
"agentDetails": {
|
||||
"title": "Access Details",
|
||||
"publicLink": "Public Link",
|
||||
"apiKey": "API Key",
|
||||
"webhookUrl": "Webhook URL",
|
||||
"generate": "Generate",
|
||||
"test": "Test",
|
||||
"learnMore": "Learn more"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -391,6 +459,153 @@
|
||||
"attach": "附件",
|
||||
"remove": "刪除附件"
|
||||
},
|
||||
"retry": "重試"
|
||||
"retry": "重試",
|
||||
"reasoning": "推理"
|
||||
},
|
||||
"agents": {
|
||||
"title": "代理",
|
||||
"description": "探索並創建結合指令、額外知識和任意技能組合的DocsGPT自訂版本",
|
||||
"newAgent": "新建代理",
|
||||
"backToAll": "返回所有代理",
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "由DocsGPT提供",
|
||||
"description": "DocsGPT提供的代理",
|
||||
"emptyState": "未找到範本代理。"
|
||||
},
|
||||
"user": {
|
||||
"title": "我的代理",
|
||||
"description": "您創建或發佈的代理",
|
||||
"emptyState": "您還沒有創建任何代理。"
|
||||
},
|
||||
"shared": {
|
||||
"title": "與我共享",
|
||||
"description": "透過公共連結匯入的代理",
|
||||
"emptyState": "未找到共享代理。"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"headings": {
|
||||
"new": "新建代理",
|
||||
"edit": "編輯代理",
|
||||
"draft": "新建代理(草稿)"
|
||||
},
|
||||
"buttons": {
|
||||
"publish": "發佈",
|
||||
"save": "儲存",
|
||||
"saveDraft": "儲存草稿",
|
||||
"cancel": "取消",
|
||||
"delete": "刪除",
|
||||
"logs": "日誌",
|
||||
"accessDetails": "存取詳情",
|
||||
"add": "新增"
|
||||
},
|
||||
"sections": {
|
||||
"meta": "中繼資料",
|
||||
"source": "來源",
|
||||
"prompt": "提示詞",
|
||||
"tools": "工具",
|
||||
"agentType": "代理類型",
|
||||
"advanced": "進階",
|
||||
"preview": "預覽"
|
||||
},
|
||||
"placeholders": {
|
||||
"agentName": "代理名稱",
|
||||
"describeAgent": "描述您的代理",
|
||||
"selectSources": "選擇來源",
|
||||
"chunksPerQuery": "每次查詢的區塊數",
|
||||
"selectType": "選擇類型",
|
||||
"selectTools": "選擇工具",
|
||||
"enterTokenLimit": "輸入權杖限制",
|
||||
"enterRequestLimit": "輸入請求限制"
|
||||
},
|
||||
"sourcePopup": {
|
||||
"title": "選擇來源",
|
||||
"searchPlaceholder": "搜尋來源...",
|
||||
"noOptionsMessage": "沒有可用的來源"
|
||||
},
|
||||
"toolsPopup": {
|
||||
"title": "選擇工具",
|
||||
"searchPlaceholder": "搜尋工具...",
|
||||
"noOptionsMessage": "沒有可用的工具"
|
||||
},
|
||||
"upload": {
|
||||
"clickToUpload": "點擊上傳",
|
||||
"dragAndDrop": " 或拖放"
|
||||
},
|
||||
"agentTypes": {
|
||||
"classic": "經典",
|
||||
"react": "ReAct"
|
||||
},
|
||||
"advanced": {
|
||||
"jsonSchema": "JSON回應架構",
|
||||
"jsonSchemaDescription": "定義JSON架構以強制執行結構化輸出格式",
|
||||
"validJson": "有效的JSON",
|
||||
"invalidJson": "無效的JSON - 修復後才能儲存",
|
||||
"tokenLimiting": "權杖限制",
|
||||
"tokenLimitingDescription": "限制此代理每天可使用的總權杖數",
|
||||
"requestLimiting": "請求限制",
|
||||
"requestLimitingDescription": "限制每天可向此代理發出的總請求數"
|
||||
},
|
||||
"preview": {
|
||||
"publishedPreview": "已發佈的代理可以在此處預覽"
|
||||
},
|
||||
"externalKb": "外部知識庫"
|
||||
},
|
||||
"logs": {
|
||||
"title": "代理日誌",
|
||||
"lastUsedAt": "最後使用時間",
|
||||
"noUsageHistory": "無使用歷史",
|
||||
"tableHeader": "代理端點日誌"
|
||||
},
|
||||
"shared": {
|
||||
"notFound": "未找到代理。請確保代理已共享。"
|
||||
},
|
||||
"preview": {
|
||||
"testMessage": "在此測試您的代理。已發佈的代理可以在對話中使用。"
|
||||
},
|
||||
"deleteConfirmation": "您確定要刪除此代理嗎?"
|
||||
},
|
||||
"components": {
|
||||
"fileUpload": {
|
||||
"clickToUpload": "Click to upload or drag and drop",
|
||||
"dropFiles": "Drop the files here",
|
||||
"fileTypes": "PNG, JPG, JPEG up to",
|
||||
"sizeLimitUnit": "MB",
|
||||
"fileSizeError": "File exceeds {{size}}MB limit"
|
||||
}
|
||||
},
|
||||
"pageNotFound": {
|
||||
"title": "404",
|
||||
"message": "The page you are looking for does not exist.",
|
||||
"goHome": "Go Back Home"
|
||||
},
|
||||
"filePicker": {
|
||||
"searchPlaceholder": "搜尋檔案和資料夾...",
|
||||
"itemsSelected": "已選擇 {{count}} 項",
|
||||
"name": "名稱",
|
||||
"lastModified": "最後修改",
|
||||
"size": "大小"
|
||||
},
|
||||
"actionButtons": {
|
||||
"openNewChat": "開啟新聊天",
|
||||
"share": "分享"
|
||||
},
|
||||
"mermaid": {
|
||||
"downloadOptions": "下載選項",
|
||||
"viewCode": "查看程式碼",
|
||||
"decreaseZoom": "縮小",
|
||||
"resetZoom": "重設縮放",
|
||||
"increaseZoom": "放大"
|
||||
},
|
||||
"navigation": {
|
||||
"agents": "代理"
|
||||
},
|
||||
"notification": {
|
||||
"ariaLabel": "通知",
|
||||
"closeAriaLabel": "關閉通知"
|
||||
},
|
||||
"prompts": {
|
||||
"textAriaLabel": "提示文字"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +185,58 @@
|
||||
"cancel": "取消",
|
||||
"addNew": "添加新的",
|
||||
"name": "名称",
|
||||
"type": "类型"
|
||||
}
|
||||
"type": "类型",
|
||||
"mcp": {
|
||||
"addServer": "Add MCP Server",
|
||||
"editServer": "Edit Server",
|
||||
"serverName": "Server Name",
|
||||
"serverUrl": "Server URL",
|
||||
"headerName": "Header Name",
|
||||
"timeout": "Timeout (seconds)",
|
||||
"testConnection": "Test Connection",
|
||||
"testing": "Testing",
|
||||
"saving": "Saving",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"noAuth": "No Authentication",
|
||||
"oauthInProgress": "Waiting for OAuth completion...",
|
||||
"oauthCompleted": "OAuth completed successfully",
|
||||
"authType": "Authentication Type",
|
||||
"defaultServerName": "My MCP Server",
|
||||
"authTypes": {
|
||||
"none": "No Authentication",
|
||||
"apiKey": "API Key",
|
||||
"bearer": "Bearer Token",
|
||||
"oauth": "OAuth",
|
||||
"basic": "Basic Authentication"
|
||||
},
|
||||
"placeholders": {
|
||||
"serverUrl": "https://api.example.com",
|
||||
"apiKey": "Your secret API key",
|
||||
"bearerToken": "Your secret token",
|
||||
"username": "Your username",
|
||||
"password": "Your password",
|
||||
"oauthScopes": "OAuth scopes (comma separated)"
|
||||
},
|
||||
"errors": {
|
||||
"nameRequired": "Server name is required",
|
||||
"urlRequired": "Server URL is required",
|
||||
"invalidUrl": "Please enter a valid URL",
|
||||
"apiKeyRequired": "API key is required",
|
||||
"tokenRequired": "Bearer token is required",
|
||||
"usernameRequired": "Username is required",
|
||||
"passwordRequired": "Password is required",
|
||||
"testFailed": "Connection test failed",
|
||||
"saveFailed": "Failed to save MCP server",
|
||||
"oauthFailed": "OAuth process failed or was cancelled",
|
||||
"oauthTimeout": "OAuth process timed out, please try again",
|
||||
"timeoutRange": "Timeout must be between 1 and 300 seconds"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrollTabsLeft": "向左滚动标签",
|
||||
"tabsAriaLabel": "设置标签",
|
||||
"scrollTabsRight": "向右滚动标签"
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
@@ -306,7 +356,8 @@
|
||||
"disclaimer": "这是您的密钥唯一一次展示机会。",
|
||||
"copy": "复制",
|
||||
"copied": "已复制",
|
||||
"confirm": "我已保存密钥"
|
||||
"confirm": "我已保存密钥",
|
||||
"apiKeyLabel": "API Key"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "您确定要删除所有对话吗?",
|
||||
@@ -324,7 +375,8 @@
|
||||
"apiKeyLabel": "API 密钥 / OAuth",
|
||||
"apiKeyPlaceholder": "输入 API 密钥 / OAuth",
|
||||
"addButton": "添加工具",
|
||||
"closeButton": "关闭"
|
||||
"closeButton": "关闭",
|
||||
"customNamePlaceholder": "Enter custom name (optional)"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "添加提示",
|
||||
@@ -349,6 +401,22 @@
|
||||
"cancel": "取消",
|
||||
"delete": "删除",
|
||||
"deleteConfirmation": "您确定要删除此块吗?"
|
||||
},
|
||||
"addAction": {
|
||||
"title": "New Action",
|
||||
"actionNamePlaceholder": "Action Name",
|
||||
"invalidFormat": "Invalid function name format. Use only letters, numbers, underscores, and hyphens.",
|
||||
"formatHelp": "Use only letters, numbers, underscores, and hyphens (e.g., `get_data`, `send_report`, etc.)",
|
||||
"addButton": "Add"
|
||||
},
|
||||
"agentDetails": {
|
||||
"title": "Access Details",
|
||||
"publicLink": "Public Link",
|
||||
"apiKey": "API Key",
|
||||
"webhookUrl": "Webhook URL",
|
||||
"generate": "Generate",
|
||||
"test": "Test",
|
||||
"learnMore": "Learn more"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -391,6 +459,153 @@
|
||||
"attach": "附件",
|
||||
"remove": "删除附件"
|
||||
},
|
||||
"retry": "重试"
|
||||
"retry": "重试",
|
||||
"reasoning": "推理"
|
||||
},
|
||||
"agents": {
|
||||
"title": "代理",
|
||||
"description": "发现并创建结合指令、额外知识和任意技能组合的DocsGPT自定义版本",
|
||||
"newAgent": "新建代理",
|
||||
"backToAll": "返回所有代理",
|
||||
"sections": {
|
||||
"template": {
|
||||
"title": "由DocsGPT提供",
|
||||
"description": "DocsGPT提供的代理",
|
||||
"emptyState": "未找到模板代理。"
|
||||
},
|
||||
"user": {
|
||||
"title": "我的代理",
|
||||
"description": "您创建或发布的代理",
|
||||
"emptyState": "您还没有创建任何代理。"
|
||||
},
|
||||
"shared": {
|
||||
"title": "与我共享",
|
||||
"description": "通过公共链接导入的代理",
|
||||
"emptyState": "未找到共享代理。"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"headings": {
|
||||
"new": "新建代理",
|
||||
"edit": "编辑代理",
|
||||
"draft": "新建代理(草稿)"
|
||||
},
|
||||
"buttons": {
|
||||
"publish": "发布",
|
||||
"save": "保存",
|
||||
"saveDraft": "保存草稿",
|
||||
"cancel": "取消",
|
||||
"delete": "删除",
|
||||
"logs": "日志",
|
||||
"accessDetails": "访问详情",
|
||||
"add": "添加"
|
||||
},
|
||||
"sections": {
|
||||
"meta": "元数据",
|
||||
"source": "来源",
|
||||
"prompt": "提示词",
|
||||
"tools": "工具",
|
||||
"agentType": "代理类型",
|
||||
"advanced": "高级",
|
||||
"preview": "预览"
|
||||
},
|
||||
"placeholders": {
|
||||
"agentName": "代理名称",
|
||||
"describeAgent": "描述您的代理",
|
||||
"selectSources": "选择来源",
|
||||
"chunksPerQuery": "每次查询的块数",
|
||||
"selectType": "选择类型",
|
||||
"selectTools": "选择工具",
|
||||
"enterTokenLimit": "输入令牌限制",
|
||||
"enterRequestLimit": "输入请求限制"
|
||||
},
|
||||
"sourcePopup": {
|
||||
"title": "选择来源",
|
||||
"searchPlaceholder": "搜索来源...",
|
||||
"noOptionsMessage": "没有可用的来源"
|
||||
},
|
||||
"toolsPopup": {
|
||||
"title": "选择工具",
|
||||
"searchPlaceholder": "搜索工具...",
|
||||
"noOptionsMessage": "没有可用的工具"
|
||||
},
|
||||
"upload": {
|
||||
"clickToUpload": "点击上传",
|
||||
"dragAndDrop": " 或拖放"
|
||||
},
|
||||
"agentTypes": {
|
||||
"classic": "经典",
|
||||
"react": "ReAct"
|
||||
},
|
||||
"advanced": {
|
||||
"jsonSchema": "JSON响应架构",
|
||||
"jsonSchemaDescription": "定义JSON架构以强制执行结构化输出格式",
|
||||
"validJson": "有效的JSON",
|
||||
"invalidJson": "无效的JSON - 修复后才能保存",
|
||||
"tokenLimiting": "令牌限制",
|
||||
"tokenLimitingDescription": "限制此代理每天可使用的总令牌数",
|
||||
"requestLimiting": "请求限制",
|
||||
"requestLimitingDescription": "限制每天可向此代理发出的总请求数"
|
||||
},
|
||||
"preview": {
|
||||
"publishedPreview": "已发布的代理可以在此处预览"
|
||||
},
|
||||
"externalKb": "外部知识库"
|
||||
},
|
||||
"logs": {
|
||||
"title": "代理日志",
|
||||
"lastUsedAt": "最后使用时间",
|
||||
"noUsageHistory": "无使用历史",
|
||||
"tableHeader": "代理端点日志"
|
||||
},
|
||||
"shared": {
|
||||
"notFound": "未找到代理。请确保代理已共享。"
|
||||
},
|
||||
"preview": {
|
||||
"testMessage": "在此测试您的代理。已发布的代理可以在对话中使用。"
|
||||
},
|
||||
"deleteConfirmation": "您确定要删除此代理吗?"
|
||||
},
|
||||
"components": {
|
||||
"fileUpload": {
|
||||
"clickToUpload": "Click to upload or drag and drop",
|
||||
"dropFiles": "Drop the files here",
|
||||
"fileTypes": "PNG, JPG, JPEG up to",
|
||||
"sizeLimitUnit": "MB",
|
||||
"fileSizeError": "File exceeds {{size}}MB limit"
|
||||
}
|
||||
},
|
||||
"pageNotFound": {
|
||||
"title": "404",
|
||||
"message": "The page you are looking for does not exist.",
|
||||
"goHome": "Go Back Home"
|
||||
},
|
||||
"filePicker": {
|
||||
"searchPlaceholder": "搜索文件和文件夹...",
|
||||
"itemsSelected": "已选择 {{count}} 项",
|
||||
"name": "名称",
|
||||
"lastModified": "最后修改",
|
||||
"size": "大小"
|
||||
},
|
||||
"actionButtons": {
|
||||
"openNewChat": "打开新聊天",
|
||||
"share": "分享"
|
||||
},
|
||||
"mermaid": {
|
||||
"downloadOptions": "下载选项",
|
||||
"viewCode": "查看代码",
|
||||
"decreaseZoom": "缩小",
|
||||
"resetZoom": "重置缩放",
|
||||
"increaseZoom": "放大"
|
||||
},
|
||||
"navigation": {
|
||||
"agents": "代理"
|
||||
},
|
||||
"notification": {
|
||||
"ariaLabel": "通知",
|
||||
"closeAriaLabel": "关闭通知"
|
||||
},
|
||||
"prompts": {
|
||||
"textAriaLabel": "提示文本"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function AddActionModal({
|
||||
>
|
||||
<div>
|
||||
<h2 className="text-jet dark:text-bright-gray px-3 text-xl font-semibold">
|
||||
New Action
|
||||
{t('modals.addAction.title')}
|
||||
</h2>
|
||||
<div className="relative mt-6 px-3">
|
||||
<Input
|
||||
@@ -57,7 +57,7 @@ export default function AddActionModal({
|
||||
}}
|
||||
borderVariant="thin"
|
||||
labelBgClassName="bg-white dark:bg-charleston-green-2"
|
||||
placeholder="Action Name"
|
||||
placeholder={t('modals.addAction.actionNamePlaceholder')}
|
||||
required={true}
|
||||
/>
|
||||
<p
|
||||
@@ -66,8 +66,8 @@ export default function AddActionModal({
|
||||
}`}
|
||||
>
|
||||
{functionNameError
|
||||
? 'Invalid function name format. Use only letters, numbers, underscores, and hyphens.'
|
||||
: 'Use only letters, numbers, underscores, and hyphens (e.g., `get_data`, `send_report`, etc.)'}
|
||||
? t('modals.addAction.invalidFormat')
|
||||
: t('modals.addAction.formatHelp')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-row-reverse gap-1 px-3">
|
||||
@@ -75,7 +75,7 @@ export default function AddActionModal({
|
||||
onClick={handleAddAction}
|
||||
className="bg-purple-30 hover:bg-violets-are-blue rounded-3xl px-5 py-2 text-sm text-white transition-all"
|
||||
>
|
||||
Add
|
||||
{t('modals.addAction.addButton')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { Agent } from '../agents/types';
|
||||
@@ -24,6 +25,7 @@ export default function AgentDetailsModal({
|
||||
modalState,
|
||||
setModalState,
|
||||
}: AgentDetailsModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const token = useSelector(selectToken);
|
||||
|
||||
const [sharedToken, setSharedToken] = useState<string | null>(
|
||||
@@ -86,13 +88,13 @@ export default function AgentDetailsModal({
|
||||
>
|
||||
<div>
|
||||
<h2 className="text-jet dark:text-bright-gray text-xl font-semibold">
|
||||
Access Details
|
||||
{t('modals.agentDetails.title')}
|
||||
</h2>
|
||||
<div className="mt-8 flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
|
||||
Public Link
|
||||
{t('modals.agentDetails.publicLink')}
|
||||
</h2>
|
||||
</div>
|
||||
{sharedToken ? (
|
||||
@@ -117,7 +119,9 @@ export default function AgentDetailsModal({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="text-sm">Learn more</span>
|
||||
<span className="text-sm">
|
||||
{t('modals.agentDetails.learnMore')}
|
||||
</span>
|
||||
<img
|
||||
src="/src/assets/external-link.svg"
|
||||
alt="External link"
|
||||
@@ -133,14 +137,14 @@ export default function AgentDetailsModal({
|
||||
{loadingStates.publicLink ? (
|
||||
<Spinner size="small" color="#976af3" />
|
||||
) : (
|
||||
'Generate'
|
||||
t('modals.agentDetails.generate')
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
|
||||
API Key
|
||||
{t('modals.agentDetails.apiKey')}
|
||||
</h2>
|
||||
{apiKey ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -162,7 +166,7 @@ export default function AgentDetailsModal({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Test
|
||||
{t('modals.agentDetails.test')}
|
||||
<img
|
||||
src="/src/assets/external-link.svg"
|
||||
alt="External link"
|
||||
@@ -174,14 +178,14 @@ export default function AgentDetailsModal({
|
||||
</div>
|
||||
) : (
|
||||
<button className="border-purple-30 text-purple-30 hover:bg-purple-30 w-28 rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white">
|
||||
Generate
|
||||
{t('modals.agentDetails.generate')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
|
||||
Webhook URL
|
||||
{t('modals.agentDetails.webhookUrl')}
|
||||
</h2>
|
||||
</div>
|
||||
{webhookUrl ? (
|
||||
@@ -202,7 +206,9 @@ export default function AgentDetailsModal({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="text-sm">Learn more</span>
|
||||
<span className="text-sm">
|
||||
{t('modals.agentDetails.learnMore')}
|
||||
</span>
|
||||
<img
|
||||
src="/src/assets/external-link.svg"
|
||||
alt="External link"
|
||||
@@ -218,7 +224,7 @@ export default function AgentDetailsModal({
|
||||
{loadingStates.webhook ? (
|
||||
<Spinner size="small" color="#976af3" />
|
||||
) : (
|
||||
'Generate'
|
||||
t('modals.agentDetails.generate')
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function ConfigToolModal({
|
||||
value={customName}
|
||||
onChange={(e) => setCustomName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder="Enter custom name (optional)"
|
||||
placeholder={t('modals.configTool.customNamePlaceholder')}
|
||||
labelBgClassName="bg-white dark:bg-charleston-green-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import Dropdown from '../components/Dropdown';
|
||||
import Input from '../components/Input';
|
||||
import { CreateAPIKeyModalProps, Doc } from '../models/misc';
|
||||
import { selectSourceDocs, selectToken } from '../preferences/preferenceSlice';
|
||||
import WrapperModal from './WrapperModal';
|
||||
|
||||
const embeddingsName =
|
||||
import.meta.env.VITE_EMBEDDINGS_NAME ||
|
||||
'huggingface_sentence-transformers/all-mpnet-base-v2';
|
||||
|
||||
export default function CreateAPIKeyModal({
|
||||
close,
|
||||
createAPIKey,
|
||||
}: CreateAPIKeyModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const token = useSelector(selectToken);
|
||||
const docs = useSelector(selectSourceDocs);
|
||||
|
||||
const [APIKeyName, setAPIKeyName] = React.useState<string>('');
|
||||
const [sourcePath, setSourcePath] = React.useState<{
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
} | null>(null);
|
||||
const [prompt, setPrompt] = React.useState<{
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
} | null>(null);
|
||||
const [activePrompts, setActivePrompts] = React.useState<
|
||||
{ name: string; id: string; type: string }[]
|
||||
>([]);
|
||||
const [chunk, setChunk] = React.useState<string>('2');
|
||||
const chunkOptions = ['0', '2', '4', '6', '8', '10'];
|
||||
|
||||
const extractDocPaths = () =>
|
||||
docs
|
||||
? docs
|
||||
.filter((doc) => doc.model === embeddingsName)
|
||||
.map((doc: Doc) => {
|
||||
if ('id' in doc) {
|
||||
return {
|
||||
name: doc.name,
|
||||
id: doc.id as string,
|
||||
type: 'local',
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: doc.name,
|
||||
id: doc.id ?? 'default',
|
||||
type: doc.type ?? 'default',
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleFetchPrompts = async () => {
|
||||
try {
|
||||
const response = await userService.getPrompts(token);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch prompts');
|
||||
}
|
||||
const promptsData = await response.json();
|
||||
setActivePrompts(promptsData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
handleFetchPrompts();
|
||||
}, []);
|
||||
return (
|
||||
<WrapperModal close={close} className="p-4">
|
||||
<div className="mb-6">
|
||||
<span className="text-jet dark:text-bright-gray text-xl">
|
||||
{t('modals.createAPIKey.label')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative mt-5 mb-4">
|
||||
<Input
|
||||
type="text"
|
||||
className="rounded-md"
|
||||
value={APIKeyName}
|
||||
placeholder={t('modals.createAPIKey.apiKeyName')}
|
||||
onChange={(e) => setAPIKeyName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
labelBgClassName="bg-white dark:bg-charleston-green-2"
|
||||
></Input>
|
||||
</div>
|
||||
<div className="my-4">
|
||||
<Dropdown
|
||||
placeholder={t('modals.createAPIKey.sourceDoc')}
|
||||
selectedValue={sourcePath ? sourcePath.name : null}
|
||||
onSelect={(selection: { name: string; id: string; type: string }) => {
|
||||
setSourcePath(selection);
|
||||
}}
|
||||
options={extractDocPaths()}
|
||||
size="w-full"
|
||||
rounded="xl"
|
||||
border="border"
|
||||
/>
|
||||
</div>
|
||||
<div className="my-4">
|
||||
<Dropdown
|
||||
options={activePrompts}
|
||||
selectedValue={prompt ? prompt.name : null}
|
||||
placeholder={t('modals.createAPIKey.prompt')}
|
||||
onSelect={(value: { name: string; id: string; type: string }) =>
|
||||
setPrompt(value)
|
||||
}
|
||||
size="w-full"
|
||||
border="border"
|
||||
/>
|
||||
</div>
|
||||
<div className="my-4">
|
||||
<p className="text-jet dark:text-bright-gray mb-2 ml-2 font-semibold">
|
||||
{t('modals.createAPIKey.chunks')}
|
||||
</p>
|
||||
<Dropdown
|
||||
options={chunkOptions}
|
||||
selectedValue={chunk}
|
||||
onSelect={(value: string) => setChunk(value)}
|
||||
size="w-full"
|
||||
border="border"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
disabled={!sourcePath || APIKeyName.length === 0 || !prompt}
|
||||
onClick={() => {
|
||||
if (sourcePath && prompt) {
|
||||
const payload: any = {
|
||||
name: APIKeyName,
|
||||
prompt_id: prompt.id,
|
||||
chunks: chunk,
|
||||
};
|
||||
if (sourcePath.type === 'default') {
|
||||
payload.retriever = sourcePath.id;
|
||||
}
|
||||
if (sourcePath.type === 'local') {
|
||||
payload.source = sourcePath.id;
|
||||
}
|
||||
createAPIKey(payload);
|
||||
}
|
||||
}}
|
||||
className="bg-purple-30 hover:bg-violets-are-blue float-right mt-4 rounded-full px-5 py-2 text-sm text-white disabled:opacity-50"
|
||||
>
|
||||
{t('modals.createAPIKey.create')}
|
||||
</button>
|
||||
</WrapperModal>
|
||||
);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export default function DeleteConvModal({
|
||||
<ConfirmationModal
|
||||
message={t('modals.deleteConv.confirm')}
|
||||
modalState={modalState}
|
||||
setModalState={setModalState}
|
||||
setModalState={(state) => dispatch(setModalState(state))}
|
||||
submitLabel={t('modals.deleteConv.delete')}
|
||||
handleSubmit={handleSubmit}
|
||||
handleCancel={handleCancel}
|
||||
|
||||
@@ -18,14 +18,6 @@ interface MCPServerModalProps {
|
||||
onServerSaved: () => void;
|
||||
}
|
||||
|
||||
const authTypes = [
|
||||
{ label: 'No Authentication', value: 'none' },
|
||||
{ label: 'API Key', value: 'api_key' },
|
||||
{ label: 'Bearer Token', value: 'bearer' },
|
||||
{ label: 'OAuth', value: 'oauth' },
|
||||
// { label: 'Basic Authentication', value: 'basic' },
|
||||
];
|
||||
|
||||
export default function MCPServerModal({
|
||||
modalState,
|
||||
setModalState,
|
||||
@@ -36,8 +28,16 @@ export default function MCPServerModal({
|
||||
const token = useSelector(selectToken);
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const authTypes = [
|
||||
{ label: t('settings.tools.mcp.authTypes.none'), value: 'none' },
|
||||
{ label: t('settings.tools.mcp.authTypes.apiKey'), value: 'api_key' },
|
||||
{ label: t('settings.tools.mcp.authTypes.bearer'), value: 'bearer' },
|
||||
{ label: t('settings.tools.mcp.authTypes.oauth'), value: 'oauth' },
|
||||
// { label: t('settings.tools.mcp.authTypes.basic'), value: 'basic' },
|
||||
];
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: server?.displayName || 'My MCP Server',
|
||||
name: server?.displayName || t('settings.tools.mcp.defaultServerName'),
|
||||
server_url: server?.server_url || '',
|
||||
auth_type: server?.auth_type || 'none',
|
||||
api_key: '',
|
||||
@@ -72,7 +72,7 @@ export default function MCPServerModal({
|
||||
|
||||
const resetForm = () => {
|
||||
setFormData({
|
||||
name: 'My MCP Server',
|
||||
name: t('settings.tools.mcp.defaultServerName'),
|
||||
server_url: '',
|
||||
auth_type: 'none',
|
||||
api_key: '',
|
||||
@@ -133,7 +133,7 @@ export default function MCPServerModal({
|
||||
typeof timeoutValue === 'number' &&
|
||||
(timeoutValue < 1 || timeoutValue > 300)
|
||||
)
|
||||
newErrors.timeout = 'Timeout must be between 1 and 300 seconds';
|
||||
newErrors.timeout = t('settings.tools.mcp.errors.timeoutRange');
|
||||
|
||||
if (authFieldChecks[formData.auth_type])
|
||||
authFieldChecks[formData.auth_type]();
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SaveAPIKeyModalProps } from '../models/misc';
|
||||
import WrapperModal from './WrapperModal';
|
||||
|
||||
export default function SaveAPIKeyModal({
|
||||
apiKey,
|
||||
close,
|
||||
}: SaveAPIKeyModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isCopied, setIsCopied] = React.useState(false);
|
||||
|
||||
const handleCopyKey = () => {
|
||||
navigator.clipboard.writeText(apiKey);
|
||||
setIsCopied(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<WrapperModal close={close}>
|
||||
<h1 className="text-jet dark:text-bright-gray my-0 text-xl font-medium">
|
||||
{t('modals.saveKey.note')}
|
||||
</h1>
|
||||
<h3 className="text-outer-space dark:text-silver text-sm font-normal">
|
||||
{t('modals.saveKey.disclaimer')}
|
||||
</h3>
|
||||
<div className="flex justify-between py-2">
|
||||
<div>
|
||||
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
|
||||
API Key
|
||||
</h2>
|
||||
<span className="text-jet dark:text-bright-gray text-sm leading-7 font-normal">
|
||||
{apiKey}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className="border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue my-1 h-10 w-20 rounded-full border border-solid p-2 text-sm hover:text-white"
|
||||
onClick={handleCopyKey}
|
||||
>
|
||||
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={close}
|
||||
className="bg-philippine-yellow rounded-full px-4 py-3 font-medium text-black hover:bg-[#E6B91A]"
|
||||
>
|
||||
{t('modals.saveKey.confirm')}
|
||||
</button>
|
||||
</WrapperModal>
|
||||
);
|
||||
}
|
||||
@@ -39,18 +39,3 @@ export type DocumentsProps = {
|
||||
paginatedDocuments: Doc[] | null;
|
||||
handleDeleteDocument: (index: number, document: Doc) => void;
|
||||
};
|
||||
|
||||
export type CreateAPIKeyModalProps = {
|
||||
close: () => void;
|
||||
createAPIKey: (payload: {
|
||||
name: string;
|
||||
source: string;
|
||||
prompt_id: string;
|
||||
chunks: string;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
export type SaveAPIKeyModalProps = {
|
||||
apiKey: string;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@ function AddPrompt({
|
||||
className="border-silver dark:border-silver/40 h-56 w-full resize-none rounded-lg border-2 px-3 py-2 outline-hidden dark:bg-transparent dark:text-white"
|
||||
value={newPromptContent}
|
||||
onChange={(e) => setNewPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
aria-label={t('prompts.textAriaLabel')}
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse">
|
||||
@@ -126,7 +126,7 @@ function EditPrompt({
|
||||
className="border-silver dark:border-silver/40 h-56 w-full resize-none rounded-lg border-2 px-3 py-2 outline-hidden dark:bg-transparent dark:text-white"
|
||||
value={editPromptContent}
|
||||
onChange={(e) => setEditPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
aria-label={t('prompts.textAriaLabel')}
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse gap-4">
|
||||
|
||||
@@ -90,9 +90,27 @@ export function getLocalApiKey(): string | null {
|
||||
return key;
|
||||
}
|
||||
|
||||
export function getLocalRecentDocs(): Doc[] | null {
|
||||
const docs = localStorage.getItem('DocsGPTRecentDocs');
|
||||
return docs ? (JSON.parse(docs) as Doc[]) : null;
|
||||
export function getLocalRecentDocs(sourceDocs?: Doc[] | null): Doc[] | null {
|
||||
const docsString = localStorage.getItem('DocsGPTRecentDocs');
|
||||
const selectedDocs = docsString ? (JSON.parse(docsString) as Doc[]) : null;
|
||||
|
||||
if (!sourceDocs || !selectedDocs || selectedDocs.length === 0) {
|
||||
return selectedDocs;
|
||||
}
|
||||
const isDocAvailable = (selected: Doc) => {
|
||||
return sourceDocs.some((source) => {
|
||||
if (source.id && selected.id) {
|
||||
return source.id === selected.id;
|
||||
}
|
||||
return source.name === selected.name && source.date === selected.date;
|
||||
});
|
||||
};
|
||||
|
||||
const validDocs = selectedDocs.filter(isDocAvailable);
|
||||
|
||||
setLocalRecentDocs(validDocs.length > 0 ? validDocs : null);
|
||||
|
||||
return validDocs.length > 0 ? validDocs : null;
|
||||
}
|
||||
|
||||
export function getLocalPrompt(): string | null {
|
||||
|
||||
@@ -8,7 +8,11 @@ import {
|
||||
import { Agent } from '../agents/types';
|
||||
import { ActiveState, Doc } from '../models/misc';
|
||||
import { RootState } from '../store';
|
||||
import { setLocalApiKey, setLocalRecentDocs } from './preferenceApi';
|
||||
import {
|
||||
setLocalApiKey,
|
||||
setLocalRecentDocs,
|
||||
getLocalRecentDocs,
|
||||
} from './preferenceApi';
|
||||
|
||||
export interface Preference {
|
||||
apiKey: string;
|
||||
@@ -178,6 +182,22 @@ prefListenerMiddleware.startListening({
|
||||
},
|
||||
});
|
||||
|
||||
prefListenerMiddleware.startListening({
|
||||
matcher: isAnyOf(setSourceDocs),
|
||||
effect: (_action, listenerApi) => {
|
||||
const state = listenerApi.getState() as RootState;
|
||||
const sourceDocs = state.preference.sourceDocs;
|
||||
if (sourceDocs && sourceDocs.length > 0) {
|
||||
const validatedDocs = getLocalRecentDocs(sourceDocs);
|
||||
if (validatedDocs !== null) {
|
||||
listenerApi.dispatch(setSelectedDocs(validatedDocs));
|
||||
} else {
|
||||
listenerApi.dispatch(setSelectedDocs([]));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const selectApiKey = (state: RootState) => state.preference.apiKey;
|
||||
export const selectApiKeyStatus = (state: RootState) =>
|
||||
!!state.preference.apiKey;
|
||||
|
||||
@@ -27,8 +27,8 @@ import {
|
||||
} from '../preferences/preferenceSlice';
|
||||
import Upload from '../upload/Upload';
|
||||
import { formatDate } from '../utils/dateTimeUtils';
|
||||
import FileTreeComponent from '../components/FileTreeComponent';
|
||||
import ConnectorTreeComponent from '../components/ConnectorTreeComponent';
|
||||
import FileTree from '../components/FileTree';
|
||||
import ConnectorTree from '../components/ConnectorTree';
|
||||
import Chunks from '../components/Chunks';
|
||||
|
||||
const formatTokens = (tokens: number): string => {
|
||||
@@ -273,13 +273,13 @@ export default function Sources({
|
||||
<div className="mt-8 flex flex-col">
|
||||
{documentToView.isNested ? (
|
||||
documentToView.type === 'connector:file' ? (
|
||||
<ConnectorTreeComponent
|
||||
<ConnectorTree
|
||||
docId={documentToView.id || ''}
|
||||
sourceName={documentToView.name}
|
||||
onBackToDocuments={() => setDocumentToView(undefined)}
|
||||
/>
|
||||
) : (
|
||||
<FileTreeComponent
|
||||
<FileTree
|
||||
docId={documentToView.id || ''}
|
||||
sourceName={documentToView.name}
|
||||
onBackToDocuments={() => setDocumentToView(undefined)}
|
||||
|
||||
@@ -192,7 +192,7 @@ export default function Tools() {
|
||||
<div className="flex w-full flex-col items-center justify-center py-12">
|
||||
<img
|
||||
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
|
||||
alt="No tools found"
|
||||
alt={t('settings.tools.noToolsFound')}
|
||||
className="mx-auto mb-6 h-32 w-32"
|
||||
/>
|
||||
<p className="text-center text-lg text-gray-500 dark:text-gray-400">
|
||||
|
||||
Reference in New Issue
Block a user