Show the Telegram default chat in the connect form and the drawer

The connect form shows field hints; Telegram's Default chat ID says what
it does and how to find a chat's id. In the drawer, a chat set on the
account shows as set there instead of as a choice.
This commit is contained in:
arc53-machine committed 2026-09-29 10:41:54 +01:00
1 parent c3e83ff9dd
commit 607c2296e3
13 files changed
+198 -31

No files matched your search

+3 -2
View File
@@ -316,8 +316,9 @@ _BUILT_IN: tuple[ConnectorDefinition, ...] = (
required=False,
parameter="chat_id",
hint=(
"Messages go to this chat, and the AI cannot pick another. Add the bot to the chat, send "
"it a message, then find the chat's id in https://api.telegram.org/bot<token>/getUpdates."
"Optional. Messages go to this chat, and the AI cannot pick another. Add the bot to the "
"chat, send it a message, then find the chat's id in "
"https://api.telegram.org/bot<token>/getUpdates."
),
),
),
@@ -42,6 +42,21 @@ function ParameterRow({
}
};
if (parameter.set_by === 'account')
return (
<li data-parameter={parameter.name} className="flex flex-col">
<p className="text-foreground font-mono text-xs wrap-anywhere">
{parameter.name}
</p>
<p className="text-muted-foreground text-xs wrap-anywhere">
{t('settings.connectors.parameters.fromAccount', {
value: String(parameter.value ?? ''),
interpolation: { escapeValue: false },
})}
</p>
</li>
);
const choiceLabel = t('settings.connectors.parameters.choiceLabel', {
parameter: parameter.name,
interpolation: { escapeValue: false },
@@ -0,0 +1,61 @@
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, opts?: { defaultValue?: string }) =>
opts?.defaultValue ?? key,
}),
}));
import CredentialForm from './CredentialForm';
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
describe('CredentialForm', () => {
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();
});
it('shows a field hint under the field and links it to the input', async () => {
await act(async () => {
root.render(
<CredentialForm
connectorKey="telegram"
idPrefix="connect-telegram"
fields={[
{ key: 'token', label: 'Bot token', secret: true, required: true },
{
key: 'chat_id',
label: 'Default chat ID',
secret: false,
required: false,
hint: 'Messages go to this chat.',
},
]}
values={{}}
onChange={() => undefined}
/>,
);
});
const input = container.querySelector<HTMLInputElement>(
'#connect-telegram-chat_id',
)!;
expect(input.type).toBe('text');
const hintId = input.getAttribute('aria-describedby')!;
expect(document.getElementById(hintId)?.textContent).toBe(
'Messages go to this chat.',
);
expect(container.textContent).toContain('Default chat ID');
});
});
+44 -15
View File
@@ -1,5 +1,7 @@
import type { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
import { FormField } from '../components/ui/form-field';
import { Input } from '../components/ui/input';
import type { CredentialField } from './types';
@@ -7,7 +9,8 @@ import type { CredentialField } from './types';
* The short form an API-key connector asks for, generated from its catalog
* `credential_fields` (or `setup_fields`). Secrets are masked. Labels come
* from `settings.connectors.fields.<connector>_<key>`, then
* `settings.connectors.fields.<key>`, then the catalog label.
* `settings.connectors.fields.<key>`, then the catalog label; a field's hint
* from `settings.connectors.fieldHints.<connector>_<key>`, then the catalog.
*/
export default function CredentialForm({
connectorKey,
@@ -28,23 +31,49 @@ export default function CredentialForm({
const { t } = useTranslation();
return (
<div className="flex flex-col gap-5">
{fields.map((field) => (
<Input
key={field.key}
id={`${idPrefix}-${field.key}`}
label={t(`settings.connectors.fields.${connectorKey}_${field.key}`, {
{fields.map((field) => {
const id = `${idPrefix}-${field.key}`;
const label = t(
`settings.connectors.fields.${connectorKey}_${field.key}`,
{
defaultValue: t(`settings.connectors.fields.${field.key}`, {
defaultValue: field.label,
}),
})}
labelSurface={labelSurface}
type={field.secret ? 'password' : 'text'}
autoComplete={field.secret ? 'new-password' : 'off'}
required={field.required}
value={values[field.key] ?? ''}
onChange={(e) => onChange({ ...values, [field.key]: e.target.value })}
/>
))}
},
);
const inputProps = {
id,
type: field.secret ? 'password' : 'text',
autoComplete: field.secret ? 'new-password' : 'off',
value: values[field.key] ?? '',
onChange: (e: ChangeEvent<HTMLInputElement>) =>
onChange({ ...values, [field.key]: e.target.value }),
};
if (!field.hint)
return (
<Input
key={field.key}
label={label}
labelSurface={labelSurface}
required={field.required}
{...inputProps}
/>
);
return (
<FormField
key={field.key}
label={label}
required={field.required}
labelSurface={labelSurface}
hint={t(
`settings.connectors.fieldHints.${connectorKey}_${field.key}`,
{ defaultValue: field.hint },
)}
>
<Input {...inputProps} />
</FormField>
);
})}
</div>
);
}
@@ -243,6 +243,26 @@ describe('ToolPermissions', () => {
);
});
it('shows a chat the account sets as set there, not as a choice', async () => {
await render(
tool([
action('telegram_send_message', 'write', 'ask', [
{ ...param('chat_id', true, '-100'), set_by: 'account' },
]),
]),
);
await act(async () =>
button('settings.connectors.parameters.show').click(),
);
const row = container.querySelector<HTMLElement>(
'[data-parameter="chat_id"]',
)!;
expect(row.textContent).toContain(
'settings.connectors.parameters.fromAccount',
);
expect(row.querySelector('[data-mode]')).toBeNull();
});
it('says what Ask first means outside chat', async () => {
await render(tool([action('create_issue', 'write', 'ask')]));
expect(container.textContent).toContain(
+6
View File
@@ -33,6 +33,10 @@ export type CredentialField = {
label: string;
secret: boolean;
required: boolean;
/** A tool parameter this field sets for every call (Telegram's chat). */
parameter?: string | null;
/** English help under the field; translated when the locale has it. */
hint?: string | null;
};
export type ConnectorDefinition = {
@@ -100,6 +104,8 @@ export type ActionParameter = {
/** Sent with `value` on every call; the AI never sees it. */
fixed: boolean;
value: string | number | boolean | null;
/** Who fixed it: the tool's own setting, or the account (a default chat). */
set_by?: 'tool' | 'account' | null;
};
export type ConnectionToolAction = {
+7 -2
View File
@@ -1242,7 +1242,8 @@
"postgres_token": "Verbindungszeichenfolge",
"brave_token": "API-Schlüssel",
"github_access_token": "Persönliches Zugriffstoken",
"repo_url": "Repository"
"repo_url": "Repository",
"telegram_chat_id": "Standard-Chat-ID"
},
"wizard": {
"connectTitle": "{{name}} verbinden",
@@ -1356,7 +1357,11 @@
"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."
"hint": "Ein Wert mit „Immer verwenden“ wird bei jedem Aufruf gesendet. Die KI sieht ihn nicht und kann ihn nicht ändern.",
"fromAccount": "In diesem Konto festgelegt: {{value}}"
},
"fieldHints": {
"telegram_chat_id": "Optional. Nachrichten gehen an diesen Chat, und die KI kann keinen anderen wählen. Füge den Bot zum Chat hinzu, sende ihm eine Nachricht und finde die ID des Chats unter https://api.telegram.org/bot<token>/getUpdates."
}
}
},
+7 -2
View File
@@ -1248,7 +1248,8 @@
"postgres_token": "Connection string",
"brave_token": "API key",
"github_access_token": "Personal access token",
"repo_url": "Repository"
"repo_url": "Repository",
"telegram_chat_id": "Default chat ID"
},
"wizard": {
"connectTitle": "Connect {{name}}",
@@ -1362,7 +1363,11 @@
"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."
"hint": "A value set to Always use is sent on every call. The AI never sees it and cannot change it.",
"fromAccount": "Set on this account: {{value}}"
},
"fieldHints": {
"telegram_chat_id": "Optional. Messages go to this chat, and the AI cannot pick another. Add the bot to the chat, send it a message, then find the chat's id in https://api.telegram.org/bot<token>/getUpdates."
}
}
},
+7 -2
View File
@@ -1242,7 +1242,8 @@
"postgres_token": "Cadena de conexión",
"brave_token": "Clave de API",
"github_access_token": "Token de acceso personal",
"repo_url": "Repositorio"
"repo_url": "Repositorio",
"telegram_chat_id": "ID del chat predeterminado"
},
"wizard": {
"connectTitle": "Conectar {{name}}",
@@ -1356,7 +1357,11 @@
"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."
"hint": "Un valor con «Usar siempre» se envía en cada llamada. La IA nunca lo ve ni puede cambiarlo.",
"fromAccount": "Definido en esta cuenta: {{value}}"
},
"fieldHints": {
"telegram_chat_id": "Opcional. Los mensajes van a este chat y la IA no puede elegir otro. Añade el bot al chat, envíale un mensaje y busca el id del chat en https://api.telegram.org/bot<token>/getUpdates."
}
}
},
+7 -2
View File
@@ -1230,7 +1230,8 @@
"postgres_token": "接続文字列",
"brave_token": "API キー",
"github_access_token": "個人用アクセストークン",
"repo_url": "リポジトリ"
"repo_url": "リポジトリ",
"telegram_chat_id": "既定のチャット ID"
},
"wizard": {
"connectTitle": "{{name}} に接続",
@@ -1337,7 +1338,11 @@
"valueLabel": "{{parameter}} の値",
"save": "保存",
"saveFailed": "値を保存できませんでした。もう一度お試しください。",
"hint": "「常に使用」の値は毎回の呼び出しで送信されます。AI はこの値を見ることも変更することもできません。"
"hint": "「常に使用」の値は毎回の呼び出しで送信されます。AI はこの値を見ることも変更することもできません。",
"fromAccount": "このアカウントで設定済み: {{value}}"
},
"fieldHints": {
"telegram_chat_id": "任意。メッセージはこのチャットに送られ、AI は別のチャットを選べません。ボットをチャットに追加してメッセージを送り、https://api.telegram.org/bot<token>/getUpdates でチャットの id を確認してください。"
}
}
},
+7 -2
View File
@@ -1304,7 +1304,8 @@
"postgres_token": "Строка подключения",
"brave_token": "API-ключ",
"github_access_token": "Персональный токен доступа",
"repo_url": "Репозиторий"
"repo_url": "Репозиторий",
"telegram_chat_id": "ID чата по умолчанию"
},
"wizard": {
"connectTitle": "Подключить {{name}}",
@@ -1432,7 +1433,11 @@
"valueLabel": "Значение {{parameter}}",
"save": "Сохранить",
"saveFailed": "Не удалось сохранить значение. Попробуйте ещё раз.",
"hint": "Значение «Всегда использовать» отправляется при каждом вызове. ИИ его не видит и не может изменить."
"hint": "Значение «Всегда использовать» отправляется при каждом вызове. ИИ его не видит и не может изменить.",
"fromAccount": "Задано в этом аккаунте: {{value}}"
},
"fieldHints": {
"telegram_chat_id": "Необязательно. Сообщения уходят в этот чат, и ИИ не может выбрать другой. Добавьте бота в чат, отправьте ему сообщение и найдите id чата на https://api.telegram.org/bot<token>/getUpdates."
}
}
},
+7 -2
View File
@@ -1230,7 +1230,8 @@
"postgres_token": "連線字串",
"brave_token": "API 金鑰",
"github_access_token": "個人存取權杖",
"repo_url": "儲存庫"
"repo_url": "儲存庫",
"telegram_chat_id": "預設聊天 ID"
},
"wizard": {
"connectTitle": "連線 {{name}}",
@@ -1337,7 +1338,11 @@
"valueLabel": "{{parameter}} 的值",
"save": "儲存",
"saveFailed": "無法儲存此值。請再試一次。",
"hint": "設為「一律使用」的值會在每次呼叫時送出。AI 看不到它,也無法變更。"
"hint": "設為「一律使用」的值會在每次呼叫時送出。AI 看不到它,也無法變更。",
"fromAccount": "已在此帳戶中設定:{{value}}"
},
"fieldHints": {
"telegram_chat_id": "選填。訊息會傳送到此聊天,AI 無法選擇其他聊天。將機器人加入聊天並傳送一則訊息給它,然後在 https://api.telegram.org/bot<token>/getUpdates 中找到該聊天的 id。"
}
}
},
+7 -2
View File
@@ -1230,7 +1230,8 @@
"postgres_token": "连接字符串",
"brave_token": "API 密钥",
"github_access_token": "个人访问令牌",
"repo_url": "仓库"
"repo_url": "仓库",
"telegram_chat_id": "默认聊天 ID"
},
"wizard": {
"connectTitle": "连接 {{name}}",
@@ -1337,7 +1338,11 @@
"valueLabel": "{{parameter}} 的值",
"save": "保存",
"saveFailed": "无法保存该值。请重试。",
"hint": "设为“始终使用”的值会在每次调用时发送。AI 看不到它,也无法更改。"
"hint": "设为“始终使用”的值会在每次调用时发送。AI 看不到它,也无法更改。",
"fromAccount": "已在此账户中设置:{{value}}"
},
"fieldHints": {
"telegram_chat_id": "可选。消息会发送到此聊天,AI 无法选择其他聊天。将机器人加入聊天并给它发一条消息,然后在 https://api.telegram.org/bot<token>/getUpdates 中找到该聊天的 id。"
}
}
},