mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-11 03:12:55 +00:00
Ask an item's owner without naming strangers, and keep the canvas notice reachable
When the read doesn't name whom to ask, the notice asks the item's owner without naming them. Items the reader may not see show their kind and a short id instead of "Unnamed item". The workflow canvas notice fits narrow screens, and closing it leaves a chip with the number of items not running that opens it again.
This commit is contained in:
1 parent
825cf5d866
commit
ec19dae22c
14 files changed
+349
-55
No files matched your search
@@ -101,6 +101,7 @@ import AgentPreviewSheet from './components/AgentPreviewSheet';
|
||||
import SectionShell from '../navigation/SectionShell';
|
||||
import ResourceStatusNotice, {
|
||||
type NamedResource,
|
||||
unnamedResourceLabel,
|
||||
} from './components/ResourceStatusNotice';
|
||||
import {
|
||||
confirmTakeOver,
|
||||
@@ -332,13 +333,11 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
return (
|
||||
prompts.find((prompt) => prompt.id === sponsor.id)?.name ||
|
||||
agent.prompt_name ||
|
||||
t('agents.form.sponsors.unknownItem')
|
||||
unnamedResourceLabel(t, sponsor)
|
||||
);
|
||||
}
|
||||
const tool = selectedTools.find((item) => item.id === sponsor.id);
|
||||
return tool
|
||||
? getToolDisplayName(tool)
|
||||
: t('agents.form.sponsors.unknownItem');
|
||||
return tool ? getToolDisplayName(tool) : unnamedResourceLabel(t, sponsor);
|
||||
},
|
||||
[agent.prompt_name, prompts, resolveSourceLabel, selectedTools, t],
|
||||
);
|
||||
|
||||
@@ -5,7 +5,10 @@ import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import type { Agent, ResourceSponsor, ResourceState } from '../types';
|
||||
import ResourceStatusNotice from './ResourceStatusNotice';
|
||||
import ResourceStatusNotice, {
|
||||
noteKey,
|
||||
unnamedResourceLabel,
|
||||
} from './ResourceStatusNotice';
|
||||
|
||||
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
|
||||
|
||||
@@ -230,6 +233,60 @@ describe('ResourceStatusNotice', () => {
|
||||
expect(deleted.textContent).not.toContain('agents.form.resourceStates.ask');
|
||||
});
|
||||
|
||||
it("asks the item's owner without naming them when the reader may not know them", async () => {
|
||||
await render(editorAgent, [
|
||||
stoppedItem({
|
||||
reason: 'owner_lost_access',
|
||||
contact: null,
|
||||
contact_role: 'resource_owner',
|
||||
}),
|
||||
stoppedItem({
|
||||
key: 'tool:t2',
|
||||
id: 't2',
|
||||
reason: 'connection_needs_reconnect',
|
||||
contact: null,
|
||||
contact_role: 'resource_owner',
|
||||
}),
|
||||
stoppedItem({
|
||||
key: 'tool:t3',
|
||||
id: 't3',
|
||||
reason: 'connection_removed',
|
||||
contact: null,
|
||||
contact_role: 'resource_owner',
|
||||
}),
|
||||
]);
|
||||
const [lost, reconnect, removed] = Array.from(
|
||||
container.querySelectorAll('li'),
|
||||
);
|
||||
expect(lost.textContent).toContain(
|
||||
'agents.form.resourceStates.ask.shareAgainOwner',
|
||||
);
|
||||
expect(reconnect.textContent).toContain(
|
||||
'agents.form.resourceStates.ask.signInAgainOwner',
|
||||
);
|
||||
expect(removed.textContent).toContain(
|
||||
'agents.form.resourceStates.ask.connectAgainOwner',
|
||||
);
|
||||
});
|
||||
|
||||
it('names a nameless item by its kind and a short id', () => {
|
||||
const t = ((key: string, options?: Record<string, string>) =>
|
||||
`${key}|${options?.id}`) as never;
|
||||
expect(
|
||||
unnamedResourceLabel(t, {
|
||||
type: 'source',
|
||||
id: '0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0',
|
||||
}),
|
||||
).toBe('agents.form.resourceStates.unnamed.source|0f1e2d3c');
|
||||
});
|
||||
|
||||
it("has words for a tool on each person's own account", () => {
|
||||
expect(noteKey('per_user_account')).toBe(
|
||||
'agents.form.resourceStates.note.perUserAccount',
|
||||
);
|
||||
expect(noteKey(null)).toBeNull();
|
||||
});
|
||||
|
||||
it('notes that a stopped prompt falls back to the default', async () => {
|
||||
await render(baseAgent, [
|
||||
stoppedItem({ key: 'prompt:p1', type: 'prompt', id: 'p1' }),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { TFunction } from 'i18next';
|
||||
import { Info, TriangleAlert, UserRound } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -10,6 +11,7 @@ import type {
|
||||
Agent,
|
||||
ResourceSponsor,
|
||||
ResourceState,
|
||||
ResourceStateNote,
|
||||
ResourceStateReason,
|
||||
} from '../types';
|
||||
|
||||
@@ -64,17 +66,43 @@ export function reasonKey(
|
||||
}
|
||||
}
|
||||
|
||||
/** Whom to ask, as the key that says so; null when the reader can act. */
|
||||
/** The words for a running item's `note`, or null when it has none. */
|
||||
export function noteKey(note: ResourceStateNote | null | undefined) {
|
||||
return note === 'per_user_account'
|
||||
? 'agents.form.resourceStates.note.perUserAccount'
|
||||
: null;
|
||||
}
|
||||
|
||||
/** A name for an item the reader may not see: its kind and a short id. */
|
||||
export function unnamedResourceLabel(
|
||||
t: TFunction,
|
||||
item: Pick<NamedResource, 'type' | 'id'>,
|
||||
): string {
|
||||
return t(`agents.form.resourceStates.unnamed.${item.type}`, {
|
||||
id: item.id.slice(0, 8),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whom to ask, as the key that says so; null when the reader can act. The
|
||||
* person is named when the read names them (`contact`), else only as the
|
||||
* item's owner.
|
||||
*/
|
||||
function askKey(item: ResourceState): string | null {
|
||||
if (item.reason === 'connector_disabled')
|
||||
return 'agents.form.resourceStates.ask.admin';
|
||||
if (!item.contact) return null;
|
||||
const suffix = item.contact
|
||||
? ''
|
||||
: item.contact_role === 'resource_owner'
|
||||
? 'Owner'
|
||||
: null;
|
||||
if (suffix === null) return null;
|
||||
if (item.reason === 'owner_lost_access')
|
||||
return 'agents.form.resourceStates.ask.shareAgain';
|
||||
return `agents.form.resourceStates.ask.shareAgain${suffix}`;
|
||||
if (item.reason === 'connection_needs_reconnect')
|
||||
return 'agents.form.resourceStates.ask.signInAgain';
|
||||
return `agents.form.resourceStates.ask.signInAgain${suffix}`;
|
||||
if (item.reason === 'connection_removed')
|
||||
return 'agents.form.resourceStates.ask.connectAgain';
|
||||
return `agents.form.resourceStates.ask.connectAgain${suffix}`;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ export type ResourceStateReason =
|
||||
| 'connection_removed'
|
||||
| 'connector_disabled';
|
||||
|
||||
/** Something to know about an item that runs (`ResourceState.note`). */
|
||||
export type ResourceStateNote = 'per_user_account';
|
||||
|
||||
/** A person the page names: their email when on file, else their user id. */
|
||||
export type ResourcePerson = { user_id: string; label: string };
|
||||
|
||||
@@ -57,11 +60,19 @@ export type ResourceState = {
|
||||
state: 'active' | 'stopped';
|
||||
/** Null while it runs. */
|
||||
reason: ResourceStateReason | null;
|
||||
/** `per_user_account`: it runs on each person's own account. */
|
||||
note?: ResourceStateNote | null;
|
||||
/** Who it ran with the access of, when someone else added it. */
|
||||
sponsor?: ResourcePerson | null;
|
||||
/** Someone other than the reader who can fix it. */
|
||||
/** Someone other than the reader who can fix it, when the reader may know them. */
|
||||
contact?: ResourcePerson | null;
|
||||
/** The service of a connected tool, or of one a connection reason stopped. */
|
||||
/** Who can fix it (`resource_owner`), named or not. */
|
||||
contact_role?: 'resource_owner' | null;
|
||||
/**
|
||||
* The service of a connected tool, or of one a connection reason stopped.
|
||||
* `id` only when the reader may reconnect
|
||||
* it; the account's own name only for its owner.
|
||||
*/
|
||||
connection?: {
|
||||
id: string | null;
|
||||
connector_key: string | null;
|
||||
|
||||
@@ -58,7 +58,9 @@ import {
|
||||
import { useSponsorPrompt } from '../useSponsorPrompt';
|
||||
import ResourceStatusNotice, {
|
||||
type NamedResource,
|
||||
unnamedResourceLabel,
|
||||
} from '../components/ResourceStatusNotice';
|
||||
import FloatingResourceNotice from './components/FloatingResourceNotice';
|
||||
import { useSignInAgain } from '../../connectors/SignInAgainNotice';
|
||||
import WorkflowDetailsSheet, {
|
||||
type WorkflowDetailsSave,
|
||||
@@ -255,7 +257,6 @@ function WorkflowBuilderInner() {
|
||||
// Keys of stopped node resources the caller agreed to run with their
|
||||
// access; sent as ``confirm_sponsor`` with the next save.
|
||||
const [takeovers, setTakeovers] = useState<string[]>([]);
|
||||
const [resourceNoticeOpen, setResourceNoticeOpen] = useState(true);
|
||||
// Bumped after a reconnect so the run state is read again.
|
||||
const [resourcesReloadKey, setResourcesReloadKey] = useState(0);
|
||||
const signInAgain = useSignInAgain({
|
||||
@@ -1694,7 +1695,7 @@ function WorkflowBuilderInner() {
|
||||
const known = (
|
||||
item.type === 'tool' ? nodeRefNames.tools : nodeRefNames.sources
|
||||
).find((entry) => entry.id.toLowerCase() === item.id)?.label;
|
||||
return known || t('agents.form.sponsors.unknownItem');
|
||||
return known || unnamedResourceLabel(t, item);
|
||||
},
|
||||
[nodeRefNames, t],
|
||||
);
|
||||
@@ -1751,7 +1752,6 @@ function WorkflowBuilderInner() {
|
||||
);
|
||||
const showResourceNotice =
|
||||
canManageAgent &&
|
||||
resourceNoticeOpen &&
|
||||
(stoppedResources.length > 0 ||
|
||||
workflowResources.sponsors.some((sponsor) => sponsor.active));
|
||||
|
||||
@@ -2009,32 +2009,21 @@ function WorkflowBuilderInner() {
|
||||
className="bg-muted relative min-w-0 flex-1"
|
||||
>
|
||||
{showResourceNotice && (
|
||||
<div className="bg-card absolute top-4 left-4 z-20 max-h-[60%] w-full max-w-md overflow-y-auto rounded-xl shadow-md">
|
||||
<div className="relative p-3 pr-10">
|
||||
<ResourceStatusNotice
|
||||
agent={resourceNoticeAgent}
|
||||
stopped={stoppedResources}
|
||||
resolveName={resolveResourceName}
|
||||
takeovers={takeovers}
|
||||
showAttachNote={false}
|
||||
onTakeOver={(item) => void takeOverResource(item)}
|
||||
onUndoTakeover={(key) =>
|
||||
setTakeovers((prev) => prev.filter((k) => k !== key))
|
||||
}
|
||||
onRemove={removeResource}
|
||||
onReconnect={reconnectResource}
|
||||
/>
|
||||
<div className="absolute top-2.5 right-2.5">
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() => setResourceNoticeOpen(false)}
|
||||
label={t('agents.close')}
|
||||
icon={X}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FloatingResourceNotice stoppedCount={stoppedResources.length}>
|
||||
<ResourceStatusNotice
|
||||
agent={resourceNoticeAgent}
|
||||
stopped={stoppedResources}
|
||||
resolveName={resolveResourceName}
|
||||
takeovers={takeovers}
|
||||
showAttachNote={false}
|
||||
onTakeOver={(item) => void takeOverResource(item)}
|
||||
onUndoTakeover={(key) =>
|
||||
setTakeovers((prev) => prev.filter((k) => k !== key))
|
||||
}
|
||||
onRemove={removeResource}
|
||||
onReconnect={reconnectResource}
|
||||
/>
|
||||
</FloatingResourceNotice>
|
||||
)}
|
||||
<WorkflowModelsContext.Provider value={modelNames}>
|
||||
<ReactFlow
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { act } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
|
||||
import FloatingResourceNotice from './FloatingResourceNotice';
|
||||
|
||||
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: { count?: number }) =>
|
||||
options?.count !== undefined ? `${key}:${options.count}` : key,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('FloatingResourceNotice', () => {
|
||||
let container: HTMLDivElement;
|
||||
let root: Root;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await act(async () => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
const render = async (stoppedCount: number) =>
|
||||
act(async () => {
|
||||
root.render(
|
||||
<FloatingResourceNotice stoppedCount={stoppedCount}>
|
||||
<p data-testid="notice">notice</p>
|
||||
</FloatingResourceNotice>,
|
||||
);
|
||||
});
|
||||
|
||||
const byLabel = (label: string) =>
|
||||
container.querySelector<HTMLButtonElement>(`[aria-label="${label}"]`);
|
||||
|
||||
it('fits the canvas on a narrow screen', async () => {
|
||||
await render(1);
|
||||
const panel = container.querySelector('[data-testid="notice"]')!
|
||||
.parentElement!.parentElement!;
|
||||
expect(panel.className).not.toContain('w-full');
|
||||
expect(panel.className).toContain('max-w-[calc(100%-2rem)]');
|
||||
});
|
||||
|
||||
it('closes to a chip that opens it again', async () => {
|
||||
await render(2);
|
||||
await act(async () => byLabel('agents.close')!.click());
|
||||
expect(container.querySelector('[data-testid="notice"]')).toBeNull();
|
||||
const chip = Array.from(container.querySelectorAll('button')).find((b) =>
|
||||
b.textContent?.includes('agents.form.resourceStates.chip:2'),
|
||||
)!;
|
||||
await act(async () => chip.click());
|
||||
expect(container.querySelector('[data-testid="notice"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('leaves no chip once nothing is stopped', async () => {
|
||||
await render(0);
|
||||
await act(async () => byLabel('agents.close')!.click());
|
||||
expect(container.innerHTML).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { TriangleAlert, X } from 'lucide-react';
|
||||
import { useState, type ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { IconButton } from '@/components/ui/icon-button';
|
||||
|
||||
type FloatingResourceNoticeProps = {
|
||||
/** Stopped node resources; a closed notice leaves a chip while any remain. */
|
||||
stoppedCount: number;
|
||||
/** The notice itself. */
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* The resource notice floating over the workflow canvas. Closing it leaves
|
||||
* a chip that says how many items aren't running and opens it again.
|
||||
*/
|
||||
export default function FloatingResourceNotice({
|
||||
stoppedCount,
|
||||
children,
|
||||
}: FloatingResourceNoticeProps) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
if (!open) {
|
||||
if (stoppedCount === 0) return null;
|
||||
return (
|
||||
<div className="bg-card absolute top-4 left-4 z-20 rounded-full shadow-md">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
shape="pill"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<TriangleAlert className="text-warning" />
|
||||
{t('agents.form.resourceStates.chip', { count: stoppedCount })}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-card absolute top-4 left-4 z-20 max-h-[60%] w-md max-w-[calc(100%-2rem)] overflow-y-auto rounded-xl shadow-md">
|
||||
<div className="relative p-3 pr-10">
|
||||
{children}
|
||||
<div className="absolute top-2.5 right-2.5">
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() => setOpen(false)}
|
||||
label={t('agents.close')}
|
||||
icon={X}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2424,13 +2424,25 @@
|
||||
"shareAgain": "Bitte {{person}}, es erneut zu teilen.",
|
||||
"signInAgain": "Bitte {{person}}, sich erneut anzumelden.",
|
||||
"connectAgain": "Bitte {{person}}, das Konto erneut zu verbinden.",
|
||||
"admin": "Bitte einen Admin, es wieder zu aktivieren."
|
||||
"admin": "Bitte einen Admin, es wieder zu aktivieren.",
|
||||
"shareAgainOwner": "Bitte den Eigentümer, es erneut zu teilen.",
|
||||
"signInAgainOwner": "Bitte den Eigentümer, sich erneut anzumelden.",
|
||||
"connectAgainOwner": "Bitte den Eigentümer, das Konto erneut zu verbinden."
|
||||
},
|
||||
"promptFallback": "Bis dahin verwendet der Agent den Standard-Prompt.",
|
||||
"serviceFallback": "Dienst",
|
||||
"remove": "Entfernen",
|
||||
"removeLabel": "{{name}} entfernen",
|
||||
"reconnectLabel": "{{name}} neu verbinden"
|
||||
"reconnectLabel": "{{name}} neu verbinden",
|
||||
"unnamed": {
|
||||
"tool": "Tool {{id}}",
|
||||
"source": "Quelle {{id}}",
|
||||
"prompt": "Prompt {{id}}"
|
||||
},
|
||||
"note": {
|
||||
"perUserAccount": "Jede Person verwendet ihr eigenes Konto"
|
||||
},
|
||||
"chip": "Läuft nicht: {{count}}"
|
||||
},
|
||||
"sponsorConfirm": {
|
||||
"title": "Mit deinem Zugriff ausführen?",
|
||||
|
||||
@@ -2442,13 +2442,25 @@
|
||||
"shareAgain": "Ask {{person}} to share it again.",
|
||||
"signInAgain": "Ask {{person}} to sign in again.",
|
||||
"connectAgain": "Ask {{person}} to connect the account again.",
|
||||
"admin": "Ask an admin to turn it back on."
|
||||
"admin": "Ask an admin to turn it back on.",
|
||||
"shareAgainOwner": "Ask its owner to share it again.",
|
||||
"signInAgainOwner": "Ask its owner to sign in again.",
|
||||
"connectAgainOwner": "Ask its owner to connect the account again."
|
||||
},
|
||||
"promptFallback": "The agent uses the default prompt until then.",
|
||||
"serviceFallback": "the service",
|
||||
"remove": "Remove",
|
||||
"removeLabel": "Remove {{name}}",
|
||||
"reconnectLabel": "Reconnect {{name}}"
|
||||
"reconnectLabel": "Reconnect {{name}}",
|
||||
"unnamed": {
|
||||
"tool": "Tool {{id}}",
|
||||
"source": "Source {{id}}",
|
||||
"prompt": "Prompt {{id}}"
|
||||
},
|
||||
"note": {
|
||||
"perUserAccount": "Each person uses their own account"
|
||||
},
|
||||
"chip": "Not running: {{count}}"
|
||||
},
|
||||
"sponsorConfirm": {
|
||||
"title": "Run with your access?",
|
||||
|
||||
@@ -2424,13 +2424,25 @@
|
||||
"shareAgain": "Pide a {{person}} que lo vuelva a compartir.",
|
||||
"signInAgain": "Pide a {{person}} que vuelva a iniciar sesión.",
|
||||
"connectAgain": "Pide a {{person}} que vuelva a conectar la cuenta.",
|
||||
"admin": "Pide a un administrador que lo vuelva a activar."
|
||||
"admin": "Pide a un administrador que lo vuelva a activar.",
|
||||
"shareAgainOwner": "Pide a su propietario que lo vuelva a compartir.",
|
||||
"signInAgainOwner": "Pide a su propietario que vuelva a iniciar sesión.",
|
||||
"connectAgainOwner": "Pide a su propietario que vuelva a conectar la cuenta."
|
||||
},
|
||||
"promptFallback": "Mientras tanto, el agente usa el prompt predeterminado.",
|
||||
"serviceFallback": "el servicio",
|
||||
"remove": "Quitar",
|
||||
"removeLabel": "Quitar {{name}}",
|
||||
"reconnectLabel": "Reconectar {{name}}"
|
||||
"reconnectLabel": "Reconectar {{name}}",
|
||||
"unnamed": {
|
||||
"tool": "Herramienta {{id}}",
|
||||
"source": "Fuente {{id}}",
|
||||
"prompt": "Prompt {{id}}"
|
||||
},
|
||||
"note": {
|
||||
"perUserAccount": "Cada persona usa su propia cuenta"
|
||||
},
|
||||
"chip": "Sin ejecutar: {{count}}"
|
||||
},
|
||||
"sponsorConfirm": {
|
||||
"title": "¿Ejecutar con tu acceso?",
|
||||
|
||||
@@ -2409,13 +2409,25 @@
|
||||
"shareAgain": "{{person}} に再度共有するよう依頼してください。",
|
||||
"signInAgain": "{{person}} に再度サインインするよう依頼してください。",
|
||||
"connectAgain": "{{person}} にアカウントを再接続するよう依頼してください。",
|
||||
"admin": "管理者にオンに戻すよう依頼してください。"
|
||||
"admin": "管理者にオンに戻すよう依頼してください。",
|
||||
"shareAgainOwner": "所有者に再度共有するよう依頼してください。",
|
||||
"signInAgainOwner": "所有者に再度サインインするよう依頼してください。",
|
||||
"connectAgainOwner": "所有者にアカウントを再接続するよう依頼してください。"
|
||||
},
|
||||
"promptFallback": "それまでエージェントはデフォルトのプロンプトを使用します。",
|
||||
"serviceFallback": "サービス",
|
||||
"remove": "削除",
|
||||
"removeLabel": "{{name}} を削除",
|
||||
"reconnectLabel": "{{name}} を再接続"
|
||||
"reconnectLabel": "{{name}} を再接続",
|
||||
"unnamed": {
|
||||
"tool": "ツール {{id}}",
|
||||
"source": "ソース {{id}}",
|
||||
"prompt": "プロンプト {{id}}"
|
||||
},
|
||||
"note": {
|
||||
"perUserAccount": "各自が自分のアカウントを使用します"
|
||||
},
|
||||
"chip": "実行されていません: {{count}}"
|
||||
},
|
||||
"sponsorConfirm": {
|
||||
"title": "あなたのアクセス権で実行しますか?",
|
||||
|
||||
@@ -2516,13 +2516,25 @@
|
||||
"shareAgain": "Попросите {{person}} снова поделиться им.",
|
||||
"signInAgain": "Попросите {{person}} снова войти в аккаунт.",
|
||||
"connectAgain": "Попросите {{person}} снова подключить аккаунт.",
|
||||
"admin": "Попросите администратора снова включить его."
|
||||
"admin": "Попросите администратора снова включить его.",
|
||||
"shareAgainOwner": "Попросите владельца снова поделиться им.",
|
||||
"signInAgainOwner": "Попросите владельца снова войти в аккаунт.",
|
||||
"connectAgainOwner": "Попросите владельца снова подключить аккаунт."
|
||||
},
|
||||
"promptFallback": "До тех пор агент использует промпт по умолчанию.",
|
||||
"serviceFallback": "сервис",
|
||||
"remove": "Убрать",
|
||||
"removeLabel": "Убрать {{name}}",
|
||||
"reconnectLabel": "Переподключить {{name}}"
|
||||
"reconnectLabel": "Переподключить {{name}}",
|
||||
"unnamed": {
|
||||
"tool": "Инструмент {{id}}",
|
||||
"source": "Источник {{id}}",
|
||||
"prompt": "Промпт {{id}}"
|
||||
},
|
||||
"note": {
|
||||
"perUserAccount": "Каждый использует свой аккаунт"
|
||||
},
|
||||
"chip": "Не работает: {{count}}"
|
||||
},
|
||||
"sponsorConfirm": {
|
||||
"title": "Запускать с вашим доступом?",
|
||||
|
||||
@@ -2409,13 +2409,25 @@
|
||||
"shareAgain": "請 {{person}} 重新分享。",
|
||||
"signInAgain": "請 {{person}} 重新登入。",
|
||||
"connectAgain": "請 {{person}} 重新連接該帳戶。",
|
||||
"admin": "請管理員重新開啟。"
|
||||
"admin": "請管理員重新開啟。",
|
||||
"shareAgainOwner": "請其擁有者重新分享。",
|
||||
"signInAgainOwner": "請其擁有者重新登入。",
|
||||
"connectAgainOwner": "請其擁有者重新連接該帳戶。"
|
||||
},
|
||||
"promptFallback": "在此之前,代理會使用預設提示詞。",
|
||||
"serviceFallback": "該服務",
|
||||
"remove": "移除",
|
||||
"removeLabel": "移除 {{name}}",
|
||||
"reconnectLabel": "重新連接 {{name}}"
|
||||
"reconnectLabel": "重新連接 {{name}}",
|
||||
"unnamed": {
|
||||
"tool": "工具 {{id}}",
|
||||
"source": "來源 {{id}}",
|
||||
"prompt": "提示詞 {{id}}"
|
||||
},
|
||||
"note": {
|
||||
"perUserAccount": "每個人使用自己的帳戶"
|
||||
},
|
||||
"chip": "未在執行:{{count}}"
|
||||
},
|
||||
"sponsorConfirm": {
|
||||
"title": "以你的存取權限執行?",
|
||||
|
||||
@@ -2409,13 +2409,25 @@
|
||||
"shareAgain": "请让 {{person}} 重新共享。",
|
||||
"signInAgain": "请让 {{person}} 重新登录。",
|
||||
"connectAgain": "请让 {{person}} 重新连接该账户。",
|
||||
"admin": "请让管理员重新开启。"
|
||||
"admin": "请让管理员重新开启。",
|
||||
"shareAgainOwner": "请让其所有者重新共享。",
|
||||
"signInAgainOwner": "请让其所有者重新登录。",
|
||||
"connectAgainOwner": "请让其所有者重新连接该账户。"
|
||||
},
|
||||
"promptFallback": "在此之前,智能体使用默认提示词。",
|
||||
"serviceFallback": "该服务",
|
||||
"remove": "移除",
|
||||
"removeLabel": "移除 {{name}}",
|
||||
"reconnectLabel": "重新连接 {{name}}"
|
||||
"reconnectLabel": "重新连接 {{name}}",
|
||||
"unnamed": {
|
||||
"tool": "工具 {{id}}",
|
||||
"source": "来源 {{id}}",
|
||||
"prompt": "提示词 {{id}}"
|
||||
},
|
||||
"note": {
|
||||
"perUserAccount": "每个人使用自己的账户"
|
||||
},
|
||||
"chip": "未在运行:{{count}}"
|
||||
},
|
||||
"sponsorConfirm": {
|
||||
"title": "以你的访问权限运行?",
|
||||
|
||||
Reference in new issue
Block a user