coaia-visualizer 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +9 -0
- package/Dockerfile.app +50 -0
- package/Dockerfile.test +24 -0
- package/LIVE_MODE_DESIGN.md +435 -0
- package/MCP_TESTING_COMPLETE.md +302 -0
- package/MCP_TESTING_IMPLEMENTATION_SUMMARY.md +317 -0
- package/MCP_TESTING_SETUP.md +268 -0
- package/NAMING.md +218 -0
- package/QUICK_START_MCP_TESTING.md +236 -0
- package/WS__issue_8__coaia-visualizer__260207.code-workspace +45 -0
- package/app/api/audio/[filename]/route.ts +37 -0
- package/app/api/charts/[id]/route.ts +48 -35
- package/app/api/watch/route.ts +42 -0
- package/app/page.tsx +103 -53
- package/cli.ts +56 -3
- package/components/add-master-chart.tsx +230 -0
- package/components/chart-detail-editable.tsx +27 -16
- package/components/chart-list.tsx +13 -1
- package/components/create-chart-form.tsx +248 -0
- package/components/data-stats.tsx +9 -7
- package/components/live-indicator.tsx +14 -0
- package/components/ui/dialog.tsx +143 -0
- package/components/ui/label.tsx +24 -0
- package/direct-test.sh +180 -0
- package/dist/cli.js +52 -3
- package/docker-compose.test.yml +69 -0
- package/hooks/use-live-polling.ts +45 -0
- package/jgwill.coaia-visualizer-8--496dca71-d476-4ac9-ba9f-376add118dd8--260208.txt +2612 -0
- package/lib/chart-editor.ts +281 -68
- package/mcp/Dockerfile +21 -0
- package/mcp/README.md +25 -6
- package/mcp/src/api-client.ts +15 -3
- package/mcp/src/index.ts +17 -2
- package/mcp/src/tools/index.ts +21 -1
- package/mcp/test_mcp/.gemini/settings.json +18 -0
- package/mcp-config.json +14 -0
- package/package.json +2 -2
- package/run-mcp-tests.sh +99 -0
- package/samples/tradingchart.jsonl +31 -0
- package/test-data/test-master.jsonl +11 -0
- package/test-run.log +101 -0
- package/test-scripts/README.md +239 -0
- package/test-scripts/run-all-tests.sh +38 -0
- package/test-scripts/test-01-basic-operations.sh +87 -0
- package/test-scripts/test-02-telescope-creation.sh +91 -0
- package/test-scripts/test-03-navigation.sh +87 -0
- package/validate-mcp.sh +136 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"folders": [
|
|
3
|
+
{
|
|
4
|
+
"path": "."
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"path": "../../../src/_sessiondata/496dca71-d476-4ac9-ba9f-376add118dd8"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "../../../src/_sessiondata/55158baa-dea7-45c8-9941-540433cfe551"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "../../../cesaret/book/_/tcc/winter_solstice/drop/ceremonies/4c8623a1-c2e1-4ebb-a7f3-ab8ef8376172/plans"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "../../../home/mia/workspace/stcmastery-copilot-acp-55158baa-dea7-45c8-9941-540433cfe551--2602072108"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"settings": {
|
|
20
|
+
"mcp": {
|
|
21
|
+
"servers": {
|
|
22
|
+
"charts_55158baa-dea7-45c8-9941-540433cfe551__LiveNarrativeWitnessMode": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": [
|
|
25
|
+
"-y",
|
|
26
|
+
"coaia-narrative@0.10.1",
|
|
27
|
+
"--memory-path",
|
|
28
|
+
"${input:memory_path}"
|
|
29
|
+
],
|
|
30
|
+
"env": {
|
|
31
|
+
"COAIA_TOOLS": "STC_TOOLS,NARRATIVE_TOOLS,CORE_TOOLS"
|
|
32
|
+
},
|
|
33
|
+
"type": "stdio"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"inputs": [
|
|
37
|
+
{
|
|
38
|
+
"id": "memory_path",
|
|
39
|
+
"type": "promptString",
|
|
40
|
+
"description": "Memory File Path"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { promises as fs } from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export async function GET(
|
|
6
|
+
request: NextRequest,
|
|
7
|
+
{ params }: { params: { filename: string } }
|
|
8
|
+
) {
|
|
9
|
+
const audioDir = process.env.COAIAV_AUDIO_DIR || './audio'
|
|
10
|
+
const filename = params.filename
|
|
11
|
+
|
|
12
|
+
// Security: only allow .mp3 files
|
|
13
|
+
if (!filename.endsWith('.mp3')) {
|
|
14
|
+
return NextResponse.json(
|
|
15
|
+
{ error: 'Only MP3 files allowed' },
|
|
16
|
+
{ status: 400 }
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const audioPath = path.join(audioDir, filename)
|
|
22
|
+
const file = await fs.readFile(audioPath)
|
|
23
|
+
|
|
24
|
+
return new NextResponse(file, {
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'audio/mpeg',
|
|
27
|
+
'Content-Length': file.length.toString(),
|
|
28
|
+
'Cache-Control': 'public, max-age=31536000'
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
} catch (error: any) {
|
|
32
|
+
return NextResponse.json(
|
|
33
|
+
{ error: `Audio file not found: ${filename}` },
|
|
34
|
+
{ status: 404 }
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -24,14 +24,14 @@ export const GET = withAuth(async (
|
|
|
24
24
|
const content = await fs.readFile(memoryPath, 'utf-8')
|
|
25
25
|
const records = parseJSONL(content)
|
|
26
26
|
const parsedData = organizeData(records)
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
const chartId = params.id
|
|
29
29
|
const chart = parsedData.charts.find(c => c.id === chartId)
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
if (!chart) {
|
|
32
32
|
return errorResponse(`Chart not found: ${chartId}`, 404)
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
return successResponse({ chart })
|
|
36
36
|
} catch (error: any) {
|
|
37
37
|
return errorResponse(`Failed to read chart: ${error.message}`, 500)
|
|
@@ -48,11 +48,14 @@ export const GET = withAuth(async (
|
|
|
48
48
|
* addCurrentRealityObservation?: string
|
|
49
49
|
* updateCurrentRealityObservation?: { index: number, text: string }
|
|
50
50
|
* deleteCurrentRealityObservation?: number
|
|
51
|
-
* addActionStep?: { description: string, dueDate?: string }
|
|
51
|
+
* addActionStep?: { description: string, currentReality: string, dueDate?: string }
|
|
52
52
|
* updateActionStep?: { actionName: string, description: string }
|
|
53
53
|
* toggleActionCompletion?: string (action name)
|
|
54
|
+
* markActionComplete?: string (action name)
|
|
55
|
+
* updateActionProgress?: { actionStepName: string, progressObservation: string, updateCurrentReality?: boolean }
|
|
54
56
|
* updateActionDueDate?: { actionName: string, dueDate: string | null }
|
|
55
57
|
* deleteActionStep?: string (action name)
|
|
58
|
+
* createTelescopedChart?: { actionName: string }
|
|
56
59
|
* updateDueDate?: string | null
|
|
57
60
|
* }
|
|
58
61
|
*/
|
|
@@ -69,53 +72,53 @@ export const POST = withAuth(async (
|
|
|
69
72
|
try {
|
|
70
73
|
const chartId = params.id
|
|
71
74
|
const body = await request.json()
|
|
72
|
-
|
|
75
|
+
|
|
73
76
|
// Read current data
|
|
74
77
|
const content = await fs.readFile(memoryPath, 'utf-8')
|
|
75
78
|
const records = parseJSONL(content)
|
|
76
79
|
const parsedData = organizeData(records)
|
|
77
|
-
|
|
80
|
+
|
|
78
81
|
// Verify chart exists
|
|
79
82
|
const chart = parsedData.charts.find(c => c.id === chartId)
|
|
80
83
|
if (!chart) {
|
|
81
84
|
return errorResponse(`Chart not found: ${chartId}`, 404)
|
|
82
85
|
}
|
|
83
|
-
|
|
86
|
+
|
|
84
87
|
// Create editor
|
|
85
88
|
const editor = new ChartEditor(parsedData)
|
|
86
|
-
|
|
89
|
+
|
|
87
90
|
// Apply updates based on body
|
|
88
91
|
const updates: string[] = []
|
|
89
|
-
|
|
92
|
+
|
|
90
93
|
if (body.updateDesiredOutcome) {
|
|
91
94
|
editor.updateDesiredOutcome(chartId, body.updateDesiredOutcome)
|
|
92
95
|
updates.push('Updated desired outcome')
|
|
93
96
|
}
|
|
94
|
-
|
|
97
|
+
|
|
95
98
|
if (body.addCurrentRealityObservation) {
|
|
96
99
|
editor.addCurrentRealityObservation(chartId, body.addCurrentRealityObservation)
|
|
97
100
|
updates.push('Added current reality observation')
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
if (body.updateCurrentReality) {
|
|
101
|
-
const observations = Array.isArray(body.updateCurrentReality)
|
|
102
|
-
? body.updateCurrentReality
|
|
104
|
+
const observations = Array.isArray(body.updateCurrentReality)
|
|
105
|
+
? body.updateCurrentReality
|
|
103
106
|
: [body.updateCurrentReality]
|
|
104
107
|
editor.updateCurrentReality(chartId, observations)
|
|
105
108
|
updates.push(`Updated current reality with ${observations.length} observation(s)`)
|
|
106
109
|
}
|
|
107
|
-
|
|
110
|
+
|
|
108
111
|
if (body.updateCurrentRealityObservation) {
|
|
109
112
|
const { index, text } = body.updateCurrentRealityObservation
|
|
110
113
|
editor.updateCurrentRealityObservation(chartId, index, text)
|
|
111
114
|
updates.push(`Updated current reality observation ${index}`)
|
|
112
115
|
}
|
|
113
|
-
|
|
116
|
+
|
|
114
117
|
if (typeof body.deleteCurrentRealityObservation === 'number') {
|
|
115
118
|
editor.deleteCurrentRealityObservation(chartId, body.deleteCurrentRealityObservation)
|
|
116
119
|
updates.push(`Deleted current reality observation ${body.deleteCurrentRealityObservation}`)
|
|
117
120
|
}
|
|
118
|
-
|
|
121
|
+
|
|
119
122
|
if (body.addActionStep) {
|
|
120
123
|
const { description, currentReality, dueDate } = body.addActionStep
|
|
121
124
|
if (!currentReality) {
|
|
@@ -124,13 +127,13 @@ export const POST = withAuth(async (
|
|
|
124
127
|
const result = editor.addActionStep(chartId, description, currentReality, dueDate)
|
|
125
128
|
updates.push(`Added action step as telescoped chart ${result.chartId}`)
|
|
126
129
|
}
|
|
127
|
-
|
|
130
|
+
|
|
128
131
|
if (body.updateActionStep) {
|
|
129
132
|
const { actionName, description } = body.updateActionStep
|
|
130
133
|
editor.updateActionStep(chartId, actionName, description)
|
|
131
134
|
updates.push(`Updated action step ${actionName}`)
|
|
132
135
|
}
|
|
133
|
-
|
|
136
|
+
|
|
134
137
|
if (body.toggleActionCompletion) {
|
|
135
138
|
editor.toggleActionCompletion(body.toggleActionCompletion)
|
|
136
139
|
updates.push(`Toggled completion for ${body.toggleActionCompletion}`)
|
|
@@ -146,44 +149,54 @@ export const POST = withAuth(async (
|
|
|
146
149
|
editor.updateActionProgress(actionStepName, progressObservation, updateCurrentReality)
|
|
147
150
|
updates.push(`Updated progress for ${actionStepName}`)
|
|
148
151
|
}
|
|
149
|
-
|
|
152
|
+
|
|
150
153
|
if (body.updateActionDueDate) {
|
|
151
154
|
const { actionName, dueDate } = body.updateActionDueDate
|
|
152
155
|
editor.updateActionDueDate(actionName, dueDate)
|
|
153
156
|
updates.push(`Updated due date for ${actionName}`)
|
|
154
157
|
}
|
|
155
|
-
|
|
158
|
+
|
|
156
159
|
if (body.deleteActionStep) {
|
|
157
160
|
editor.deleteActionStep(body.deleteActionStep)
|
|
158
161
|
updates.push(`Deleted action step ${body.deleteActionStep}`)
|
|
159
162
|
}
|
|
160
|
-
|
|
163
|
+
|
|
164
|
+
if (body.createTelescopedChart) {
|
|
165
|
+
const { actionName } = body.createTelescopedChart
|
|
166
|
+
const chart = parsedData.charts.find(c => c.id === chartId)
|
|
167
|
+
if (!chart) {
|
|
168
|
+
return errorResponse(`Chart not found: ${chartId}`, 404)
|
|
169
|
+
}
|
|
170
|
+
const newChartId = editor.createTelescopedChartFromAction(chart.chartEntity.name, actionName)
|
|
171
|
+
updates.push(`Created telescoped chart ${newChartId} from action ${actionName}`)
|
|
172
|
+
}
|
|
173
|
+
|
|
161
174
|
if ('updateDueDate' in body) {
|
|
162
175
|
editor.updateChartDueDate(chartId, body.updateDueDate)
|
|
163
176
|
updates.push('Updated chart due date')
|
|
164
177
|
}
|
|
165
|
-
|
|
178
|
+
|
|
166
179
|
if (updates.length === 0) {
|
|
167
180
|
return errorResponse('No valid updates provided', 400)
|
|
168
181
|
}
|
|
169
|
-
|
|
182
|
+
|
|
170
183
|
// Export and save
|
|
171
184
|
const newContent = editor.exportToJSONL()
|
|
172
185
|
const backupPath = `${memoryPath}.backup-${Date.now()}`
|
|
173
186
|
await fs.copyFile(memoryPath, backupPath)
|
|
174
187
|
await fs.writeFile(memoryPath, newContent, 'utf-8')
|
|
175
|
-
|
|
188
|
+
|
|
176
189
|
// Get updated chart
|
|
177
190
|
const updatedData = editor.getUpdatedData()
|
|
178
191
|
const updatedChart = updatedData.charts.find(c => c.id === chartId)
|
|
179
|
-
|
|
192
|
+
|
|
180
193
|
return successResponse({
|
|
181
194
|
chart: updatedChart,
|
|
182
195
|
updates,
|
|
183
196
|
backup: backupPath,
|
|
184
197
|
message: `Chart updated: ${updates.join(', ')}`
|
|
185
198
|
})
|
|
186
|
-
|
|
199
|
+
|
|
187
200
|
} catch (error: any) {
|
|
188
201
|
return errorResponse(`Failed to update chart: ${error.message}`, 500)
|
|
189
202
|
}
|
|
@@ -205,34 +218,34 @@ export const DELETE = withAuth(async (
|
|
|
205
218
|
|
|
206
219
|
try {
|
|
207
220
|
const chartId = params.id
|
|
208
|
-
|
|
221
|
+
|
|
209
222
|
// Read current data
|
|
210
223
|
const content = await fs.readFile(memoryPath, 'utf-8')
|
|
211
224
|
const records = parseJSONL(content)
|
|
212
225
|
const parsedData = organizeData(records)
|
|
213
|
-
|
|
226
|
+
|
|
214
227
|
// Verify chart exists
|
|
215
228
|
const chart = parsedData.charts.find(c => c.id === chartId)
|
|
216
229
|
if (!chart) {
|
|
217
230
|
return errorResponse(`Chart not found: ${chartId}`, 404)
|
|
218
231
|
}
|
|
219
|
-
|
|
232
|
+
|
|
220
233
|
// Collect all entities to delete
|
|
221
234
|
const entitiesToDelete = new Set<string>()
|
|
222
235
|
entitiesToDelete.add(chartId)
|
|
223
236
|
entitiesToDelete.add(`${chartId}_desired_outcome`)
|
|
224
237
|
entitiesToDelete.add(`${chartId}_current_reality`)
|
|
225
|
-
|
|
238
|
+
|
|
226
239
|
// Add action steps
|
|
227
240
|
chart.actions.forEach(action => {
|
|
228
241
|
entitiesToDelete.add(action.name)
|
|
229
242
|
})
|
|
230
|
-
|
|
243
|
+
|
|
231
244
|
// Add narrative beats
|
|
232
245
|
chart.narrativeBeats.forEach(beat => {
|
|
233
246
|
entitiesToDelete.add(beat.name)
|
|
234
247
|
})
|
|
235
|
-
|
|
248
|
+
|
|
236
249
|
// Recursively delete sub-charts
|
|
237
250
|
const deleteSubCharts = (subChart: typeof chart) => {
|
|
238
251
|
entitiesToDelete.add(subChart.id)
|
|
@@ -243,7 +256,7 @@ export const DELETE = withAuth(async (
|
|
|
243
256
|
subChart.subCharts.forEach(deleteSubCharts)
|
|
244
257
|
}
|
|
245
258
|
chart.subCharts.forEach(deleteSubCharts)
|
|
246
|
-
|
|
259
|
+
|
|
247
260
|
// Filter out deleted entities and their relations
|
|
248
261
|
const filteredRecords = records.filter(record => {
|
|
249
262
|
if (record.type === 'entity') {
|
|
@@ -253,19 +266,19 @@ export const DELETE = withAuth(async (
|
|
|
253
266
|
return !entitiesToDelete.has(record.from) && !entitiesToDelete.has(record.to)
|
|
254
267
|
}
|
|
255
268
|
})
|
|
256
|
-
|
|
269
|
+
|
|
257
270
|
// Write back
|
|
258
271
|
const newContent = filteredRecords.map(r => JSON.stringify(r)).join('\n') + '\n'
|
|
259
272
|
const backupPath = `${memoryPath}.backup-${Date.now()}`
|
|
260
273
|
await fs.copyFile(memoryPath, backupPath)
|
|
261
274
|
await fs.writeFile(memoryPath, newContent, 'utf-8')
|
|
262
|
-
|
|
275
|
+
|
|
263
276
|
return successResponse({
|
|
264
277
|
message: `Chart ${chartId} deleted successfully`,
|
|
265
278
|
deletedEntities: Array.from(entitiesToDelete),
|
|
266
279
|
backup: backupPath
|
|
267
280
|
})
|
|
268
|
-
|
|
281
|
+
|
|
269
282
|
} catch (error: any) {
|
|
270
283
|
return errorResponse(`Failed to delete chart: ${error.message}`, 500)
|
|
271
284
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { promises as fs } from 'fs'
|
|
3
|
+
|
|
4
|
+
export async function GET() {
|
|
5
|
+
const memoryPath = process.env.COAIAV_MEMORY_PATH
|
|
6
|
+
|
|
7
|
+
if (!memoryPath) {
|
|
8
|
+
return NextResponse.json(
|
|
9
|
+
{ error: 'No memory file configured' },
|
|
10
|
+
{ status: 400 }
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const stats = await fs.stat(memoryPath)
|
|
16
|
+
const content = await fs.readFile(memoryPath, 'utf-8')
|
|
17
|
+
const lines = content.trim().split('\n').filter(l => l.trim())
|
|
18
|
+
|
|
19
|
+
// Count narrative beats
|
|
20
|
+
const beatCount = lines.filter(line => {
|
|
21
|
+
try {
|
|
22
|
+
const record = JSON.parse(line)
|
|
23
|
+
return record.type === 'entity' &&
|
|
24
|
+
record.metadata?.type === 'narrative_beat'
|
|
25
|
+
} catch {
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
}).length
|
|
29
|
+
|
|
30
|
+
return NextResponse.json({
|
|
31
|
+
lastModified: stats.mtime.getTime(),
|
|
32
|
+
beatCount,
|
|
33
|
+
fileSize: stats.size,
|
|
34
|
+
totalRecords: lines.length
|
|
35
|
+
})
|
|
36
|
+
} catch (error: any) {
|
|
37
|
+
return NextResponse.json(
|
|
38
|
+
{ error: `Watch failed: ${error.message}` },
|
|
39
|
+
{ status: 500 }
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
package/app/page.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// app/page.tsx
|
|
2
1
|
"use client"
|
|
3
2
|
|
|
4
3
|
import { useState, useEffect } from "react"
|
|
@@ -6,14 +5,17 @@ import { FileUpload } from "@/components/file-upload"
|
|
|
6
5
|
import { ChartList } from "@/components/chart-list"
|
|
7
6
|
import { ChartDetailEditable } from "@/components/chart-detail-editable"
|
|
8
7
|
import { DataStats } from "@/components/data-stats"
|
|
8
|
+
import { CreateChartForm } from "@/components/create-chart-form"
|
|
9
9
|
import type { ParsedData, Chart } from "@/lib/types"
|
|
10
10
|
import { parseJSONL, organizeData } from "@/lib/jsonl-parser"
|
|
11
11
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
12
|
-
import { Download, Upload, RefreshCw, Save, Edit3 } from "lucide-react"
|
|
12
|
+
import { Download, Upload, RefreshCw, Save, Edit3, Plus } from "lucide-react"
|
|
13
13
|
import { Button } from "@/components/ui/button"
|
|
14
14
|
import { useToast } from "@/hooks/use-toast"
|
|
15
15
|
import { Badge } from "@/components/ui/badge"
|
|
16
16
|
import { ThemeToggle } from "@/components/theme-toggle"
|
|
17
|
+
import { LiveIndicator } from "@/components/live-indicator"
|
|
18
|
+
import { useLivePolling } from "@/hooks/use-live-polling"
|
|
17
19
|
|
|
18
20
|
export default function Page() {
|
|
19
21
|
const [data, setData] = useState<ParsedData | null>(null)
|
|
@@ -24,9 +26,19 @@ export default function Page() {
|
|
|
24
26
|
const [isAutoLoaded, setIsAutoLoaded] = useState(false)
|
|
25
27
|
const [isLoading, setIsLoading] = useState(false)
|
|
26
28
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
|
|
29
|
+
const [showCreateForm, setShowCreateForm] = useState(false)
|
|
27
30
|
const { toast } = useToast()
|
|
28
31
|
|
|
29
|
-
//
|
|
32
|
+
// Live mode configuration
|
|
33
|
+
const liveMode = typeof window !== 'undefined' && process.env.NEXT_PUBLIC_LIVE_MODE === 'true'
|
|
34
|
+
const pollInterval = typeof window !== 'undefined' ? parseInt(process.env.NEXT_PUBLIC_POLL_INTERVAL || '2000') : 2000
|
|
35
|
+
|
|
36
|
+
const { isLive } = useLivePolling({
|
|
37
|
+
enabled: liveMode && isAutoLoaded,
|
|
38
|
+
interval: pollInterval,
|
|
39
|
+
onReload: handleReload
|
|
40
|
+
})
|
|
41
|
+
|
|
30
42
|
useEffect(() => {
|
|
31
43
|
async function autoLoad() {
|
|
32
44
|
try {
|
|
@@ -55,6 +67,7 @@ export default function Page() {
|
|
|
55
67
|
setSelectedChart(null)
|
|
56
68
|
setFileName(name)
|
|
57
69
|
setHasUnsavedChanges(false)
|
|
70
|
+
setShowCreateForm(false)
|
|
58
71
|
} catch (error) {
|
|
59
72
|
console.error("Failed to parse JSONL:", error)
|
|
60
73
|
toast({
|
|
@@ -105,12 +118,10 @@ export default function Page() {
|
|
|
105
118
|
try {
|
|
106
119
|
const jsonlLines: string[] = []
|
|
107
120
|
|
|
108
|
-
// Export all entities
|
|
109
121
|
for (const entity of data.entities.values()) {
|
|
110
122
|
jsonlLines.push(JSON.stringify(entity))
|
|
111
123
|
}
|
|
112
124
|
|
|
113
|
-
// Export all relations
|
|
114
125
|
for (const relation of data.relations) {
|
|
115
126
|
jsonlLines.push(JSON.stringify(relation))
|
|
116
127
|
}
|
|
@@ -147,12 +158,10 @@ export default function Page() {
|
|
|
147
158
|
|
|
148
159
|
const jsonlLines: string[] = []
|
|
149
160
|
|
|
150
|
-
// Export all entities
|
|
151
161
|
for (const entity of data.entities.values()) {
|
|
152
162
|
jsonlLines.push(JSON.stringify(entity))
|
|
153
163
|
}
|
|
154
164
|
|
|
155
|
-
// Export all relations
|
|
156
165
|
for (const relation of data.relations) {
|
|
157
166
|
jsonlLines.push(JSON.stringify(relation))
|
|
158
167
|
}
|
|
@@ -172,7 +181,6 @@ export default function Page() {
|
|
|
172
181
|
setData(updatedData)
|
|
173
182
|
setHasUnsavedChanges(true)
|
|
174
183
|
|
|
175
|
-
// Update selected chart if it exists in new data
|
|
176
184
|
if (selectedChart) {
|
|
177
185
|
const updatedChart = updatedData.charts.find((c) => c.id === selectedChart.id)
|
|
178
186
|
if (updatedChart) {
|
|
@@ -191,12 +199,27 @@ export default function Page() {
|
|
|
191
199
|
const handleNavigateBack = () => {
|
|
192
200
|
if (chartNavigationStack.length > 0) {
|
|
193
201
|
const newStack = [...chartNavigationStack]
|
|
194
|
-
const
|
|
202
|
+
const previousChartId = newStack.pop()!.id
|
|
195
203
|
setChartNavigationStack(newStack)
|
|
196
|
-
|
|
204
|
+
// Find the refreshed parent chart from current data (with populated subCharts)
|
|
205
|
+
const refreshedParent = data.charts.find((c) => c.id === previousChartId)
|
|
206
|
+
if (refreshedParent) {
|
|
207
|
+
setSelectedChart(refreshedParent)
|
|
208
|
+
}
|
|
197
209
|
}
|
|
198
210
|
}
|
|
199
211
|
|
|
212
|
+
const handleChartCreated = (newData: ParsedData) => {
|
|
213
|
+
setData(newData)
|
|
214
|
+
setFileName("new-chart.jsonl")
|
|
215
|
+
setHasUnsavedChanges(true)
|
|
216
|
+
setShowCreateForm(false)
|
|
217
|
+
toast({
|
|
218
|
+
title: "Chart created",
|
|
219
|
+
description: "Your new structural tension chart has been created",
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
200
223
|
return (
|
|
201
224
|
<div className="min-h-screen bg-background">
|
|
202
225
|
<header className="border-b border-border bg-card sticky top-0 z-10">
|
|
@@ -225,6 +248,7 @@ export default function Page() {
|
|
|
225
248
|
)}
|
|
226
249
|
</div>
|
|
227
250
|
<div className="flex items-center gap-2 flex-wrap">
|
|
251
|
+
{liveMode && isAutoLoaded && <LiveIndicator isLive={isLive} />}
|
|
228
252
|
<ThemeToggle />
|
|
229
253
|
{data && (
|
|
230
254
|
<>
|
|
@@ -274,45 +298,64 @@ export default function Page() {
|
|
|
274
298
|
|
|
275
299
|
<main className="container mx-auto px-4 py-4 md:py-8">
|
|
276
300
|
{!data ? (
|
|
277
|
-
<div className="max-w-2xl mx-auto">
|
|
278
|
-
<
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
<
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
301
|
+
<div className="max-w-2xl mx-auto space-y-6">
|
|
302
|
+
<Tabs value={showCreateForm ? "create" : "upload"} onValueChange={(v) => setShowCreateForm(v === "create")}>
|
|
303
|
+
<TabsList className="w-full">
|
|
304
|
+
<TabsTrigger value="upload" className="flex-1">
|
|
305
|
+
<Upload className="w-4 h-4 mr-2" />
|
|
306
|
+
Upload File
|
|
307
|
+
</TabsTrigger>
|
|
308
|
+
<TabsTrigger value="create" className="flex-1">
|
|
309
|
+
<Plus className="w-4 h-4 mr-2" />
|
|
310
|
+
Create New Chart
|
|
311
|
+
</TabsTrigger>
|
|
312
|
+
</TabsList>
|
|
313
|
+
|
|
314
|
+
<TabsContent value="upload" className="mt-6">
|
|
315
|
+
<FileUpload onFileLoad={handleFileLoad} />
|
|
316
|
+
<div className="mt-8 p-4 md:p-6 bg-muted/30 rounded-lg">
|
|
317
|
+
<h2 className="text-lg md:text-xl font-semibold mb-3">About This Tool</h2>
|
|
318
|
+
<p className="text-sm md:text-base text-muted-foreground leading-relaxed mb-4">
|
|
319
|
+
This visualizer helps you explore and edit structural tension charts created by the coaia-narrative
|
|
320
|
+
MCP server. Edit desired outcomes, add observations to current reality, manage action steps, set due
|
|
321
|
+
dates, and save changes back to your JSONL file.
|
|
322
|
+
</p>
|
|
323
|
+
<div className="space-y-2">
|
|
324
|
+
<h3 className="text-sm md:text-base font-semibold">Editing Features:</h3>
|
|
325
|
+
<ul className="text-sm md:text-base text-muted-foreground space-y-1">
|
|
326
|
+
<li className="flex items-start gap-2">
|
|
327
|
+
<span className="text-chart-1 mt-1">•</span>
|
|
328
|
+
<span>Edit desired outcomes inline</span>
|
|
329
|
+
</li>
|
|
330
|
+
<li className="flex items-start gap-2">
|
|
331
|
+
<span className="text-chart-2 mt-1">•</span>
|
|
332
|
+
<span>Add, edit, and delete current reality observations</span>
|
|
333
|
+
</li>
|
|
334
|
+
<li className="flex items-start gap-2">
|
|
335
|
+
<span className="text-chart-3 mt-1">•</span>
|
|
336
|
+
<span>Create and manage action steps</span>
|
|
337
|
+
</li>
|
|
338
|
+
<li className="flex items-start gap-2">
|
|
339
|
+
<span className="text-chart-4 mt-1">•</span>
|
|
340
|
+
<span>Set and update due dates for charts and actions</span>
|
|
341
|
+
</li>
|
|
342
|
+
<li className="flex items-start gap-2">
|
|
343
|
+
<span className="text-chart-5 mt-1">•</span>
|
|
344
|
+
<span>Toggle action completion status</span>
|
|
345
|
+
</li>
|
|
346
|
+
<li className="flex items-start gap-2">
|
|
347
|
+
<span className="text-primary mt-1">•</span>
|
|
348
|
+
<span>Auto-save to local filesystem when launched via CLI</span>
|
|
349
|
+
</li>
|
|
350
|
+
</ul>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
</TabsContent>
|
|
354
|
+
|
|
355
|
+
<TabsContent value="create" className="mt-6">
|
|
356
|
+
<CreateChartForm onChartCreated={handleChartCreated} />
|
|
357
|
+
</TabsContent>
|
|
358
|
+
</Tabs>
|
|
316
359
|
</div>
|
|
317
360
|
) : (
|
|
318
361
|
<div className="space-y-4 md:space-y-6">
|
|
@@ -334,18 +377,25 @@ export default function Page() {
|
|
|
334
377
|
selectedChart={selectedChart}
|
|
335
378
|
onSelectChart={setSelectedChart}
|
|
336
379
|
mode="hierarchy"
|
|
380
|
+
onDataUpdate={handleDataUpdate}
|
|
337
381
|
/>
|
|
338
382
|
</TabsContent>
|
|
339
383
|
<TabsContent value="list" className="mt-4">
|
|
340
|
-
<ChartList
|
|
384
|
+
<ChartList
|
|
385
|
+
data={data}
|
|
386
|
+
selectedChart={selectedChart}
|
|
387
|
+
onSelectChart={setSelectedChart}
|
|
388
|
+
mode="list"
|
|
389
|
+
onDataUpdate={handleDataUpdate}
|
|
390
|
+
/>
|
|
341
391
|
</TabsContent>
|
|
342
392
|
</Tabs>
|
|
343
393
|
</div>
|
|
344
394
|
<div className="lg:col-span-2">
|
|
345
395
|
{selectedChart ? (
|
|
346
|
-
<ChartDetailEditable
|
|
347
|
-
chart={selectedChart}
|
|
348
|
-
data={data}
|
|
396
|
+
<ChartDetailEditable
|
|
397
|
+
chart={selectedChart}
|
|
398
|
+
data={data}
|
|
349
399
|
onUpdate={handleDataUpdate}
|
|
350
400
|
onNavigateToSubChart={handleNavigateToSubChart}
|
|
351
401
|
onNavigateBack={chartNavigationStack.length > 0 ? handleNavigateBack : undefined}
|