All the channel approval
This commit is contained in:
66
deploy/patch-i18n-channel-users.mjs
Normal file
66
deploy/patch-i18n-channel-users.mjs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Run: node patch-i18n-channel-users.mjs
|
||||||
|
* Adds channelUsers i18n keys to all 4 message files.
|
||||||
|
* Run from the pieced-portal root.
|
||||||
|
*/
|
||||||
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
|
|
||||||
|
const newKeys = {
|
||||||
|
en: {
|
||||||
|
title: "Authorized Users",
|
||||||
|
description: "Manage which users can interact with your assistant on each channel. Add their numeric user ID to authorize access.",
|
||||||
|
users: "users",
|
||||||
|
placeholder: "Enter numeric user ID…",
|
||||||
|
add: "Add",
|
||||||
|
remove: "Remove",
|
||||||
|
alreadyAdded: "This user ID is already authorized.",
|
||||||
|
telegramIdHelp: "To find your Telegram user ID:\n1. Open Telegram and message @userinfobot\n2. It instantly replies with your numeric ID\n3. Enter that number here",
|
||||||
|
discordIdHelp: "To find your Discord user ID:\n1. Enable Developer Mode in Discord settings (Advanced)\n2. Right-click your name → Copy User ID\n3. Enter that number here",
|
||||||
|
emailIdHelp: "Enter the email address that should be authorized to interact with the assistant.",
|
||||||
|
},
|
||||||
|
de: {
|
||||||
|
title: "Autorisierte Benutzer",
|
||||||
|
description: "Verwalten Sie, welche Benutzer mit Ihrem Assistenten auf jedem Kanal interagieren können. Fügen Sie die numerische Benutzer-ID hinzu, um den Zugang zu autorisieren.",
|
||||||
|
users: "Benutzer",
|
||||||
|
placeholder: "Numerische Benutzer-ID eingeben…",
|
||||||
|
add: "Hinzufügen",
|
||||||
|
remove: "Entfernen",
|
||||||
|
alreadyAdded: "Diese Benutzer-ID ist bereits autorisiert.",
|
||||||
|
telegramIdHelp: "So finden Sie Ihre Telegram-Benutzer-ID:\n1. Öffnen Sie Telegram und schreiben Sie @userinfobot\n2. Der Bot antwortet sofort mit Ihrer numerischen ID\n3. Geben Sie diese Nummer hier ein",
|
||||||
|
discordIdHelp: "So finden Sie Ihre Discord-Benutzer-ID:\n1. Aktivieren Sie den Entwicklermodus in den Discord-Einstellungen (Erweitert)\n2. Rechtsklick auf Ihren Namen → Benutzer-ID kopieren\n3. Geben Sie diese Nummer hier ein",
|
||||||
|
emailIdHelp: "Geben Sie die E-Mail-Adresse ein, die zur Interaktion mit dem Assistenten autorisiert werden soll.",
|
||||||
|
},
|
||||||
|
fr: {
|
||||||
|
title: "Utilisateurs autorisés",
|
||||||
|
description: "Gérez les utilisateurs pouvant interagir avec votre assistant sur chaque canal. Ajoutez leur identifiant numérique pour autoriser l'accès.",
|
||||||
|
users: "utilisateurs",
|
||||||
|
placeholder: "Entrez l'identifiant numérique…",
|
||||||
|
add: "Ajouter",
|
||||||
|
remove: "Supprimer",
|
||||||
|
alreadyAdded: "Cet identifiant est déjà autorisé.",
|
||||||
|
telegramIdHelp: "Pour trouver votre identifiant Telegram :\n1. Ouvrez Telegram et envoyez un message à @userinfobot\n2. Il répond instantanément avec votre identifiant numérique\n3. Entrez ce numéro ici",
|
||||||
|
discordIdHelp: "Pour trouver votre identifiant Discord :\n1. Activez le mode développeur dans les paramètres Discord (Avancé)\n2. Clic droit sur votre nom → Copier l'identifiant\n3. Entrez ce numéro ici",
|
||||||
|
emailIdHelp: "Entrez l'adresse e-mail qui doit être autorisée à interagir avec l'assistant.",
|
||||||
|
},
|
||||||
|
it: {
|
||||||
|
title: "Utenti autorizzati",
|
||||||
|
description: "Gestisci quali utenti possono interagire con il tuo assistente su ogni canale. Aggiungi il loro ID numerico per autorizzare l'accesso.",
|
||||||
|
users: "utenti",
|
||||||
|
placeholder: "Inserisci l'ID numerico…",
|
||||||
|
add: "Aggiungi",
|
||||||
|
remove: "Rimuovi",
|
||||||
|
alreadyAdded: "Questo ID utente è già autorizzato.",
|
||||||
|
telegramIdHelp: "Per trovare il tuo ID Telegram:\n1. Apri Telegram e invia un messaggio a @userinfobot\n2. Risponde istantaneamente con il tuo ID numerico\n3. Inserisci quel numero qui",
|
||||||
|
discordIdHelp: "Per trovare il tuo ID Discord:\n1. Attiva la Modalità sviluppatore nelle impostazioni Discord (Avanzate)\n2. Clic destro sul tuo nome → Copia ID utente\n3. Inserisci quel numero qui",
|
||||||
|
emailIdHelp: "Inserisci l'indirizzo e-mail che deve essere autorizzato a interagire con l'assistente.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [lang, keys] of Object.entries(newKeys)) {
|
||||||
|
const path = `src/messages/${lang}.json`;
|
||||||
|
const json = JSON.parse(readFileSync(path, "utf8"));
|
||||||
|
json.channelUsers = keys;
|
||||||
|
writeFileSync(path, JSON.stringify(json, null, 2) + "\n");
|
||||||
|
console.log(`Patched ${path} — added channelUsers section`);
|
||||||
|
}
|
||||||
@@ -6,6 +6,9 @@ import { StatusBadge } from "@/components/ui/status-badge";
|
|||||||
import { UsageDisplay } from "@/components/dashboard/usage-display";
|
import { UsageDisplay } from "@/components/dashboard/usage-display";
|
||||||
import { PackageList } from "@/components/packages/package-list";
|
import { PackageList } from "@/components/packages/package-list";
|
||||||
import { WorkspaceEditor } from "@/components/packages/workspace-editor";
|
import { WorkspaceEditor } from "@/components/packages/workspace-editor";
|
||||||
|
import { ChannelUsers } from "@/components/channel-users/channel-users";
|
||||||
|
|
||||||
|
const CHANNEL_PACKAGES = ["telegram", "discord", "email"];
|
||||||
|
|
||||||
export default async function TenantDetailPage({
|
export default async function TenantDetailPage({
|
||||||
params,
|
params,
|
||||||
@@ -20,7 +23,6 @@ export default async function TenantDetailPage({
|
|||||||
|
|
||||||
const tenant = await getTenant(name);
|
const tenant = await getTenant(name);
|
||||||
if (!tenant) notFound();
|
if (!tenant) notFound();
|
||||||
console.log("tenant spec:", JSON.stringify(tenant.spec));
|
|
||||||
|
|
||||||
// Scope check
|
// Scope check
|
||||||
if (
|
if (
|
||||||
@@ -32,6 +34,10 @@ export default async function TenantDetailPage({
|
|||||||
|
|
||||||
const enabledPackages = tenant.spec.packages || [];
|
const enabledPackages = tenant.spec.packages || [];
|
||||||
const workspaceFiles = tenant.spec.workspaceFiles || {};
|
const workspaceFiles = tenant.spec.workspaceFiles || {};
|
||||||
|
const enabledChannels = enabledPackages.filter((pkg) =>
|
||||||
|
CHANNEL_PACKAGES.includes(pkg)
|
||||||
|
);
|
||||||
|
const channelUsers = tenant.spec.channelUsers || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -70,8 +76,19 @@ export default async function TenantDetailPage({
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Channel Users (authorized users per channel) */}
|
||||||
|
{enabledChannels.length > 0 && (
|
||||||
|
<section className="mb-8 animate-in animate-in-delay-3">
|
||||||
|
<ChannelUsers
|
||||||
|
tenantName={name}
|
||||||
|
enabledChannels={enabledChannels}
|
||||||
|
initialChannelUsers={channelUsers}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Workspace files */}
|
{/* Workspace files */}
|
||||||
<section className="animate-in animate-in-delay-3">
|
<section className="animate-in animate-in-delay-4">
|
||||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
|
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
|
||||||
{t("workspaceFiles")}
|
{t("workspaceFiles")}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export async function PATCH(
|
|||||||
if (body.displayName !== undefined)
|
if (body.displayName !== undefined)
|
||||||
specPatch.displayName = body.displayName;
|
specPatch.displayName = body.displayName;
|
||||||
if (body.agentName !== undefined) specPatch.agentName = body.agentName;
|
if (body.agentName !== undefined) specPatch.agentName = body.agentName;
|
||||||
|
if (body.channelUsers !== undefined)
|
||||||
|
specPatch.channelUsers = body.channelUsers;
|
||||||
|
|
||||||
const updated = await patchTenantSpec(name, specPatch);
|
const updated = await patchTenantSpec(name, specPatch);
|
||||||
return NextResponse.json(updated);
|
return NextResponse.json(updated);
|
||||||
|
|||||||
192
src/components/channel-users/channel-users.tsx
Normal file
192
src/components/channel-users/channel-users.tsx
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useCallback } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
/** Maps channel IDs to the instructions for finding the user ID. */
|
||||||
|
const CHANNEL_ID_HELP: Record<string, string> = {
|
||||||
|
telegram: "telegramIdHelp",
|
||||||
|
discord: "discordIdHelp",
|
||||||
|
email: "emailIdHelp",
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ChannelUsersProps {
|
||||||
|
tenantName: string;
|
||||||
|
/** Currently enabled channel packages (e.g. ["telegram", "discord"]) */
|
||||||
|
enabledChannels: string[];
|
||||||
|
/** Current channelUsers from the PiecedTenant spec */
|
||||||
|
initialChannelUsers: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ChannelUsers({
|
||||||
|
tenantName,
|
||||||
|
enabledChannels,
|
||||||
|
initialChannelUsers,
|
||||||
|
}: ChannelUsersProps) {
|
||||||
|
const t = useTranslations("channelUsers");
|
||||||
|
const router = useRouter();
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [inputValues, setInputValues] = useState<Record<string, string>>({});
|
||||||
|
const [channelUsers, setChannelUsers] =
|
||||||
|
useState<Record<string, string[]>>(initialChannelUsers);
|
||||||
|
|
||||||
|
const updateChannelUsers = useCallback(
|
||||||
|
async (updated: Record<string, string[]>) => {
|
||||||
|
setSaving(true);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/tenants/${tenantName}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ channelUsers: updated }),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
throw new Error(data.error || "Update failed");
|
||||||
|
}
|
||||||
|
setChannelUsers(updated);
|
||||||
|
router.refresh();
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(e.message);
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[tenantName, router]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAdd = useCallback(
|
||||||
|
(channel: string) => {
|
||||||
|
const userId = inputValues[channel]?.trim();
|
||||||
|
if (!userId) return;
|
||||||
|
|
||||||
|
const current = channelUsers[channel] || [];
|
||||||
|
if (current.includes(userId)) {
|
||||||
|
setError(t("alreadyAdded"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = {
|
||||||
|
...channelUsers,
|
||||||
|
[channel]: [...current, userId],
|
||||||
|
};
|
||||||
|
setInputValues((prev) => ({ ...prev, [channel]: "" }));
|
||||||
|
updateChannelUsers(updated);
|
||||||
|
},
|
||||||
|
[channelUsers, inputValues, updateChannelUsers, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRemove = useCallback(
|
||||||
|
(channel: string, userId: string) => {
|
||||||
|
const current = channelUsers[channel] || [];
|
||||||
|
const updated = {
|
||||||
|
...channelUsers,
|
||||||
|
[channel]: current.filter((id) => id !== userId),
|
||||||
|
};
|
||||||
|
updateChannelUsers(updated);
|
||||||
|
},
|
||||||
|
[channelUsers, updateChannelUsers]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (enabledChannels.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-1">
|
||||||
|
{t("title")}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-text-muted mb-4">{t("description")}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="text-xs text-red-400 bg-red-400/10 border border-red-400/20 rounded-lg px-3 py-2">
|
||||||
|
{error}
|
||||||
|
<button
|
||||||
|
onClick={() => setError("")}
|
||||||
|
className="ml-2 text-red-300 hover:text-red-200"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{enabledChannels.map((channel) => {
|
||||||
|
const users = channelUsers[channel] || [];
|
||||||
|
const helpKey = CHANNEL_ID_HELP[channel];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={channel}
|
||||||
|
className="bg-surface-2 border border-border rounded-lg p-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<h4 className="text-sm font-medium text-text-primary capitalize">
|
||||||
|
{channel}
|
||||||
|
</h4>
|
||||||
|
<span className="text-xs text-text-muted tabular-nums">
|
||||||
|
{users.length} {t("users")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{helpKey && (
|
||||||
|
<p className="text-xs text-text-secondary bg-surface-1 border border-border rounded-lg p-3 mb-3 whitespace-pre-line">
|
||||||
|
{t(helpKey)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Current users */}
|
||||||
|
{users.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1.5 mb-3">
|
||||||
|
{users.map((userId) => (
|
||||||
|
<span
|
||||||
|
key={userId}
|
||||||
|
className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-mono bg-accent/10 text-accent border border-accent/20 rounded-full"
|
||||||
|
>
|
||||||
|
{userId}
|
||||||
|
<button
|
||||||
|
onClick={() => handleRemove(channel, userId)}
|
||||||
|
disabled={saving}
|
||||||
|
className="text-accent/60 hover:text-red-400 transition-colors disabled:opacity-50"
|
||||||
|
title={t("remove")}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Add user */}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputValues[channel] || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setInputValues((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[channel]: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") handleAdd(channel);
|
||||||
|
}}
|
||||||
|
placeholder={t("placeholder")}
|
||||||
|
className="flex-1 px-3 py-2 bg-surface-1 border border-border rounded-lg text-sm text-text-primary font-mono placeholder:text-text-muted focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => handleAdd(channel)}
|
||||||
|
disabled={saving || !inputValues[channel]?.trim()}
|
||||||
|
className="px-4 py-2 text-sm font-medium bg-accent text-white rounded-lg hover:bg-accent-dim transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{saving ? "…" : t("add")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -237,5 +237,17 @@
|
|||||||
"statusHealthy": "OK",
|
"statusHealthy": "OK",
|
||||||
"statusDown": "Ausgefallen",
|
"statusDown": "Ausgefallen",
|
||||||
"spendChf": "Kosten (CHF)"
|
"spendChf": "Kosten (CHF)"
|
||||||
|
},
|
||||||
|
"channelUsers": {
|
||||||
|
"title": "Autorisierte Benutzer",
|
||||||
|
"description": "Verwalten Sie, welche Benutzer mit Ihrem Assistenten auf jedem Kanal interagieren können. Fügen Sie die numerische Benutzer-ID hinzu, um den Zugang zu autorisieren.",
|
||||||
|
"users": "Benutzer",
|
||||||
|
"placeholder": "Numerische Benutzer-ID eingeben…",
|
||||||
|
"add": "Hinzufügen",
|
||||||
|
"remove": "Entfernen",
|
||||||
|
"alreadyAdded": "Diese Benutzer-ID ist bereits autorisiert.",
|
||||||
|
"telegramIdHelp": "So finden Sie Ihre Telegram-Benutzer-ID:\n1. Öffnen Sie Telegram und schreiben Sie @userinfobot\n2. Der Bot antwortet sofort mit Ihrer numerischen ID\n3. Geben Sie diese Nummer hier ein",
|
||||||
|
"discordIdHelp": "So finden Sie Ihre Discord-Benutzer-ID:\n1. Aktivieren Sie den Entwicklermodus in den Discord-Einstellungen (Erweitert)\n2. Rechtsklick auf Ihren Namen → Benutzer-ID kopieren\n3. Geben Sie diese Nummer hier ein",
|
||||||
|
"emailIdHelp": "Geben Sie die E-Mail-Adresse ein, die zur Interaktion mit dem Assistenten autorisiert werden soll."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,5 +237,17 @@
|
|||||||
"statusHealthy": "Healthy",
|
"statusHealthy": "Healthy",
|
||||||
"statusDown": "Down",
|
"statusDown": "Down",
|
||||||
"spendChf": "Spend (CHF)"
|
"spendChf": "Spend (CHF)"
|
||||||
|
},
|
||||||
|
"channelUsers": {
|
||||||
|
"title": "Authorized Users",
|
||||||
|
"description": "Manage which users can interact with your assistant on each channel. Add their numeric user ID to authorize access.",
|
||||||
|
"users": "users",
|
||||||
|
"placeholder": "Enter numeric user ID…",
|
||||||
|
"add": "Add",
|
||||||
|
"remove": "Remove",
|
||||||
|
"alreadyAdded": "This user ID is already authorized.",
|
||||||
|
"telegramIdHelp": "To find your Telegram user ID:\n1. Open Telegram and message @userinfobot\n2. It instantly replies with your numeric ID\n3. Enter that number here",
|
||||||
|
"discordIdHelp": "To find your Discord user ID:\n1. Enable Developer Mode in Discord settings (Advanced)\n2. Right-click your name → Copy User ID\n3. Enter that number here",
|
||||||
|
"emailIdHelp": "Enter the email address that should be authorized to interact with the assistant."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,5 +237,17 @@
|
|||||||
"statusHealthy": "OK",
|
"statusHealthy": "OK",
|
||||||
"statusDown": "Hors service",
|
"statusDown": "Hors service",
|
||||||
"spendChf": "Coûts (CHF)"
|
"spendChf": "Coûts (CHF)"
|
||||||
|
},
|
||||||
|
"channelUsers": {
|
||||||
|
"title": "Utilisateurs autorisés",
|
||||||
|
"description": "Gérez les utilisateurs pouvant interagir avec votre assistant sur chaque canal. Ajoutez leur identifiant numérique pour autoriser l'accès.",
|
||||||
|
"users": "utilisateurs",
|
||||||
|
"placeholder": "Entrez l'identifiant numérique…",
|
||||||
|
"add": "Ajouter",
|
||||||
|
"remove": "Supprimer",
|
||||||
|
"alreadyAdded": "Cet identifiant est déjà autorisé.",
|
||||||
|
"telegramIdHelp": "Pour trouver votre identifiant Telegram :\n1. Ouvrez Telegram et envoyez un message à @userinfobot\n2. Il répond instantanément avec votre identifiant numérique\n3. Entrez ce numéro ici",
|
||||||
|
"discordIdHelp": "Pour trouver votre identifiant Discord :\n1. Activez le mode développeur dans les paramètres Discord (Avancé)\n2. Clic droit sur votre nom → Copier l'identifiant\n3. Entrez ce numéro ici",
|
||||||
|
"emailIdHelp": "Entrez l'adresse e-mail qui doit être autorisée à interagir avec l'assistant."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,5 +237,17 @@
|
|||||||
"statusHealthy": "OK",
|
"statusHealthy": "OK",
|
||||||
"statusDown": "Non disponibile",
|
"statusDown": "Non disponibile",
|
||||||
"spendChf": "Costi (CHF)"
|
"spendChf": "Costi (CHF)"
|
||||||
|
},
|
||||||
|
"channelUsers": {
|
||||||
|
"title": "Utenti autorizzati",
|
||||||
|
"description": "Gestisci quali utenti possono interagire con il tuo assistente su ogni canale. Aggiungi il loro ID numerico per autorizzare l'accesso.",
|
||||||
|
"users": "utenti",
|
||||||
|
"placeholder": "Inserisci l'ID numerico…",
|
||||||
|
"add": "Aggiungi",
|
||||||
|
"remove": "Rimuovi",
|
||||||
|
"alreadyAdded": "Questo ID utente è già autorizzato.",
|
||||||
|
"telegramIdHelp": "Per trovare il tuo ID Telegram:\n1. Apri Telegram e invia un messaggio a @userinfobot\n2. Risponde istantaneamente con il tuo ID numerico\n3. Inserisci quel numero qui",
|
||||||
|
"discordIdHelp": "Per trovare il tuo ID Discord:\n1. Attiva la Modalità sviluppatore nelle impostazioni Discord (Avanzate)\n2. Clic destro sul tuo nome → Copia ID utente\n3. Inserisci quel numero qui",
|
||||||
|
"emailIdHelp": "Inserisci l'indirizzo e-mail che deve essere autorizzato a interagire con l'assistente."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface PiecedTenantSpec {
|
|||||||
plan?: string;
|
plan?: string;
|
||||||
packages?: string[];
|
packages?: string[];
|
||||||
workspaceFiles?: Record<string, string>;
|
workspaceFiles?: Record<string, string>;
|
||||||
|
channelUsers?: Record<string, string[]>;
|
||||||
suspend?: boolean;
|
suspend?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user