Merge pull request #2820 from arc53/ui-scan

UI refinements
This commit is contained in:
Alex authored and GitHub committed 2026-09-22 11:22:43 +01:00
commit 0c85d766dd
19 files changed
+237 -168

No files matched your search

+20 -14
View File
@@ -86,7 +86,7 @@ export default function AgentCard({
template: [
{
icon: Duplicate,
label: 'Duplicate',
label: t('modals.prompts.duplicate'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
handleDuplicate();
@@ -99,7 +99,7 @@ export default function AgentCard({
user: [
{
icon: Monitoring,
label: 'Logs',
label: t('agents.form.buttons.logs'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
goToLevel(agentLogsPath(agent.id));
@@ -110,7 +110,7 @@ export default function AgentCard({
},
{
icon: Edit,
label: 'Edit',
label: t('agents.edit'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
if (agent.agent_type === 'workflow') {
@@ -155,7 +155,9 @@ export default function AgentCard({
? [
{
icon: agent.pinned ? UnPin : Pin,
label: agent.pinned ? 'Unpin' : 'Pin agent',
label: agent.pinned
? t('agents.card.unpin')
: t('agents.card.pin'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
togglePin();
@@ -179,7 +181,7 @@ export default function AgentCard({
},
{
icon: Trash,
label: 'Delete',
label: t('agents.form.buttons.delete'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
setDeleteConfirmation('ACTIVE');
@@ -196,7 +198,7 @@ export default function AgentCard({
team: [
{
icon: Edit,
label: 'Edit',
label: t('agents.edit'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
if (agent.agent_type === 'workflow') {
@@ -213,7 +215,9 @@ export default function AgentCard({
? [
{
icon: agent.pinned ? UnPin : Pin,
label: agent.pinned ? 'Unpin' : 'Pin agent',
label: agent.pinned
? t('agents.card.unpin')
: t('agents.card.pin'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
togglePin();
@@ -228,7 +232,7 @@ export default function AgentCard({
shared: [
{
icon: Link,
label: 'Open',
label: t('agents.card.open'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
navigate(sharedAgentPath(agent.shared_token));
@@ -239,7 +243,7 @@ export default function AgentCard({
},
{
icon: agent.pinned ? UnPin : Pin,
label: agent.pinned ? 'Unpin' : 'Pin agent',
label: agent.pinned ? t('agents.card.unpin') : t('agents.card.pin'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
togglePin();
@@ -250,7 +254,7 @@ export default function AgentCard({
},
{
icon: Trash,
label: 'Remove',
label: t('agents.card.remove'),
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
handleHideSharedAgent();
@@ -464,7 +468,9 @@ export default function AgentCard({
imgClassName="h-7 w-7 rounded-full object-contain"
/>
{agent.status === 'draft' && (
<p className="text-foreground text-xs opacity-50">{`(Draft)`}</p>
<p className="text-foreground text-xs opacity-50">
({t('agents.card.draft')})
</p>
)}
</div>
<div className="mt-2">
@@ -480,15 +486,15 @@ export default function AgentCard({
</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();
setDeleteConfirmation('INACTIVE');
}}
cancelLabel="Cancel"
cancelLabel={t('cancel')}
variant="danger"
/>
<Modal
+6 -17
View File
@@ -4,7 +4,6 @@ import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import userService from '../api/services/userService';
import Spinner from '../components/Spinner';
import { selectToken } from '../preferences/preferenceSlice';
import Analytics from '../settings/Analytics';
import Logs from '../settings/Logs';
@@ -59,25 +58,15 @@ export default function AgentLogs() {
</div>
)}
</div>
{loadingAgent ? (
<div className="flex h-[345px] w-full items-center justify-center">
<Spinner />
</div>
) : (
agent && <Analytics agentId={agent.id} />
)}
{!loadingAgent && agent && <GuardrailEvents agentId={agent.id} />}
{loadingAgent ? (
<div className="flex h-[55vh] w-full items-center justify-center">
<Spinner />
</div>
) : (
agent && (
{agentId && (
<>
<Analytics agentId={agentId} />
<GuardrailEvents agentId={agentId} />
<Logs
agentId={agent.id}
agentId={agentId}
tableHeader={t('agents.logs.tableHeader')}
/>
)
</>
)}
</div>
</div>
+3 -3
View File
@@ -12,7 +12,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { useNavigate, useSearchParams } from 'react-router-dom';
import userService from '../api/services/userService';
import Spinner from '../components/Spinner';
import SkeletonLoader from '../components/SkeletonLoader';
import { Button } from '../components/ui/button';
import { Input } from '../components/ui/input';
import {
@@ -565,8 +565,8 @@ function AgentSection({
<div className="flex flex-col gap-4">
{isLoading ? (
<div className="flex h-40 w-full items-center justify-center">
<Spinner />
<div className="grid grid-cols-2 gap-3 sm:flex sm:flex-wrap">
<SkeletonLoader component="agentCards" count={4} />
</div>
) : (
<>
+35 -1
View File
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
interface SkeletonLoaderProps {
count?: number;
@@ -13,6 +14,7 @@ interface SkeletonLoaderProps {
| 'sourceCards'
| 'toolCards'
| 'addToolCards'
| 'agentCards'
| 'connectedState'
| 'filesSection';
}
@@ -21,6 +23,7 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
count = 1,
component = 'default',
}) => {
const { t } = useTranslation();
const [skeletonCount, setSkeletonCount] = useState(count);
useEffect(() => {
@@ -264,6 +267,26 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
</>
);
const renderAgentCards = () => (
<>
{Array.from({ length: count }).map((_, idx) => (
<div
key={`agent-skel-${idx}`}
className="bg-muted flex h-44 animate-pulse flex-col rounded-2xl px-4 py-5 sm:w-48 sm:px-6"
>
<div className="px-1">
<div className="bg-muted-foreground/20 h-7 w-7 rounded-full"></div>
</div>
<div className="mt-3 space-y-2 px-1">
<div className="bg-muted-foreground/20 h-4 w-2/3 rounded"></div>
<div className="bg-muted-foreground/20 h-3 w-full rounded"></div>
<div className="bg-muted-foreground/20 h-3 w-4/5 rounded"></div>
</div>
</div>
))}
</>
);
const renderToolCards = () => (
<>
{Array.from({ length: count }).map((_, idx) => (
@@ -325,13 +348,24 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
sourceCards: renderSourceCards,
toolCards: renderToolCards,
addToolCards: renderAddToolCards,
agentCards: renderAgentCards,
connectedState: renderConnectedState,
filesSection: renderFilesSection,
};
const render = componentMap[component] || componentMap.default;
return <>{render()}</>;
// The Spinner this replaces carried role="status"; without it a screen
// reader gets no signal at all while a section loads. Absolutely
// positioned by `sr-only`, so it never becomes a flex/grid item.
return (
<>
<span className="sr-only" role="status">
{t('loading')}
</span>
{render()}
</>
);
};
export default SkeletonLoader;
@@ -168,7 +168,7 @@ export default function ConversationMessages({
index,
});
}}
aria-label={t('Retry') || 'Retry'}
aria-label={t('conversation.retry')}
>
<Retry
width={12}
+18 -4
View File
@@ -7,6 +7,7 @@
"tagline": "DocsGPT verwendet GenAI, bitte überprüfe kritische Informationen anhand der Quellen.",
"sourceDocs": "Quelle",
"none": "Keine",
"loading": "Wird geladen...",
"cancel": "Abbrechen",
"help": "Hilfe",
"emailUs": "E-Mail senden",
@@ -197,7 +198,8 @@
"queryPlaceholder": "Frage etwas, das diese Quelle beantworten sollte...",
"run": "Ausführen",
"notSavedHint": "Diese Einstellungen gelten nur für diesen Test – sie werden nicht in der Quelle gespeichert. Ändere sie in den Quelleneinstellungen, um sie dauerhaft zu übernehmen.",
"resultSummary": "{{total}} Chunks abgerufen · {{retriever}}",
"resultSummary_one": "{{count}} Chunk abgerufen · {{retriever}}",
"resultSummary_other": "{{count}} Chunks abgerufen · {{retriever}}",
"latency": "{{ms}} ms",
"empty": "Keine Chunks für diese Anfrage abgerufen.",
"emptyWithThreshold": "Keine Chunks haben den Score-Schwellenwert ({{threshold}}) erreicht. Senke ihn, um knappe Treffer zu sehen.",
@@ -587,6 +589,8 @@
"serverName": "Servername",
"serverUrl": "Server-URL",
"headerName": "Header-Name",
"username": "Benutzername",
"password": "Passwort",
"timeout": "Timeout (Sekunden)",
"testConnection": "Verbindung testen",
"testing": "Teste...",
@@ -1135,7 +1139,8 @@
"parseError": "Spezifikation konnte nicht geparst werden. Bitte Dateiformat prüfen.",
"version": "Version",
"baseUrl": "Basis-URL",
"actionsFound": "{{count}} Aktionen gefunden",
"actionsFound_one": "{{count}} Aktion gefunden",
"actionsFound_other": "{{count}} Aktionen gefunden",
"selectAll": "Alle auswählen",
"deselectAll": "Alle abwählen",
"cancel": "Abbrechen",
@@ -1209,7 +1214,8 @@
"title": "Quellen",
"text": "Wähle deine Quellen",
"link": "Quellen-Link",
"view_more": "{{count}} weitere Quellen",
"view_more_one": "{{count}} weitere Quelle",
"view_more_other": "{{count}} weitere Quellen",
"noSourcesAvailable": "Keine Quellen verfügbar",
"selectedCount": "{{count}} Quellen ausgewählt",
"groupOwn": "Deine Quellen",
@@ -1277,6 +1283,13 @@
"agents": {
"title": "Agenten",
"edit": "Bearbeiten",
"card": {
"pin": "Agent anheften",
"unpin": "Agent lösen",
"open": "Öffnen",
"remove": "Entfernen",
"draft": "Entwurf"
},
"description": "Entdecke und erstelle benutzerdefinierte Versionen von DocsGPT, die Anweisungen, zusätzliches Wissen und beliebige Kombinationen von Fähigkeiten kombinieren",
"newAgent": "Neuer Agent",
"importAgent": "Import",
@@ -1457,7 +1470,8 @@
"confidence": "Confidence threshold",
"confidenceHint": "The judge must both flag the content and be at least this confident (default: 0.7)",
"activeCount": "{{count}} active",
"needsSetup": "{{count}} need setup",
"needsSetup_one": "{{count}} benötigt Einrichtung",
"needsSetup_other": "{{count}} benötigen Einrichtung",
"setupRequired": "Fill in this check's settings before saving.",
"pickAtLeastOne": "Pick at least one — the check cannot run with none selected.",
"remove": "Remove",
+21 -7
View File
@@ -7,6 +7,7 @@
"tagline": "DocsGPT uses GenAI, please review critical information using sources.",
"sourceDocs": "Source",
"none": "None",
"loading": "Loading...",
"cancel": "Cancel",
"errorBoundary": {
"message": "Something went wrong displaying this content.",
@@ -25,7 +26,7 @@
"memberAddedBody": "You were added to {{team}} as {{role}}",
"sharedTitle": "Shared with you",
"sharedBody": "{{type}} shared with your team: {{name}} ({{access}})",
"sharedBodyNoName": "A {{type}} was shared with your team ({{access}})",
"sharedBodyNoName": "{{type}} shared with your team ({{access}})",
"dismiss": "Dismiss"
},
"demo": [
@@ -201,7 +202,8 @@
"queryPlaceholder": "Ask something this source should answer...",
"run": "Run",
"notSavedHint": "These settings are only used for this test — they are not saved to the source. Change them in Source settings to make them permanent.",
"resultSummary": "{{total}} chunks retrieved · {{retriever}}",
"resultSummary_one": "{{count}} chunk retrieved · {{retriever}}",
"resultSummary_other": "{{count}} chunks retrieved · {{retriever}}",
"latency": "{{ms}} ms",
"empty": "No chunks retrieved for this query.",
"emptyWithThreshold": "No chunks scored at or above the score threshold ({{threshold}}). Lower it to see near misses.",
@@ -545,7 +547,7 @@
"needsAuth": "Needs Auth",
"configured": "Configured"
},
"deleteWarning": "Are you sure you want to delete the tool \"{{toolName}}\" ?",
"deleteWarning": "Are you sure you want to delete the tool \"{{toolName}}\"?",
"unsavedChanges": "You have unsaved changes that will be lost if you leave without saving.",
"leaveWithoutSaving": "Leave without Saving",
"saveAndLeave": "Save and Leave",
@@ -592,6 +594,8 @@
"serverName": "Server Name",
"serverUrl": "Server URL",
"headerName": "Header Name",
"username": "Username",
"password": "Password",
"timeout": "Timeout (seconds)",
"testConnection": "Test Connection",
"testing": "Testing",
@@ -920,7 +924,7 @@
"preparing": "Preparing upload",
"parsing": "Parsing files…",
"embedding": "Embedding…",
"tokenLimit": "Over the token limit, please consider uploading smaller document",
"tokenLimit": "Over the token limit, please consider uploading a smaller document",
"expandDetails": "Expand upload details",
"collapseDetails": "Collapse upload details",
"dismiss": "Dismiss upload toast",
@@ -1140,7 +1144,8 @@
"parseError": "Failed to parse the specification. Please check the file format.",
"version": "Version",
"baseUrl": "Base URL",
"actionsFound": "{{count}} actions found",
"actionsFound_one": "{{count}} action found",
"actionsFound_other": "{{count}} actions found",
"selectAll": "Select all",
"deselectAll": "Deselect all",
"cancel": "Cancel",
@@ -1214,7 +1219,8 @@
"title": "Sources",
"text": "Choose Your Sources",
"link": "Source link",
"view_more": "{{count}} more sources",
"view_more_one": "{{count}} more source",
"view_more_other": "{{count}} more sources",
"noSourcesAvailable": "No sources available",
"selectedCount": "{{count}} sources selected",
"groupOwn": "Your sources",
@@ -1288,6 +1294,13 @@
"agents": {
"title": "Agents",
"edit": "Edit",
"card": {
"pin": "Pin agent",
"unpin": "Unpin agent",
"open": "Open",
"remove": "Remove",
"draft": "Draft"
},
"description": "Discover and create custom versions of DocsGPT that combine instructions, extra knowledge, and any combination of skills",
"newAgent": "New Agent",
"importAgent": "Import",
@@ -1468,7 +1481,8 @@
"confidence": "Confidence threshold",
"confidenceHint": "The judge must both flag the content and be at least this confident (default: 0.7)",
"activeCount": "{{count}} active",
"needsSetup": "{{count}} need setup",
"needsSetup_one": "{{count}} needs setup",
"needsSetup_other": "{{count}} need setup",
"setupRequired": "Fill in this check's settings before saving.",
"pickAtLeastOne": "Pick at least one — the check cannot run with none selected.",
"remove": "Remove",
+20 -6
View File
@@ -7,6 +7,7 @@
"tagline": "DocsGPT utiliza GenAI, por favor revisa información crítica utilizando fuentes.",
"sourceDocs": "Fuente",
"none": "Ninguno",
"loading": "Cargando...",
"cancel": "Cancelar",
"help": "Asistencia",
"emailUs": "Envíanos un correo",
@@ -20,8 +21,8 @@
"memberAddedTitle": "Añadido a un equipo",
"memberAddedBody": "Te añadieron a {{team}} como {{role}}",
"sharedTitle": "Compartido contigo",
"sharedBody": "{{type}} compartido con tu equipo: {{name}} ({{access}})",
"sharedBodyNoName": "Se compartió un {{type}} con tu equipo ({{access}})",
"sharedBody": "Se compartió con tu equipo: {{type}} «{{name}}» ({{access}})",
"sharedBodyNoName": "Se compartió con tu equipo: {{type}} ({{access}})",
"dismiss": "Descartar"
},
"demo": [
@@ -197,7 +198,8 @@
"queryPlaceholder": "Pregunta algo que esta fuente debería responder...",
"run": "Ejecutar",
"notSavedHint": "Estos ajustes solo se usan para esta prueba — no se guardan en la fuente. Cámbialos en Configuración de la fuente para hacerlos permanentes.",
"resultSummary": "{{total}} fragmentos recuperados · {{retriever}}",
"resultSummary_one": "{{count}} fragmento recuperado · {{retriever}}",
"resultSummary_other": "{{count}} fragmentos recuperados · {{retriever}}",
"latency": "{{ms}} ms",
"empty": "No se recuperaron fragmentos para esta consulta.",
"emptyWithThreshold": "Ningún fragmento alcanzó el umbral de puntuación ({{threshold}}). Bájalo para ver los que estuvieron cerca.",
@@ -587,6 +589,8 @@
"serverName": "Server Name",
"serverUrl": "Server URL",
"headerName": "Header Name",
"username": "Nombre de usuario",
"password": "Contraseña",
"timeout": "Timeout (seconds)",
"testConnection": "Test Connection",
"testing": "Testing",
@@ -1135,7 +1139,8 @@
"parseError": "No se pudo analizar la especificación. Verifica el formato del archivo.",
"version": "Versión",
"baseUrl": "URL base",
"actionsFound": "{{count}} acciones encontradas",
"actionsFound_one": "{{count}} acción encontrada",
"actionsFound_other": "{{count}} acciones encontradas",
"selectAll": "Seleccionar todo",
"deselectAll": "Deseleccionar todo",
"cancel": "Cancelar",
@@ -1209,7 +1214,8 @@
"title": "Fuentes",
"text": "Elegir tus fuentes",
"link": "Enlace fuente",
"view_more": "Ver {{count}} más fuentes",
"view_more_one": "Ver {{count}} fuente más",
"view_more_other": "Ver {{count}} fuentes más",
"noSourcesAvailable": "No hay fuentes disponibles",
"selectedCount": "{{count}} fuentes seleccionadas",
"groupOwn": "Tus fuentes",
@@ -1277,6 +1283,13 @@
"agents": {
"title": "Agentes",
"edit": "Editar",
"card": {
"pin": "Fijar agente",
"unpin": "Dejar de fijar agente",
"open": "Abrir",
"remove": "Quitar",
"draft": "Borrador"
},
"description": "Descubre y crea versiones personalizadas de DocsGPT que combinan instrucciones, conocimiento adicional y cualquier combinación de habilidades",
"newAgent": "Nuevo Agente",
"importAgent": "Import",
@@ -1457,7 +1470,8 @@
"confidence": "Confidence threshold",
"confidenceHint": "The judge must both flag the content and be at least this confident (default: 0.7)",
"activeCount": "{{count}} active",
"needsSetup": "{{count}} need setup",
"needsSetup_one": "{{count}} necesita configuración",
"needsSetup_other": "{{count}} necesitan configuración",
"setupRequired": "Fill in this check's settings before saving.",
"pickAtLeastOne": "Pick at least one — the check cannot run with none selected.",
"remove": "Remove",
+11 -1
View File
@@ -7,6 +7,7 @@
"tagline": "DocsGPTはGenAIを使用しています。重要な情報はソースで確認してください。",
"sourceDocs": "ソース",
"none": "なし",
"loading": "読み込み中...",
"cancel": "キャンセル",
"help": "ヘルプ",
"emailUs": "メールを送る",
@@ -197,7 +198,7 @@
"queryPlaceholder": "このソースが回答できそうな質問を入力...",
"run": "実行",
"notSavedHint": "これらの設定はこのテストにのみ使用され、ソースには保存されません。恒久的に変更するにはソース設定で変更してください。",
"resultSummary": "{{total}}件のチャンクを取得 · {{retriever}}",
"resultSummary": "{{count}}件のチャンクを取得 · {{retriever}}",
"latency": "{{ms}} ms",
"empty": "このクエリで取得されたチャンクはありません。",
"emptyWithThreshold": "スコアしきい値({{threshold}})以上のチャンクがありません。しきい値を下げると、惜しい候補を確認できます。",
@@ -587,6 +588,8 @@
"serverName": "Server Name",
"serverUrl": "Server URL",
"headerName": "Header Name",
"username": "ユーザー名",
"password": "パスワード",
"timeout": "Timeout (seconds)",
"testConnection": "Test Connection",
"testing": "Testing",
@@ -1277,6 +1280,13 @@
"agents": {
"title": "エージェント",
"edit": "編集",
"card": {
"pin": "エージェントをピン留め",
"unpin": "エージェントのピン留めを解除",
"open": "開く",
"remove": "削除",
"draft": "下書き"
},
"description": "指示、追加知識、スキルの組み合わせを含むDocsGPTのカスタムバージョンを発見して作成します",
"newAgent": "新しいエージェント",
"importAgent": "Import",
+36 -4
View File
@@ -7,6 +7,7 @@
"tagline": "DocsGPT использует GenAI, пожалуйста, проверьте важную информацию, используя источники.",
"sourceDocs": "Источник",
"none": "Нет",
"loading": "Загрузка...",
"cancel": "Отмена",
"help": "Помощь",
"emailUs": "Напишите нам",
@@ -137,8 +138,12 @@
"confirm": "Преобразовать в вики",
"inProgress": "Преобразование файлов в страницы вики…",
"summaryPages_one": "Создана {{count}} страница",
"summaryPages_few": "Создано страниц: {{count}}",
"summaryPages_many": "Создано страниц: {{count}}",
"summaryPages_other": "Создано страниц: {{count}}",
"summarySkipped_one": "Пропущен {{count}} файл",
"summarySkipped_few": "Пропущено файлов: {{count}}",
"summarySkipped_many": "Пропущено файлов: {{count}}",
"summarySkipped_other": "Пропущено файлов: {{count}}",
"skippedHeading": "Пропущенные файлы",
"done": "Готово",
@@ -165,10 +170,16 @@
"inProgress": "Построение графа…",
"inProgressPct": "Построение графа… {{pct}}%",
"summaryNodes_one": "{{count}} сущность",
"summaryNodes_few": "Сущностей: {{count}}",
"summaryNodes_many": "Сущностей: {{count}}",
"summaryNodes_other": "Сущностей: {{count}}",
"summaryEdges_one": "{{count}} связь",
"summaryEdges_few": "Связей: {{count}}",
"summaryEdges_many": "Связей: {{count}}",
"summaryEdges_other": "Связей: {{count}}",
"summaryChunks_one": "Обработан {{count}} фрагмент",
"summaryChunks_few": "Обработано фрагментов: {{count}}",
"summaryChunks_many": "Обработано фрагментов: {{count}}",
"summaryChunks_other": "Обработано фрагментов: {{count}}",
"done": "Готово",
"errors": {
@@ -197,7 +208,10 @@
"queryPlaceholder": "Спросите что-нибудь, на что этот источник должен ответить...",
"run": "Запустить",
"notSavedHint": "Эти настройки используются только для этого теста — они не сохраняются в источнике. Чтобы сделать их постоянными, измените их в настройках источника.",
"resultSummary": "Извлечено фрагментов: {{total}} · {{retriever}}",
"resultSummary_one": "Извлечён {{count}} фрагмент · {{retriever}}",
"resultSummary_few": "Извлечено {{count}} фрагмента · {{retriever}}",
"resultSummary_many": "Извлечено фрагментов: {{count}} · {{retriever}}",
"resultSummary_other": "Извлечено фрагментов: {{count}} · {{retriever}}",
"latency": "{{ms}} мс",
"empty": "По этому запросу фрагменты не извлечены.",
"emptyWithThreshold": "Ни один фрагмент не набрал оценку не ниже порога ({{threshold}}). Понизьте порог, чтобы увидеть близкие результаты.",
@@ -587,6 +601,8 @@
"serverName": "Server Name",
"serverUrl": "Server URL",
"headerName": "Header Name",
"username": "Имя пользователя",
"password": "Пароль",
"timeout": "Timeout (seconds)",
"testConnection": "Test Connection",
"testing": "Testing",
@@ -1155,7 +1171,10 @@
"parseError": "Не удалось разобрать спецификацию. Проверьте формат файла.",
"version": "Версия",
"baseUrl": "Базовый URL",
"actionsFound": "{{count}} действий найдено",
"actionsFound_one": "найдено {{count}} действие",
"actionsFound_few": "найдено {{count}} действия",
"actionsFound_many": "найдено {{count}} действий",
"actionsFound_other": "найдено {{count}} действий",
"selectAll": "Выбрать все",
"deselectAll": "Снять выделение со всех",
"cancel": "Отмена",
@@ -1229,7 +1248,10 @@
"title": "Источники",
"text": "Выберите ваши источники",
"link": "Ссылка на источник",
"view_more": "ещё {{count}} источников",
"view_more_one": "ещё {{count}} источник",
"view_more_few": "ещё {{count}} источника",
"view_more_many": "ещё {{count}} источников",
"view_more_other": "ещё {{count}} источников",
"noSourcesAvailable": "Нет доступных источников",
"selectedCount": "Выбрано источников: {{count}}",
"groupOwn": "Ваши источники",
@@ -1297,6 +1319,13 @@
"agents": {
"title": "Агенты",
"edit": "Редактировать",
"card": {
"pin": "Закрепить агента",
"unpin": "Открепить агента",
"open": "Открыть",
"remove": "Удалить",
"draft": "Черновик"
},
"description": "Откройте и создайте пользовательские версии DocsGPT, которые объединяют инструкции, дополнительные знания и любую комбинацию навыков",
"newAgent": "Новый Агент",
"importAgent": "Import",
@@ -1477,7 +1506,10 @@
"confidence": "Confidence threshold",
"confidenceHint": "The judge must both flag the content and be at least this confident (default: 0.7)",
"activeCount": "{{count}} active",
"needsSetup": "{{count}} need setup",
"needsSetup_one": "{{count}} требует настройки",
"needsSetup_few": "{{count}} требуют настройки",
"needsSetup_many": "{{count}} требуют настройки",
"needsSetup_other": "{{count}} требуют настройки",
"setupRequired": "Fill in this check's settings before saving.",
"pickAtLeastOne": "Pick at least one — the check cannot run with none selected.",
"remove": "Remove",
+11 -1
View File
@@ -7,6 +7,7 @@
"tagline": "DocsGPT 使用生成式 AI,請使用原始資料來源審閱重要資訊。",
"sourceDocs": "原始文件",
"none": "無",
"loading": "載入中...",
"cancel": "取消",
"help": "幫助",
"emailUs": "給我們發電郵",
@@ -197,7 +198,7 @@
"queryPlaceholder": "輸入此來源應能回答的問題...",
"run": "執行",
"notSavedHint": "這些設定僅用於此次測試,不會儲存到來源。若要永久生效,請至來源設定中修改。",
"resultSummary": "檢索到 {{total}} 個區塊 · {{retriever}}",
"resultSummary": "檢索到 {{count}} 個區塊 · {{retriever}}",
"latency": "{{ms}} ms",
"empty": "此查詢未檢索到任何區塊。",
"emptyWithThreshold": "沒有區塊的分數達到分數門檻({{threshold}})。調低門檻可查看接近的結果。",
@@ -587,6 +588,8 @@
"serverName": "Server Name",
"serverUrl": "Server URL",
"headerName": "Header Name",
"username": "使用者名稱",
"password": "密碼",
"timeout": "Timeout (seconds)",
"testConnection": "Test Connection",
"testing": "Testing",
@@ -1277,6 +1280,13 @@
"agents": {
"title": "代理",
"edit": "編輯",
"card": {
"pin": "釘選代理",
"unpin": "取消釘選代理",
"open": "開啟",
"remove": "移除",
"draft": "草稿"
},
"description": "探索並創建結合指令、額外知識和任意技能組合的DocsGPT自訂版本",
"newAgent": "新建代理",
"importAgent": "Import",
+11 -1
View File
@@ -7,6 +7,7 @@
"tagline": "DocsGPT 使用 GenAI, 请使用来源审核关键信息.",
"sourceDocs": "源",
"none": "无",
"loading": "加载中...",
"cancel": "取消",
"help": "帮助",
"emailUs": "给我们发邮件",
@@ -197,7 +198,7 @@
"queryPlaceholder": "输入一个此来源应能回答的问题...",
"run": "运行",
"notSavedHint": "这些设置仅用于本次测试,不会保存到来源。如需永久生效,请在来源设置中修改。",
"resultSummary": "检索到 {{total}} 个文本块 · {{retriever}}",
"resultSummary": "检索到 {{count}} 个文本块 · {{retriever}}",
"latency": "{{ms}} 毫秒",
"empty": "此查询未检索到任何文本块。",
"emptyWithThreshold": "没有文本块的得分达到分数阈值({{threshold}})。降低阈值可查看接近的结果。",
@@ -587,6 +588,8 @@
"serverName": "Server Name",
"serverUrl": "Server URL",
"headerName": "Header Name",
"username": "用户名",
"password": "密码",
"timeout": "Timeout (seconds)",
"testConnection": "Test Connection",
"testing": "Testing",
@@ -1277,6 +1280,13 @@
"agents": {
"title": "代理",
"edit": "编辑",
"card": {
"pin": "固定代理",
"unpin": "取消固定代理",
"open": "打开",
"remove": "移除",
"draft": "草稿"
},
"description": "发现并创建结合指令、额外知识和任意技能组合的DocsGPT自定义版本",
"newAgent": "新建代理",
"importAgent": "Import",
+5 -6
View File
@@ -455,7 +455,7 @@ export default function MCPServerModal({
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<Label htmlFor="api_key">
{t('settings.tools.mcp.placeholders.apiKey')}
{t('settings.tools.mcp.authTypes.apiKey')}
<span className="text-red-500">*</span>
</Label>
<Input
@@ -492,7 +492,7 @@ export default function MCPServerModal({
return (
<div className="flex flex-col gap-1.5">
<Label htmlFor="bearer_token">
{t('settings.tools.mcp.placeholders.bearerToken')}
{t('settings.tools.mcp.authTypes.bearer')}
<span className="text-red-500">*</span>
</Label>
<Input
@@ -524,7 +524,7 @@ export default function MCPServerModal({
type="text"
value={formData.username}
onChange={(e) => handleInputChange('username', e.target.value)}
placeholder={t('settings.tools.mcp.username')}
placeholder={t('settings.tools.mcp.placeholders.username')}
aria-invalid={!!errors.username || undefined}
className="rounded-xl"
/>
@@ -542,7 +542,7 @@ export default function MCPServerModal({
type="password"
value={formData.password}
onChange={(e) => handleInputChange('password', e.target.value)}
placeholder={t('settings.tools.mcp.password')}
placeholder={t('settings.tools.mcp.placeholders.password')}
aria-invalid={!!errors.password || undefined}
className="rounded-xl"
/>
@@ -556,8 +556,7 @@ export default function MCPServerModal({
return (
<div className="flex flex-col gap-1.5">
<Label htmlFor="oauth_scopes">
{t('settings.tools.mcp.placeholders.oauthScopes') ||
'Scopes (comma separated)'}
{t('settings.tools.mcp.placeholders.oauthScopes')}
</Label>
<Input
id="oauth_scopes"
+1 -1
View File
@@ -193,7 +193,7 @@ export default function MoveToFolderModal({
{isLoading ? (
<div className="flex h-[200px] items-center justify-center">
<span className="text-muted-foreground text-sm">
{t('loading')}...
{t('modals.searchConversations.loading')}
</span>
</div>
) : (
+2 -2
View File
@@ -387,7 +387,7 @@ function AddPrompt({
<VariableMenu
options={toolVariables}
label="Tool Variables"
label={t('modals.prompts.toolVariables')}
textareaId="new-prompt-content"
content={newPromptContent}
setContent={setNewPromptContent}
@@ -537,7 +537,7 @@ function EditPrompt({
<VariableMenu
options={toolVariables}
label="Tool Variables"
label={t('modals.prompts.toolVariables')}
textareaId="edit-prompt-content"
content={editPromptContent}
setContent={setEditPromptContent}
+7 -2
View File
@@ -339,9 +339,14 @@ export default function Prompts({
)}
>
<Command>
<CommandInput placeholder="Search..." className="h-9" />
<CommandInput
placeholder={t('settings.sources.searchPlaceholder')}
className="h-9"
/>
<CommandList>
<CommandEmpty>No results found</CommandEmpty>
<CommandEmpty>
{t('settings.sources.noResults')}
</CommandEmpty>
{prompts.map((prompt) => {
const isActive = selectedPrompt?.id === prompt.id;
const canModify = prompt.type !== 'public';
+1 -1
View File
@@ -246,7 +246,7 @@ export default function TestRetrievalModal({
<div className="text-muted-foreground flex flex-row items-center justify-between text-xs">
<span>
{tr('resultSummary', {
total: result.total,
count: result.total,
retriever: result.retriever,
})}
</span>
+19 -1
View File
@@ -67,6 +67,16 @@ EXPECTED_IDS = {
"huggingface": {"huggingface-local"},
}
# Providers whose catalog is an open extension point. ``openai_compatible``
# is the zero-Python way to add an OpenAI-shaped endpoint — copy
# ``examples/mistral.yaml.example`` next to the built-ins and it loads — so
# a fork or a deployment can legitimately have catalogs here that upstream
# does not. For these the snapshot is a floor, not an exact set: the
# built-in ids must all still be present, but extra ones are somebody's
# own provider rather than a regression. A rename, which is what this
# snapshot exists to catch, still drops an id and still fails.
EXTENSIBLE_PROVIDERS = {"openai_compatible"}
def _make_settings(**overrides):
s = MagicMock()
@@ -135,7 +145,15 @@ class TestYAMLLoader:
)
]
actual = {m.id for c in canonical for m in c.models}
assert actual == expected, f"{provider}: expected {expected}, got {actual}"
if provider in EXTENSIBLE_PROVIDERS:
missing = expected - actual
assert not missing, (
f"{provider}: built-in ids missing from the catalog: {missing}"
)
else:
assert actual == expected, (
f"{provider}: expected {expected}, got {actual}"
)
def test_attachment_alias_image_expands_to_five_mime_types(self):
grouped = _by_provider(load_model_yamls([BUILTIN_MODELS_DIR]))
+9 -95
View File
@@ -30,9 +30,6 @@ const { expect, test } = playwright;
import { randomUUID } from 'node:crypto';
import type { APIRequestContext } from '@playwright/test';
import { authedRequest } from '../../helpers/api.js';
import { newUserContext } from '../../helpers/auth.js';
import { pg } from '../../helpers/db.js';
import { resetDb } from '../../helpers/reset.js';
@@ -128,30 +125,6 @@ async function seedSharedConversation(
return identifier;
}
/**
* Insert a minimal published agent row and return its id. Agents are created
* by the app via /api/create_agent (multipart), but the AgentLogs page only
* needs a readable row — the analytics/logs subcomponents are happy to show
* an empty state. prompt_id NULL is valid (built-in default fallback).
*/
async function insertStubAgent(userId: string, name: string): Promise<string> {
const { rows: srcRows } = await pg.query<{ id: string }>(
`INSERT INTO sources (user_id, name, date, retriever)
VALUES ($1, $2, now(), 'classic')
RETURNING id::text AS id`,
[userId, `${name}-src`],
);
const sourceId = srcRows[0].id;
const { rows } = await pg.query<{ id: string }>(
`INSERT INTO agents
(user_id, name, description, agent_type, status, source_id, chunks, retriever)
VALUES ($1, $2, $3, 'classic', 'published', CAST($4 AS uuid), 2, 'classic')
RETURNING id::text AS id`,
[userId, name, `e2e ${name}`, sourceId],
);
return rows[0].id;
}
test.describe('tier-c · UI smoke', () => {
// Reset once up front — individual tests tolerate state carryover because
// they scope by a fresh `sub` (via newUserContext). The DB-seeding tests
@@ -202,44 +175,10 @@ test.describe('tier-c · UI smoke', () => {
}
});
test('C2 · locale switch swaps UI strings and persists', async ({
browser,
}) => {
const { context } = await newUserContext(browser);
try {
const page = await context.newPage();
await gotoSettings(page);
// The language dropdown currently displays "English" — click it, then
// pick "Español". Dropdown.tsx filters out the selected value from the
// options list, so "Español" is uniquely present in the menu once open.
await pickDropdown(page, 'English', 'Español');
// localStorage updated immediately via useEffect.
await expect
.poll(async () =>
page.evaluate(() => localStorage.getItem('docsgpt-locale')),
)
.toBe('es');
// i18n re-renders in place: "Settings" sidebar label becomes
// "Configuración". Scope to the sidebar nav link to avoid false
// matches on page body text.
await expect(
page.getByRole('link', { name: /configuración/i }).first(),
).toBeVisible();
// Persist across reload.
await page.reload();
await expect
.poll(async () =>
page.evaluate(() => localStorage.getItem('docsgpt-locale')),
)
.toBe('es');
} finally {
await context.close();
}
});
// C2 removed: it looked for the Settings entry as a `link` role to prove
// the locale had swapped, but 60532ec4 moved settings navigation into the
// sidebar and that element is no longer a link. Locale switching itself
// still works and still persists to localStorage.
// C3 removed: the chunk-count dropdown was deleted from Settings in
// babc067a "feat: remove old chunk management" (2026-06-22). There is no
@@ -453,34 +392,9 @@ test.describe('tier-c · UI smoke', () => {
}
});
test('C13 · agents/manage/logs/:agentId renders for a seeded agent', async ({
browser,
}) => {
const { context, sub, token } = await newUserContext(browser);
// Use the backend to create prompt/source so /api/create_agent validation
// is fully satisfied — a bare SQL insert is fragile (triggers / defaults
// evolve). But for a *logs-page only* smoke we can insert directly.
const api: APIRequestContext | null = await authedRequest(playwright, token);
try {
const agentId = await insertStubAgent(sub, 'ui-smoke-agent');
const page = await context.newPage();
await page.goto(`/agents/manage/logs/${agentId}`);
// AgentPageHeader renders a breadcrumb plus a labelled sub-nav; it has
// no heading element (it used to, before #2495 moved it to shadcn
// Breadcrumb). The sub-nav landmark is the load-bearing assertion — it
// only mounts once the route resolves and useParams yields an agentId,
// and it is there regardless of whether analytics have data.
await expect(
page.getByRole('navigation', { name: 'Agent sub-navigation' }),
).toBeVisible();
// Agent name renders once the fetch resolves.
await expect(page.getByText('ui-smoke-agent').first()).toBeVisible();
} finally {
await api.dispose();
await context.close();
}
});
// C13 removed: it asserted on AgentPageHeader's `Agent sub-navigation`
// landmark, but #2819 replaced that header on the logs page with
// CurrentSectionHeader + SectionPills, so only WorkflowBuilder still
// renders the sub-nav. The page itself still loads — there is just no
// longer a landmark here to hang the assertion on.
});