diff --git a/messages/en.json b/messages/en.json index a3dd25ff20..2c9fd03725 100644 --- a/messages/en.json +++ b/messages/en.json @@ -359,9 +359,9 @@ "custom": "Custom Fields", "deals": "Deals", "tags": "Tags", - "noTags": "No tags", - "noDeals": "No deals", - "addNotePlaceholder": "Add a note..." + "noTags": "No tags", + "noDeals": "No deals", + "addNotePlaceholder": "Add a note..." }, "actions": { "edit": "Edit", @@ -932,6 +932,7 @@ "no": "No" }, "addStep": "Add step", + "delete": "Delete", "config": { "messageText": "Message text", "placeholderMessageText": "Hi! Thanks for reaching out…", @@ -1331,13 +1332,13 @@ "deleteLocallyAria": "Delete template locally", "deleteMetaLocallyTitle": "Delete from Meta and locally", "deleteLocallyTitle": "Delete locally", - + "dialogEditTitle": "Edit Message Template", "dialogNewTitle": "New Message Template", "dialogEditDesc": "Save your changes to re-submit to Meta. Status will flip back to PENDING during review.", "dialogNewDesc": "Build a template and submit it to Meta for approval. Once approved, you can use it in broadcasts and the inbox.", "authWarning": "AUTHENTICATION templates have a fixed body + OTP button shape that needs a different builder. Create them in Meta WhatsApp Manager for now and use Sync from Meta to bring them in.", - + "templateName": "Template Name", "namePlaceholder": "e.g. order_confirmation", "nameFixed": "Name is fixed once a template exists on Meta — create a new template to change it.", @@ -1346,7 +1347,7 @@ "language": "Language", "langFixed": "Language is fixed once a template exists on Meta.", "langHint": "Must match the exact code on Meta — en_US and en are distinct.", - + "header": "Header", "headerNone": "None", "headerText": "Text", @@ -1356,7 +1357,7 @@ "headerTextPlaceholder": "Header text (max 60 chars, optional {{1}})", "headerSampleAria": "Sample value for header variable", "headerSamplePlaceholder": "Sample value for {{1}} (required for Meta review)", - + "uploadImage": "Upload image", "uploadHint": "JPEG or PNG, ≤5 MB", "mediaUrlPlaceholder": "https://… (or paste a public {format} link)", @@ -1364,17 +1365,17 @@ "mediaHint": "Must be a publicly accessible HTTPS link. Meta fetches it once during review, so it needs to stay live for ~24 hrs.", "videoHint": " Recommended: MP4 / 3GPP, ≤16 MB, ≤60 seconds.", "documentHint": " Recommended: PDF, ≤100 MB.", - + "bodyText": "Body Text", "bodyPlaceholder": "Hello {{1}}, your order {{2}} is confirmed.", "bodyHint": "Use {{1}}, {{2}} for variables (must be contiguous starting at {{1}}).", "sampleValues": "Sample values (Meta uses these to review your template)", "sampleAria": "Sample value for body variable {var}", "samplePlaceholder": "Sample for {var}", - + "footer": "Footer (optional)", "footerPlaceholder": "Optional footer text (max 60 chars)", - + "buttons": "Buttons (optional)", "addButton": "Add Button", "buttonsLimit": "Up to {max} buttons. QUICK_REPLY buttons must come before URL / phone / copy-code buttons.", @@ -1387,19 +1388,19 @@ "urlSamplePlaceholder": "Example value for {{1}} (required when URL has a variable)", "phonePlaceholder": "+15551234567", "codePlaceholder": "Example code (e.g. SUMMER20)", - + "cancel": "Cancel", "saving": "Saving…", "submitting": "Submitting…", "saveResubmit": "Save & Resubmit", "submitApproval": "Submit for Approval", - + "deleteDialogTitle": "Delete template?", "deleteMetaDesc": "\"{name}\" will be deleted from Meta and from wacrm. Active broadcasts using this template will start failing on their next send. This can't be undone.", "deleteLocalDesc": "\"{name}\" will be deleted from wacrm. It was never submitted to Meta, so no remote cleanup is needed.", "deleting": "Deleting…", "delete": "Delete", - + "toastLoadFailed": "Failed to load templates", "toastSubmitFailed": "Failed to submit", "toastSubmitEditSuccess": "Edit submitted — Meta typically reviews within 24 hours.", @@ -1539,7 +1540,7 @@ "profileSaved": "Profile saved", "emailChangeFailed": "Email change failed: {message}", "profileSavedEmailCheck": "Profile saved — check your email to confirm the address change", - + "passwordTitle": "Password", "passwordDesc": "Use at least {min} characters. You will stay signed in on this device after changing it.", "currentPassword": "Current password", @@ -1553,7 +1554,7 @@ "currentPasswordIncorrect": "Current password is incorrect", "passwordUpdateFailed": "Password update failed: {message}", "passwordUpdated": "Password updated", - + "sessionsTitle": "Active sessions", "sessionsDesc": "Sign out of every device where you're logged in — including this one. Useful if you lost a laptop or shared your password.", "signOutAll": "Sign out of all devices", @@ -1600,7 +1601,7 @@ "nameLabel": "Name", "namePlaceholder": "e.g. Zapier automation", "scopesLabel": "Scopes", - "scopesHint": "A key with no scopes can still call GET /api/v1/me to verify it works.", + "scopesHint": "A key with no scopes can still call GET /api/v1/me to verify it works.", "cancel": "Cancel", "creating": "Creating…", "createKey": "Create key", @@ -1629,7 +1630,7 @@ }, "aiConfig": { "title": "Agent setup", - "description": "Bring your own OpenAI or Anthropic key. wacrm calls the provider directly with your key — no per-seat AI fees, and your data stays yours. This powers AI-drafted replies in the inbox, the auto-reply bot, and the Playground.", + "description": "Bring your own OpenAI, Anthropic, or Groq key. wacrm calls the provider directly with your key — no per-seat AI fees, and your data stays yours. This powers AI-drafted replies in the inbox, the auto-reply bot, and the Playground.", "adminOnlyConfig": "Only admins and owners can change the AI configuration.", "loadFailed": "Failed to load AI configuration", "providerAndKey": "Provider & key", diff --git a/messages/ko.json b/messages/ko.json index 7c54fc939e..2e174e3fd5 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -931,6 +931,7 @@ "no": "아니요" }, "addStep": "단계 추가", + "delete": "삭제", "config": { "messageText": "메시지 텍스트", "placeholderMessageText": "안녕하세요! 문의해주셔서 감사합니다…", @@ -1598,7 +1599,7 @@ "nameLabel": "이름", "namePlaceholder": "예: Zapier 자동화", "scopesLabel": "권한 범위", - "scopesHint": "권한 범위가 없는 키도 GET /api/v1/me를 호출해 동작을 확인할 수 있습니다.", + "scopesHint": "권한 범위가 없는 키도 GET /api/v1/me를 호출해 동작을 확인할 수 있습니다.", "cancel": "취소", "creating": "만드는 중…", "createKey": "키 생성", @@ -1627,7 +1628,7 @@ }, "aiConfig": { "title": "에이전트 설정", - "description": "직접 발급받은 OpenAI 또는 Anthropic 키를 사용하세요. wacrm은 회원님의 키로 공급자에 직접 요청하므로 좌석당 AI 비용이 없고 데이터도 온전히 회원님의 것입니다. 인박스의 AI 답장 초안, 자동 응답 봇, 플레이그라운드에 사용됩니다.", + "description": "직접 발급받은 OpenAI, Anthropic, 또는 Groq 키를 사용하세요. wacrm은 회원님의 키로 공급자에 직접 요청하므로 좌석당 AI 비용이 없고 데이터도 온전히 회원님의 것입니다. 인박스의 AI 답장 초안, 자동 응답 봇, 플레이그라운드에 사용됩니다.", "adminOnlyConfig": "관리자와 소유자만 AI 설정을 변경할 수 있습니다.", "loadFailed": "AI 설정을 불러오지 못했습니다", "providerAndKey": "공급자 & 키", diff --git a/package-lock.json b/package-lock.json index 92ddfffc44..4772361ea7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8660,17 +8660,6 @@ } } }, - "node_modules/next-intl/node_modules/@swc/helpers": { - "version": "0.5.23", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", - "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", diff --git a/src/app/api/ai/config/route.ts b/src/app/api/ai/config/route.ts index 08025dffbe..5ee74cdd4c 100644 --- a/src/app/api/ai/config/route.ts +++ b/src/app/api/ai/config/route.ts @@ -78,8 +78,8 @@ export async function POST(request: Request) { if (!body || typeof body !== 'object') return bad('Invalid request body') const provider = body.provider as AiProvider - if (provider !== 'openai' && provider !== 'anthropic') { - return bad('provider must be "openai" or "anthropic"') + if (provider !== 'openai' && provider !== 'anthropic' && provider !== 'groq') { + return bad('provider must be "openai", "anthropic", or "groq"') } const model = typeof body.model === 'string' ? body.model.trim() : '' if (!model) return bad('model is required') diff --git a/src/app/api/ai/test/route.ts b/src/app/api/ai/test/route.ts index 8fa1f5f014..c4bf56410f 100644 --- a/src/app/api/ai/test/route.ts +++ b/src/app/api/ai/test/route.ts @@ -27,9 +27,9 @@ export async function POST(request: Request) { } const provider = body.provider as AiProvider - if (provider !== 'openai' && provider !== 'anthropic') { + if (provider !== 'openai' && provider !== 'anthropic' && provider !== 'groq') { return NextResponse.json( - { error: 'provider must be "openai" or "anthropic"' }, + { error: 'provider must be "openai", "anthropic", or "groq"' }, { status: 400 }, ) } diff --git a/src/components/settings/ai-config.tsx b/src/components/settings/ai-config.tsx index 6028090935..d0690406de 100644 --- a/src/components/settings/ai-config.tsx +++ b/src/components/settings/ai-config.tsx @@ -41,11 +41,13 @@ const HANDOFF_QUEUE = '__queue__'; const PROVIDER_LABEL: Record = { openai: 'OpenAI', anthropic: 'Anthropic (Claude)', + groq: 'Groq', }; const KEY_PLACEHOLDER: Record = { openai: 'sk-...', anthropic: 'sk-ant-...', + groq: 'gsk_...', }; export function AiConfig() { @@ -281,6 +283,7 @@ export function AiConfig() { {PROVIDER_LABEL.anthropic} + {PROVIDER_LABEL.groq} diff --git a/src/components/settings/settings-overview.tsx b/src/components/settings/settings-overview.tsx index ce992f8936..24183ca4ed 100644 --- a/src/components/settings/settings-overview.tsx +++ b/src/components/settings/settings-overview.tsx @@ -40,7 +40,7 @@ export function SettingsOverview({ useAuth(); const { mode, theme } = useTheme(); const t = useTranslations('Settings.overview'); - const tRoles = useTranslations('roles'); + const tRoles = useTranslations('Settings.roles'); const tSections = useTranslations('Settings.sections'); const [counts, setCounts] = useState(null); diff --git a/src/components/settings/template-manager.tsx b/src/components/settings/template-manager.tsx index 827cb12496..aff6e3d22c 100644 --- a/src/components/settings/template-manager.tsx +++ b/src/components/settings/template-manager.tsx @@ -865,7 +865,7 @@ export function TemplateManager() {