From 40c1599e64c1fb30155581ab87f88fb2fe6cf14b Mon Sep 17 00:00:00 2001 From: Arifuzzamanjoy Date: Tue, 21 Jul 2026 02:42:31 +0600 Subject: [PATCH 1/2] feat: integrate Groq API and resolve next-intl parsing issues This commit introduces native support for the Groq API as an AI provider alongside OpenAI and Anthropic. It also resolves a series of translation parsing errors that were crashing the settings pages. Changes included: - Replaced t() with t.raw() for strings containing raw HTML tags or literal curly braces to bypass ICU parsing errors. - Cleaned up invalid HTML attributes inside ICU translation tags (scopesHint). - Relaxed the ai_configs_provider_check database constraint to allow 'groq'. - Replicated OpenAI's provider adapter for Groq's compatibility API endpoint. - Updated Settings UI and translations to natively support Groq with llama-3.1-8b-instant as the default model. --- messages/en.json | 4 +- messages/ko.json | 4 +- package-lock.json | 11 --- src/app/api/ai/config/route.ts | 4 +- src/app/api/ai/test/route.ts | 4 +- src/components/settings/ai-config.tsx | 3 + src/components/settings/settings-overview.tsx | 2 +- src/components/settings/template-manager.tsx | 4 +- src/components/settings/whatsapp-config.tsx | 10 +-- src/lib/ai/defaults.ts | 1 + src/lib/ai/generate.ts | 4 ++ src/lib/ai/providers/groq.ts | 69 +++++++++++++++++++ src/lib/ai/types.ts | 2 +- supabase/migrations/001_initial_schema.sql | 32 ++++----- supabase/migrations/006_automations.sql | 8 +-- supabase/migrations/009_message_actions.sql | 2 +- supabase/migrations/010_flows.sql | 8 +-- supabase/migrations/017_account_sharing.sql | 4 +- supabase/migrations/027_notifications.sql | 2 +- .../migrations/035_interactive_messages.sql | 2 +- supabase/migrations/037_ai_provider_groq.sql | 8 +++ 21 files changed, 131 insertions(+), 57 deletions(-) create mode 100644 src/lib/ai/providers/groq.ts create mode 100644 supabase/migrations/037_ai_provider_groq.sql diff --git a/messages/en.json b/messages/en.json index a3dd25ff20..d56c4eb8cd 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1600,7 +1600,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 +1629,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..b675b0aac0 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -1598,7 +1598,7 @@ "nameLabel": "이름", "namePlaceholder": "예: Zapier 자동화", "scopesLabel": "권한 범위", - "scopesHint": "권한 범위가 없는 키도 GET /api/v1/me를 호출해 동작을 확인할 수 있습니다.", + "scopesHint": "권한 범위가 없는 키도 GET /api/v1/me를 호출해 동작을 확인할 수 있습니다.", "cancel": "취소", "creating": "만드는 중…", "createKey": "키 생성", @@ -1627,7 +1627,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() {