@igoruehara/canvas-flow 0.1.9 → 0.1.11
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.
- package/README.md +4 -3
- package/bin/canvas-flow.js +80 -6
- package/package.json +1 -1
- package/public/assets/{index-qV8twxcq.js → index-CAYKh0bn.js} +78 -78
- package/public/index.html +1 -1
- package/server/auth/auth-controller.d.ts +2 -0
- package/server/auth/auth-service.d.ts +2 -0
- package/server/auth/auth-service.js +5 -0
- package/server/auth/auth-service.js.map +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Canvas Flow
|
|
2
2
|
|
|
3
|
-
Canvas Flow
|
|
3
|
+
Canvas Flow é uma aplicação web local para criar, testar e executar agentes de IA multiagente em formato visual, com canais prontos para WhatsApp e Web Widget. O pacote npm sobe frontend e backend juntos em um único comando.
|
|
4
4
|
|
|
5
5
|
Use quando quiser:
|
|
6
6
|
|
|
7
|
-
- criar
|
|
7
|
+
- criar agentes de IA para atendimento e automação;
|
|
8
|
+
- publicar agentes nos canais WhatsApp e Web Widget;
|
|
8
9
|
- conectar prompts, ferramentas, webhooks e documentos;
|
|
9
10
|
- testar fluxos localmente antes de publicar;
|
|
10
11
|
- usar RAG com documentos;
|
|
@@ -42,7 +43,7 @@ http://localhost:3333
|
|
|
42
43
|
- execucao local de frontend e API no mesmo processo Node;
|
|
43
44
|
- configuracao de provedores de IA pela UI ou pelo `config.json`;
|
|
44
45
|
- RAG com Milvus/Zilliz ou Azure AI Search;
|
|
45
|
-
- entrada
|
|
46
|
+
- canais de entrada para WhatsApp, Web Widget, API e webhook;
|
|
46
47
|
- chaves de API para consumir fluxos publicados;
|
|
47
48
|
- validacao do ambiente com `doctor`.
|
|
48
49
|
|
package/bin/canvas-flow.js
CHANGED
|
@@ -465,6 +465,19 @@ function joinCorsOrigins(config, publicUrl, port) {
|
|
|
465
465
|
].join(',');
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
+
function isLoopbackUrl(value) {
|
|
469
|
+
try {
|
|
470
|
+
const hostname = new URL(value).hostname.toLowerCase();
|
|
471
|
+
return hostname === 'localhost'
|
|
472
|
+
|| hostname === '::1'
|
|
473
|
+
|| hostname === '[::1]'
|
|
474
|
+
|| hostname === '0.0.0.0'
|
|
475
|
+
|| hostname.startsWith('127.');
|
|
476
|
+
} catch {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
468
481
|
function applyEnvironment(config, paths, flags) {
|
|
469
482
|
const port = Number(flags.port || config.server.port || 3333);
|
|
470
483
|
const publicUrl = String(flags['public-url'] || config.server.publicUrl || `http://localhost:${port}`).replace(/\/$/, '');
|
|
@@ -515,11 +528,18 @@ function applyEnvironment(config, paths, flags) {
|
|
|
515
528
|
setEnv('MONGO_SERVER_SELECTION_TIMEOUT_MS', config.database.mongoServerSelectionTimeoutMs);
|
|
516
529
|
setEnv('MONGO_CONNECT_TIMEOUT_MS', config.database.mongoConnectTimeoutMs);
|
|
517
530
|
|
|
518
|
-
|
|
531
|
+
const loginRequired = asBool(config.auth.login);
|
|
532
|
+
const exposeApiTokenToFrontend = config.auth.exposeApiTokenToFrontend === true
|
|
533
|
+
|| (!loginRequired && isLoopbackUrl(publicUrl));
|
|
534
|
+
setBoolEnv('CANVAS_FLOW_LOGIN', loginRequired);
|
|
519
535
|
setEnv('CANVAS_FLOW_LOGIN_TTL_HOURS', config.auth.loginTtlHours);
|
|
520
536
|
setEnv('CANVAS_FLOW_LOGIN_THROTTLE_WINDOW_MS', config.auth.loginThrottleWindowMs || 600000);
|
|
521
537
|
setEnv('CANVAS_FLOW_LOGIN_MAX_ATTEMPTS', config.auth.loginMaxAttempts || 8);
|
|
522
538
|
setEnv('CANVAS_FLOW_API_TOKEN', config.auth.apiToken);
|
|
539
|
+
delete process.env.CANVAS_FLOW_FRONTEND_API_TOKEN;
|
|
540
|
+
if (!loginRequired && exposeApiTokenToFrontend) {
|
|
541
|
+
setEnv('CANVAS_FLOW_FRONTEND_API_TOKEN', config.auth.apiToken);
|
|
542
|
+
}
|
|
523
543
|
setEnv('CANVAS_FLOW_JWT_SECRET', config.auth.jwtSecret);
|
|
524
544
|
setEnv('CANVAS_FLOW_MEDIA_PROXY_SECRET', config.auth.mediaProxySecret);
|
|
525
545
|
setEnv('CANVAS_FLOW_MEDIA_PROXY_TTL_SECONDS', config.auth.mediaProxyTtlSeconds);
|
|
@@ -815,6 +835,59 @@ function sleep(ms) {
|
|
|
815
835
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
816
836
|
}
|
|
817
837
|
|
|
838
|
+
async function checkHttpOk(url, timeoutMs = 1200) {
|
|
839
|
+
const controller = new AbortController();
|
|
840
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
841
|
+
try {
|
|
842
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
843
|
+
return response.ok;
|
|
844
|
+
} catch {
|
|
845
|
+
return false;
|
|
846
|
+
} finally {
|
|
847
|
+
clearTimeout(timer);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function startStartupStatus(publicUrl, options = {}) {
|
|
852
|
+
const healthUrl = `${String(publicUrl || '').replace(/\/$/, '')}/health`;
|
|
853
|
+
const startedAt = Date.now();
|
|
854
|
+
let stopped = false;
|
|
855
|
+
|
|
856
|
+
console.log('Canvas Flow startup: loading application bundle...');
|
|
857
|
+
console.log(`Canvas Flow startup: waiting for backend health at ${healthUrl}`);
|
|
858
|
+
|
|
859
|
+
const interval = setInterval(() => {
|
|
860
|
+
const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
|
|
861
|
+
console.log(`Canvas Flow startup: still starting (${elapsedSeconds}s elapsed)...`);
|
|
862
|
+
}, 2500);
|
|
863
|
+
|
|
864
|
+
const stop = () => {
|
|
865
|
+
if (stopped) return;
|
|
866
|
+
stopped = true;
|
|
867
|
+
clearInterval(interval);
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
void (async () => {
|
|
871
|
+
const deadline = Date.now() + 90000;
|
|
872
|
+
while (!stopped && Date.now() < deadline) {
|
|
873
|
+
if (await checkHttpOk(healthUrl)) {
|
|
874
|
+
stop();
|
|
875
|
+
console.log(`Canvas Flow ready: ${publicUrl}`);
|
|
876
|
+
if (options.openBrowser) openBrowser(publicUrl);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
await sleep(500);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (!stopped) {
|
|
883
|
+
stop();
|
|
884
|
+
console.log('Canvas Flow startup: health check is still pending. Keep this terminal open and watch the backend logs above.');
|
|
885
|
+
}
|
|
886
|
+
})();
|
|
887
|
+
|
|
888
|
+
return { stop };
|
|
889
|
+
}
|
|
890
|
+
|
|
818
891
|
function mongoConnectionOptions(config) {
|
|
819
892
|
return {
|
|
820
893
|
serverSelectionTimeoutMS: Number(config.database?.mongoServerSelectionTimeoutMs || 8000),
|
|
@@ -1147,12 +1220,13 @@ async function start(flags) {
|
|
|
1147
1220
|
}
|
|
1148
1221
|
|
|
1149
1222
|
const shouldOpen = flags.open === true || (flags.open !== false && config.server.openBrowser === true);
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1223
|
+
const startupStatus = startStartupStatus(runtime.publicUrl, { openBrowser: shouldOpen });
|
|
1224
|
+
try {
|
|
1225
|
+
require(SERVER_ENTRY);
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
startupStatus.stop();
|
|
1228
|
+
throw error;
|
|
1153
1229
|
}
|
|
1154
|
-
|
|
1155
|
-
require(SERVER_ENTRY);
|
|
1156
1230
|
}
|
|
1157
1231
|
|
|
1158
1232
|
async function main() {
|
package/package.json
CHANGED