upfynai-code 3.0.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +69 -92
  2. package/bin/cli.js +191 -0
  3. package/dist/client/assets/AppContent-M14Au3SB.js +542 -0
  4. package/{client/dist/assets/BrowserPanel-0TLEl-IC.js → dist/client/assets/BrowserPanel-TFKm2NDJ.js} +2 -2
  5. package/dist/client/assets/DashboardPanel-C88HjsCh.js +1 -0
  6. package/dist/client/assets/FileTree-DvO1xnDE.js +1 -0
  7. package/{client/dist/assets/GitPanel-C_xFM-N2.js → dist/client/assets/GitPanel-D-slVlyy.js} +2 -2
  8. package/dist/client/assets/LoginModal-Chi4SYcr.js +21 -0
  9. package/{client/dist/assets/MarkdownPreview-CESjI261.js → dist/client/assets/MarkdownPreview-CuIix2u9.js} +1 -1
  10. package/dist/client/assets/MermaidBlock-Dq9uFv82.js +2 -0
  11. package/dist/client/assets/Onboarding-QYXx24dX.js +1 -0
  12. package/{client/dist/assets/PreviewPanel-CqCa92Tf.js → dist/client/assets/PreviewPanel-Dd8q-jo0.js} +1 -1
  13. package/dist/client/assets/SetupForm-CrspaUva.js +1 -0
  14. package/dist/client/assets/WorkflowsPanel-DIlYAdhB.js +1 -0
  15. package/dist/client/assets/index-CnNNzw9A.css +1 -0
  16. package/{client/dist/assets/index-HaY-3pK1.js → dist/client/assets/index-rUkK9FDP.js} +26 -26
  17. package/{client/dist/assets/vendor-codemirror-D2ALgpaX.js → dist/client/assets/vendor-codemirror-jc6nyJQg.js} +1 -1
  18. package/{client/dist/assets/vendor-diff-DNQpbhrT.js → dist/client/assets/vendor-diff-THJmAcEI.js} +1 -1
  19. package/{client/dist/assets/vendor-icons-GyYE35HP.js → dist/client/assets/vendor-icons-CfjIpdrD.js} +145 -155
  20. package/{client/dist/assets/vendor-markdown-CimbIo6Y.js → dist/client/assets/vendor-markdown-Cdm6NEGf.js} +1 -1
  21. package/dist/client/assets/vendor-mermaid-DTPaBx-U.js +2559 -0
  22. package/{client/dist/assets/vendor-react-96lCPsRK.js → dist/client/assets/vendor-react-wFkb6mSf.js} +1 -1
  23. package/{client/dist/assets/vendor-syntax-LS_Nt30I.js → dist/client/assets/vendor-syntax-C_UZR7tc.js} +1 -1
  24. package/dist/client/favicon.png +0 -0
  25. package/dist/client/icons/icon-128x128.png +0 -0
  26. package/dist/client/icons/icon-144x144.png +0 -0
  27. package/dist/client/icons/icon-152x152.png +0 -0
  28. package/dist/client/icons/icon-192x192.png +0 -0
  29. package/dist/client/icons/icon-384x384.png +0 -0
  30. package/dist/client/icons/icon-512x512.png +0 -0
  31. package/dist/client/icons/icon-72x72.png +0 -0
  32. package/dist/client/icons/icon-96x96.png +0 -0
  33. package/{client/dist → dist/client}/index.html +37 -36
  34. package/dist/client/logo-128.png +0 -0
  35. package/dist/client/logo-256.png +0 -0
  36. package/dist/client/logo-32.png +0 -0
  37. package/dist/client/logo-512.png +0 -0
  38. package/dist/client/logo-64.png +0 -0
  39. package/dist/client/logo.png +0 -0
  40. package/{client/dist → dist/client}/manifest.json +12 -12
  41. package/{client/dist → dist/client}/mcp-docs.html +1 -1
  42. package/{client/dist → dist/client}/sw.js +2 -2
  43. package/package.json +56 -105
  44. package/scripts/postinstall.js +9 -0
  45. package/scripts/prepublish.js +77 -0
  46. package/src/animation.js +228 -0
  47. package/src/auth.js +142 -0
  48. package/src/config.js +40 -0
  49. package/src/connect.js +416 -0
  50. package/src/launch.js +81 -0
  51. package/src/mcp.js +57 -0
  52. package/src/permissions.js +140 -0
  53. package/src/persistent-shell.js +261 -0
  54. package/src/server.js +54 -0
  55. package/client/dist/assets/AppContent-CwrTP6TW.js +0 -545
  56. package/client/dist/assets/CanvasFullScreen-D1GWQsGL.js +0 -1
  57. package/client/dist/assets/CanvasWorkspace-D7ORj358.js +0 -163
  58. package/client/dist/assets/DashboardPanel-BV7ybUDe.js +0 -1
  59. package/client/dist/assets/FileTree-5qfhBqdE.js +0 -1
  60. package/client/dist/assets/LoginModal-CImJHRjX.js +0 -13
  61. package/client/dist/assets/MermaidBlock-BFM21cwe.js +0 -2
  62. package/client/dist/assets/Onboarding-B3cteLu2.js +0 -1
  63. package/client/dist/assets/SetupForm-P6dsYgHO.js +0 -1
  64. package/client/dist/assets/WorkflowsPanel-CBoN80kc.js +0 -1
  65. package/client/dist/assets/index-46kkVu2i.css +0 -1
  66. package/client/dist/assets/pdf-CE_K4jFx.js +0 -12
  67. package/client/dist/assets/vendor-canvas-BZV40eAE.css +0 -1
  68. package/client/dist/assets/vendor-canvas-DvHJ_Pn2.js +0 -49
  69. package/client/dist/assets/vendor-mermaid-DucWyDEe.js +0 -2556
  70. package/client/dist/favicon.png +0 -0
  71. package/client/dist/icons/icon-128x128.png +0 -0
  72. package/client/dist/icons/icon-144x144.png +0 -0
  73. package/client/dist/icons/icon-152x152.png +0 -0
  74. package/client/dist/icons/icon-192x192.png +0 -0
  75. package/client/dist/icons/icon-384x384.png +0 -0
  76. package/client/dist/icons/icon-512x512.png +0 -0
  77. package/client/dist/icons/icon-72x72.png +0 -0
  78. package/client/dist/icons/icon-96x96.png +0 -0
  79. package/client/dist/logo-128.png +0 -0
  80. package/client/dist/logo-256.png +0 -0
  81. package/client/dist/logo-32.png +0 -0
  82. package/client/dist/logo-512.png +0 -0
  83. package/client/dist/logo-64.png +0 -0
  84. package/commands/upfynai-connect.md +0 -59
  85. package/commands/upfynai-disconnect.md +0 -31
  86. package/commands/upfynai-doctor.md +0 -99
  87. package/commands/upfynai-export.md +0 -49
  88. package/commands/upfynai-local.md +0 -82
  89. package/commands/upfynai-status.md +0 -75
  90. package/commands/upfynai-stop.md +0 -49
  91. package/commands/upfynai-uninstall.md +0 -58
  92. package/commands/upfynai.md +0 -69
  93. package/scripts/build-client.js +0 -17
  94. package/scripts/fix-node-pty.js +0 -67
  95. package/scripts/install-commands.js +0 -78
  96. package/server/agent-loop.js +0 -242
  97. package/server/auto-compact.js +0 -99
  98. package/server/browser.js +0 -131
  99. package/server/claude-sdk.js +0 -797
  100. package/server/cli-ui.js +0 -798
  101. package/server/cli.js +0 -751
  102. package/server/constants/config.js +0 -31
  103. package/server/cursor-cli.js +0 -270
  104. package/server/database/auth.db +0 -0
  105. package/server/database/db.js +0 -1547
  106. package/server/database/init.sql +0 -70
  107. package/server/index.js +0 -3813
  108. package/server/load-env.js +0 -26
  109. package/server/mcp-server.js +0 -621
  110. package/server/middleware/auth.js +0 -184
  111. package/server/middleware/relayHelpers.js +0 -44
  112. package/server/middleware/sandboxRouter.js +0 -174
  113. package/server/openai-codex.js +0 -403
  114. package/server/openrouter.js +0 -137
  115. package/server/projects.js +0 -1807
  116. package/server/provider-factory.js +0 -174
  117. package/server/relay-client.js +0 -390
  118. package/server/routes/agent.js +0 -1234
  119. package/server/routes/auth.js +0 -559
  120. package/server/routes/browser.js +0 -419
  121. package/server/routes/canvas.js +0 -53
  122. package/server/routes/cli-auth.js +0 -263
  123. package/server/routes/codex.js +0 -396
  124. package/server/routes/commands.js +0 -707
  125. package/server/routes/composio.js +0 -176
  126. package/server/routes/cursor.js +0 -770
  127. package/server/routes/dashboard.js +0 -295
  128. package/server/routes/git.js +0 -1208
  129. package/server/routes/keys.js +0 -34
  130. package/server/routes/mcp-utils.js +0 -48
  131. package/server/routes/mcp.js +0 -661
  132. package/server/routes/payments.js +0 -227
  133. package/server/routes/projects.js +0 -754
  134. package/server/routes/sessions.js +0 -146
  135. package/server/routes/settings.js +0 -261
  136. package/server/routes/taskmaster.js +0 -1928
  137. package/server/routes/user.js +0 -106
  138. package/server/routes/vapi-chat.js +0 -624
  139. package/server/routes/voice.js +0 -235
  140. package/server/routes/webhooks.js +0 -166
  141. package/server/routes/workflows.js +0 -312
  142. package/server/sandbox.js +0 -120
  143. package/server/services/browser-ai.js +0 -154
  144. package/server/services/composio.js +0 -204
  145. package/server/services/sessionRegistry.js +0 -139
  146. package/server/services/whisperService.js +0 -84
  147. package/server/services/workflowScheduler.js +0 -211
  148. package/server/tests/relay-flow.test.js +0 -570
  149. package/server/tests/sessions.test.js +0 -259
  150. package/server/utils/commandParser.js +0 -303
  151. package/server/utils/email.js +0 -66
  152. package/server/utils/gitConfig.js +0 -24
  153. package/server/utils/mcp-detector.js +0 -198
  154. package/server/utils/taskmaster-websocket.js +0 -129
  155. package/shared/integrationCatalog.d.ts +0 -12
  156. package/shared/integrationCatalog.js +0 -172
  157. package/shared/modelConstants.js +0 -96
  158. /package/{shared → dist}/agents/claude.js +0 -0
  159. /package/{shared → dist}/agents/codex.js +0 -0
  160. /package/{shared → dist}/agents/cursor.js +0 -0
  161. /package/{shared → dist}/agents/detect.js +0 -0
  162. /package/{shared → dist}/agents/exec.js +0 -0
  163. /package/{shared → dist}/agents/files.js +0 -0
  164. /package/{shared → dist}/agents/git.js +0 -0
  165. /package/{shared → dist}/agents/gitagent.js +0 -0
  166. /package/{shared → dist}/agents/index.js +0 -0
  167. /package/{shared → dist}/agents/shell.js +0 -0
  168. /package/{shared → dist}/agents/utils.js +0 -0
  169. /package/{client/dist → dist/client}/api-docs.html +0 -0
  170. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  171. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  172. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  173. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  174. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  175. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  176. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  177. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  178. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  179. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  180. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  181. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  182. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  183. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  184. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  185. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  186. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  187. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  188. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  189. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  190. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  191. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  192. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  193. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  194. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  195. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  196. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  197. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  198. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  199. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  200. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  201. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  202. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  203. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  204. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  205. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  206. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  207. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  208. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  209. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  210. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  211. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  212. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  213. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  214. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  215. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  216. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  217. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  218. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  219. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  220. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  221. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  222. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  223. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  224. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  225. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  226. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  227. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  228. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  229. /package/{client/dist → dist/client}/assets/vendor-i18n-DCFGyhQR.js +0 -0
  230. /package/{client/dist → dist/client}/assets/vendor-xterm-CZq1hqo1.js +0 -0
  231. /package/{client/dist → dist/client}/assets/vendor-xterm-qxJ8_QYu.css +0 -0
  232. /package/{client/dist → dist/client}/clear-cache.html +0 -0
  233. /package/{client/dist → dist/client}/convert-icons.md +0 -0
  234. /package/{client/dist → dist/client}/favicon.svg +0 -0
  235. /package/{client/dist → dist/client}/generate-icons.js +0 -0
  236. /package/{client/dist → dist/client}/icons/claude-ai-icon.svg +0 -0
  237. /package/{client/dist → dist/client}/icons/codex-white.svg +0 -0
  238. /package/{client/dist → dist/client}/icons/codex.svg +0 -0
  239. /package/{client/dist → dist/client}/icons/cursor-white.svg +0 -0
  240. /package/{client/dist → dist/client}/icons/cursor.svg +0 -0
  241. /package/{client/dist → dist/client}/icons/icon-128x128.svg +0 -0
  242. /package/{client/dist → dist/client}/icons/icon-144x144.svg +0 -0
  243. /package/{client/dist → dist/client}/icons/icon-152x152.svg +0 -0
  244. /package/{client/dist → dist/client}/icons/icon-192x192.svg +0 -0
  245. /package/{client/dist → dist/client}/icons/icon-384x384.svg +0 -0
  246. /package/{client/dist → dist/client}/icons/icon-512x512.svg +0 -0
  247. /package/{client/dist → dist/client}/icons/icon-72x72.svg +0 -0
  248. /package/{client/dist → dist/client}/icons/icon-96x96.svg +0 -0
  249. /package/{client/dist → dist/client}/icons/icon-template.svg +0 -0
  250. /package/{client/dist → dist/client}/logo.svg +0 -0
  251. /package/{client/dist → dist/client}/offline.html +0 -0
  252. /package/{client/dist → dist/client}/screenshots/cli-selection.png +0 -0
  253. /package/{client/dist → dist/client}/screenshots/desktop-main.png +0 -0
  254. /package/{client/dist → dist/client}/screenshots/mobile-chat.png +0 -0
  255. /package/{client/dist → dist/client}/screenshots/tools-modal.png +0 -0
  256. /package/{shared → dist}/gitagent/index.js +0 -0
  257. /package/{shared → dist}/gitagent/parser.js +0 -0
  258. /package/{shared → dist}/gitagent/prompt-builder.js +0 -0
@@ -1,235 +0,0 @@
1
- import express from 'express';
2
- import multer from 'multer';
3
- import path from 'path';
4
- import os from 'os';
5
- import { promises as fs } from 'fs';
6
- import { isWhisperAvailable, transcribeLocal } from '../services/whisperService.js';
7
-
8
- const router = express.Router();
9
- const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 25 * 1024 * 1024 } }); // 25MB max
10
-
11
- /**
12
- * Helper: Get user's OpenAI key (BYOK) or server env key.
13
- * Imported lazily to avoid circular deps.
14
- */
15
- async function getOpenAIKey(userId) {
16
- try {
17
- const { credentialsDb } = await import('../database/db.js');
18
- const creds = await credentialsDb.getCredentials(userId, 'openai_key');
19
- const active = creds.find(c => c.is_active);
20
- return active?.credential_value || process.env.OPENAI_API_KEY || null;
21
- } catch {
22
- return process.env.OPENAI_API_KEY || null;
23
- }
24
- }
25
-
26
- /**
27
- * Transcribe audio via self-hosted Whisper (WHISPER_URL) or OpenAI API.
28
- * Returns { text, source } or null if all methods fail.
29
- */
30
- async function transcribeAudio(fileBuffer, originalname, mimetype, userId) {
31
- const FormData = (await import('form-data')).default;
32
-
33
- // Strategy 1: Self-hosted Whisper
34
- const whisperUrl = process.env.WHISPER_URL;
35
- if (whisperUrl) {
36
- try {
37
- const formData = new FormData();
38
- formData.append('file', fileBuffer, {
39
- filename: originalname || 'audio.webm',
40
- contentType: mimetype || 'audio/webm'
41
- });
42
- formData.append('model', 'whisper-1');
43
- formData.append('response_format', 'json');
44
- formData.append('language', 'en');
45
-
46
- const endpoint = whisperUrl.replace(/\/+$/, '') + '/v1/audio/transcriptions';
47
- const response = await fetch(endpoint, {
48
- method: 'POST',
49
- headers: formData.getHeaders(),
50
- body: formData
51
- });
52
-
53
- if (response.ok) {
54
- const data = await response.json();
55
- if (data.text) return { text: data.text, source: 'self-hosted' };
56
- }
57
- } catch {
58
- // Self-hosted failed, try next
59
- }
60
- }
61
-
62
- // Strategy 2: OpenAI Whisper API (BYOK or server key)
63
- const apiKey = await getOpenAIKey(userId);
64
- if (apiKey) {
65
- try {
66
- const formData = new FormData();
67
- formData.append('file', fileBuffer, {
68
- filename: originalname || 'audio.webm',
69
- contentType: mimetype || 'audio/webm'
70
- });
71
- formData.append('model', 'whisper-1');
72
- formData.append('response_format', 'json');
73
- formData.append('language', 'en');
74
-
75
- const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
76
- method: 'POST',
77
- headers: {
78
- 'Authorization': `Bearer ${apiKey}`,
79
- ...formData.getHeaders()
80
- },
81
- body: formData
82
- });
83
-
84
- if (response.ok) {
85
- const data = await response.json();
86
- if (data.text) return { text: data.text, source: 'openai' };
87
- }
88
- } catch {
89
- // OpenAI failed, try next
90
- }
91
- }
92
-
93
- // Strategy 3: Local nodejs-whisper fallback
94
- const localAvailable = await isWhisperAvailable();
95
- if (localAvailable) {
96
- const tmpFile = path.join(os.tmpdir(), `stt-${Date.now()}.webm`);
97
- await fs.writeFile(tmpFile, fileBuffer);
98
- try {
99
- const text = await transcribeLocal(tmpFile);
100
- if (text) return { text, source: 'local' };
101
- } finally {
102
- await fs.unlink(tmpFile).catch(() => {});
103
- }
104
- }
105
-
106
- return null;
107
- }
108
-
109
- /**
110
- * POST /api/voice/stt — Speech-to-text
111
- * Priority: 1) Self-hosted Whisper (WHISPER_URL), 2) OpenAI API, 3) Local nodejs-whisper
112
- */
113
- router.post('/stt', upload.single('audio'), async (req, res) => {
114
- try {
115
- if (!req.file) {
116
- return res.status(400).json({ error: 'No audio file provided' });
117
- }
118
-
119
- const mode = req.body?.mode || 'default';
120
- const result = await transcribeAudio(req.file.buffer, req.file.originalname, req.file.mimetype, req.user?.id);
121
-
122
- if (!result) {
123
- return res.status(500).json({ error: 'Speech recognition is temporarily unavailable. Please try again.' });
124
- }
125
-
126
- let text = result.text;
127
-
128
- // Enhancement modes (prompt, vibe, architect)
129
- if (text && mode !== 'default') {
130
- const apiKey = await getOpenAIKey(req.user?.id);
131
- if (apiKey) {
132
- text = await enhanceTranscription(text, mode, apiKey);
133
- }
134
- }
135
-
136
- return res.json({ text, source: result.source });
137
- } catch (error) {
138
- res.status(500).json({ error: 'Transcription failed' });
139
- }
140
- });
141
-
142
- /**
143
- * POST /api/voice/tts — Text-to-speech using Edge-TTS
144
- * Returns audio/mp3 blob
145
- */
146
- router.post('/tts', async (req, res) => {
147
- try {
148
- const { text, voice = 'en-US-AriaNeural' } = req.body;
149
- if (!text || typeof text !== 'string') {
150
- return res.status(400).json({ error: 'Text is required' });
151
- }
152
-
153
- // Truncate very long text to avoid excessive audio generation
154
- const truncated = text.length > 5000 ? text.slice(0, 5000) : text;
155
-
156
- const { EdgeTTS } = await import('edge-tts-universal');
157
- const tts = new EdgeTTS(truncated, voice);
158
- const result = await tts.synthesize();
159
- const audioBuffer = Buffer.from(await result.audio.arrayBuffer());
160
-
161
- res.setHeader('Content-Type', 'audio/mp3');
162
- res.setHeader('Content-Length', audioBuffer.length);
163
- res.send(audioBuffer);
164
- } catch (error) {
165
- res.status(500).json({ error: 'TTS synthesis failed' });
166
- }
167
- });
168
-
169
- /**
170
- * GET /api/voice/voices — List available TTS voices
171
- */
172
- router.get('/voices', async (req, res) => {
173
- try {
174
- const { listVoices } = await import('edge-tts-universal');
175
- const voices = await listVoices();
176
-
177
- // Filter to English voices and simplify the response
178
- const englishVoices = voices
179
- .filter(v => v.Locale?.startsWith('en-'))
180
- .map(v => ({
181
- id: v.ShortName,
182
- name: v.FriendlyName || v.ShortName,
183
- locale: v.Locale,
184
- gender: v.Gender
185
- }));
186
-
187
- res.json({ voices: englishVoices });
188
- } catch (error) {
189
- res.status(500).json({ error: 'Failed to fetch voices' });
190
- }
191
- });
192
-
193
- /**
194
- * Enhance transcribed text using GPT (prompt engineering, vibe coding, etc.)
195
- */
196
- async function enhanceTranscription(text, mode, apiKey) {
197
- try {
198
- const OpenAI = (await import('openai')).default;
199
- const openai = new OpenAI({ apiKey });
200
-
201
- let systemMessage, prompt, temperature = 0.7, maxTokens = 800;
202
-
203
- switch (mode) {
204
- case 'prompt':
205
- systemMessage = 'You are an expert prompt engineer who creates clear, detailed, and effective prompts.';
206
- prompt = `Transform the following rough instruction into a clear, detailed, and context-aware AI prompt.\n\nYour enhanced prompt should:\n1. Be specific and unambiguous\n2. Include relevant context and constraints\n3. Specify the desired output format\n4. Use clear, actionable language\n\nRough instruction: "${text}"\n\nEnhanced prompt:`;
207
- break;
208
- case 'vibe':
209
- case 'instructions':
210
- case 'architect':
211
- systemMessage = 'You are a helpful assistant that formats ideas into clear, actionable instructions for AI agents.';
212
- temperature = 0.5;
213
- prompt = `Transform the following idea into clear, well-structured instructions that an AI agent can easily understand and execute.\n\nIMPORTANT RULES:\n- Format as clear, step-by-step instructions\n- Add reasonable implementation details based on common patterns\n- Only include details directly related to what was asked\n- Do NOT add features or functionality not mentioned\n- Keep the original intent and scope intact\n\nIdea: "${text}"\n\nAgent instructions:`;
214
- break;
215
- default:
216
- return text;
217
- }
218
-
219
- const completion = await openai.chat.completions.create({
220
- model: 'gpt-4o-mini',
221
- messages: [
222
- { role: 'system', content: systemMessage },
223
- { role: 'user', content: prompt }
224
- ],
225
- temperature,
226
- max_tokens: maxTokens
227
- });
228
-
229
- return completion.choices[0]?.message?.content || text;
230
- } catch {
231
- return text; // Fallback to original on error
232
- }
233
- }
234
-
235
- export default router;
@@ -1,166 +0,0 @@
1
- import express from 'express';
2
- import { webhookDb } from '../database/db.js';
3
-
4
- const router = express.Router();
5
-
6
- // GET /api/webhooks — list all webhooks for the user
7
- router.get('/', async (req, res) => {
8
- try {
9
- const webhooks = await webhookDb.getAll(req.user.id);
10
- res.json({ webhooks });
11
- } catch (error) {
12
- res.status(500).json({ error: 'Failed to fetch webhooks' });
13
- }
14
- });
15
-
16
- // POST /api/webhooks — create a webhook
17
- router.post('/', async (req, res) => {
18
- try {
19
- const { name, url, method, headers, description } = req.body;
20
- if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
21
- if (!url || !url.trim()) return res.status(400).json({ error: 'URL is required' });
22
-
23
- try { new URL(url); } catch { return res.status(400).json({ error: 'Invalid URL' }); }
24
-
25
- const allowedMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
26
- const m = (method || 'POST').toUpperCase();
27
- if (!allowedMethods.includes(m)) return res.status(400).json({ error: 'Invalid HTTP method' });
28
-
29
- // Validate headers is valid JSON if provided
30
- let headersStr = '{}';
31
- if (headers) {
32
- if (typeof headers === 'string') {
33
- try { JSON.parse(headers); headersStr = headers; } catch { return res.status(400).json({ error: 'Headers must be valid JSON' }); }
34
- } else {
35
- headersStr = JSON.stringify(headers);
36
- }
37
- }
38
-
39
- const webhook = await webhookDb.create(req.user.id, {
40
- name: name.trim(),
41
- url: url.trim(),
42
- method: m,
43
- headers: headersStr,
44
- description: description?.trim() || null
45
- });
46
- res.json({ success: true, webhook });
47
- } catch (error) {
48
- res.status(500).json({ error: 'Failed to create webhook' });
49
- }
50
- });
51
-
52
- // PUT /api/webhooks/:id — update a webhook
53
- router.put('/:id', async (req, res) => {
54
- try {
55
- const { name, url, method, headers, description } = req.body;
56
- if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
57
- if (!url || !url.trim()) return res.status(400).json({ error: 'URL is required' });
58
-
59
- try { new URL(url); } catch { return res.status(400).json({ error: 'Invalid URL' }); }
60
-
61
- let headersStr = '{}';
62
- if (headers) {
63
- if (typeof headers === 'string') {
64
- try { JSON.parse(headers); headersStr = headers; } catch { return res.status(400).json({ error: 'Headers must be valid JSON' }); }
65
- } else {
66
- headersStr = JSON.stringify(headers);
67
- }
68
- }
69
-
70
- const updated = await webhookDb.update(Number(req.params.id), req.user.id, {
71
- name: name.trim(),
72
- url: url.trim(),
73
- method: (method || 'POST').toUpperCase(),
74
- headers: headersStr,
75
- description: description?.trim() || null
76
- });
77
-
78
- if (!updated) return res.status(404).json({ error: 'Webhook not found' });
79
- res.json({ success: true });
80
- } catch (error) {
81
- res.status(500).json({ error: 'Failed to update webhook' });
82
- }
83
- });
84
-
85
- // DELETE /api/webhooks/:id — delete a webhook
86
- router.delete('/:id', async (req, res) => {
87
- try {
88
- const deleted = await webhookDb.delete(Number(req.params.id), req.user.id);
89
- if (!deleted) return res.status(404).json({ error: 'Webhook not found' });
90
- res.json({ success: true });
91
- } catch (error) {
92
- res.status(500).json({ error: 'Failed to delete webhook' });
93
- }
94
- });
95
-
96
- // POST /api/webhooks/:id/test — fire a test request to the webhook
97
- router.post('/:id/test', async (req, res) => {
98
- try {
99
- const webhook = await webhookDb.getById(Number(req.params.id), req.user.id);
100
- if (!webhook) return res.status(404).json({ error: 'Webhook not found' });
101
-
102
- let parsedHeaders = {};
103
- try { parsedHeaders = JSON.parse(webhook.headers || '{}'); } catch { /* ignore */ }
104
-
105
- const controller = new AbortController();
106
- const timeout = setTimeout(() => controller.abort(), 10000);
107
-
108
- try {
109
- const fetchOptions = {
110
- method: webhook.method,
111
- headers: {
112
- 'Content-Type': 'application/json',
113
- 'User-Agent': 'UpfynAI-Webhook/1.0',
114
- ...parsedHeaders
115
- },
116
- signal: controller.signal
117
- };
118
-
119
- // Add test body for methods that support it
120
- if (['POST', 'PUT', 'PATCH'].includes(webhook.method)) {
121
- fetchOptions.body = JSON.stringify({
122
- event: 'test',
123
- webhook_id: webhook.id,
124
- webhook_name: webhook.name,
125
- timestamp: new Date().toISOString()
126
- });
127
- }
128
-
129
- const response = await fetch(webhook.url, fetchOptions);
130
- clearTimeout(timeout);
131
-
132
- let body;
133
- const contentType = response.headers.get('content-type') || '';
134
- if (contentType.includes('application/json')) {
135
- body = await response.json();
136
- } else {
137
- body = await response.text();
138
- if (body.length > 2000) body = body.slice(0, 2000) + '...';
139
- }
140
-
141
- await webhookDb.updateLastTriggered(webhook.id);
142
-
143
- res.json({
144
- success: true,
145
- result: {
146
- status: response.status,
147
- statusText: response.statusText,
148
- body,
149
- headers: Object.fromEntries(response.headers.entries())
150
- }
151
- });
152
- } catch (fetchError) {
153
- clearTimeout(timeout);
154
- res.json({
155
- success: false,
156
- result: {
157
- error: fetchError.name === 'AbortError' ? 'Request timed out (10s)' : fetchError.message
158
- }
159
- });
160
- }
161
- } catch (error) {
162
- res.status(500).json({ error: 'Failed to test webhook' });
163
- }
164
- });
165
-
166
- export default router;