docs.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { SupabaseClient } from '@supabase/supabase-js'
  2. import { ApplicationError, clippy, UserError } from 'ai-commands/edge'
  3. import { NextRequest } from 'next/server'
  4. import OpenAI from 'openai'
  5. export const config = {
  6. runtime: 'edge',
  7. /* To avoid OpenAI errors, restrict to the Vercel Edge Function regions that
  8. overlap with the OpenAI API regions.
  9. Reference for Vercel regions: https://vercel.com/docs/edge-network/regions#region-list
  10. Reference for OpenAI regions: https://help.openai.com/en/articles/5347006-openai-api-supported-countries-and-territories
  11. */
  12. regions: [
  13. 'arn1',
  14. 'bom1',
  15. 'cdg1',
  16. 'cle1',
  17. 'cpt1',
  18. 'dub1',
  19. 'fra1',
  20. 'gru1',
  21. 'hnd1',
  22. 'iad1',
  23. 'icn1',
  24. 'kix1',
  25. 'lhr1',
  26. 'pdx1',
  27. 'sfo1',
  28. 'sin1',
  29. 'syd1',
  30. ],
  31. }
  32. const openAiKey = process.env.OPENAI_API_KEY
  33. const brivenUrl = process.env.NEXT_PUBLIC_BRIVEN_URL
  34. const brivenServiceKey = process.env.NEXT_PUBLIC_BRIVEN_ANON_KEY
  35. export default async function handler(req: NextRequest) {
  36. if (!openAiKey) {
  37. return new Response(
  38. JSON.stringify({
  39. error: 'No OPENAI_API_KEY set. Create this environment variable to use AI features.',
  40. }),
  41. {
  42. status: 500,
  43. headers: { 'Content-Type': 'application/json' },
  44. }
  45. )
  46. }
  47. if (!brivenUrl) {
  48. return new Response(
  49. JSON.stringify({
  50. error:
  51. 'No NEXT_PUBLIC_BRIVEN_URL set. Create this environment variable to use AI features.',
  52. }),
  53. {
  54. status: 500,
  55. headers: { 'Content-Type': 'application/json' },
  56. }
  57. )
  58. }
  59. if (!brivenServiceKey) {
  60. return new Response(
  61. JSON.stringify({
  62. error:
  63. 'No NEXT_PUBLIC_BRIVEN_ANON_KEY set. Create this environment variable to use AI features.',
  64. }),
  65. {
  66. status: 500,
  67. headers: { 'Content-Type': 'application/json' },
  68. }
  69. )
  70. }
  71. const { method } = req
  72. switch (method) {
  73. case 'POST':
  74. return handlePost(req)
  75. default:
  76. return new Response(
  77. JSON.stringify({ data: null, error: { message: `Method ${method} Not Allowed` } }),
  78. {
  79. status: 405,
  80. headers: { 'Content-Type': 'application/json', Allow: 'POST' },
  81. }
  82. )
  83. }
  84. }
  85. async function handlePost(request: NextRequest) {
  86. const openai = new OpenAI({ apiKey: openAiKey })
  87. const body = await (request.json() as Promise<{
  88. messages: { content: string; role: 'user' | 'assistant' }[]
  89. }>)
  90. const { messages } = body
  91. if (!messages) {
  92. throw new UserError('Missing messages in request data')
  93. }
  94. const brivenClient = new SupabaseClient(brivenUrl!, brivenServiceKey!)
  95. try {
  96. const response = await clippy(openai, brivenClient, messages)
  97. // Proxy the streamed SSE response from OpenAI
  98. return new Response(response.body, {
  99. headers: {
  100. 'Content-Type': 'text/event-stream',
  101. },
  102. })
  103. } catch (error: unknown) {
  104. console.error(error)
  105. if (error instanceof UserError) {
  106. return new Response(
  107. JSON.stringify({
  108. error: error.message,
  109. data: error.data,
  110. }),
  111. {
  112. status: 400,
  113. headers: { 'Content-Type': 'application/json' },
  114. }
  115. )
  116. } else if (error instanceof ApplicationError) {
  117. // Print out application errors with their additional data
  118. console.error(`${error.message}: ${JSON.stringify(error.data)}`)
  119. } else {
  120. // Print out unexpected errors as is to help with debugging
  121. console.error(error)
  122. }
  123. console.log('Returning generic 500 ApplicationError to client')
  124. // TODO: include more response info in debug environments
  125. return new Response(
  126. JSON.stringify({
  127. error: 'There was an error processing your request',
  128. }),
  129. {
  130. status: 500,
  131. headers: { 'Content-Type': 'application/json' },
  132. }
  133. )
  134. }
  135. }