chore: support google gemini and deepseek in ai text demo
This commit is contained in:
parent
4384a1d43f
commit
971b0d65a0
@ -181,6 +181,8 @@ FAL_API_KEY=""
|
||||
FIREWORKS_API_KEY=""
|
||||
OPENAI_API_KEY=""
|
||||
REPLICATE_API_TOKEN=""
|
||||
GOOGLE_API_KEY=""
|
||||
DEEPSEEK_API_KEY=""
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Web Content Analyzer (Firecrawl)
|
||||
|
@ -24,8 +24,10 @@
|
||||
"knip": "knip"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/deepseek": "^0.2.16",
|
||||
"@ai-sdk/fal": "^0.1.12",
|
||||
"@ai-sdk/fireworks": "^0.2.14",
|
||||
"@ai-sdk/google": "^1.2.22",
|
||||
"@ai-sdk/google-vertex": "^2.2.24",
|
||||
"@ai-sdk/openai": "^1.1.13",
|
||||
"@ai-sdk/replicate": "^0.2.8",
|
||||
|
81
pnpm-lock.yaml
generated
81
pnpm-lock.yaml
generated
@ -8,12 +8,18 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@ai-sdk/deepseek':
|
||||
specifier: ^0.2.16
|
||||
version: 0.2.16(zod@3.25.64)
|
||||
'@ai-sdk/fal':
|
||||
specifier: ^0.1.12
|
||||
version: 0.1.12(zod@3.25.64)
|
||||
'@ai-sdk/fireworks':
|
||||
specifier: ^0.2.14
|
||||
version: 0.2.14(zod@3.25.64)
|
||||
'@ai-sdk/google':
|
||||
specifier: ^1.2.22
|
||||
version: 1.2.22(zod@3.25.64)
|
||||
'@ai-sdk/google-vertex':
|
||||
specifier: ^2.2.24
|
||||
version: 2.2.24(zod@3.25.64)
|
||||
@ -381,6 +387,12 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.0.0
|
||||
|
||||
'@ai-sdk/deepseek@0.2.16':
|
||||
resolution: {integrity: sha512-pIlwtjNehCpDr1wqxtSbXshynW4CiwS6S3yAKHzHi73QtmS2Hg9kE1DB0zgENKaZLmbsc4UgigGM6FzuUd4M8Q==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.0.0
|
||||
|
||||
'@ai-sdk/fal@0.1.12':
|
||||
resolution: {integrity: sha512-Z0pUUR3qwLTj4HXgGJSes5fwjUbSowsMiKbpYKGl6V51sQeUk2EjZctdN4+a+GBuDNCP6Y32Wi8ejM54OMee+w==}
|
||||
engines: {node: '>=18'}
|
||||
@ -405,12 +417,24 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.0.0
|
||||
|
||||
'@ai-sdk/google@1.2.22':
|
||||
resolution: {integrity: sha512-Ppxu3DIieF1G9pyQ5O1Z646GYR0gkC57YdBqXJ82qvCdhEhZHu0TWhmnOoeIWe2olSbuDeoOY+MfJrW8dzS3Hw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.0.0
|
||||
|
||||
'@ai-sdk/openai-compatible@0.2.14':
|
||||
resolution: {integrity: sha512-icjObfMCHKSIbywijaoLdZ1nSnuRnWgMEMLgwoxPJgxsUHMx0aVORnsLUid4SPtdhHI3X2masrt6iaEQLvOSFw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.0.0
|
||||
|
||||
'@ai-sdk/openai-compatible@0.2.16':
|
||||
resolution: {integrity: sha512-LkvfcM8slJedRyJa/MiMiaOzcMjV1zNDwzTHEGz7aAsgsQV0maLfmJRi/nuSwf5jmp0EouC+JXXDUj2l94HgQw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.0.0
|
||||
|
||||
'@ai-sdk/openai@1.1.13':
|
||||
resolution: {integrity: sha512-IdChK1pJTW3NQis02PG/hHTG0gZSyQIMOLPt7f7ES56C0xH2yaKOU1Tp2aib7pZzWGwDlzTOW2h5TtAB8+V6CQ==}
|
||||
engines: {node: '>=18'}
|
||||
@ -593,28 +617,24 @@ packages:
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@biomejs/cli-linux-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
||||
resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@biomejs/cli-linux-x64@1.9.4':
|
||||
resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@biomejs/cli-win32-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
|
||||
@ -1487,79 +1507,67 @@ packages:
|
||||
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.0.5':
|
||||
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.0.4':
|
||||
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.0.4':
|
||||
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
|
||||
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
|
||||
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.33.5':
|
||||
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.33.5':
|
||||
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.33.5':
|
||||
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.33.5':
|
||||
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.33.5':
|
||||
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
|
||||
@ -1666,56 +1674,48 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.2.1':
|
||||
resolution: {integrity: sha512-gXDX5lIboebbjhiMT6kFgu4svQyjoSed6dHyjx5uZsjlvTwOAnZpn13w9XDaIMFFHw7K8CpBK7HfDKw0VZvUXQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.1.2':
|
||||
resolution: {integrity: sha512-9CF1Pnivij7+M3G74lxr+e9h6o2YNIe7QtExWq1KUK4hsOLTBv6FJikEwCaC3NeYTflzrm69E5UfwEAbV2U9/g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.2.1':
|
||||
resolution: {integrity: sha512-3v0pF/adKZkBWfUffmB/ROa+QcNTrnmYG4/SS+r52HPwAK479XcWoES2I+7F7lcbqc7mTeVXrIvb4h6rR/iDKg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.1.2':
|
||||
resolution: {integrity: sha512-tINV7WmcTUf4oM/eN3Yuu/f8jQ5C6AkueZPKeALs/qfdfX57eNv4Ij7rt0SA6iZ8+fMobVfcFVv664Op0caCCg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.2.1':
|
||||
resolution: {integrity: sha512-RbsVq2iB6KFJRZ2cHrU67jLVLKeuOIhnQB05ygu5fCNgg8oTewxweJE8XlLV+Ii6Y6u4EHwETdUiRNXIAfpBww==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.1.2':
|
||||
resolution: {integrity: sha512-jf2IseC4WRsGkzeUw/cK3wci9pxR53GlLAt30+y+B+2qAQxMw6WAC3QrANIKxkcoPU3JFh/10uFfmoMDF9JXKg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.2.1':
|
||||
resolution: {integrity: sha512-QHsMLAyAIu6/fWjHmkN/F78EFPKmhQlyX5C8pRIS2RwVA7z+t9cTb0IaYWC3EHLOTjsU7MNQW+n2xGXr11QPpg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.1.2':
|
||||
resolution: {integrity: sha512-wvg7MlfnaociP7k8lxLX4s2iBJm4BrNiNFhVUY+Yur5yhAJHfkS8qPPeDEUH8rQiY0PX3u/P7Q/wcg6Mv6GSAA==}
|
||||
@ -2267,37 +2267,31 @@ packages:
|
||||
resolution: {integrity: sha512-BhEzNLjn4HjP8+Q18D3/jeIDBxW7OgoJYIjw2CaaysnYneoTlij8hPTKxHfyqq4IGM3fFs9TLR/k338M3zkQ7g==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-resolver/binding-linux-arm64-musl@11.2.0':
|
||||
resolution: {integrity: sha512-yxbMYUgRmN2V8x8XoxmD/Qq6aG7YIW3ToMDILfmcfeeRRVieEJ3DOWBT0JSE+YgrOy79OyFDH/1lO8VnqLmDQQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-resolver/binding-linux-riscv64-gnu@11.2.0':
|
||||
resolution: {integrity: sha512-QG1UfgC2N2qhW1tOnDCgB/26vn1RCshR5sYPhMeaxO1gMQ3kEKbZ3QyBXxrG1IX5qsXYj5hPDJLDYNYUjRcOpg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-resolver/binding-linux-s390x-gnu@11.2.0':
|
||||
resolution: {integrity: sha512-uqTDsQdi6mrkSV1gvwbuT8jf/WFl6qVDVjNlx7IPSaAByrNiJfPrhTmH8b+Do58Dylz7QIRZgxQ8CHIZSyBUdg==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-resolver/binding-linux-x64-gnu@11.2.0':
|
||||
resolution: {integrity: sha512-GZdHXhJ7p6GaQg9MjRqLebwBf8BLvGIagccI6z5yMj4fV3LU4QuDfwSEERG+R6oQ/Su9672MBqWwncvKcKT68w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-resolver/binding-linux-x64-musl@11.2.0':
|
||||
resolution: {integrity: sha512-YBAC3GOicYznReG2twE7oFPSeK9Z1f507z1EYWKg6HpGYRYRlJyszViu7PrhMT85r/MumDTs429zm+CNqpFWOA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-resolver/binding-wasm32-wasi@11.2.0':
|
||||
resolution: {integrity: sha512-+qlIg45CPVPy+Jn3vqU1zkxA/AAv6e/2Ax/ImX8usZa8Tr2JmQn/93bmSOOOnr9fXRV9d0n4JyqYzSWxWPYDEw==}
|
||||
@ -3917,28 +3911,24 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.0.14':
|
||||
resolution: {integrity: sha512-gVkJdnR/L6iIcGYXx64HGJRmlme2FGr/aZH0W6u4A3RgPMAb+6ELRLi+UBiH83RXBm9vwCfkIC/q8T51h8vUJQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.0.14':
|
||||
resolution: {integrity: sha512-EE+EQ+c6tTpzsg+LGO1uuusjXxYx0Q00JE5ubcIGfsogSKth8n8i2BcS2wYTQe4jXGs+BQs35l78BIPzgwLddw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.0.14':
|
||||
resolution: {integrity: sha512-KCCOzo+L6XPT0oUp2Jwh233ETRQ/F6cwUnMnR0FvMUCbkDAzHbcyOgpfuAtRa5HD0WbTbH4pVD+S0pn1EhNfbw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.0.14':
|
||||
resolution: {integrity: sha512-AHObFiFL9lNYcm3tZSPqa/cHGpM5wOrNmM2uOMoKppp+0Hom5uuyRh0QkOp7jftsHZdrZUpmoz0Mp6vhh2XtUg==}
|
||||
@ -5264,28 +5254,24 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.29.2:
|
||||
resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.29.2:
|
||||
resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.29.2:
|
||||
resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.29.2:
|
||||
resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==}
|
||||
@ -6622,6 +6608,13 @@ snapshots:
|
||||
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.64)
|
||||
zod: 3.25.64
|
||||
|
||||
'@ai-sdk/deepseek@0.2.16(zod@3.25.64)':
|
||||
dependencies:
|
||||
'@ai-sdk/openai-compatible': 0.2.16(zod@3.25.64)
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.64)
|
||||
zod: 3.25.64
|
||||
|
||||
'@ai-sdk/fal@0.1.12(zod@3.25.64)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
@ -6653,12 +6646,24 @@ snapshots:
|
||||
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.64)
|
||||
zod: 3.25.64
|
||||
|
||||
'@ai-sdk/google@1.2.22(zod@3.25.64)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.64)
|
||||
zod: 3.25.64
|
||||
|
||||
'@ai-sdk/openai-compatible@0.2.14(zod@3.25.64)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.64)
|
||||
zod: 3.25.64
|
||||
|
||||
'@ai-sdk/openai-compatible@0.2.16(zod@3.25.64)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
'@ai-sdk/provider-utils': 2.2.8(zod@3.25.64)
|
||||
zod: 3.25.64
|
||||
|
||||
'@ai-sdk/openai@1.1.13(zod@3.25.64)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.0.8
|
||||
|
@ -1,10 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { checkWebContentAnalysisCreditsAction } from '@/actions/check-web-content-analysis-credits';
|
||||
import {
|
||||
type UrlInputFormProps,
|
||||
urlSchema,
|
||||
} from '@/ai/text/utils/web-content-analyzer';
|
||||
import type { UrlInputFormProps } from '@/ai/text/utils/web-content-analyzer';
|
||||
import { webContentAnalyzerConfig } from '@/ai/text/utils/web-content-analyzer-config';
|
||||
import { LoginWrapper } from '@/components/auth/login-wrapper';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -16,6 +13,13 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { useLocalePathname } from '@/i18n/navigation';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
@ -36,7 +40,7 @@ import { useDebounce } from '../utils/performance';
|
||||
|
||||
// Form schema for URL input
|
||||
const urlFormSchema = z.object({
|
||||
url: urlSchema,
|
||||
url: z.string().url().optional(), // Allow empty string for initial state
|
||||
});
|
||||
|
||||
type UrlFormData = z.infer<typeof urlFormSchema>;
|
||||
@ -45,6 +49,8 @@ export function UrlInputForm({
|
||||
onSubmit,
|
||||
isLoading,
|
||||
disabled = false,
|
||||
modelProvider,
|
||||
setModelProvider,
|
||||
}: UrlInputFormProps) {
|
||||
const [creditInfo, setCreditInfo] = useState<{
|
||||
hasEnoughCredits: boolean;
|
||||
@ -133,7 +139,7 @@ export function UrlInputForm({
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
onSubmit(data.url);
|
||||
onSubmit(data.url ?? '', modelProvider);
|
||||
};
|
||||
|
||||
const handleFormSubmit = form.handleSubmit(handleSubmit);
|
||||
@ -144,6 +150,23 @@ export function UrlInputForm({
|
||||
return (
|
||||
<>
|
||||
<div className="w-full max-w-2xl mx-auto">
|
||||
{/* Model Provider Selection (for mobile/smaller screens, optional) */}
|
||||
<div className="flex justify-end items-center mb-4">
|
||||
<Select
|
||||
value={modelProvider}
|
||||
onValueChange={setModelProvider}
|
||||
disabled={isLoading || disabled}
|
||||
>
|
||||
<SelectTrigger id="model-provider-select-form" className="w-40">
|
||||
<SelectValue placeholder="Select model" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="openai">OpenAI GPT-4o</SelectItem>
|
||||
<SelectItem value="gemini">Google Gemini</SelectItem>
|
||||
<SelectItem value="deepseek">DeepSeek</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleFormSubmit} className="space-y-4">
|
||||
<FormField
|
||||
@ -216,7 +239,7 @@ export function UrlInputForm({
|
||||
) : isAuthenticated ? (
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isFormDisabled || !urlValue.trim()}
|
||||
disabled={isFormDisabled || !urlValue?.trim()}
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
|
@ -3,6 +3,7 @@
|
||||
import type {
|
||||
AnalysisState,
|
||||
AnalyzeContentResponse,
|
||||
ModelProvider,
|
||||
WebContentAnalyzerProps,
|
||||
} from '@/ai/text/utils/web-content-analyzer';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -192,150 +193,161 @@ export function WebContentAnalyzer({ className }: WebContentAnalyzerProps) {
|
||||
// Use reducer for better state management and performance
|
||||
const [state, dispatch] = useReducer(analysisReducer, initialState);
|
||||
|
||||
// Model provider state
|
||||
const [modelProvider, setModelProvider] = useState<ModelProvider>('openai');
|
||||
|
||||
// Enhanced error state
|
||||
const [analyzedError, setAnalyzedError] =
|
||||
useState<WebContentAnalyzerError | null>(null);
|
||||
|
||||
// Handle analysis submission with enhanced error handling
|
||||
const handleAnalyzeUrl = useCallback(async (url: string) => {
|
||||
// Reset state and start analysis
|
||||
dispatch({ type: 'START_ANALYSIS', payload: { url } });
|
||||
setAnalyzedError(null);
|
||||
const handleAnalyzeUrl = useCallback(
|
||||
async (url: string, provider: ModelProvider) => {
|
||||
// Reset state and start analysis
|
||||
dispatch({ type: 'START_ANALYSIS', payload: { url } });
|
||||
setAnalyzedError(null);
|
||||
|
||||
try {
|
||||
// Use retry mechanism for the API call
|
||||
const result = await withRetry(async () => {
|
||||
const response = await fetch('/api/analyze-content', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ url }),
|
||||
});
|
||||
try {
|
||||
// Use retry mechanism for the API call
|
||||
const result = await withRetry(async () => {
|
||||
const response = await fetch('/api/analyze-content', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ url, modelProvider: provider }),
|
||||
});
|
||||
|
||||
const data: AnalyzeContentResponse = await response.json();
|
||||
const data: AnalyzeContentResponse = await response.json();
|
||||
|
||||
// Handle HTTP errors
|
||||
if (!response.ok) {
|
||||
// Create specific error based on status code
|
||||
let errorType = ErrorType.UNKNOWN;
|
||||
let severity = ErrorSeverity.MEDIUM;
|
||||
let retryable = true;
|
||||
// Handle HTTP errors
|
||||
if (!response.ok) {
|
||||
// Create specific error based on status code
|
||||
let errorType = ErrorType.UNKNOWN;
|
||||
let severity = ErrorSeverity.MEDIUM;
|
||||
let retryable = true;
|
||||
|
||||
switch (response.status) {
|
||||
case 400:
|
||||
errorType = ErrorType.VALIDATION;
|
||||
retryable = false;
|
||||
break;
|
||||
case 401:
|
||||
errorType = ErrorType.AUTHENTICATION;
|
||||
severity = ErrorSeverity.HIGH;
|
||||
retryable = false;
|
||||
break;
|
||||
case 402:
|
||||
errorType = ErrorType.CREDITS;
|
||||
severity = ErrorSeverity.HIGH;
|
||||
retryable = false;
|
||||
break;
|
||||
case 408:
|
||||
errorType = ErrorType.TIMEOUT;
|
||||
break;
|
||||
case 422:
|
||||
errorType = ErrorType.SCRAPING;
|
||||
break;
|
||||
case 429:
|
||||
errorType = ErrorType.RATE_LIMIT;
|
||||
break;
|
||||
case 503:
|
||||
errorType = ErrorType.SERVICE_UNAVAILABLE;
|
||||
severity = ErrorSeverity.HIGH;
|
||||
break;
|
||||
default:
|
||||
errorType = ErrorType.NETWORK;
|
||||
switch (response.status) {
|
||||
case 400:
|
||||
errorType = ErrorType.VALIDATION;
|
||||
retryable = false;
|
||||
break;
|
||||
case 401:
|
||||
errorType = ErrorType.AUTHENTICATION;
|
||||
severity = ErrorSeverity.HIGH;
|
||||
retryable = false;
|
||||
break;
|
||||
case 402:
|
||||
errorType = ErrorType.CREDITS;
|
||||
severity = ErrorSeverity.HIGH;
|
||||
retryable = false;
|
||||
break;
|
||||
case 408:
|
||||
errorType = ErrorType.TIMEOUT;
|
||||
break;
|
||||
case 422:
|
||||
errorType = ErrorType.SCRAPING;
|
||||
break;
|
||||
case 429:
|
||||
errorType = ErrorType.RATE_LIMIT;
|
||||
break;
|
||||
case 503:
|
||||
errorType = ErrorType.SERVICE_UNAVAILABLE;
|
||||
severity = ErrorSeverity.HIGH;
|
||||
break;
|
||||
default:
|
||||
errorType = ErrorType.NETWORK;
|
||||
}
|
||||
|
||||
throw new WebContentAnalyzerError(
|
||||
errorType,
|
||||
data.error || `HTTP ${response.status}: ${response.statusText}`,
|
||||
data.error || 'Failed to analyze website. Please try again.',
|
||||
severity,
|
||||
retryable
|
||||
);
|
||||
}
|
||||
|
||||
throw new WebContentAnalyzerError(
|
||||
errorType,
|
||||
data.error || `HTTP ${response.status}: ${response.statusText}`,
|
||||
data.error || 'Failed to analyze website. Please try again.',
|
||||
severity,
|
||||
retryable
|
||||
);
|
||||
}
|
||||
if (!data.success || !data.data) {
|
||||
throw new WebContentAnalyzerError(
|
||||
ErrorType.ANALYSIS,
|
||||
data.error || 'Analysis failed',
|
||||
data.error ||
|
||||
'Failed to analyze website content. Please try again.',
|
||||
ErrorSeverity.MEDIUM,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!data.success || !data.data) {
|
||||
throw new WebContentAnalyzerError(
|
||||
ErrorType.ANALYSIS,
|
||||
data.error || 'Analysis failed',
|
||||
data.error ||
|
||||
'Failed to analyze website content. Please try again.',
|
||||
ErrorSeverity.MEDIUM,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
// Update state to analyzing stage
|
||||
dispatch({ type: 'SET_LOADING_STAGE', payload: { stage: 'analyzing' } });
|
||||
|
||||
// Simulate a brief delay for analyzing stage to show progress
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// Set results and complete analysis
|
||||
dispatch({
|
||||
type: 'SET_RESULTS',
|
||||
payload: {
|
||||
results: result.data!.analysis,
|
||||
screenshot: result.data!.screenshot,
|
||||
},
|
||||
});
|
||||
|
||||
// Show success toast - defer to avoid flushSync during render
|
||||
setTimeout(() => {
|
||||
toast.success('Website analysis completed successfully!', {
|
||||
description: `Analyzed ${new URL(url).hostname}`,
|
||||
return data;
|
||||
});
|
||||
}, 0);
|
||||
} catch (error) {
|
||||
// Classify the error
|
||||
const analyzedError =
|
||||
error instanceof WebContentAnalyzerError ? error : classifyError(error);
|
||||
|
||||
// Log the error
|
||||
logError(analyzedError, { url, component: 'WebContentAnalyzer' });
|
||||
// Update state to analyzing stage
|
||||
dispatch({
|
||||
type: 'SET_LOADING_STAGE',
|
||||
payload: { stage: 'analyzing' },
|
||||
});
|
||||
|
||||
// Update state with error
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
payload: { error: analyzedError.userMessage },
|
||||
});
|
||||
// Simulate a brief delay for analyzing stage to show progress
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
// Set the analyzed error for the ErrorDisplay component
|
||||
setAnalyzedError(analyzedError);
|
||||
// Set results and complete analysis
|
||||
dispatch({
|
||||
type: 'SET_RESULTS',
|
||||
payload: {
|
||||
results: result.data!.analysis,
|
||||
screenshot: result.data!.screenshot,
|
||||
},
|
||||
});
|
||||
|
||||
// Show error toast with appropriate severity - defer to avoid flushSync during render
|
||||
const toastOptions = {
|
||||
description: analyzedError.userMessage,
|
||||
};
|
||||
// Show success toast - defer to avoid flushSync during render
|
||||
setTimeout(() => {
|
||||
toast.success('Website analysis completed successfully!', {
|
||||
description: `Analyzed ${new URL(url).hostname}`,
|
||||
});
|
||||
}, 0);
|
||||
} catch (error) {
|
||||
// Classify the error
|
||||
const analyzedError =
|
||||
error instanceof WebContentAnalyzerError
|
||||
? error
|
||||
: classifyError(error);
|
||||
|
||||
setTimeout(() => {
|
||||
switch (analyzedError.severity) {
|
||||
case ErrorSeverity.CRITICAL:
|
||||
case ErrorSeverity.HIGH:
|
||||
toast.error('Analysis Failed', toastOptions);
|
||||
break;
|
||||
case ErrorSeverity.MEDIUM:
|
||||
toast.warning('Analysis Failed', toastOptions);
|
||||
break;
|
||||
case ErrorSeverity.LOW:
|
||||
toast.info('Analysis Issue', toastOptions);
|
||||
break;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}, []);
|
||||
// Log the error
|
||||
logError(analyzedError, { url, component: 'WebContentAnalyzer' });
|
||||
|
||||
// Update state with error
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
payload: { error: analyzedError.userMessage },
|
||||
});
|
||||
|
||||
// Set the analyzed error for the ErrorDisplay component
|
||||
setAnalyzedError(analyzedError);
|
||||
|
||||
// Show error toast with appropriate severity - defer to avoid flushSync during render
|
||||
const toastOptions = {
|
||||
description: analyzedError.userMessage,
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
switch (analyzedError.severity) {
|
||||
case ErrorSeverity.CRITICAL:
|
||||
case ErrorSeverity.HIGH:
|
||||
toast.error('Analysis Failed', toastOptions);
|
||||
break;
|
||||
case ErrorSeverity.MEDIUM:
|
||||
toast.warning('Analysis Failed', toastOptions);
|
||||
break;
|
||||
case ErrorSeverity.LOW:
|
||||
toast.info('Analysis Issue', toastOptions);
|
||||
break;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Handle starting a new analysis
|
||||
const handleNewAnalysis = useCallback(() => {
|
||||
@ -374,6 +386,8 @@ export function WebContentAnalyzer({ className }: WebContentAnalyzerProps) {
|
||||
onSubmit={handleAnalyzeUrl}
|
||||
isLoading={state.isLoading}
|
||||
disabled={state.isLoading}
|
||||
modelProvider={modelProvider}
|
||||
setModelProvider={setModelProvider}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -100,16 +100,23 @@ export const webContentAnalyzerConfig = {
|
||||
},
|
||||
|
||||
/**
|
||||
* OpenAI analysis options
|
||||
* AI model providers
|
||||
*/
|
||||
openai: {
|
||||
model: 'gpt-4o-mini',
|
||||
temperature: 0.1, // Low temperature for consistent results
|
||||
/**
|
||||
* Token optimization settings
|
||||
*/
|
||||
maxTokens: 2000, // Limit response tokens for performance
|
||||
},
|
||||
gemini: {
|
||||
model: 'gemini-2.0-flash',
|
||||
temperature: 0.1,
|
||||
maxTokens: 2000,
|
||||
},
|
||||
deepseek: {
|
||||
model: 'deepseek-chat',
|
||||
temperature: 0.1,
|
||||
maxTokens: 2000,
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
@ -15,6 +15,7 @@ export interface AnalysisResults {
|
||||
// API Request/Response Interfaces
|
||||
export interface AnalyzeContentRequest {
|
||||
url: string;
|
||||
modelProvider: ModelProvider;
|
||||
}
|
||||
|
||||
export interface AnalyzeContentResponse {
|
||||
@ -66,14 +67,19 @@ export interface AnalysisState {
|
||||
}
|
||||
|
||||
// Component Props Interfaces
|
||||
export type ModelProvider = 'openai' | 'gemini' | 'deepseek';
|
||||
|
||||
export interface WebContentAnalyzerProps {
|
||||
className?: string;
|
||||
modelProvider?: ModelProvider;
|
||||
}
|
||||
|
||||
export interface UrlInputFormProps {
|
||||
onSubmit: (url: string) => void;
|
||||
onSubmit: (url: string, modelProvider: ModelProvider) => void;
|
||||
isLoading: boolean;
|
||||
disabled?: boolean;
|
||||
modelProvider: ModelProvider;
|
||||
setModelProvider: (provider: ModelProvider) => void;
|
||||
}
|
||||
|
||||
export interface AnalysisResultsProps {
|
||||
@ -114,6 +120,7 @@ export const analysisResultsSchema = z.object({
|
||||
// API Request Schema
|
||||
export const analyzeContentRequestSchema = z.object({
|
||||
url: urlSchema,
|
||||
modelProvider: z.enum(['openai', 'gemini', 'deepseek']),
|
||||
});
|
||||
|
||||
// API Response Schema
|
||||
|
@ -19,7 +19,9 @@ import {
|
||||
} from '@/ai/text/utils/web-content-analyzer-config';
|
||||
import { consumeCredits, hasEnoughCredits } from '@/credits/credits';
|
||||
import { getSession } from '@/lib/server';
|
||||
import { openai } from '@ai-sdk/openai';
|
||||
import { createDeepSeek } from '@ai-sdk/deepseek';
|
||||
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
||||
import { createOpenAI } from '@ai-sdk/openai';
|
||||
import FirecrawlApp from '@mendable/firecrawl-js';
|
||||
import { generateObject } from 'ai';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
@ -195,15 +197,50 @@ async function scrapeWebpage(
|
||||
});
|
||||
}
|
||||
|
||||
// Analyze content using OpenAI with retry logic
|
||||
// Analyze content using selected provider with retry logic
|
||||
async function analyzeContent(
|
||||
content: string,
|
||||
url: string
|
||||
url: string,
|
||||
provider: string
|
||||
): Promise<AnalysisResults> {
|
||||
return withRetry(async () => {
|
||||
try {
|
||||
let model: any;
|
||||
let temperature: number | undefined;
|
||||
let maxTokens: number | undefined;
|
||||
switch (provider) {
|
||||
case 'openai':
|
||||
model = createOpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
}).chat(webContentAnalyzerConfig.openai.model);
|
||||
temperature = webContentAnalyzerConfig.openai.temperature;
|
||||
maxTokens = webContentAnalyzerConfig.openai.maxTokens;
|
||||
break;
|
||||
case 'gemini':
|
||||
model = createGoogleGenerativeAI({
|
||||
apiKey: process.env.GOOGLE_API_KEY,
|
||||
}).chat(webContentAnalyzerConfig.gemini.model);
|
||||
temperature = webContentAnalyzerConfig.gemini.temperature;
|
||||
maxTokens = webContentAnalyzerConfig.gemini.maxTokens;
|
||||
break;
|
||||
case 'deepseek':
|
||||
model = createDeepSeek({
|
||||
apiKey: process.env.DEEPSEEK_API_KEY,
|
||||
}).chat(webContentAnalyzerConfig.deepseek.model);
|
||||
temperature = webContentAnalyzerConfig.deepseek.temperature;
|
||||
maxTokens = webContentAnalyzerConfig.deepseek.maxTokens;
|
||||
break;
|
||||
default:
|
||||
throw new WebContentAnalyzerError(
|
||||
ErrorType.VALIDATION,
|
||||
'Invalid model provider',
|
||||
'Please select a valid model provider.',
|
||||
ErrorSeverity.MEDIUM,
|
||||
false
|
||||
);
|
||||
}
|
||||
const { object } = await generateObject({
|
||||
model: openai(webContentAnalyzerConfig.openai.model),
|
||||
model,
|
||||
schema: analysisSchema,
|
||||
prompt: `
|
||||
Analyze the following webpage content and extract structured information.
|
||||
@ -216,8 +253,8 @@ async function analyzeContent(
|
||||
- For features and use cases: provide empty arrays if none are found
|
||||
- Ensure the title and description are meaningful and based on the actual content
|
||||
`,
|
||||
temperature: webContentAnalyzerConfig.openai.temperature,
|
||||
maxTokens: webContentAnalyzerConfig.openai.maxTokens,
|
||||
temperature,
|
||||
maxTokens,
|
||||
});
|
||||
|
||||
return {
|
||||
@ -229,11 +266,9 @@ async function analyzeContent(
|
||||
if (error instanceof WebContentAnalyzerError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Check for specific OpenAI/AI errors
|
||||
if (error instanceof Error) {
|
||||
const message = error.message.toLowerCase();
|
||||
|
||||
if (message.includes('rate limit') || message.includes('quota')) {
|
||||
throw new WebContentAnalyzerError(
|
||||
ErrorType.RATE_LIMIT,
|
||||
@ -244,7 +279,6 @@ async function analyzeContent(
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
if (message.includes('timeout') || message.includes('aborted')) {
|
||||
throw new WebContentAnalyzerError(
|
||||
ErrorType.TIMEOUT,
|
||||
@ -256,7 +290,6 @@ async function analyzeContent(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Classify and throw the error
|
||||
throw classifyError(error);
|
||||
}
|
||||
@ -295,7 +328,8 @@ export async function POST(req: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
const { url } = validationResult.data;
|
||||
const { url, modelProvider } = validationResult.data;
|
||||
console.log('modelProvider', modelProvider, 'url', url);
|
||||
|
||||
// Additional URL validation
|
||||
const urlValidation = validateUrl(url);
|
||||
@ -402,8 +436,8 @@ export async function POST(req: NextRequest) {
|
||||
// Step 1: Scrape webpage
|
||||
const { content, screenshot } = await scrapeWebpage(url);
|
||||
|
||||
// Step 2: Analyze content with AI
|
||||
const analysis = await analyzeContent(content, url);
|
||||
// Step 2: Analyze content with AI (pass provider)
|
||||
const analysis = await analyzeContent(content, url, modelProvider);
|
||||
|
||||
// Step 3: Consume credits (only on successful analysis)
|
||||
await consumeCredits({
|
||||
|
Loading…
Reference in New Issue
Block a user