Download MD no histórico, relatórios externos e service systemd
- Botão de download .md no modal de detalhe do histórico (agente e pipeline) - Relatórios de execução gravados também em ~/agent_reports/ (configurável via AGENT_REPORTS_DIR) - Service systemd (user) para iniciar o orchestrator no boot com auto-restart
This commit is contained in:
139
package-lock.json
generated
139
package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"express-rate-limit": "^8.2.1",
|
"express-rate-limit": "^8.2.1",
|
||||||
"helmet": "^8.1.0",
|
"helmet": "^8.1.0",
|
||||||
|
"multer": "^2.0.2",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
@@ -30,6 +31,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/append-field": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/array-flatten": {
|
"node_modules/array-flatten": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
@@ -60,6 +67,23 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-from": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/busboy": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||||
|
"dependencies": {
|
||||||
|
"streamsearch": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -137,6 +161,21 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/concat-stream": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||||
|
"engines": [
|
||||||
|
"node >= 6.0"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.0.2",
|
||||||
|
"typedarray": "^0.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
@@ -591,12 +630,51 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mkdirp": {
|
||||||
|
"version": "0.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||||
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.6"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mkdirp": "bin/cmd.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/multer": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"append-field": "^1.0.0",
|
||||||
|
"busboy": "^1.6.0",
|
||||||
|
"concat-stream": "^2.0.0",
|
||||||
|
"mkdirp": "^0.5.6",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"type-is": "^1.6.18",
|
||||||
|
"xtend": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
@@ -627,6 +705,15 @@
|
|||||||
"uuid": "dist/bin/uuid"
|
"uuid": "dist/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-inspect": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.13.4",
|
"version": "1.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||||
@@ -727,6 +814,20 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@@ -885,6 +986,23 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/streamsearch": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/toidentifier": {
|
"node_modules/toidentifier": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
@@ -907,6 +1025,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typedarray": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
@@ -916,6 +1040,12 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/utils-merge": {
|
"node_modules/utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
@@ -967,6 +1097,15 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"express-rate-limit": "^8.2.1",
|
"express-rate-limit": "^8.2.1",
|
||||||
"helmet": "^8.1.0",
|
"helmet": "^8.1.0",
|
||||||
|
"multer": "^2.0.2",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
|
|||||||
@@ -2813,6 +2813,118 @@ tbody tr:hover td {
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropzone {
|
||||||
|
border: 2px dashed var(--border-secondary);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone:hover,
|
||||||
|
.dropzone.dragover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: rgba(139, 92, 246, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-content i,
|
||||||
|
.dropzone-content svg {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-content p {
|
||||||
|
font-size: 13px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-browse {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
text-decoration: underline;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-hint {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 8px 0 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-list:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-list + .dropzone-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-file {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-file-name {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-file-size {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 11px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-file-remove {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
line-height: 1;
|
||||||
|
border-radius: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-file-remove:hover {
|
||||||
|
color: var(--error);
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.form-actions {
|
.form-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|||||||
@@ -974,6 +974,19 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Arquivos de Contexto</label>
|
||||||
|
<div class="dropzone" id="execute-dropzone">
|
||||||
|
<input type="file" id="execute-files" multiple hidden />
|
||||||
|
<div class="dropzone-content">
|
||||||
|
<i data-lucide="upload-cloud"></i>
|
||||||
|
<p>Arraste arquivos aqui ou <button type="button" class="dropzone-browse">selecione</button></p>
|
||||||
|
<span class="dropzone-hint">Até 20 arquivos, 10MB cada</span>
|
||||||
|
</div>
|
||||||
|
<ul class="dropzone-list" id="execute-file-list"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="quick-templates">
|
<div class="quick-templates">
|
||||||
<p class="quick-templates-label">Templates rápidos</p>
|
<p class="quick-templates-label">Templates rápidos</p>
|
||||||
<div class="quick-templates-grid">
|
<div class="quick-templates-grid">
|
||||||
@@ -1174,6 +1187,18 @@
|
|||||||
</label>
|
</label>
|
||||||
<textarea class="textarea" id="pipeline-execute-input" rows="4" placeholder="Descreva a tarefa inicial para o pipeline..."></textarea>
|
<textarea class="textarea" id="pipeline-execute-input" rows="4" placeholder="Descreva a tarefa inicial para o pipeline..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Arquivos de Contexto</label>
|
||||||
|
<div class="dropzone" id="pipeline-execute-dropzone">
|
||||||
|
<input type="file" id="pipeline-execute-files" multiple hidden />
|
||||||
|
<div class="dropzone-content">
|
||||||
|
<i data-lucide="upload-cloud"></i>
|
||||||
|
<p>Arraste arquivos aqui ou <button type="button" class="dropzone-browse">selecione</button></p>
|
||||||
|
<span class="dropzone-hint">Até 20 arquivos, 10MB cada</span>
|
||||||
|
</div>
|
||||||
|
<ul class="dropzone-list" id="pipeline-execute-file-list"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn--ghost" type="button" data-modal-close="pipeline-execute-modal-overlay">Cancelar</button>
|
<button class="btn btn--ghost" type="button" data-modal-close="pipeline-execute-modal-overlay">Cancelar</button>
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ const API = {
|
|||||||
create(data) { return API.request('POST', '/agents', data); },
|
create(data) { return API.request('POST', '/agents', data); },
|
||||||
update(id, data) { return API.request('PUT', `/agents/${id}`, data); },
|
update(id, data) { return API.request('PUT', `/agents/${id}`, data); },
|
||||||
delete(id) { return API.request('DELETE', `/agents/${id}`); },
|
delete(id) { return API.request('DELETE', `/agents/${id}`); },
|
||||||
execute(id, task, instructions) { return API.request('POST', `/agents/${id}/execute`, { task, instructions }); },
|
execute(id, task, instructions, contextFiles) {
|
||||||
|
const body = { task, instructions };
|
||||||
|
if (contextFiles && contextFiles.length > 0) body.contextFiles = contextFiles;
|
||||||
|
return API.request('POST', `/agents/${id}/execute`, body);
|
||||||
|
},
|
||||||
cancel(id, executionId) { return API.request('POST', `/agents/${id}/cancel/${executionId}`); },
|
cancel(id, executionId) { return API.request('POST', `/agents/${id}/cancel/${executionId}`); },
|
||||||
continue(id, sessionId, message) { return API.request('POST', `/agents/${id}/continue`, { sessionId, message }); },
|
continue(id, sessionId, message) { return API.request('POST', `/agents/${id}/continue`, { sessionId, message }); },
|
||||||
export(id) { return API.request('GET', `/agents/${id}/export`); },
|
export(id) { return API.request('GET', `/agents/${id}/export`); },
|
||||||
@@ -78,9 +82,10 @@ const API = {
|
|||||||
create(data) { return API.request('POST', '/pipelines', data); },
|
create(data) { return API.request('POST', '/pipelines', data); },
|
||||||
update(id, data) { return API.request('PUT', `/pipelines/${id}`, data); },
|
update(id, data) { return API.request('PUT', `/pipelines/${id}`, data); },
|
||||||
delete(id) { return API.request('DELETE', `/pipelines/${id}`); },
|
delete(id) { return API.request('DELETE', `/pipelines/${id}`); },
|
||||||
execute(id, input, workingDirectory) {
|
execute(id, input, workingDirectory, contextFiles) {
|
||||||
const body = { input };
|
const body = { input };
|
||||||
if (workingDirectory) body.workingDirectory = workingDirectory;
|
if (workingDirectory) body.workingDirectory = workingDirectory;
|
||||||
|
if (contextFiles && contextFiles.length > 0) body.contextFiles = contextFiles;
|
||||||
return API.request('POST', `/pipelines/${id}/execute`, body);
|
return API.request('POST', `/pipelines/${id}/execute`, body);
|
||||||
},
|
},
|
||||||
cancel(id) { return API.request('POST', `/pipelines/${id}/cancel`); },
|
cancel(id) { return API.request('POST', `/pipelines/${id}/cancel`); },
|
||||||
@@ -119,6 +124,21 @@ const API = {
|
|||||||
save(data) { return API.request('PUT', '/settings', data); },
|
save(data) { return API.request('PUT', '/settings', data); },
|
||||||
},
|
},
|
||||||
|
|
||||||
|
uploads: {
|
||||||
|
async send(files) {
|
||||||
|
const form = new FormData();
|
||||||
|
for (const f of files) form.append('files', f);
|
||||||
|
const response = await fetch('/api/uploads', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'X-Client-Id': API.clientId },
|
||||||
|
body: form,
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (!response.ok) throw new Error(data.error || 'Erro no upload');
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
reports: {
|
reports: {
|
||||||
list() { return API.request('GET', '/reports'); },
|
list() { return API.request('GET', '/reports'); },
|
||||||
get(filename) { return API.request('GET', `/reports/${encodeURIComponent(filename)}`); },
|
get(filename) { return API.request('GET', `/reports/${encodeURIComponent(filename)}`); },
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ const App = {
|
|||||||
wsReconnectTimer: null,
|
wsReconnectTimer: null,
|
||||||
_initialized: false,
|
_initialized: false,
|
||||||
_lastAgentName: '',
|
_lastAgentName: '',
|
||||||
|
_executeDropzone: null,
|
||||||
|
_pipelineDropzone: null,
|
||||||
|
|
||||||
sectionTitles: {
|
sectionTitles: {
|
||||||
dashboard: 'Dashboard',
|
dashboard: 'Dashboard',
|
||||||
@@ -32,6 +34,9 @@ const App = {
|
|||||||
App.setupEventListeners();
|
App.setupEventListeners();
|
||||||
App.setupKeyboardShortcuts();
|
App.setupKeyboardShortcuts();
|
||||||
|
|
||||||
|
App._executeDropzone = Utils.initDropzone('execute-dropzone', 'execute-files', 'execute-file-list');
|
||||||
|
App._pipelineDropzone = Utils.initDropzone('pipeline-execute-dropzone', 'pipeline-execute-files', 'pipeline-execute-file-list');
|
||||||
|
|
||||||
const initialSection = location.hash.replace('#', '') || 'dashboard';
|
const initialSection = location.hash.replace('#', '') || 'dashboard';
|
||||||
App.navigateTo(App.sections.includes(initialSection) ? initialSection : 'dashboard');
|
App.navigateTo(App.sections.includes(initialSection) ? initialSection : 'dashboard');
|
||||||
App.startPeriodicRefresh();
|
App.startPeriodicRefresh();
|
||||||
@@ -841,11 +846,20 @@ const App = {
|
|||||||
const selectEl = document.getElementById('execute-agent-select');
|
const selectEl = document.getElementById('execute-agent-select');
|
||||||
const agentName = selectEl?.selectedOptions[0]?.text || 'Agente';
|
const agentName = selectEl?.selectedOptions[0]?.text || 'Agente';
|
||||||
|
|
||||||
|
let contextFiles = null;
|
||||||
|
const dropzone = App._executeDropzone;
|
||||||
|
if (dropzone && dropzone.getFiles().length > 0) {
|
||||||
|
Toast.info('Fazendo upload dos arquivos...');
|
||||||
|
const uploadResult = await API.uploads.send(dropzone.getFiles());
|
||||||
|
contextFiles = uploadResult.files;
|
||||||
|
}
|
||||||
|
|
||||||
Terminal.disableChat();
|
Terminal.disableChat();
|
||||||
App._lastAgentName = agentName;
|
App._lastAgentName = agentName;
|
||||||
|
|
||||||
await API.agents.execute(agentId, task, instructions);
|
await API.agents.execute(agentId, task, instructions, contextFiles);
|
||||||
|
|
||||||
|
if (dropzone) dropzone.reset();
|
||||||
Modal.close('execute-modal-overlay');
|
Modal.close('execute-modal-overlay');
|
||||||
App.navigateTo('terminal');
|
App.navigateTo('terminal');
|
||||||
Toast.info('Execução iniciada');
|
Toast.info('Execução iniciada');
|
||||||
|
|||||||
@@ -335,6 +335,8 @@ const AgentsUI = {
|
|||||||
const instructionsEl = document.getElementById('execute-instructions');
|
const instructionsEl = document.getElementById('execute-instructions');
|
||||||
if (instructionsEl) instructionsEl.value = '';
|
if (instructionsEl) instructionsEl.value = '';
|
||||||
|
|
||||||
|
if (App._executeDropzone) App._executeDropzone.reset();
|
||||||
|
|
||||||
AgentsUI._loadSavedTasks();
|
AgentsUI._loadSavedTasks();
|
||||||
|
|
||||||
Modal.open('execute-modal-overlay');
|
Modal.open('execute-modal-overlay');
|
||||||
|
|||||||
@@ -188,6 +188,10 @@ const HistoryUI = {
|
|||||||
Modal.open('execution-detail-modal-overlay');
|
Modal.open('execution-detail-modal-overlay');
|
||||||
Utils.refreshIcons(content);
|
Utils.refreshIcons(content);
|
||||||
|
|
||||||
|
content.querySelector('[data-action="download-result-md"]')?.addEventListener('click', () => {
|
||||||
|
HistoryUI._downloadResultMd(exec);
|
||||||
|
});
|
||||||
|
|
||||||
content.querySelectorAll('.pipeline-step-prompt-toggle').forEach((btn) => {
|
content.querySelectorAll('.pipeline-step-prompt-toggle').forEach((btn) => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
const stepCard = btn.closest('.pipeline-step-detail');
|
const stepCard = btn.closest('.pipeline-step-detail');
|
||||||
@@ -217,6 +221,12 @@ const HistoryUI = {
|
|||||||
: '';
|
: '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
${exec.result ? `
|
||||||
|
<div class="report-actions">
|
||||||
|
<button class="btn btn-ghost btn-sm" data-action="download-result-md" type="button">
|
||||||
|
<i data-lucide="download"></i> Download .md
|
||||||
|
</button>
|
||||||
|
</div>` : ''}
|
||||||
<div class="execution-detail-meta">
|
<div class="execution-detail-meta">
|
||||||
<div class="execution-detail-row">
|
<div class="execution-detail-row">
|
||||||
<span class="execution-detail-label">Agente</span>
|
<span class="execution-detail-label">Agente</span>
|
||||||
@@ -326,7 +336,14 @@ const HistoryUI = {
|
|||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
const hasResults = steps.some(s => s.result);
|
||||||
return `
|
return `
|
||||||
|
${hasResults ? `
|
||||||
|
<div class="report-actions">
|
||||||
|
<button class="btn btn-ghost btn-sm" data-action="download-result-md" type="button">
|
||||||
|
<i data-lucide="download"></i> Download .md
|
||||||
|
</button>
|
||||||
|
</div>` : ''}
|
||||||
<div class="execution-detail-meta">
|
<div class="execution-detail-meta">
|
||||||
<div class="execution-detail-row">
|
<div class="execution-detail-row">
|
||||||
<span class="execution-detail-label">Pipeline</span>
|
<span class="execution-detail-label">Pipeline</span>
|
||||||
@@ -374,6 +391,36 @@ const HistoryUI = {
|
|||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_downloadResultMd(exec) {
|
||||||
|
let md = '';
|
||||||
|
const name = exec.type === 'pipeline'
|
||||||
|
? (exec.pipelineName || 'Pipeline')
|
||||||
|
: (exec.agentName || 'Agente');
|
||||||
|
|
||||||
|
if (exec.type === 'pipeline') {
|
||||||
|
md += `# ${name}\n\n`;
|
||||||
|
const steps = Array.isArray(exec.steps) ? exec.steps : [];
|
||||||
|
steps.forEach((step, i) => {
|
||||||
|
md += `## Passo ${i + 1} — ${step.agentName || 'Agente'}\n\n`;
|
||||||
|
if (step.result) md += `${step.result}\n\n`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
md += exec.result || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
|
||||||
|
const filename = `${slug}-${new Date(exec.startedAt || Date.now()).toISOString().slice(0, 10)}.md`;
|
||||||
|
|
||||||
|
const blob = new Blob([md], { type: 'text/markdown' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
Toast.success('Download iniciado');
|
||||||
|
},
|
||||||
|
|
||||||
async retryExecution(id) {
|
async retryExecution(id) {
|
||||||
try {
|
try {
|
||||||
await API.executions.retry(id);
|
await API.executions.retry(id);
|
||||||
|
|||||||
@@ -370,6 +370,8 @@ const PipelinesUI = {
|
|||||||
const workdirEl = document.getElementById('pipeline-execute-workdir');
|
const workdirEl = document.getElementById('pipeline-execute-workdir');
|
||||||
if (workdirEl) workdirEl.value = '';
|
if (workdirEl) workdirEl.value = '';
|
||||||
|
|
||||||
|
if (App._pipelineDropzone) App._pipelineDropzone.reset();
|
||||||
|
|
||||||
Modal.open('pipeline-execute-modal-overlay');
|
Modal.open('pipeline-execute-modal-overlay');
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -384,7 +386,16 @@ const PipelinesUI = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await API.pipelines.execute(pipelineId, input, workingDirectory);
|
let contextFiles = null;
|
||||||
|
const dropzone = App._pipelineDropzone;
|
||||||
|
if (dropzone && dropzone.getFiles().length > 0) {
|
||||||
|
Toast.info('Fazendo upload dos arquivos...');
|
||||||
|
const uploadResult = await API.uploads.send(dropzone.getFiles());
|
||||||
|
contextFiles = uploadResult.files;
|
||||||
|
}
|
||||||
|
|
||||||
|
await API.pipelines.execute(pipelineId, input, workingDirectory, contextFiles);
|
||||||
|
if (dropzone) dropzone.reset();
|
||||||
Modal.close('pipeline-execute-modal-overlay');
|
Modal.close('pipeline-execute-modal-overlay');
|
||||||
App.navigateTo('terminal');
|
App.navigateTo('terminal');
|
||||||
Toast.info('Pipeline iniciado');
|
Toast.info('Pipeline iniciado');
|
||||||
|
|||||||
@@ -35,6 +35,71 @@ const Utils = {
|
|||||||
if (pending.length === 0) return;
|
if (pending.length === 0) return;
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
formatFileSize(bytes) {
|
||||||
|
if (bytes < 1024) return bytes + ' B';
|
||||||
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||||
|
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||||
|
},
|
||||||
|
|
||||||
|
initDropzone(dropzoneId, fileInputId, fileListId) {
|
||||||
|
const zone = document.getElementById(dropzoneId);
|
||||||
|
const input = document.getElementById(fileInputId);
|
||||||
|
const list = document.getElementById(fileListId);
|
||||||
|
if (!zone || !input || !list) return null;
|
||||||
|
|
||||||
|
const state = { files: [] };
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
list.innerHTML = state.files.map((f, i) => `
|
||||||
|
<li class="dropzone-file">
|
||||||
|
<span class="dropzone-file-name">${Utils.escapeHtml(f.name)}</span>
|
||||||
|
<span class="dropzone-file-size">${Utils.formatFileSize(f.size)}</span>
|
||||||
|
<button type="button" class="dropzone-file-remove" data-index="${i}" title="Remover">×</button>
|
||||||
|
</li>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
const content = zone.querySelector('.dropzone-content');
|
||||||
|
if (content) content.style.display = state.files.length > 0 ? 'none' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFiles(fileList) {
|
||||||
|
for (const f of fileList) {
|
||||||
|
if (state.files.length >= 20) break;
|
||||||
|
if (f.size > 10 * 1024 * 1024) continue;
|
||||||
|
const dupe = state.files.some(x => x.name === f.name && x.size === f.size);
|
||||||
|
if (!dupe) state.files.push(f);
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
zone.addEventListener('click', (e) => {
|
||||||
|
if (e.target.closest('.dropzone-file-remove')) {
|
||||||
|
const idx = parseInt(e.target.closest('.dropzone-file-remove').dataset.index);
|
||||||
|
state.files.splice(idx, 1);
|
||||||
|
render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!e.target.closest('.dropzone-file')) input.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('change', () => {
|
||||||
|
if (input.files.length > 0) addFiles(input.files);
|
||||||
|
input.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.classList.add('dragover'); });
|
||||||
|
zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
|
||||||
|
zone.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
zone.classList.remove('dragover');
|
||||||
|
if (e.dataTransfer.files.length > 0) addFiles(e.dataTransfer.files);
|
||||||
|
});
|
||||||
|
|
||||||
|
state.reset = () => { state.files = []; render(); };
|
||||||
|
state.getFiles = () => state.files;
|
||||||
|
return state;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
window.Utils = Utils;
|
window.Utils = Utils;
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import { fileURLToPath } from 'url';
|
|||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const REPORTS_DIR = join(__dirname, '..', '..', 'data', 'reports');
|
const REPORTS_DIR = join(__dirname, '..', '..', 'data', 'reports');
|
||||||
|
const EXTERNAL_REPORTS_DIR = process.env.AGENT_REPORTS_DIR || join(process.env.HOME || '/home/fred', 'agent_reports');
|
||||||
|
|
||||||
function ensureDir() {
|
function ensureDir() {
|
||||||
if (!existsSync(REPORTS_DIR)) mkdirSync(REPORTS_DIR, { recursive: true });
|
if (!existsSync(REPORTS_DIR)) mkdirSync(REPORTS_DIR, { recursive: true });
|
||||||
|
if (!existsSync(EXTERNAL_REPORTS_DIR)) mkdirSync(EXTERNAL_REPORTS_DIR, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeFilename(name) {
|
function sanitizeFilename(name) {
|
||||||
@@ -83,7 +85,9 @@ export function generateAgentReport(execution) {
|
|||||||
|
|
||||||
lines.push('', '---', '', `_Relatório gerado automaticamente em ${formatDate(new Date().toISOString())}_`);
|
lines.push('', '---', '', `_Relatório gerado automaticamente em ${formatDate(new Date().toISOString())}_`);
|
||||||
|
|
||||||
writeFileSync(filepath, lines.join('\n'), 'utf-8');
|
const content = lines.join('\n');
|
||||||
|
writeFileSync(filepath, content, 'utf-8');
|
||||||
|
try { writeFileSync(join(EXTERNAL_REPORTS_DIR, filename), content, 'utf-8'); } catch {}
|
||||||
return { filename, filepath };
|
return { filename, filepath };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +187,8 @@ export function generatePipelineReport(execution) {
|
|||||||
|
|
||||||
lines.push('---', '', `_Relatório gerado automaticamente em ${formatDate(new Date().toISOString())}_`);
|
lines.push('---', '', `_Relatório gerado automaticamente em ${formatDate(new Date().toISOString())}_`);
|
||||||
|
|
||||||
writeFileSync(filepath, lines.join('\n'), 'utf-8');
|
const content = lines.join('\n');
|
||||||
|
writeFileSync(filepath, content, 'utf-8');
|
||||||
|
try { writeFileSync(join(EXTERNAL_REPORTS_DIR, filename), content, 'utf-8'); } catch {}
|
||||||
return { filename, filepath };
|
return { filename, filepath };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { execFile } from 'child_process';
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
import multer from 'multer';
|
||||||
import * as manager from '../agents/manager.js';
|
import * as manager from '../agents/manager.js';
|
||||||
import { tasksStore, settingsStore, executionsStore, webhooksStore, notificationsStore, secretsStore, agentVersionsStore } from '../store/db.js';
|
import { tasksStore, settingsStore, executionsStore, webhooksStore, notificationsStore, secretsStore, agentVersionsStore } from '../store/db.js';
|
||||||
import * as scheduler from '../agents/scheduler.js';
|
import * as scheduler from '../agents/scheduler.js';
|
||||||
@@ -10,12 +11,30 @@ import * as pipeline from '../agents/pipeline.js';
|
|||||||
import { getBinPath, updateMaxConcurrent } from '../agents/executor.js';
|
import { getBinPath, updateMaxConcurrent } from '../agents/executor.js';
|
||||||
import { invalidateAgentMapCache } from '../agents/pipeline.js';
|
import { invalidateAgentMapCache } from '../agents/pipeline.js';
|
||||||
import { cached } from '../cache/index.js';
|
import { cached } from '../cache/index.js';
|
||||||
import { readdirSync, readFileSync, unlinkSync, existsSync } from 'fs';
|
import { readdirSync, readFileSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
||||||
import { join, dirname, resolve as pathResolve } from 'path';
|
import { join, dirname, resolve as pathResolve, extname } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
const __apiDirname = dirname(fileURLToPath(import.meta.url));
|
const __apiDirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const REPORTS_DIR = join(__apiDirname, '..', '..', 'data', 'reports');
|
const REPORTS_DIR = join(__apiDirname, '..', '..', 'data', 'reports');
|
||||||
|
const UPLOADS_DIR = join(__apiDirname, '..', '..', 'data', 'uploads');
|
||||||
|
|
||||||
|
if (!existsSync(UPLOADS_DIR)) mkdirSync(UPLOADS_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const upload = multer({
|
||||||
|
storage: multer.diskStorage({
|
||||||
|
destination: (req, file, cb) => {
|
||||||
|
const sessionDir = join(UPLOADS_DIR, req.uploadSessionId || 'tmp');
|
||||||
|
if (!existsSync(sessionDir)) mkdirSync(sessionDir, { recursive: true });
|
||||||
|
cb(null, sessionDir);
|
||||||
|
},
|
||||||
|
filename: (req, file, cb) => {
|
||||||
|
const safe = file.originalname.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 200);
|
||||||
|
cb(null, `${Date.now()}-${safe}`);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024, files: 20 },
|
||||||
|
});
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
export const hookRouter = Router();
|
export const hookRouter = Router();
|
||||||
@@ -122,12 +141,36 @@ router.delete('/agents/:id', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/uploads', (req, res, next) => {
|
||||||
|
req.uploadSessionId = uuidv4();
|
||||||
|
next();
|
||||||
|
}, upload.array('files', 20), (req, res) => {
|
||||||
|
try {
|
||||||
|
const files = (req.files || []).map(f => ({
|
||||||
|
originalName: f.originalname,
|
||||||
|
path: f.path,
|
||||||
|
size: f.size,
|
||||||
|
}));
|
||||||
|
res.json({ sessionId: req.uploadSessionId, files });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function buildContextFilesPrompt(contextFiles) {
|
||||||
|
if (!Array.isArray(contextFiles) || contextFiles.length === 0) return '';
|
||||||
|
const lines = contextFiles.map(f => `- ${f.path} (${f.originalName})`);
|
||||||
|
return `\n\nArquivos de contexto anexados (leia cada um deles antes de iniciar):\n${lines.join('\n')}`;
|
||||||
|
}
|
||||||
|
|
||||||
router.post('/agents/:id/execute', (req, res) => {
|
router.post('/agents/:id/execute', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { task, instructions } = req.body;
|
const { task, instructions, contextFiles } = req.body;
|
||||||
if (!task) return res.status(400).json({ error: 'task é obrigatório' });
|
if (!task) return res.status(400).json({ error: 'task é obrigatório' });
|
||||||
const clientId = req.headers['x-client-id'] || null;
|
const clientId = req.headers['x-client-id'] || null;
|
||||||
const executionId = manager.executeTask(req.params.id, task, instructions, (msg) => wsCallback(msg, clientId));
|
const filesPrompt = buildContextFilesPrompt(contextFiles);
|
||||||
|
const fullTask = task + filesPrompt;
|
||||||
|
const executionId = manager.executeTask(req.params.id, fullTask, instructions, (msg) => wsCallback(msg, clientId));
|
||||||
res.status(202).json({ executionId, status: 'started' });
|
res.status(202).json({ executionId, status: 'started' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const status = err.message.includes('não encontrado') ? 404 : 400;
|
const status = err.message.includes('não encontrado') ? 404 : 400;
|
||||||
@@ -417,12 +460,14 @@ router.delete('/pipelines/:id', (req, res) => {
|
|||||||
|
|
||||||
router.post('/pipelines/:id/execute', async (req, res) => {
|
router.post('/pipelines/:id/execute', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { input, workingDirectory } = req.body;
|
const { input, workingDirectory, contextFiles } = req.body;
|
||||||
if (!input) return res.status(400).json({ error: 'input é obrigatório' });
|
if (!input) return res.status(400).json({ error: 'input é obrigatório' });
|
||||||
const clientId = req.headers['x-client-id'] || null;
|
const clientId = req.headers['x-client-id'] || null;
|
||||||
const options = {};
|
const options = {};
|
||||||
if (workingDirectory) options.workingDirectory = workingDirectory;
|
if (workingDirectory) options.workingDirectory = workingDirectory;
|
||||||
const result = pipeline.executePipeline(req.params.id, input, (msg) => wsCallback(msg, clientId), options);
|
const filesPrompt = buildContextFilesPrompt(contextFiles);
|
||||||
|
const fullInput = input + filesPrompt;
|
||||||
|
const result = pipeline.executePipeline(req.params.id, fullInput, (msg) => wsCallback(msg, clientId), options);
|
||||||
result.catch(() => {});
|
result.catch(() => {});
|
||||||
res.status(202).json({ pipelineId: req.params.id, status: 'started' });
|
res.status(202).json({ pipelineId: req.params.id, status: 'started' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user