Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
24 KiB
JSON
1 line
24 KiB
JSON
{"repo": "anmcollectivefun-ops/meditech-clinicops", "files": {"supabase/functions/generate-text-suggestion/index.ts": {"8d8675ca11bc09558288637672139db03c173815": {"t": 1786068274.1742, "n": 23109, "h": "a165755ad93179b9", "c": "// @ts-nocheck\n// Supabase Edge Functions run in Deno. The Next.js project typecheck does not\n// resolve URL imports, so this file is type-checked by the Supabase/Deno toolchain.\n// @ts-ignore Deno URL import\nimport { serve } from 'https://deno.land/std@0.224.0/http/server.ts'\n// @ts-ignore Deno URL import\nimport { createClient } from 'https://esm.sh/@supabase/supabase-js@2.49.8'\n\nconst corsHeaders = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Headers': 'authorization, x-client-info, x-supabase-api-version, apikey, content-type',\n 'Access-Control-Allow-Methods': 'POST, OPTIONS',\n}\n\nconst safeArray = (value: unknown) => Array.isArray(value) ? value : []\n\nconst safeSelect = async (supabase: ReturnType<typeof createClient>, table: string, columns: string, eventId: string) => {\n const { data, error } = await supabase\n .from(table)\n .select(columns)\n .eq('event_id', eventId)\n\n if (error) {\n console.warn(`AI text context ${table} load error:`, error.message)\n return []\n }\n\n return data || []\n}\n\nconst parseAiJson = (content: string) => {\n try {\n return JSON.parse(content)\n } catch {\n const start = content.indexOf('{')\n const end = content.lastIndexOf('}')\n if (start >= 0 && end > start) {\n return JSON.parse(content.slice(start, end + 1))\n }\n throw new Error('AI response did not contain valid JSON.')\n }\n}\n\nconst fallbackSuggestion = ({\n sectionKey,\n fieldKey,\n currentValue,\n event,\n tone,\n length,\n mode,\n documentType,\n relatedEntityTitle,\n additionalInstruction,\n}: Record<string, any>) => {\n const eventTitle = event?.title || 'wydarzenie'\n const location = event?.location ? ` w lokalizacji ${event.location}` : ''\n const short = String(length || '').toLowerCase().includes('krót')\n const isMedicalDocument = mode === 'medical_document' || ['medical_documents', 'medical_docs', 'patient_consents'].includes(String(sectionKey || ''))\n const procedure = relatedEntityTitle || event?.title || '[NAZWA PROCEDURY]'\n const groundedContext = String(additionalInstruction || '').trim()\n\n let suggestion = currentValue || ''\n\n if (!suggestion && isMedicalDocument) {\n if (documentType === 'questionnaire') {\n suggestion = [\n `WYWIAD MEDYCZNY PRZED PROCEDURĄ: ${procedure}`,\n '',\n 'Status dokumentu: WERSJA ROBOCZA DO WERYFIKACJI MEDYCZNEJ',\n 'Pacjent: [IMIE_PACJENTA] [NAZWISKO_PACJENTA]',\n 'Data wypełnienia: [DATA]',\n '',\n '1. Cel wywiadu',\n 'Zebranie informacji istotnych dla bezpiecznej kwalifikacji pacjenta do procedury.',\n '',\n '2. Pytania ogólne',\n '- Choroby przewlekłe: [TAK/NIE/OPIS]',\n '- Stale przyjmowane leki: [TAK/NIE/OPIS]',\n '- Alergie lub nadwrażliwości: [TAK/NIE/OPIS]',\n '- Ciąża lub karmienie piersią: [TAK/NIE/NIE DOTYCZY]',\n '- Wcześniejsze zabiegi w tym obszarze: [TAK/NIE/OPIS]',\n '',\n '3. Przeciwwskazania i czynniki ryzyka',\n '- Aktywna infekcja lub stan zapalny: [TAK/NIE]',\n '- Zaburzenia gojenia lub skłonność do bliznowców: [TAK/NIE]',\n '- Inne istotne informacje: [OPIS]',\n '',\n '4. Oświadczenie pacjenta',\n 'Oświadczam, że podane informacje są zgodne z moją wiedzą.',\n '',\n 'Podpis pacjenta: ____________________ Podpis personelu: ____________________',\n '',\n 'Uwaga: dokument wymaga zatwierdzenia przez osobę uprawnioną przed użyciem.'\n ].join('\\n')\n } else if (documentType === 'aftercare' || documentType === 'precare') {\n const phase = documentType === 'precare' ? 'PRZED PROCEDURĄ' : 'PO PROCEDURZE'\n suggestion = [\n `ZALECENIA DLA PACJENTA ${phase}: ${procedure}`,\n '',\n 'Status dokumentu: WERSJA ROBOCZA DO WERYFIKACJI MEDYCZNEJ',\n '',\n '1. Cel dokumentu',\n 'Przekazanie pacjentowi zrozumiałych zaleceń organizacyjnych i medycznych związanych z procedurą.',\n '',\n '2. Zalecenia ogólne',\n '- Stosować się do indywidualnych zaleceń osoby wykonującej procedurę.',\n '- Nie pomijać informacji o lekach, alergiach i chorobach przewlekłych.',\n '- Skontaktować się z placówką w razie niepokojących objawów lub wątpliwości.',\n '',\n '3. Objawy wymagające kontaktu',\n '- nasilający się ból, obrzęk lub zaczerwienienie,',\n '- objawy reakcji alergicznej,',\n '- gorączka, infekcja lub zaburzenia gojenia,',\n '- każdy objaw budzący niepokój pacjenta.',\n '',\n '4. Kontrola',\n 'Termin kontroli / follow-up: [TERMIN]',\n '',\n 'Uwaga: dokument wymaga zatwierdzenia przez osobę uprawnioną przed użyciem.'\n ].join('\\n')\n } else if (documentType === 'followup') {\n suggestion = [\n `FOLLOW-UP DO PACJENTA PO WIZYCIE / PROCEDURZE: ${procedure}`,\n '',\n 'Status treści: WERSJA ROBOCZA DO WERYFIKACJI',\n '',\n 'Dzień dobry [IMIE_PACJENTA],',\n '',\n 'kontaktujemy się po wizycie, aby upewnić się, że wszystko przebiega prawidłowo.',\n '',\n 'Prosimy o kontakt z placówką, jeśli pojawiły się niepokojące objawy, nasilony ból, obrzęk, zaczerwienienie, objawy infekcji lub reakcja alergiczna.',\n '',\n 'Termin kontroli / kolejnego kontaktu: [TERMIN]',\n '',\n 'Pozdrawiamy,',\n '[NAZWA_PLACOWKI]',\n '',\n 'Uwaga: treść wymaga zatwierdzenia przez osobę uprawnioną przed wysyłką.'\n ].join('\\n')\n } else if (documentType === 'rodo') {\n suggestion = [\n 'INFORMACJA O PRZETWARZANIU DANYCH OSOBOWYCH I ZGODY PACJENTA',\n '',\n 'Status dokumentu: WERSJA ROBOCZA DO WERYFIKACJI PRAWNEJ',\n '',\n '1. Administrator danych',\n 'Administratorem danych jest: [NAZWA_PLACOWKI], [ADRES], [KONTAKT].',\n '',\n '2. Cele przetwarzania danych',\n '- obsługa pacjenta i rezerwacji,',\n '- prowadzenie dokumentacji związanej z wizytą,',\n '- kontakt organizacyjny,',\n '- działania marketingowe wyłącznie po wyrażeniu odrębnej zgody.',\n '',\n '3. Zgody szczegółowe',\n '[ ] Wyrażam zgodę na kontakt SMS/e-mail w sprawach organizacyjnych.',\n '[ ] Wyrażam zgodę na kontakt marketingowy.',\n '[ ] Wyrażam zgodę na wykorzystanie wizerunku / zdjęć przed i po, jeżeli dotyczy.',\n '',\n 'Podpis pacjenta: ____________________ Data: [DATA]',\n '',\n 'Uwaga: dokument wymaga weryfikacji prawnej przed użyciem.'\n ].join('\\n')\n } else {\n suggestion = [\n `ZGODA PACJENTA NA PROCEDURĘ: ${procedure}`,\n '',\n 'Status dokumentu: WERSJA ROBOCZA DO WERYFIKACJI MEDYCZNO-PRAWNEJ',\n 'Pacjent: [IMIE_PACJENTA] [NAZWISKO_PACJENTA]',\n 'Data: [DATA]',\n 'Placówka: [NAZWA_PLACOWKI]',\n '',\n '1. Opis procedury',\n 'Pacjent został poinformowany o charakterze, celu i spodziewanym przebiegu procedury.',\n '',\n '2. Możliwe przeciwwskazania',\n '- aktywne infekcje lub stany zapalne,',\n '- ciąża lub karmienie piersią, jeżeli dotyczy procedury,',\n '- alergie lub nadwrażliwości na stosowane preparaty,',\n '- inne przeciwwskazania wskazane przez osobę kwalifikującą.',\n '',\n '3. Możliwe działania niepożądane / powikłania',\n '- ból, obrzęk, zaczerwienienie, siniaki,',\n '- reakcja alergiczna,',\n '- infekcja lub zaburzenia gojenia,',\n '- efekt odbiegający od oczekiwań pacjenta.',\n '',\n '4. Alternatywy i możliwość odmowy',\n 'Pacjent został poinformowany o możliwości rezygnacji z procedury oraz o dostępnych alternatywach, jeżeli występują.',\n '',\n '5. Oświadczenia pacjenta',\n '[ ] Oświadczam, że miałem/am możliwość zadania pytań.',\n '[ ] Oświadczam, że przekazałem/am prawdziwe informacje o stanie zdrowia.',\n '[ ] Wyrażam świadomą zgodę na wykonanie procedury.',\n '',\n '6. Zalecenia',\n 'Pacjent otrzymał zalecenia przed i po procedurze oraz został poinformowany o konieczności kontaktu w razie niepokojących objawów.',\n '',\n 'Podpis pacjenta: ____________________ Podpis osoby uprawnionej: ____________________',\n '',\n 'Uwaga: dokument wymaga zatwierdzenia przez osobę uprawnioną przed użyciem z pacjentem.'\n ].join('\\n')\n }\n }\n\n if (!suggestion && sectionKey === 'doctors') {\n const doctorName = relatedEntityTitle || '[IMIE I NAZWISKO LEKARZA]'\n suggestion = [\n `${doctorName} - profil specjalisty`,\n '',\n groundedContext || 'Uzupełnij specjalizację, zakres pracy, doświadczenie i obszary zabiegowe lekarza.',\n '',\n 'Opis jest szkicem do weryfikacji. Nie dopisuj tytułów, certyfikatów, lat doświadczenia ani nazw procedur bez potwierdzonych danych.'\n ].join('\\n')\n }\n\n if (!suggestion && sectionKey === 'preparations') {\n const preparationName = relatedEntityTitle || '[NAZWA PREPARATU]'\n suggestion = [\n `${preparationName} - opis preparatu`,\n '',\n groundedContext || 'Uzupełnij kategorię, zastosowanie i ważne uwagi dla zespołu.',\n '',\n 'Opis jest szkicem do weryfikacji z dokumentacją producenta. Nie dopisuj składu, wskazań, certyfikatów ani obietnic efektu bez potwierdzonych danych.'\n ].join('\\n')\n }\n\n if (!suggestion) {\n if (String(fieldKey || '').includes('title')) {\n suggestion = sectionKey === 'faq'\n ? 'Najważniejsze informacje'\n : sectionKey === 'transport'\n ? 'Transport i dojazd'\n : sectionKey === 'menu'\n ? 'Menu wydarzenia'\n : `Sekcja wydarzenia ${eventTitle}`\n } else if (String(fieldKey || '').includes('cta')) {\n suggestion = sectionKey === 'menu'\n ? 'Wybierz menu'\n : sectionKey === 'transport'\n ? 'Potwierdź transport'\n : sectionKey === 'workshops'\n ? 'Zapisz się'\n : 'Sprawdź szczegóły'\n } else {\n suggestion = short\n ? `Sprawdź aktualne informacje dotyczące wydarzenia ${eventTitle}${location}.`\n : `Tutaj znajdziesz aktualne informacje dotyczące wydarzenia ${eventTitle}${location}. Organizator może aktualizować tę sekcję na bieżąco, dlatego warto wracać do strony przed wydarzeniem.`\n }\n }\n\n if (tone === 'eco' && !suggestion.toLowerCase().includes('cyfrow')) {\n suggestion += short\n ? ' Korzystamy z cyfrowego flow, aby ograniczyć wydruki.'\n : ' To element cyfrowego centrum uczestnika, które pomaga ograniczać wydruki i szybciej aktualizować informacje.'\n }\n\n return {\n suggestion,\n reason: isMedicalDocument\n ? 'Użyto bezpiecznego lokalnego szkieletu dokumentu medycznego. Treść jest projektem do zatwierdzenia.'\n : ['doctors', 'preparations'].includes(String(sectionKey || ''))\n ? 'Użyto bezpiecznego lokalnego szkicu opartego wyłącznie na danych wpisanych w formularzu.'\n : 'Użyto podstawowych danych wydarzenia i lokalnego fallbacku bez wywołania modelu AI.',\n missing_context: isMedicalDocument\n ? ['Uzupełnij nazwę procedury, przeciwwskazania, możliwe powikłania, zalecenia oraz dane placówki przed zatwierdzeniem.']\n : ['doctors', 'preparations'].includes(String(sectionKey || ''))\n ? ['Uzupełnij konkretne, zweryfikowane dane profilu przed publikacją.']\n : (event?.title ? [] : ['Uzupełnij tytuł wydarzenia, aby sugestie były dokładniejsze.']),\n }\n}\n\nserve(async (req) => {\n if (req.method === 'OPTIONS') {\n return new Response('ok', { headers: corsHeaders })\n }\n\n try {\n const body = await req.json()\n const {\n eventId,\n sectionKey,\n fieldKey,\n currentValue = '',\n relatedEntityId = null,\n relatedEntityTitle = '',\n mode = 'general',\n documentType = 'consent',\n tone = 'premium',\n length = 'średnia',\n instruction = '',\n additionalInstruction = '',\n } = body || {}\n\n if (!eventId || !sectionKey || !fieldKey) {\n return new Response(JSON.stringify({ error: 'Missing eventId, sectionKey or fieldKey' }), {\n status: 400,\n headers: { ...corsHeaders, 'Content-Type': 'application/json' },\n })\n }\n\n const supabaseUrl = Deno.env.get('SUPABASE_URL')\n const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') || Deno.env.get('SUPABASE_ANON_KEY')\n\n if (!supabaseUrl || !supabaseKey) {\n throw new Error('Missing Supabase Edge Function environment variables.')\n }\n\n const supabase = createClient(supabaseUrl, supabaseKey)\n\n const { data: event, error: eventError } = await supabase\n .from('b2b_events')\n .select(`\n id,\n title,\n description,\n location,\n event_date,\n start_date,\n end_date,\n menu_section_title,\n menu_section_description,\n gadgets_section_title,\n gadgets_section_description,\n transport_section_title,\n transport_section_description,\n workshops_section_title,\n workshops_section_description,\n faq_section_title,\n faq_section_description,\n eventpass_section_title,\n eventpass_section_description\n `)\n .eq('id', eventId)\n .maybeSingle()\n\n if (eventError) {\n console.warn('AI text event context error:', eventError.message)\n }\n\n const context: Record<string, unknown> = {\n event: event || null,\n sectionKey,\n fieldKey,\n currentValue,\n relatedEntityId,\n relatedEntityTitle,\n mode,\n documentType,\n tone,\n length,\n instruction,\n additionalInstruction,\n items: {},\n }\n\n if (['workshops', 'sessions', 'agenda', 'speakers'].includes(sectionKey)) {\n context.items.sessions = await safeSelect(\n supabase,\n 'event_sessions',\n 'id, title, description, session_type, speaker_name, location, start_time, end_time, is_public, is_active',\n eventId\n )\n context.items.speakers = await safeSelect(\n supabase,\n 'speakers',\n 'id, first_name, last_name, title, bio, is_visible',\n eventId\n )\n }\n\n if (sectionKey === 'menu') {\n context.items.meals = await safeSelect(\n supabase,\n 'event_meals',\n 'id, name, description, meal_type, dietary_category, allergens, is_active',\n eventId\n )\n context.items.cateringOffers = await safeSelect(\n supabase,\n 'event_catering_offers',\n 'company_name, notes, avoids_plastic, local_products, reusable_packaging, food_donation_possible, portioning_by_rsvp, status',\n eventId\n )\n }\n\n if (sectionKey === 'transport') {\n const [transportChoices, carpoolingAds, carpoolingReservations, routes] = await Promise.all([\n safeSelect(supabase, 'event_attendee_transport_choices', 'transport, transport_type, status', eventId),\n safeSelect(supabase, 'carpooling_ads', 'route_from, seats_avail, used, status', eventId),\n safeSelect(supabase, 'carpooling_reservations', 'status, route_from', eventId),\n safeSelect(supabase, 'organized_transport_routes', 'route_name, public_title, public_description, vehicle_type, is_public, is_active', eventId),\n ])\n context.items.transport = {\n transportChoicesCount: safeArray(transportChoices).length,\n carpoolingAdsCount: safeArray(carpoolingAds).length,\n carpoolingReservationsCount: safeArray(carpoolingReservations).length,\n publicRoutes: routes,\n }\n }\n\n if (sectionKey === 'gadgets') {\n context.items.gadgets = await safeSelect(\n supabase,\n 'event_gadgets',\n 'id, name, public_label, description, category, stock_quantity, track_stock, is_active, allow_decline',\n eventId\n )\n }\n\n if (sectionKey === 'eco') {\n const { data: report } = await supabase\n .from('eco_ai_reports')\n .select('report, confidence, updated_at')\n .eq('event_id', eventId)\n .maybeSingle()\n\n context.items.ecoReport = report?.report || null\n }\n\n if (sectionKey === 'faq') {\n const [meals, routes, sessions] = await Promise.all([\n safeSelect(supabase, 'event_meals', 'name, dietary_category, is_active', eventId),\n safeSelect(supabase, 'organized_transport_routes', 'public_title, route_name, is_public, is_active', eventId),\n safeSelect(supabase, 'event_sessions', 'title, session_type, is_public, is_active', eventId),\n ])\n context.items.faqSignals = {\n mealsCount: safeArray(meals).length,\n routesCount: safeArray(routes).length,\n sessionsCount: safeArray(sessions).length,\n }\n }\n\n if (['doctors', 'preparations'].includes(sectionKey)) {\n context.items.clinicProfiles = await safeSelect(\n supabase,\n 'event_partners',\n 'id, type, first_name, last_name, title, company, bio, sponsor_name, sponsor_category, is_visible',\n eventId\n )\n }\n\n const isMedicalDocument = mode === 'medical_document' || ['medical_documents', 'medical_docs', 'patient_consents'].includes(String(sectionKey || ''))\n const isGroundedClinicProfile = ['doctors', 'preparations'].includes(String(sectionKey || ''))\n const fallback = fallbackSuggestion({ sectionKey, fieldKey, currentValue, event, tone, length, mode, documentType, relatedEntityTitle, additionalInstruction })\n\n try {\n const apiKey = Deno.env.get('DEEPSEEK_API_KEY')\n if (!apiKey) throw new Error('Missing DEEPSEEK_API_KEY')\n\n const model = Deno.env.get('AI_MODEL') || 'deepseek-chat'\n\n const response = await fetch('https://api.deepseek.com/chat/completions', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n model,\n response_format: { type: 'json_object' },\n messages: [\n {\n role: 'system',\n content: isMedicalDocument\n ? [\n 'Jesteś asystentem tworzenia roboczych szablonów dokumentów dla placówki medycznej.',\n 'Tworzysz wyłącznie projekt dokumentu po polsku, do późniejszej weryfikacji przez osobę uprawnioną medycznie i prawnie.',\n 'Nie udzielasz diagnozy, nie kwalifikujesz pacjenta, nie zastępujesz lekarza ani prawnika.',\n 'Nie twierdzisz, że dokument jest zgodny z prawem lub gotowy do użycia.',\n 'Każdy dokument oznacz jako wersję roboczą do zatwierdzenia.',\n 'Nie wymyślaj konkretnych dawek, procedur, przeciwwskazań ani powikłań, jeśli nie wynikają z kontekstu lub instrukcji.',\n 'Używaj sekcji: dane pacjenta, dane placówki, opis procedury, przeciwwskazania, ryzyka/powikłania, alternatywy, oświadczenia pacjenta, zalecenia, podpisy, wersja dokumentu.',\n 'Dla wywiadu medycznego twórz pytania i pola odpowiedzi, nie rozpoznania.',\n 'Dla RODO twórz szkic administracyjny z miejscami na dane placówki i zgody szczegółowe.',\n 'Zwróć wyłącznie JSON w kształcie: {\"suggestion\":\"...\",\"reason\":\"...\",\"missing_context\":[\"...\"]}.',\n ].join(' ')\n : isGroundedClinicProfile\n ? [\n 'Jesteś asystentem tworzenia krótkich, profesjonalnych opisów dla systemu ClinicOps w placówce medycznej.',\n 'Pisz po polsku i twórz treści wyłącznie na podstawie danych przekazanych w context, currentValue oraz additionalInstruction.',\n 'Nie wymyślaj faktów. Nie dopisuj certyfikatów, tytułów, lat doświadczenia, uczelni, producentów, składu, wskazań, przeciwwskazań, efektów klinicznych ani obietnic rezultatów, jeśli nie wynikają wprost z danych.',\n 'Dla lekarza wolno opisać tylko podane imię i nazwisko, specjalizację/rolę, gabinet/zespół i podany opis.',\n 'Dla preparatu wolno opisać tylko podaną nazwę, kategorię/zastosowanie i podany opis. W razie braków wskaż, że opis wymaga weryfikacji z dokumentacją producenta.',\n 'Tekst ma być neutralny, bez diagnozowania, bez kwalifikowania pacjenta i bez medycznych obietnic.',\n 'Jeśli brakuje danych, napisz bezpieczny szkic i wpisz braki do missing_context zamiast uzupełniać je samodzielnie.',\n 'Zwróć wyłącznie JSON w kształcie: {\"suggestion\":\"...\",\"reason\":\"...\",\"missing_context\":[\"...\"]}.',\n ].join(' ')\n : [\n 'Jesteś AI copywriterem i asystentem w panelu ClinicOps.',\n 'Pomagasz tworzyć treści strony wydarzenia na podstawie danych już uzupełnionych w plannerze.',\n 'Pisz po polsku. Pisz konkretnie, nowocześnie i naturalnie.',\n 'Nie wymyślaj faktów, prelegentów, godzin, cen ani lokalizacji.',\n 'Jeśli czegoś brakuje, użyj neutralnego sformułowania.',\n 'Nie nadpisuj danych. Zwracasz tylko propozycję tekstu.',\n 'Dopasuj ton i długość do parametrów.',\n 'Zwróć wyłącznie JSON w kształcie: {\"suggestion\":\"...\",\"reason\":\"...\",\"missing_context\":[\"...\"]}.',\n ].join(' '),\n },\n {\n role: 'user',\n content: JSON.stringify({\n context,\n expectedJson: {\n suggestion: 'tekst propozycji',\n reason: 'krótkie wyjaśnienie, z jakich danych skorzystano',\n missing_context: ['lista brakujących danych, jeśli są'],\n },\n }),\n },\n ],\n }),\n })\n\n if (!response.ok) {\n const text = await response.text()\n throw new Error(`DeepSeek error ${response.status}: ${text}`)\n }\n\n const aiData = await response.json()\n const content = aiData?.choices?.[0]?.message?.content\n if (!content) throw new Error('DeepSeek response missing content.')\n\n const parsed = parseAiJson(content)\n\n return new Response(JSON.stringify({\n suggestion: String(parsed.suggestion || fallback.suggestion || ''),\n reason: String(parsed.reason || fallback.reason || ''),\n missing_context: Array.isArray(parsed.missing_context) ? parsed.missing_context : fallback.missing_context,\n }), {\n headers: { ...corsHeaders, 'Content-Type': 'application/json' },\n })\n } catch (aiError) {\n console.warn('AI text suggestion fallback:', aiError instanceof Error ? aiError.message : aiError)\n\n return new Response(JSON.stringify(fallback), {\n headers: { ...corsHeaders, 'Content-Type': 'application/json' },\n })\n }\n } catch (error) {\n console.error('generate-text-suggestion error:', error)\n return new Response(JSON.stringify({\n error: error instanceof Error ? error.message : 'Unknown error',\n }), {\n status: 500,\n headers: { ...corsHeaders, 'Content-Type': 'application/json' },\n })\n }\n})\n"}}}} |