mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-11 14:13:09 +00:00
Fix a tool's parameters from the connector drawer
Under Customize, each action now opens its parameters. Each one is either left to the AI or set to Always use a value, which is sent on every call and never shown to the AI. Actions with fixed values carry a small count.
This commit is contained in:
1 parent
28b56e8b03
commit
744cfde565
13 files changed
+529
-51
No files matched your search
@@ -123,6 +123,8 @@ const endpoints = {
|
||||
`/api/connections/${encodeURIComponent(id)}/tools/${encodeURIComponent(toolId)}/permissions`,
|
||||
CONNECTION_REPOSITORIES: (id: string) =>
|
||||
`/api/connections/${encodeURIComponent(id)}/repositories`,
|
||||
CONNECTION_TOOL_PARAMETERS: (id: string, toolId: string) =>
|
||||
`/api/connections/${encodeURIComponent(id)}/tools/${encodeURIComponent(toolId)}/parameters`,
|
||||
CONNECTIONS_CLAIM: '/api/connections/claim',
|
||||
TOOL_CREDENTIAL_MODE: (toolId: string) =>
|
||||
`/api/connections/tools/${encodeURIComponent(toolId)}/credential-mode`,
|
||||
|
||||
@@ -111,6 +111,21 @@ const connectorsService = {
|
||||
token,
|
||||
),
|
||||
),
|
||||
/** Fix parameters to a value (`null` lets the AI decide again). */
|
||||
setToolParameters: async (
|
||||
id: string,
|
||||
toolId: string,
|
||||
action: string,
|
||||
parameters: Record<string, string | number | boolean | null>,
|
||||
token: string | null,
|
||||
) =>
|
||||
json(
|
||||
await apiClient.put(
|
||||
endpoints.USER.CONNECTION_TOOL_PARAMETERS(id, toolId),
|
||||
{ action, parameters },
|
||||
token,
|
||||
),
|
||||
),
|
||||
setCredentialMode: async (
|
||||
toolId: string,
|
||||
mode: 'owner' | 'member',
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Input } from '../components/ui/input';
|
||||
import { ToggleGroup, ToggleGroupItem } from '../components/ui/toggle-group';
|
||||
import type { ActionParameter } from './types';
|
||||
|
||||
type Mode = 'ai' | 'fixed';
|
||||
export type ParameterValue = string | number | boolean | null;
|
||||
|
||||
const shown = (parameter: ActionParameter) =>
|
||||
parameter.fixed && parameter.value !== null ? String(parameter.value) : '';
|
||||
|
||||
function ParameterRow({
|
||||
parameter,
|
||||
readOnly,
|
||||
onSave,
|
||||
}: {
|
||||
parameter: ActionParameter;
|
||||
readOnly: boolean;
|
||||
onSave: (value: ParameterValue) => Promise<boolean>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const saved = shown(parameter);
|
||||
const [mode, setMode] = useState<Mode>(parameter.fixed ? 'fixed' : 'ai');
|
||||
const [draft, setDraft] = useState(saved);
|
||||
const [saving, setSaving] = useState(false);
|
||||
// A save (here or elsewhere) brings the stored value back.
|
||||
useEffect(() => {
|
||||
setMode(parameter.fixed ? 'fixed' : 'ai');
|
||||
setDraft(shown(parameter));
|
||||
}, [parameter]);
|
||||
|
||||
const save = async (value: ParameterValue) => {
|
||||
setSaving(true);
|
||||
const ok = await onSave(value);
|
||||
setSaving(false);
|
||||
if (!ok) {
|
||||
setMode(parameter.fixed ? 'fixed' : 'ai');
|
||||
setDraft(saved);
|
||||
}
|
||||
};
|
||||
|
||||
const choiceLabel = t('settings.connectors.parameters.choiceLabel', {
|
||||
parameter: parameter.name,
|
||||
interpolation: { escapeValue: false },
|
||||
});
|
||||
|
||||
return (
|
||||
<li data-parameter={parameter.name} className="flex flex-col gap-2">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<p className="text-foreground font-mono text-xs wrap-anywhere">
|
||||
{parameter.name}
|
||||
</p>
|
||||
{parameter.description && (
|
||||
<p
|
||||
className="text-muted-foreground line-clamp-2 text-xs"
|
||||
title={parameter.description}
|
||||
>
|
||||
{parameter.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-muted rounded-full p-1">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
size="xs"
|
||||
value={mode}
|
||||
disabled={readOnly || saving}
|
||||
aria-label={choiceLabel}
|
||||
onValueChange={(value) => {
|
||||
if (!value) return;
|
||||
setMode(value as Mode);
|
||||
// Releasing a fixed value saves at once; fixing one waits for it.
|
||||
if (value === 'ai' && parameter.fixed) save(null);
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem value="ai" data-mode="ai">
|
||||
{t('settings.connectors.parameters.ai')}
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="fixed" data-mode="fixed">
|
||||
{t('settings.connectors.parameters.fixed')}
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
</div>
|
||||
{mode === 'fixed' && (
|
||||
<form
|
||||
className="flex items-center gap-2"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (draft.trim() && draft !== saved) save(draft);
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
aria-label={t('settings.connectors.parameters.valueLabel', {
|
||||
parameter: parameter.name,
|
||||
interpolation: { escapeValue: false },
|
||||
})}
|
||||
value={draft}
|
||||
disabled={readOnly}
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
shape="pill"
|
||||
variant="outline"
|
||||
loading={saving}
|
||||
disabled={readOnly || !draft.trim() || draft === saved}
|
||||
>
|
||||
{t('settings.connectors.parameters.save')}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* One action's parameters, each left to the AI or fixed to a value that is
|
||||
* sent on every call and never shown to the AI (a Telegram chat, a project
|
||||
* key). Letting the AI decide saves at once; a fixed value saves with Save.
|
||||
*/
|
||||
export default function ActionParameters({
|
||||
parameters,
|
||||
readOnly = false,
|
||||
onSave,
|
||||
}: {
|
||||
parameters: ActionParameter[];
|
||||
readOnly?: boolean;
|
||||
/** Saves `{name: value}`; resolves false when the save failed. */
|
||||
onSave: (changes: Record<string, ParameterValue>) => Promise<boolean>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<ul className="flex flex-col gap-4">
|
||||
{parameters.map((parameter) => (
|
||||
<ParameterRow
|
||||
key={parameter.name}
|
||||
parameter={parameter}
|
||||
readOnly={readOnly}
|
||||
onSave={(value) => onSave({ [parameter.name]: value })}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{t('settings.connectors.parameters.hint')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,12 +10,15 @@ vi.mock('react-i18next', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const connectors = vi.hoisted(() => ({ setToolPermissions: vi.fn() }));
|
||||
const connectors = vi.hoisted(() => ({
|
||||
setToolPermissions: vi.fn(),
|
||||
setToolParameters: vi.fn(),
|
||||
}));
|
||||
vi.mock('../api/services/connectorsService', () => ({ default: connectors }));
|
||||
|
||||
import actionToastReducer from '../notifications/actionToastSlice';
|
||||
import ToolPermissions from './ToolPermissions';
|
||||
import type { ConnectionTool } from './types';
|
||||
import type { ActionParameter, ConnectionTool } from './types';
|
||||
|
||||
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
|
||||
|
||||
@@ -23,7 +26,21 @@ const action = (
|
||||
name: string,
|
||||
access: 'read' | 'write',
|
||||
permission: 'always' | 'ask' | 'off',
|
||||
) => ({ name, access, permission, description: `Does ${name}.` });
|
||||
parameters: ActionParameter[] = [],
|
||||
) => ({ name, access, permission, description: `Does ${name}.`, parameters });
|
||||
|
||||
const param = (
|
||||
name: string,
|
||||
fixed: boolean,
|
||||
value: ActionParameter['value'] = null,
|
||||
): ActionParameter => ({
|
||||
name,
|
||||
description: `The ${name}.`,
|
||||
type: 'string',
|
||||
required: false,
|
||||
fixed,
|
||||
value,
|
||||
});
|
||||
|
||||
const tool = (actions: ReturnType<typeof action>[]): ConnectionTool =>
|
||||
({
|
||||
@@ -127,6 +144,105 @@ describe('ToolPermissions', () => {
|
||||
expect(container.textContent).not.toContain('Old one');
|
||||
});
|
||||
|
||||
const button = (text: string) =>
|
||||
Array.from(container.querySelectorAll('button')).find(
|
||||
(b) => b.textContent === text,
|
||||
)!;
|
||||
const typeInto = (input: HTMLInputElement, value: string) => {
|
||||
const setter = Object.getOwnPropertyDescriptor(
|
||||
HTMLInputElement.prototype,
|
||||
'value',
|
||||
)!.set!;
|
||||
setter.call(input, value);
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
};
|
||||
|
||||
it('marks an action that has fixed values', async () => {
|
||||
await render(
|
||||
tool([
|
||||
action('telegram_send_message', 'write', 'ask', [
|
||||
param('text', false),
|
||||
param('chat_id', true, '-100'),
|
||||
]),
|
||||
]),
|
||||
);
|
||||
expect(container.textContent).toContain(
|
||||
'settings.connectors.parameters.fixedCount:1',
|
||||
);
|
||||
});
|
||||
|
||||
it('fixes a parameter to a value the AI cannot change', async () => {
|
||||
const send = action('telegram_send_message', 'write', 'ask', [
|
||||
param('text', false),
|
||||
param('chat_id', false),
|
||||
]);
|
||||
connectors.setToolParameters.mockResolvedValue({
|
||||
success: true,
|
||||
tool: tool([
|
||||
{
|
||||
...send,
|
||||
parameters: [param('text', false), param('chat_id', true, '-100')],
|
||||
},
|
||||
]),
|
||||
});
|
||||
await render(tool([send]));
|
||||
await act(async () =>
|
||||
button('settings.connectors.parameters.show').click(),
|
||||
);
|
||||
const row = container.querySelector<HTMLElement>(
|
||||
'[data-parameter="chat_id"]',
|
||||
)!;
|
||||
await act(async () =>
|
||||
row.querySelector<HTMLButtonElement>('[data-mode="fixed"]')!.click(),
|
||||
);
|
||||
const input = row.querySelector<HTMLInputElement>('input')!;
|
||||
await act(async () => typeInto(input, '-100'));
|
||||
await act(async () =>
|
||||
button('settings.connectors.parameters.save').click(),
|
||||
);
|
||||
expect(connectors.setToolParameters).toHaveBeenCalledWith(
|
||||
'conn-1',
|
||||
'tool-1',
|
||||
'telegram_send_message',
|
||||
{ chat_id: '-100' },
|
||||
null,
|
||||
);
|
||||
expect(container.textContent).toContain(
|
||||
'settings.connectors.parameters.fixedCount:1',
|
||||
);
|
||||
});
|
||||
|
||||
it('lets the AI decide a fixed parameter again', async () => {
|
||||
connectors.setToolParameters.mockResolvedValue({
|
||||
success: true,
|
||||
tool: tool([]),
|
||||
});
|
||||
await render(
|
||||
tool([
|
||||
action('telegram_send_message', 'write', 'ask', [
|
||||
param('chat_id', true, '-100'),
|
||||
]),
|
||||
]),
|
||||
);
|
||||
await act(async () =>
|
||||
button('settings.connectors.parameters.show').click(),
|
||||
);
|
||||
const row = container.querySelector<HTMLElement>(
|
||||
'[data-parameter="chat_id"]',
|
||||
)!;
|
||||
expect(row.querySelector<HTMLInputElement>('input')!.value).toBe('-100');
|
||||
await act(async () =>
|
||||
row.querySelector<HTMLButtonElement>('[data-mode="ai"]')!.click(),
|
||||
);
|
||||
expect(connectors.setToolParameters).toHaveBeenCalledWith(
|
||||
'conn-1',
|
||||
'tool-1',
|
||||
'telegram_send_message',
|
||||
{ chat_id: null },
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
it('says what Ask first means outside chat', async () => {
|
||||
await render(tool([action('create_issue', 'write', 'ask')]));
|
||||
expect(container.textContent).toContain(
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import connectorsService from '../api/services/connectorsService';
|
||||
import { Badge } from '../components/ui/badge';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Card } from '../components/ui/card';
|
||||
import { SectionHeader } from '../components/ui/section-header';
|
||||
@@ -17,6 +18,7 @@ import { ToggleGroup, ToggleGroupItem } from '../components/ui/toggle-group';
|
||||
import { showActionToast } from '../notifications/actionToastSlice';
|
||||
import { selectToken } from '../preferences/preferenceSlice';
|
||||
import { formatCount } from '../utils/dateTimeUtils';
|
||||
import ActionParameters, { type ParameterValue } from './ActionParameters';
|
||||
import { actionTitle } from './i18n';
|
||||
import type {
|
||||
ActionPermission,
|
||||
@@ -29,11 +31,105 @@ const PERMISSIONS: ActionPermission[] = ['always', 'ask', 'off'];
|
||||
/** Longer groups start folded to their one group choice. */
|
||||
const FOLD_AFTER = 5;
|
||||
|
||||
/**
|
||||
* One action under Customize: its name, what it does and its permission,
|
||||
* with its parameters one click away and a count of the values fixed there.
|
||||
*/
|
||||
function ActionRow({
|
||||
action,
|
||||
readOnly,
|
||||
onPermission,
|
||||
onParameters,
|
||||
}: {
|
||||
action: ConnectionToolAction;
|
||||
readOnly: boolean;
|
||||
onPermission: (permission: ActionPermission) => void;
|
||||
onParameters: (changes: Record<string, ParameterValue>) => Promise<boolean>;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [showParameters, setShowParameters] = useState(false);
|
||||
const parameters = action.parameters ?? [];
|
||||
const fixedCount = parameters.filter((parameter) => parameter.fixed).length;
|
||||
return (
|
||||
<li className="flex flex-col gap-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex min-w-0 flex-col items-start">
|
||||
<div className="flex max-w-full items-center gap-2">
|
||||
<p className="text-foreground truncate text-sm" title={action.name}>
|
||||
{actionTitle(action.name)}
|
||||
</p>
|
||||
{fixedCount > 0 && (
|
||||
<Badge variant="neutral">
|
||||
{t('settings.connectors.parameters.fixedCount', {
|
||||
count: fixedCount,
|
||||
formatted: formatCount(fixedCount),
|
||||
})}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{action.description && (
|
||||
<p
|
||||
className="text-muted-foreground line-clamp-2 text-xs"
|
||||
title={action.description}
|
||||
>
|
||||
{action.description}
|
||||
</p>
|
||||
)}
|
||||
{parameters.length > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
size="inline"
|
||||
className="mt-1"
|
||||
aria-expanded={showParameters}
|
||||
onClick={() => setShowParameters(!showParameters)}
|
||||
>
|
||||
{showParameters
|
||||
? t('settings.connectors.parameters.hide')
|
||||
: t('settings.connectors.parameters.show')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Select
|
||||
value={action.permission}
|
||||
disabled={readOnly}
|
||||
onValueChange={(value) => onPermission(value as ActionPermission)}
|
||||
>
|
||||
<SelectTrigger
|
||||
size="sm"
|
||||
className="w-32 shrink-0"
|
||||
aria-label={t('settings.connectors.permission.label', {
|
||||
action: actionTitle(action.name),
|
||||
})}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{PERMISSIONS.map((permission) => (
|
||||
<SelectItem key={permission} value={permission}>
|
||||
{t(`settings.connectors.permission.${permission}`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{showParameters && (
|
||||
<ActionParameters
|
||||
parameters={parameters}
|
||||
readOnly={readOnly}
|
||||
onSave={onParameters}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* One tool's actions in two groups, what it looks up and what it does, each
|
||||
* set at once to Allow / Ask first / Off; single actions can differ under
|
||||
* Customize. Changes save immediately; a failed save puts the previous
|
||||
* choices back and says so.
|
||||
* Customize, where each action also opens its parameters to fix values the
|
||||
* AI must always use. Changes save immediately; a failed save puts the
|
||||
* previous choices back and says so.
|
||||
*/
|
||||
export default function ToolPermissions({
|
||||
connectionId,
|
||||
@@ -83,6 +179,33 @@ export default function ToolPermissions({
|
||||
});
|
||||
};
|
||||
|
||||
const setParameters = async (
|
||||
actionName: string,
|
||||
changes: Record<string, ParameterValue>,
|
||||
) => {
|
||||
try {
|
||||
const data = await connectorsService.setToolParameters(
|
||||
connectionId,
|
||||
tool.id,
|
||||
actionName,
|
||||
changes,
|
||||
token,
|
||||
);
|
||||
if (!data?.success) throw new Error('save failed');
|
||||
setActions(data.tool.actions);
|
||||
onChange?.(data.tool);
|
||||
return true;
|
||||
} catch {
|
||||
dispatch(
|
||||
showActionToast({
|
||||
variant: 'destructive',
|
||||
message: t('settings.connectors.parameters.saveFailed'),
|
||||
}),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const groups = (['read', 'write'] as const)
|
||||
.map((access) => ({
|
||||
access,
|
||||
@@ -177,53 +300,17 @@ export default function ToolPermissions({
|
||||
{open && (
|
||||
<ul className="flex flex-col gap-3">
|
||||
{group.actions.map((action) => (
|
||||
<li
|
||||
<ActionRow
|
||||
key={action.name}
|
||||
className="flex items-start justify-between gap-3"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p
|
||||
className="text-foreground truncate text-sm"
|
||||
title={action.name}
|
||||
>
|
||||
{actionTitle(action.name)}
|
||||
</p>
|
||||
{action.description && (
|
||||
<p
|
||||
className="text-muted-foreground line-clamp-2 text-xs"
|
||||
title={action.description}
|
||||
>
|
||||
{action.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Select
|
||||
value={action.permission}
|
||||
disabled={readOnly}
|
||||
onValueChange={(value) =>
|
||||
setPermissions({
|
||||
[action.name]: value as ActionPermission,
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
size="sm"
|
||||
className="w-32 shrink-0"
|
||||
aria-label={t('settings.connectors.permission.label', {
|
||||
action: actionTitle(action.name),
|
||||
})}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{PERMISSIONS.map((permission) => (
|
||||
<SelectItem key={permission} value={permission}>
|
||||
{t(`settings.connectors.permission.${permission}`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</li>
|
||||
action={action}
|
||||
readOnly={readOnly}
|
||||
onPermission={(permission) =>
|
||||
setPermissions({ [action.name]: permission })
|
||||
}
|
||||
onParameters={(changes) =>
|
||||
setParameters(action.name, changes)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
@@ -92,11 +92,22 @@ export type Connection = {
|
||||
export type ActionAccess = 'read' | 'write';
|
||||
export type ActionPermission = 'always' | 'ask' | 'off';
|
||||
|
||||
export type ActionParameter = {
|
||||
name: string;
|
||||
description: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
/** Sent with `value` on every call; the AI never sees it. */
|
||||
fixed: boolean;
|
||||
value: string | number | boolean | null;
|
||||
};
|
||||
|
||||
export type ConnectionToolAction = {
|
||||
name: string;
|
||||
description: string;
|
||||
access: ActionAccess;
|
||||
permission: ActionPermission;
|
||||
parameters?: ActionParameter[];
|
||||
};
|
||||
|
||||
export type ConnectionTool = {
|
||||
|
||||
@@ -1344,6 +1344,19 @@
|
||||
"repositories": "Repositorys",
|
||||
"updated": "Aktualisiert {{date}}",
|
||||
"loadFailed": "Deine Repositorys konnten nicht geladen werden."
|
||||
},
|
||||
"parameters": {
|
||||
"show": "Parameter",
|
||||
"hide": "Parameter ausblenden",
|
||||
"fixedCount_one": "{{formatted}} festgelegt",
|
||||
"fixedCount_other": "{{formatted}} festgelegt",
|
||||
"ai": "KI entscheidet",
|
||||
"fixed": "Immer verwenden",
|
||||
"choiceLabel": "Wie {{parameter}} ausgefüllt wird",
|
||||
"valueLabel": "Wert für {{parameter}}",
|
||||
"save": "Speichern",
|
||||
"saveFailed": "Der Wert konnte nicht gespeichert werden. Versuche es erneut.",
|
||||
"hint": "Ein Wert mit „Immer verwenden“ wird bei jedem Aufruf gesendet. Die KI sieht ihn nicht und kann ihn nicht ändern."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1350,6 +1350,19 @@
|
||||
"repositories": "Repositories",
|
||||
"updated": "Updated {{date}}",
|
||||
"loadFailed": "Could not load your repositories."
|
||||
},
|
||||
"parameters": {
|
||||
"show": "Parameters",
|
||||
"hide": "Hide parameters",
|
||||
"fixedCount_one": "{{formatted}} fixed",
|
||||
"fixedCount_other": "{{formatted}} fixed",
|
||||
"ai": "Let AI decide",
|
||||
"fixed": "Always use",
|
||||
"choiceLabel": "How {{parameter}} is filled in",
|
||||
"valueLabel": "Value for {{parameter}}",
|
||||
"save": "Save",
|
||||
"saveFailed": "Could not save the value. Try again.",
|
||||
"hint": "A value set to Always use is sent on every call. The AI never sees it and cannot change it."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1344,6 +1344,19 @@
|
||||
"repositories": "Repositorios",
|
||||
"updated": "Actualizado {{date}}",
|
||||
"loadFailed": "No se pudieron cargar tus repositorios."
|
||||
},
|
||||
"parameters": {
|
||||
"show": "Parámetros",
|
||||
"hide": "Ocultar parámetros",
|
||||
"fixedCount_one": "{{formatted}} fijo",
|
||||
"fixedCount_other": "{{formatted}} fijos",
|
||||
"ai": "Que decida la IA",
|
||||
"fixed": "Usar siempre",
|
||||
"choiceLabel": "Cómo se rellena {{parameter}}",
|
||||
"valueLabel": "Valor de {{parameter}}",
|
||||
"save": "Guardar",
|
||||
"saveFailed": "No se pudo guardar el valor. Inténtalo de nuevo.",
|
||||
"hint": "Un valor con «Usar siempre» se envía en cada llamada. La IA nunca lo ve ni puede cambiarlo."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1326,6 +1326,18 @@
|
||||
"repositories": "リポジトリ",
|
||||
"updated": "{{date}} に更新",
|
||||
"loadFailed": "リポジトリを読み込めませんでした。"
|
||||
},
|
||||
"parameters": {
|
||||
"show": "パラメーター",
|
||||
"hide": "パラメーターを隠す",
|
||||
"fixedCount_other": "{{formatted}} 件固定",
|
||||
"ai": "AI に任せる",
|
||||
"fixed": "常に使用",
|
||||
"choiceLabel": "{{parameter}} の入力方法",
|
||||
"valueLabel": "{{parameter}} の値",
|
||||
"save": "保存",
|
||||
"saveFailed": "値を保存できませんでした。もう一度お試しください。",
|
||||
"hint": "「常に使用」の値は毎回の呼び出しで送信されます。AI はこの値を見ることも変更することもできません。"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1418,6 +1418,21 @@
|
||||
"repositories": "Репозитории",
|
||||
"updated": "Обновлён {{date}}",
|
||||
"loadFailed": "Не удалось загрузить ваши репозитории."
|
||||
},
|
||||
"parameters": {
|
||||
"show": "Параметры",
|
||||
"hide": "Скрыть параметры",
|
||||
"fixedCount_one": "{{formatted}} задан",
|
||||
"fixedCount_few": "{{formatted}} задано",
|
||||
"fixedCount_many": "{{formatted}} задано",
|
||||
"fixedCount_other": "{{formatted}} задано",
|
||||
"ai": "Решает ИИ",
|
||||
"fixed": "Всегда использовать",
|
||||
"choiceLabel": "Как заполняется {{parameter}}",
|
||||
"valueLabel": "Значение {{parameter}}",
|
||||
"save": "Сохранить",
|
||||
"saveFailed": "Не удалось сохранить значение. Попробуйте ещё раз.",
|
||||
"hint": "Значение «Всегда использовать» отправляется при каждом вызове. ИИ его не видит и не может изменить."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1326,6 +1326,18 @@
|
||||
"repositories": "儲存庫",
|
||||
"updated": "更新於 {{date}}",
|
||||
"loadFailed": "無法載入你的儲存庫。"
|
||||
},
|
||||
"parameters": {
|
||||
"show": "參數",
|
||||
"hide": "隱藏參數",
|
||||
"fixedCount_other": "已固定 {{formatted}} 個",
|
||||
"ai": "由 AI 決定",
|
||||
"fixed": "一律使用",
|
||||
"choiceLabel": "{{parameter}} 的填寫方式",
|
||||
"valueLabel": "{{parameter}} 的值",
|
||||
"save": "儲存",
|
||||
"saveFailed": "無法儲存此值。請再試一次。",
|
||||
"hint": "設為「一律使用」的值會在每次呼叫時送出。AI 看不到它,也無法變更。"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1326,6 +1326,18 @@
|
||||
"repositories": "仓库",
|
||||
"updated": "更新于 {{date}}",
|
||||
"loadFailed": "无法加载你的仓库。"
|
||||
},
|
||||
"parameters": {
|
||||
"show": "参数",
|
||||
"hide": "隐藏参数",
|
||||
"fixedCount_other": "已固定 {{formatted}} 个",
|
||||
"ai": "由 AI 决定",
|
||||
"fixed": "始终使用",
|
||||
"choiceLabel": "{{parameter}} 的填写方式",
|
||||
"valueLabel": "{{parameter}} 的值",
|
||||
"save": "保存",
|
||||
"saveFailed": "无法保存该值。请重试。",
|
||||
"hint": "设为“始终使用”的值会在每次调用时发送。AI 看不到它,也无法更改。"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user