project-graph-mcp 2.3.2 → 2.4.1
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/package.json +3 -2
- package/src/analysis/analysis-cache.ctx +9 -0
- package/src/analysis/analysis-cache.js +1 -1
- package/src/analysis/complexity.ctx +6 -0
- package/src/analysis/complexity.js +1 -1
- package/src/analysis/custom-rules.ctx +14 -0
- package/src/analysis/custom-rules.js +1 -1
- package/src/analysis/db-analysis.ctx +7 -0
- package/src/analysis/db-analysis.js +1 -1
- package/src/analysis/dead-code.ctx +6 -0
- package/src/analysis/dead-code.js +1 -1
- package/src/analysis/full-analysis.ctx +9 -0
- package/src/analysis/full-analysis.js +1 -1
- package/src/analysis/jsdoc-checker.ctx +10 -0
- package/src/analysis/jsdoc-checker.js +1 -1
- package/src/analysis/jsdoc-generator.ctx +9 -0
- package/src/analysis/jsdoc-generator.js +1 -1
- package/src/analysis/large-files.ctx +6 -0
- package/src/analysis/large-files.js +1 -1
- package/src/analysis/outdated-patterns.ctx +7 -0
- package/src/analysis/outdated-patterns.js +1 -1
- package/src/analysis/similar-functions.ctx +6 -0
- package/src/analysis/similar-functions.js +1 -1
- package/src/analysis/test-annotations.ctx +11 -0
- package/src/analysis/test-annotations.js +1 -1
- package/src/analysis/type-checker.ctx +6 -0
- package/src/analysis/type-checker.js +1 -1
- package/src/analysis/undocumented.ctx +8 -0
- package/src/analysis/undocumented.js +1 -1
- package/src/cli/cli-handlers.ctx +7 -0
- package/src/cli/cli-handlers.js +1 -1
- package/src/cli/cli.ctx +6 -0
- package/src/cli/cli.js +1 -1
- package/src/compact/ai-context.ctx +6 -0
- package/src/compact/ai-context.js +1 -1
- package/src/compact/compact-migrate.ctx +8 -0
- package/src/compact/compact-migrate.js +1 -1
- package/src/compact/compact.ctx +11 -0
- package/src/compact/compact.js +1 -1
- package/src/compact/compress.ctx +7 -0
- package/src/compact/compress.js +1 -1
- package/src/compact/ctx-resolver.ctx +2 -0
- package/src/compact/ctx-resolver.js +1 -1
- package/src/compact/ctx-to-jsdoc.ctx +11 -0
- package/src/compact/ctx-to-jsdoc.js +1 -1
- package/src/compact/doc-dialect.ctx +11 -0
- package/src/compact/doc-dialect.js +2 -2
- package/src/compact/expand.ctx +14 -0
- package/src/compact/expand.js +1 -1
- package/src/compact/framework-references.ctx +7 -0
- package/src/compact/framework-references.js +1 -1
- package/src/compact/instructions.ctx +6 -0
- package/src/compact/instructions.js +1 -1
- package/src/compact/jsdoc-builder.ctx +4 -0
- package/src/compact/jsdoc-builder.js +1 -1
- package/src/compact/mode-config.ctx +8 -0
- package/src/compact/mode-config.js +1 -1
- package/src/compact/split-declarations.ctx +6 -0
- package/src/compact/split-declarations.js +1 -1
- package/src/compact/validate-pipeline.ctx +12 -0
- package/src/compact/validate-pipeline.js +1 -1
- package/src/core/event-bus.ctx +9 -0
- package/src/core/event-bus.js +1 -1
- package/src/core/file-walker.ctx +1 -0
- package/src/core/file-walker.js +1 -1
- package/src/core/filters.ctx +12 -0
- package/src/core/filters.js +1 -1
- package/src/core/graph-builder.ctx +7 -0
- package/src/core/graph-builder.js +1 -1
- package/src/core/parser.ctx +12 -0
- package/src/core/parser.js +1 -1
- package/src/core/utils.ctx +1 -0
- package/src/core/utils.js +1 -1
- package/src/core/workspace.ctx +7 -0
- package/src/core/workspace.js +1 -1
- package/src/lang/lang-go.ctx +8 -0
- package/src/lang/lang-go.js +1 -1
- package/src/lang/lang-python.ctx +5 -0
- package/src/lang/lang-python.js +1 -1
- package/src/lang/lang-sql.ctx +10 -0
- package/src/lang/lang-sql.js +1 -1
- package/src/lang/lang-typescript.ctx +6 -0
- package/src/lang/lang-typescript.js +1 -1
- package/src/lang/lang-utils.ctx +5 -0
- package/src/lang/lang-utils.js +1 -1
- package/src/mcp/mcp-server.ctx +6 -0
- package/src/mcp/mcp-server.js +1 -1
- package/src/mcp/tool-defs.ctx +2 -0
- package/src/mcp/tool-defs.js +1 -1
- package/src/mcp/tools.ctx +13 -0
- package/src/mcp/tools.js +1 -1
- package/src/network/backend-lifecycle.ctx +10 -0
- package/src/network/backend-lifecycle.js +1 -1
- package/src/network/backend.ctx +5 -0
- package/src/network/backend.js +1 -1
- package/src/network/local-gateway.ctx +9 -0
- package/src/network/local-gateway.js +1 -1
- package/src/network/mdns.ctx +6 -0
- package/src/network/mdns.js +1 -1
- package/src/network/server.ctx +2 -0
- package/src/network/server.js +2 -2
- package/src/network/web-server.ctx +17 -0
- package/src/network/web-server.js +2 -2
- package/web/follow-controller.js +94 -25
- package/web/panels/dep-graph.js +207 -21
- package/project-graph-mcp-2.3.0.tgz +0 -0
- package/vendor/symbiote-node/CHANGELOG.md +0 -31
- package/vendor/symbiote-node/LICENSE +0 -21
- package/vendor/symbiote-node/README.md +0 -206
- package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
- package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
- package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
- package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
- package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
- package/vendor/symbiote-node/canvas/LODManager.js +0 -88
- package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
- package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
- package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
- package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
- package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
- package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
- package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
- package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
- package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
- package/vendor/symbiote-node/core/Connection.js +0 -45
- package/vendor/symbiote-node/core/Editor.js +0 -451
- package/vendor/symbiote-node/core/Frame.js +0 -31
- package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
- package/vendor/symbiote-node/core/GraphText.js +0 -210
- package/vendor/symbiote-node/core/Node.js +0 -143
- package/vendor/symbiote-node/core/Portal.js +0 -104
- package/vendor/symbiote-node/core/Socket.js +0 -185
- package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
- package/vendor/symbiote-node/engine/AgentUICommands.js +0 -100
- package/vendor/symbiote-node/engine/Executor.js +0 -371
- package/vendor/symbiote-node/engine/Graph.js +0 -314
- package/vendor/symbiote-node/engine/GraphServer.js +0 -353
- package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
- package/vendor/symbiote-node/engine/History.js +0 -83
- package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
- package/vendor/symbiote-node/engine/Persistence.js +0 -84
- package/vendor/symbiote-node/engine/Registry.js +0 -264
- package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
- package/vendor/symbiote-node/engine/cli.js +0 -404
- package/vendor/symbiote-node/engine/index.js +0 -56
- package/vendor/symbiote-node/engine/nanoid.js +0 -28
- package/vendor/symbiote-node/engine/package.json +0 -26
- package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
- package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
- package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
- package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
- package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
- package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
- package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
- package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
- package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
- package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
- package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
- package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
- package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
- package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
- package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
- package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
- package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
- package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
- package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
- package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
- package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
- package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
- package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
- package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
- package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
- package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
- package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
- package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
- package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
- package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
- package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
- package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
- package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
- package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
- package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
- package/vendor/symbiote-node/index.js +0 -103
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
- package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
- package/vendor/symbiote-node/interactions/Drag.js +0 -102
- package/vendor/symbiote-node/interactions/Selector.js +0 -132
- package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
- package/vendor/symbiote-node/interactions/Zoom.js +0 -140
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
- package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
- package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
- package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
- package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
- package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
- package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
- package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
- package/vendor/symbiote-node/layout/index.js +0 -16
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
- package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
- package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
- package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
- package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
- package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
- package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
- package/vendor/symbiote-node/package.json +0 -59
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
- package/vendor/symbiote-node/plugins/History.js +0 -384
- package/vendor/symbiote-node/plugins/Readonly.js +0 -59
- package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
- package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
- package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
- package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
- package/vendor/symbiote-node/shapes/PillShape.js +0 -91
- package/vendor/symbiote-node/shapes/RectShape.js +0 -72
- package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
- package/vendor/symbiote-node/shapes/index.js +0 -53
- package/vendor/symbiote-node/themes/Palette.js +0 -32
- package/vendor/symbiote-node/themes/Skin.js +0 -113
- package/vendor/symbiote-node/themes/Theme.js +0 -84
- package/vendor/symbiote-node/themes/carbon.js +0 -137
- package/vendor/symbiote-node/themes/dark.js +0 -137
- package/vendor/symbiote-node/themes/ebook.js +0 -138
- package/vendor/symbiote-node/themes/grey.js +0 -137
- package/vendor/symbiote-node/themes/light.js +0 -137
- package/vendor/symbiote-node/themes/neon.js +0 -138
- package/vendor/symbiote-node/themes/pcb.js +0 -273
- package/vendor/symbiote-node/themes/synthwave.js +0 -137
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ai/whisper — Audio transcription with word-level timestamps
|
|
3
|
-
*
|
|
4
|
-
* Two modes:
|
|
5
|
-
* - SSH: uploads audio to remote server via scp, runs Whisper via SSH
|
|
6
|
-
* - HTTP: sends audio to a Whisper HTTP endpoint (e.g., faster-whisper-server)
|
|
7
|
-
*
|
|
8
|
-
* SSH remote config from Mr-Computer/modules/ai-music-video/whisper-ssh.js:
|
|
9
|
-
* Host: mr-agent@mr-agent.rnd-pro.com
|
|
10
|
-
* Venv: /home/mr-agent/automations/argentine-spanish-bot/venv
|
|
11
|
-
* Script: utils/whisper-word-timing.py
|
|
12
|
-
*
|
|
13
|
-
* @module agi-graph/packs/ai/whisper
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { execSync } from 'child_process';
|
|
17
|
-
import { promises as fs } from 'fs';
|
|
18
|
-
import path from 'path';
|
|
19
|
-
|
|
20
|
-
export default {
|
|
21
|
-
type: 'ai/whisper',
|
|
22
|
-
category: 'ai',
|
|
23
|
-
icon: 'hearing',
|
|
24
|
-
|
|
25
|
-
driver: {
|
|
26
|
-
description: 'Audio transcription with word-level timestamps (SSH or HTTP mode)',
|
|
27
|
-
inputs: [
|
|
28
|
-
{ name: 'audioPath', type: 'string' },
|
|
29
|
-
],
|
|
30
|
-
outputs: [
|
|
31
|
-
{ name: 'text', type: 'string' },
|
|
32
|
-
{ name: 'words', type: 'any' },
|
|
33
|
-
{ name: 'duration', type: 'number' },
|
|
34
|
-
{ name: 'error', type: 'string' },
|
|
35
|
-
],
|
|
36
|
-
params: {
|
|
37
|
-
mode: { type: 'string', default: 'ssh', description: 'ssh | http' },
|
|
38
|
-
language: { type: 'string', default: 'es', description: 'Language code' },
|
|
39
|
-
model: { type: 'string', default: 'medium', description: 'Whisper model: tiny, base, small, medium, large-v3' },
|
|
40
|
-
device: { type: 'string', default: 'cuda', description: 'cuda | cpu' },
|
|
41
|
-
// SSH params
|
|
42
|
-
remoteHost: { type: 'string', default: 'mr-agent@mr-agent.rnd-pro.com', description: 'SSH host' },
|
|
43
|
-
remotePath: { type: 'string', default: '/home/mr-agent/automations/argentine-spanish-bot', description: 'Remote project path' },
|
|
44
|
-
remoteVenv: { type: 'string', default: '/home/mr-agent/automations/argentine-spanish-bot/venv', description: 'Remote Python venv' },
|
|
45
|
-
// HTTP params
|
|
46
|
-
endpoint: { type: 'string', default: 'http://localhost:5001', description: 'Whisper HTTP endpoint' },
|
|
47
|
-
timeout: { type: 'int', default: 300000, description: 'Max wait time (ms)' },
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
lifecycle: {
|
|
52
|
-
validate: (inputs) => {
|
|
53
|
-
if (!inputs.audioPath) return false;
|
|
54
|
-
return true;
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
cacheKey: (inputs, params) =>
|
|
58
|
-
`whisper:${params.mode}:${params.model}:${inputs.audioPath}`,
|
|
59
|
-
|
|
60
|
-
execute: async (inputs, params) => {
|
|
61
|
-
const { audioPath } = inputs;
|
|
62
|
-
const mode = params.mode || process.env.WHISPER_MODE || 'ssh';
|
|
63
|
-
|
|
64
|
-
if (mode === 'http') {
|
|
65
|
-
return executeHTTP(audioPath, params);
|
|
66
|
-
}
|
|
67
|
-
return executeSSH(audioPath, params);
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* SSH mode: scp upload → remote python exec → parse JSON output
|
|
74
|
-
* @param {string} audioPath - Local audio file path
|
|
75
|
-
* @param {Object} params - Node params
|
|
76
|
-
* @returns {Promise<Object>} Result with text, words, duration
|
|
77
|
-
*/
|
|
78
|
-
async function executeSSH(audioPath, params) {
|
|
79
|
-
const host = params.remoteHost || process.env.WHISPER_REMOTE_HOST || 'mr-agent@mr-agent.rnd-pro.com';
|
|
80
|
-
const remotePath = params.remotePath || process.env.WHISPER_REMOTE_PATH || '/home/mr-agent/automations/argentine-spanish-bot';
|
|
81
|
-
const venv = params.remoteVenv || process.env.WHISPER_REMOTE_VENV || `${remotePath}/venv`;
|
|
82
|
-
const model = params.model || process.env.WHISPER_MODEL || 'medium';
|
|
83
|
-
const device = params.device || process.env.WHISPER_DEVICE || 'cuda';
|
|
84
|
-
const language = params.language || 'es';
|
|
85
|
-
const remoteTmpDir = '/tmp/agi-graph-whisper';
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
// Verify file exists
|
|
89
|
-
await fs.access(audioPath);
|
|
90
|
-
|
|
91
|
-
const filename = path.basename(audioPath);
|
|
92
|
-
const remoteAudioPath = `${remoteTmpDir}/${filename}`;
|
|
93
|
-
|
|
94
|
-
// Ensure remote dir
|
|
95
|
-
execSync(`ssh ${host} "mkdir -p ${remoteTmpDir}"`, {
|
|
96
|
-
encoding: 'utf-8',
|
|
97
|
-
stdio: 'pipe',
|
|
98
|
-
timeout: 10000,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Upload audio
|
|
102
|
-
execSync(`scp "${audioPath}" "${host}:${remoteAudioPath}"`, {
|
|
103
|
-
encoding: 'utf-8',
|
|
104
|
-
stdio: 'pipe',
|
|
105
|
-
timeout: 60000,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
// Run Whisper
|
|
110
|
-
const pythonCmd = `${venv}/bin/python3`;
|
|
111
|
-
const whisperScript = `${remotePath}/utils/whisper-word-timing.py`;
|
|
112
|
-
|
|
113
|
-
const cmd = `"${pythonCmd}" "${whisperScript}" "${remoteAudioPath}" "${language}" --model "${model}" --device "${device}"`;
|
|
114
|
-
const fullCmd = `ssh ${host} '${cmd}'`;
|
|
115
|
-
|
|
116
|
-
const output = execSync(fullCmd, {
|
|
117
|
-
encoding: 'utf-8',
|
|
118
|
-
maxBuffer: 50 * 1024 * 1024,
|
|
119
|
-
timeout: params.timeout || 300000,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const words = JSON.parse(output);
|
|
123
|
-
const text = words.map(w => w.word).join(' ');
|
|
124
|
-
const duration = words.length > 0
|
|
125
|
-
? words[words.length - 1].end
|
|
126
|
-
: 0;
|
|
127
|
-
|
|
128
|
-
return { text, words, duration, error: null };
|
|
129
|
-
|
|
130
|
-
} finally {
|
|
131
|
-
// Cleanup remote file
|
|
132
|
-
try {
|
|
133
|
-
execSync(`ssh ${host} "rm -f ${remoteAudioPath}"`, {
|
|
134
|
-
encoding: 'utf-8',
|
|
135
|
-
stdio: 'pipe',
|
|
136
|
-
timeout: 5000,
|
|
137
|
-
});
|
|
138
|
-
} catch { /* ignore */ }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
} catch (err) {
|
|
142
|
-
return { text: null, words: null, duration: 0, error: err.message };
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* HTTP mode: POST audio to Whisper endpoint via FormData
|
|
148
|
-
* @param {string} audioPath - Local audio file path
|
|
149
|
-
* @param {Object} params - Node params
|
|
150
|
-
* @returns {Promise<Object>} Result with text, words, duration
|
|
151
|
-
*/
|
|
152
|
-
async function executeHTTP(audioPath, params) {
|
|
153
|
-
const endpoint = params.endpoint || process.env.WHISPER_ENDPOINT || 'http://localhost:5001';
|
|
154
|
-
const language = params.language || 'es';
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
const audioBuffer = await fs.readFile(audioPath);
|
|
158
|
-
const blob = new Blob([audioBuffer], { type: 'audio/wav' });
|
|
159
|
-
|
|
160
|
-
const formData = new FormData();
|
|
161
|
-
formData.append('file', blob, path.basename(audioPath));
|
|
162
|
-
formData.append('language', language);
|
|
163
|
-
formData.append('word_timestamps', 'true');
|
|
164
|
-
|
|
165
|
-
if (params.model) {
|
|
166
|
-
formData.append('model', params.model);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const response = await fetch(`${endpoint}/transcribe`, {
|
|
170
|
-
method: 'POST',
|
|
171
|
-
body: formData,
|
|
172
|
-
signal: AbortSignal.timeout(params.timeout || 300000),
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
if (!response.ok) {
|
|
176
|
-
return { text: null, words: null, duration: 0, error: `Whisper API error: ${response.status}` };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const result = await response.json();
|
|
180
|
-
const words = result.words || [];
|
|
181
|
-
const text = result.text || words.map(w => w.word).join(' ');
|
|
182
|
-
const duration = words.length > 0
|
|
183
|
-
? words[words.length - 1].end
|
|
184
|
-
: 0;
|
|
185
|
-
|
|
186
|
-
return { text, words, duration, error: null };
|
|
187
|
-
|
|
188
|
-
} catch (err) {
|
|
189
|
-
return { text: null, words: null, duration: 0, error: err.message };
|
|
190
|
-
}
|
|
191
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* data/db-query — Universal SQL query node
|
|
3
|
-
*
|
|
4
|
-
* Executes a parameterized SQL query against the connected database.
|
|
5
|
-
* Parameters are extracted from input data fields specified in params.paramFields.
|
|
6
|
-
*
|
|
7
|
-
* This is a generic node — the DB connection is injected by the host application
|
|
8
|
-
* via context.db (postgres tagged template instance).
|
|
9
|
-
*
|
|
10
|
-
* @module symbiote-node/packs/data/db-query
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export default {
|
|
14
|
-
type: 'data/db-query',
|
|
15
|
-
category: 'data',
|
|
16
|
-
icon: 'database',
|
|
17
|
-
|
|
18
|
-
driver: {
|
|
19
|
-
description: 'Execute SQL query with parameters from input data',
|
|
20
|
-
inputs: [
|
|
21
|
-
{ name: 'data', type: 'any' },
|
|
22
|
-
],
|
|
23
|
-
outputs: [
|
|
24
|
-
{ name: 'rows', type: 'any' },
|
|
25
|
-
{ name: 'data', type: 'any' },
|
|
26
|
-
],
|
|
27
|
-
params: {
|
|
28
|
-
query: { type: 'text', default: '', description: 'SQL query with $1, $2... placeholders' },
|
|
29
|
-
paramFields: { type: 'string', default: '', description: 'Comma-separated field names from input data to use as query params' },
|
|
30
|
-
outputField: { type: 'string', default: 'queryResult', description: 'Field name to store rows in data output' },
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
lifecycle: {
|
|
35
|
-
validate: (inputs, params) => {
|
|
36
|
-
return !!params.query;
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
execute: async (inputs, params, context) => {
|
|
40
|
-
const data = inputs.data || {};
|
|
41
|
-
const query = params.query;
|
|
42
|
-
|
|
43
|
-
// Extract param values from input data
|
|
44
|
-
const paramNames = (params.paramFields || '')
|
|
45
|
-
.split(',')
|
|
46
|
-
.map((s) => s.trim())
|
|
47
|
-
.filter(Boolean);
|
|
48
|
-
const paramValues = paramNames.map((field) => data[field]);
|
|
49
|
-
|
|
50
|
-
// Execute via context.db (injected by host)
|
|
51
|
-
if (!context?.db) {
|
|
52
|
-
return {
|
|
53
|
-
rows: [],
|
|
54
|
-
data: { ...data, [params.outputField || 'queryResult']: [], error: 'No DB context' },
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const rows = await context.db.unsafe(query, paramValues);
|
|
59
|
-
const outputField = params.outputField || 'queryResult';
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
rows: [...rows],
|
|
63
|
-
data: { ...data, [outputField]: [...rows] },
|
|
64
|
-
};
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
};
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* data/news-accumulate — News Accumulator
|
|
3
|
-
*
|
|
4
|
-
* Collects and stores raw news items with deduplication, filtering,
|
|
5
|
-
* period management, and category statistics. Supports horoscope
|
|
6
|
-
* and international news filtering for Argentina-focused content.
|
|
7
|
-
*
|
|
8
|
-
* Ported from Mr-Computer/automations/argentine-spanish-bot/src/services/news-accumulator.js
|
|
9
|
-
*
|
|
10
|
-
* @module agi-graph/packs/data/news-accumulate
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
|
|
16
|
-
// Non-local patterns to filter out
|
|
17
|
-
const NON_LOCAL_PATTERNS = ['en eeuu', 'desde estados unidos'];
|
|
18
|
-
const HOROSCOPE_PATTERNS = ['horóscopo', 'horoscopo', 'astrolog', 'signo del zodiaco', 'signo del zodíaco'];
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Simple hash for news dedup
|
|
22
|
-
* @param {string} str
|
|
23
|
-
* @returns {string}
|
|
24
|
-
*/
|
|
25
|
-
function hashString(str) {
|
|
26
|
-
let hash = 0;
|
|
27
|
-
for (let i = 0; i < str.length; i++) {
|
|
28
|
-
const chr = str.charCodeAt(i);
|
|
29
|
-
hash = ((hash << 5) - hash) + chr;
|
|
30
|
-
hash |= 0;
|
|
31
|
-
}
|
|
32
|
-
return Math.abs(hash).toString(36);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Generate unique ID for a news item
|
|
37
|
-
* @param {Object} item
|
|
38
|
-
* @returns {string}
|
|
39
|
-
*/
|
|
40
|
-
function generateId(item) {
|
|
41
|
-
const source = item.link || item.url || '';
|
|
42
|
-
const title = item.title || '';
|
|
43
|
-
return hashString(`${title}:${source}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Standardize a news item
|
|
48
|
-
* @param {Object} item
|
|
49
|
-
* @returns {Object}
|
|
50
|
-
*/
|
|
51
|
-
function standardizeItem(item) {
|
|
52
|
-
return {
|
|
53
|
-
id: item.id || generateId(item),
|
|
54
|
-
title: item.title || '',
|
|
55
|
-
description: item.description || item.content || '',
|
|
56
|
-
link: item.link || item.url || '',
|
|
57
|
-
category: item.category || { id: 'general', name: 'General' },
|
|
58
|
-
source: item.source || '',
|
|
59
|
-
image: item.image || null,
|
|
60
|
-
pubDate: item.pubDate || new Date().toISOString(),
|
|
61
|
-
addedAt: new Date().toISOString(),
|
|
62
|
-
processed: false,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Filter out international news
|
|
68
|
-
* @param {Array} items
|
|
69
|
-
* @returns {Array}
|
|
70
|
-
*/
|
|
71
|
-
function filterArgentinaOnly(items) {
|
|
72
|
-
return items.filter(item => {
|
|
73
|
-
const text = `${item.title} ${item.description}`.toLowerCase();
|
|
74
|
-
return !NON_LOCAL_PATTERNS.some(p => text.includes(p));
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Filter out horoscope content
|
|
80
|
-
* @param {Array} items
|
|
81
|
-
* @returns {Array}
|
|
82
|
-
*/
|
|
83
|
-
function filterOutHoroscopes(items) {
|
|
84
|
-
return items.filter(item => {
|
|
85
|
-
const text = `${item.title} ${item.description}`.toLowerCase();
|
|
86
|
-
return !HOROSCOPE_PATTERNS.some(p => text.includes(p));
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Load stored data from file
|
|
92
|
-
* @param {string} storePath
|
|
93
|
-
* @returns {Promise<Object>}
|
|
94
|
-
*/
|
|
95
|
-
async function loadStore(storePath) {
|
|
96
|
-
try {
|
|
97
|
-
const data = await readFile(storePath, 'utf-8');
|
|
98
|
-
return JSON.parse(data);
|
|
99
|
-
} catch {
|
|
100
|
-
return {
|
|
101
|
-
news: [],
|
|
102
|
-
periodStart: new Date().toISOString(),
|
|
103
|
-
categoryCounts: {},
|
|
104
|
-
processedIds: [],
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Save data to file
|
|
111
|
-
* @param {string} storePath
|
|
112
|
-
* @param {Object} data
|
|
113
|
-
*/
|
|
114
|
-
async function saveStore(storePath, data) {
|
|
115
|
-
await mkdir(path.dirname(storePath), { recursive: true });
|
|
116
|
-
await writeFile(storePath, JSON.stringify(data, null, 2));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ─── Handler Definition ────────────────────────────────────────────────
|
|
120
|
-
|
|
121
|
-
export default {
|
|
122
|
-
type: 'data/news-accumulate',
|
|
123
|
-
category: 'data',
|
|
124
|
-
icon: 'newspaper',
|
|
125
|
-
|
|
126
|
-
driver: {
|
|
127
|
-
description: 'Collect and store news with dedup, filtering, periods, and category stats',
|
|
128
|
-
inputs: [
|
|
129
|
-
{ name: 'storePath', type: 'string' },
|
|
130
|
-
],
|
|
131
|
-
outputs: [
|
|
132
|
-
{ name: 'result', type: 'any' },
|
|
133
|
-
{ name: 'error', type: 'string' },
|
|
134
|
-
],
|
|
135
|
-
params: {
|
|
136
|
-
operation: { type: 'string', default: 'get', description: 'Operation: add | get | mark-processed | new-period | stats' },
|
|
137
|
-
// add
|
|
138
|
-
newsItem: { type: 'any', default: null, description: 'News item to add' },
|
|
139
|
-
newsItems: { type: 'any', default: null, description: 'Array of news items to add (batch)' },
|
|
140
|
-
// get filters
|
|
141
|
-
categories: { type: 'any', default: null, description: 'Filter by categories array' },
|
|
142
|
-
since: { type: 'string', default: null, description: 'Get news since ISO date' },
|
|
143
|
-
filterLocal: { type: 'boolean', default: false, description: 'Filter out non-local (international) news' },
|
|
144
|
-
filterHoroscopes: { type: 'boolean', default: true, description: 'Filter out horoscope content' },
|
|
145
|
-
maxItems: { type: 'int', default: 100, description: 'Maximum items to return' },
|
|
146
|
-
// mark-processed
|
|
147
|
-
newsIds: { type: 'any', default: null, description: 'Array of news IDs to mark processed' },
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
lifecycle: {
|
|
152
|
-
validate: (inputs) => {
|
|
153
|
-
return typeof inputs.storePath === 'string' && inputs.storePath.length > 0;
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
cacheKey: () => null, // mutable state
|
|
157
|
-
|
|
158
|
-
execute: async (inputs, params) => {
|
|
159
|
-
const { storePath } = inputs;
|
|
160
|
-
const { operation } = params;
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const store = await loadStore(storePath);
|
|
164
|
-
|
|
165
|
-
switch (operation) {
|
|
166
|
-
case 'add': {
|
|
167
|
-
const itemsToAdd = params.newsItems
|
|
168
|
-
? params.newsItems
|
|
169
|
-
: params.newsItem
|
|
170
|
-
? [params.newsItem]
|
|
171
|
-
: [];
|
|
172
|
-
|
|
173
|
-
if (itemsToAdd.length === 0) return { error: 'No items to add' };
|
|
174
|
-
|
|
175
|
-
const existingIds = new Set(store.news.map(n => n.id));
|
|
176
|
-
let added = 0;
|
|
177
|
-
|
|
178
|
-
for (const raw of itemsToAdd) {
|
|
179
|
-
const item = standardizeItem(raw);
|
|
180
|
-
if (existingIds.has(item.id)) continue;
|
|
181
|
-
|
|
182
|
-
store.news.push(item);
|
|
183
|
-
existingIds.add(item.id);
|
|
184
|
-
added++;
|
|
185
|
-
|
|
186
|
-
// Update category counts
|
|
187
|
-
const catId = typeof item.category === 'object' ? item.category.id : item.category;
|
|
188
|
-
store.categoryCounts[catId] = (store.categoryCounts[catId] || 0) + 1;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
await saveStore(storePath, store);
|
|
192
|
-
return { result: { added, total: store.news.length, duplicatesSkipped: itemsToAdd.length - added } };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
case 'get': {
|
|
196
|
-
let items = store.news.filter(n => !n.processed);
|
|
197
|
-
|
|
198
|
-
// Date filter
|
|
199
|
-
if (params.since) {
|
|
200
|
-
const sinceDate = new Date(params.since);
|
|
201
|
-
items = items.filter(n => new Date(n.addedAt) >= sinceDate);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Category filter
|
|
205
|
-
if (Array.isArray(params.categories) && params.categories.length > 0) {
|
|
206
|
-
items = items.filter(n => {
|
|
207
|
-
const catId = typeof n.category === 'object' ? n.category.id : n.category;
|
|
208
|
-
return params.categories.includes(catId);
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Content filters
|
|
213
|
-
if (params.filterLocal) items = filterArgentinaOnly(items);
|
|
214
|
-
if (params.filterHoroscopes) items = filterOutHoroscopes(items);
|
|
215
|
-
|
|
216
|
-
items = items.slice(0, params.maxItems);
|
|
217
|
-
|
|
218
|
-
return { result: { items, count: items.length } };
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
case 'mark-processed': {
|
|
222
|
-
if (!Array.isArray(params.newsIds)) return { error: 'newsIds array is required' };
|
|
223
|
-
|
|
224
|
-
const idsSet = new Set(params.newsIds);
|
|
225
|
-
let marked = 0;
|
|
226
|
-
|
|
227
|
-
for (const item of store.news) {
|
|
228
|
-
if (idsSet.has(item.id) && !item.processed) {
|
|
229
|
-
item.processed = true;
|
|
230
|
-
item.processedAt = new Date().toISOString();
|
|
231
|
-
marked++;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
store.processedIds.push(...params.newsIds);
|
|
236
|
-
await saveStore(storePath, store);
|
|
237
|
-
return { result: { marked, total: params.newsIds.length } };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
case 'new-period': {
|
|
241
|
-
const archived = {
|
|
242
|
-
periodStart: store.periodStart,
|
|
243
|
-
periodEnd: new Date().toISOString(),
|
|
244
|
-
itemCount: store.news.length,
|
|
245
|
-
categoryCounts: { ...store.categoryCounts },
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
store.news = [];
|
|
249
|
-
store.periodStart = new Date().toISOString();
|
|
250
|
-
store.categoryCounts = {};
|
|
251
|
-
store.processedIds = [];
|
|
252
|
-
|
|
253
|
-
await saveStore(storePath, store);
|
|
254
|
-
return { result: { archived, message: 'New period started' } };
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
case 'stats': {
|
|
258
|
-
const total = store.news.length;
|
|
259
|
-
const processed = store.news.filter(n => n.processed).length;
|
|
260
|
-
const unprocessed = total - processed;
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
result: {
|
|
264
|
-
total,
|
|
265
|
-
processed,
|
|
266
|
-
unprocessed,
|
|
267
|
-
periodStart: store.periodStart,
|
|
268
|
-
categoryCounts: store.categoryCounts,
|
|
269
|
-
},
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
default:
|
|
274
|
-
return { error: `Unknown operation: ${operation}` };
|
|
275
|
-
}
|
|
276
|
-
} catch (err) {
|
|
277
|
-
return { error: `news-accumulate ${operation} failed: ${err.message}` };
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
};
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* data/personas — Character persona registry for TTS pipelines
|
|
3
|
-
*
|
|
4
|
-
* Manages character presets with voice settings, personality descriptions,
|
|
5
|
-
* and audio reference samples. Used as a data source node that feeds
|
|
6
|
-
* ai/tts with correct speaker/voice parameters.
|
|
7
|
-
*
|
|
8
|
-
* Persona structure (from Mr-Computer/argentine-spanish-bot):
|
|
9
|
-
* id, name, personality, voiceInstruct, speaker (Qwen3 ID),
|
|
10
|
-
* refAudio (per-language samples), pan (stereo position)
|
|
11
|
-
*
|
|
12
|
-
* Operations:
|
|
13
|
-
* get — get persona by ID
|
|
14
|
-
* list — list all personas (optionally filtered)
|
|
15
|
-
* random — pick N random personas
|
|
16
|
-
*
|
|
17
|
-
* @module agi-graph/packs/data/personas
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/** @typedef {Object} Persona
|
|
21
|
-
* @property {string} id - Unique identifier
|
|
22
|
-
* @property {string} name - Display name
|
|
23
|
-
* @property {string} personality - Character description for AI prompts
|
|
24
|
-
* @property {string} voiceInstruct - Emotion/style instruction for TTS
|
|
25
|
-
* @property {string} speaker - Qwen3-TTS speaker ID (ryan, vivian, etc.)
|
|
26
|
-
* @property {Object<string, string>} [refAudio] - Per-language voice reference paths
|
|
27
|
-
* @property {number} [pan] - Stereo position (-1.0 left to 1.0 right)
|
|
28
|
-
* @property {string} [gender] - male | female
|
|
29
|
-
* @property {string} [type] - normal | meme | dj
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Built-in persona presets (from radio-dj-config.js)
|
|
34
|
-
* @type {Persona[]}
|
|
35
|
-
*/
|
|
36
|
-
const BUILT_IN_PRESETS = [
|
|
37
|
-
{
|
|
38
|
-
id: 'dj_matias',
|
|
39
|
-
name: 'Matías',
|
|
40
|
-
personality: 'Energetic morning host, asks probing questions, uses "che" frequently',
|
|
41
|
-
voiceInstruct: 'Enthusiastic and dynamic, curious tone',
|
|
42
|
-
speaker: 'ryan',
|
|
43
|
-
pan: -0.2,
|
|
44
|
-
gender: 'male',
|
|
45
|
-
type: 'dj',
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: 'dj_lucia',
|
|
49
|
-
name: 'Lucía',
|
|
50
|
-
personality: 'Analytical co-host, provides context and facts, thoughtful responses',
|
|
51
|
-
voiceInstruct: 'Clear articulate tone, warm and engaging',
|
|
52
|
-
speaker: 'vivian',
|
|
53
|
-
pan: 0.2,
|
|
54
|
-
gender: 'female',
|
|
55
|
-
type: 'dj',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
id: 'dj_carlos',
|
|
59
|
-
name: 'Carlos',
|
|
60
|
-
personality: 'Veteran journalist, offers historical perspective, calm authority',
|
|
61
|
-
voiceInstruct: 'Deep calm authoritative voice, measured pace',
|
|
62
|
-
speaker: 'ryan',
|
|
63
|
-
pan: -0.1,
|
|
64
|
-
gender: 'male',
|
|
65
|
-
type: 'dj',
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: 'dj_sofia',
|
|
69
|
-
name: 'Sofía',
|
|
70
|
-
personality: 'Young reporter, brings fresh perspectives, occasionally interrupts with excitement',
|
|
71
|
-
voiceInstruct: 'Youthful energetic voice, sometimes excited',
|
|
72
|
-
speaker: 'vivian',
|
|
73
|
-
pan: 0.1,
|
|
74
|
-
gender: 'female',
|
|
75
|
-
type: 'dj',
|
|
76
|
-
},
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
export default {
|
|
80
|
-
type: 'data/personas',
|
|
81
|
-
category: 'data',
|
|
82
|
-
icon: 'groups',
|
|
83
|
-
|
|
84
|
-
driver: {
|
|
85
|
-
description: 'Character persona registry — voice presets with personality for TTS',
|
|
86
|
-
inputs: [
|
|
87
|
-
{ name: 'personaId', type: 'string' },
|
|
88
|
-
],
|
|
89
|
-
outputs: [
|
|
90
|
-
{ name: 'persona', type: 'any' },
|
|
91
|
-
{ name: 'personas', type: 'any' },
|
|
92
|
-
{ name: 'error', type: 'string' },
|
|
93
|
-
],
|
|
94
|
-
params: {
|
|
95
|
-
operation: { type: 'string', default: 'get', description: 'get | list | random' },
|
|
96
|
-
count: { type: 'int', default: 2, description: 'Number of personas for random operation' },
|
|
97
|
-
filterGender: { type: 'string', default: '', description: 'Filter by gender: male | female' },
|
|
98
|
-
filterType: { type: 'string', default: '', description: 'Filter by type: dj | normal | meme' },
|
|
99
|
-
customPresets: { type: 'any', default: null, description: 'Custom persona array (overrides built-in)' },
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
lifecycle: {
|
|
104
|
-
validate: (inputs, params) => {
|
|
105
|
-
const op = params?.operation || 'get';
|
|
106
|
-
if (op === 'get' && !inputs.personaId) return false;
|
|
107
|
-
return true;
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
cacheKey: (inputs, params) => {
|
|
111
|
-
const op = params.operation || 'get';
|
|
112
|
-
if (op === 'get') return `personas:get:${inputs.personaId}`;
|
|
113
|
-
if (op === 'random') return null; // Never cache random
|
|
114
|
-
return `personas:list:${params.filterGender}:${params.filterType}`;
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
execute: async (inputs, params) => {
|
|
118
|
-
const presets = params.customPresets || BUILT_IN_PRESETS;
|
|
119
|
-
const op = params.operation || 'get';
|
|
120
|
-
|
|
121
|
-
if (op === 'get') {
|
|
122
|
-
const persona = presets.find(p => p.id === inputs.personaId);
|
|
123
|
-
if (!persona) {
|
|
124
|
-
return { persona: null, personas: null, error: `Persona not found: ${inputs.personaId}` };
|
|
125
|
-
}
|
|
126
|
-
return { persona, personas: null, error: null };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (op === 'list') {
|
|
130
|
-
let filtered = [...presets];
|
|
131
|
-
if (params.filterGender) {
|
|
132
|
-
filtered = filtered.filter(p => p.gender === params.filterGender);
|
|
133
|
-
}
|
|
134
|
-
if (params.filterType) {
|
|
135
|
-
filtered = filtered.filter(p => p.type === params.filterType);
|
|
136
|
-
}
|
|
137
|
-
return { persona: null, personas: filtered, error: null };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (op === 'random') {
|
|
141
|
-
let pool = [...presets];
|
|
142
|
-
if (params.filterGender) {
|
|
143
|
-
pool = pool.filter(p => p.gender === params.filterGender);
|
|
144
|
-
}
|
|
145
|
-
if (params.filterType) {
|
|
146
|
-
pool = pool.filter(p => p.type === params.filterType);
|
|
147
|
-
}
|
|
148
|
-
// Fisher-Yates shuffle
|
|
149
|
-
for (let i = pool.length - 1; i > 0; i--) {
|
|
150
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
151
|
-
[pool[i], pool[j]] = [pool[j], pool[i]];
|
|
152
|
-
}
|
|
153
|
-
const selected = pool.slice(0, Math.min(params.count || 2, pool.length));
|
|
154
|
-
return { persona: selected[0] || null, personas: selected, error: null };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return { persona: null, personas: null, error: `Unknown operation: ${op}` };
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
};
|