@townco/ui 0.1.38 → 0.1.40
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/dist/core/lib/logger.d.ts +35 -0
- package/dist/core/lib/logger.js +108 -25
- package/dist/tui/components/LogsPanel.d.ts +5 -0
- package/dist/tui/components/LogsPanel.js +29 -0
- package/package.json +4 -3
- package/dist/gui/components/InlineToolCallSummary.d.ts +0 -14
- package/dist/gui/components/InlineToolCallSummary.js +0 -110
- package/dist/gui/components/InlineToolCallSummaryACP.d.ts +0 -15
- package/dist/gui/components/InlineToolCallSummaryACP.js +0 -90
- package/dist/gui/components/tool-call-summary.d.ts +0 -44
- package/dist/gui/components/tool-call-summary.js +0 -67
- package/dist/gui/examples/FileSystemDemo.d.ts +0 -5
- package/dist/gui/examples/FileSystemDemo.js +0 -24
- package/dist/gui/examples/FileSystemExample.d.ts +0 -17
- package/dist/gui/examples/FileSystemExample.js +0 -94
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser-compatible logger
|
|
3
3
|
* Outputs structured JSON logs to console with color-coding
|
|
4
|
+
* Also captures logs to a global store for in-app viewing
|
|
5
|
+
* In Node.js environment with logsDir option, also writes to .logs/ directory
|
|
4
6
|
*/
|
|
5
7
|
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
|
8
|
+
export interface LogEntry {
|
|
9
|
+
id: string;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
level: LogLevel;
|
|
12
|
+
service: string;
|
|
13
|
+
message: string;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get all captured logs
|
|
18
|
+
*/
|
|
19
|
+
export declare function getCapturedLogs(): LogEntry[];
|
|
20
|
+
/**
|
|
21
|
+
* Clear all captured logs
|
|
22
|
+
*/
|
|
23
|
+
export declare function clearCapturedLogs(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Subscribe to log updates
|
|
26
|
+
*/
|
|
27
|
+
type LogSubscriber = (entry: LogEntry) => void;
|
|
28
|
+
export declare function subscribeToLogs(callback: LogSubscriber): () => void;
|
|
29
|
+
/**
|
|
30
|
+
* Configure global logs directory for file writing
|
|
31
|
+
* Must be called before creating any loggers (typically at TUI startup)
|
|
32
|
+
*/
|
|
33
|
+
export declare function configureLogsDir(logsDir: string): void;
|
|
6
34
|
export declare class Logger {
|
|
7
35
|
private service;
|
|
8
36
|
private minLevel;
|
|
37
|
+
private logFilePath?;
|
|
38
|
+
private logsDir?;
|
|
39
|
+
private writeQueue;
|
|
40
|
+
private isWriting;
|
|
9
41
|
constructor(service: string, minLevel?: LogLevel);
|
|
42
|
+
private setupFileLogging;
|
|
43
|
+
private writeToFile;
|
|
10
44
|
private shouldLog;
|
|
11
45
|
private log;
|
|
12
46
|
trace(message: string, metadata?: Record<string, unknown>): void;
|
|
@@ -22,3 +56,4 @@ export declare class Logger {
|
|
|
22
56
|
* @param minLevel - Minimum log level to display (default: "debug")
|
|
23
57
|
*/
|
|
24
58
|
export declare function createLogger(service: string, minLevel?: LogLevel): Logger;
|
|
59
|
+
export {};
|
package/dist/core/lib/logger.js
CHANGED
|
@@ -1,7 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser-compatible logger
|
|
3
3
|
* Outputs structured JSON logs to console with color-coding
|
|
4
|
+
* Also captures logs to a global store for in-app viewing
|
|
5
|
+
* In Node.js environment with logsDir option, also writes to .logs/ directory
|
|
4
6
|
*/
|
|
7
|
+
// Check if running in Node.js
|
|
8
|
+
const isNode = typeof process !== "undefined" && process.versions?.node;
|
|
9
|
+
// Global logs directory configuration (set once at app startup for TUI)
|
|
10
|
+
let globalLogsDir;
|
|
11
|
+
// Global log store
|
|
12
|
+
const globalLogStore = [];
|
|
13
|
+
let logIdCounter = 0;
|
|
14
|
+
/**
|
|
15
|
+
* Get all captured logs
|
|
16
|
+
*/
|
|
17
|
+
export function getCapturedLogs() {
|
|
18
|
+
return [...globalLogStore];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Clear all captured logs
|
|
22
|
+
*/
|
|
23
|
+
export function clearCapturedLogs() {
|
|
24
|
+
globalLogStore.length = 0;
|
|
25
|
+
}
|
|
26
|
+
const logSubscribers = new Set();
|
|
27
|
+
export function subscribeToLogs(callback) {
|
|
28
|
+
logSubscribers.add(callback);
|
|
29
|
+
return () => logSubscribers.delete(callback);
|
|
30
|
+
}
|
|
31
|
+
function notifyLogSubscribers(entry) {
|
|
32
|
+
for (const callback of logSubscribers) {
|
|
33
|
+
callback(entry);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Configure global logs directory for file writing
|
|
38
|
+
* Must be called before creating any loggers (typically at TUI startup)
|
|
39
|
+
*/
|
|
40
|
+
export function configureLogsDir(logsDir) {
|
|
41
|
+
globalLogsDir = logsDir;
|
|
42
|
+
}
|
|
5
43
|
const LOG_LEVELS = {
|
|
6
44
|
trace: 0,
|
|
7
45
|
debug: 1,
|
|
@@ -18,7 +56,7 @@ const _LOG_COLORS = {
|
|
|
18
56
|
error: "#EF4444", // red
|
|
19
57
|
fatal: "#DC2626", // dark red
|
|
20
58
|
};
|
|
21
|
-
const
|
|
59
|
+
const _LOG_STYLES = {
|
|
22
60
|
trace: "color: #6B7280",
|
|
23
61
|
debug: "color: #3B82F6; font-weight: bold",
|
|
24
62
|
info: "color: #10B981; font-weight: bold",
|
|
@@ -29,6 +67,10 @@ const LOG_STYLES = {
|
|
|
29
67
|
export class Logger {
|
|
30
68
|
service;
|
|
31
69
|
minLevel;
|
|
70
|
+
logFilePath;
|
|
71
|
+
logsDir;
|
|
72
|
+
writeQueue = [];
|
|
73
|
+
isWriting = false;
|
|
32
74
|
constructor(service, minLevel = "debug") {
|
|
33
75
|
this.service = service;
|
|
34
76
|
this.minLevel = minLevel;
|
|
@@ -37,6 +79,48 @@ export class Logger {
|
|
|
37
79
|
process.env?.NODE_ENV === "production") {
|
|
38
80
|
this.minLevel = "info";
|
|
39
81
|
}
|
|
82
|
+
// Note: File logging setup is done lazily in log() method
|
|
83
|
+
// This allows loggers created before configureLogsDir() to still write to files
|
|
84
|
+
}
|
|
85
|
+
setupFileLogging() {
|
|
86
|
+
if (!isNode || !globalLogsDir)
|
|
87
|
+
return;
|
|
88
|
+
try {
|
|
89
|
+
// Dynamic import for Node.js modules
|
|
90
|
+
const path = require("node:path");
|
|
91
|
+
const fs = require("node:fs");
|
|
92
|
+
this.logsDir = globalLogsDir;
|
|
93
|
+
this.logFilePath = path.join(this.logsDir, `${this.service}.log`);
|
|
94
|
+
// Create logs directory if it doesn't exist
|
|
95
|
+
if (!fs.existsSync(this.logsDir)) {
|
|
96
|
+
fs.mkdirSync(this.logsDir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (_error) {
|
|
100
|
+
// Silently fail if we can't set up file logging
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async writeToFile(content) {
|
|
104
|
+
if (!this.logFilePath || !isNode)
|
|
105
|
+
return;
|
|
106
|
+
this.writeQueue.push(content);
|
|
107
|
+
if (this.isWriting) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.isWriting = true;
|
|
111
|
+
while (this.writeQueue.length > 0) {
|
|
112
|
+
const batch = this.writeQueue.splice(0, this.writeQueue.length);
|
|
113
|
+
const data = `${batch.join("\n")}\n`;
|
|
114
|
+
try {
|
|
115
|
+
// Dynamic import for Node.js modules
|
|
116
|
+
const fs = require("node:fs");
|
|
117
|
+
await fs.promises.appendFile(this.logFilePath, data, "utf-8");
|
|
118
|
+
}
|
|
119
|
+
catch (_error) {
|
|
120
|
+
// Silently fail
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this.isWriting = false;
|
|
40
124
|
}
|
|
41
125
|
shouldLog(level) {
|
|
42
126
|
return LOG_LEVELS[level] >= LOG_LEVELS[this.minLevel];
|
|
@@ -46,38 +130,37 @@ export class Logger {
|
|
|
46
130
|
return;
|
|
47
131
|
}
|
|
48
132
|
const entry = {
|
|
133
|
+
id: `log_${++logIdCounter}`,
|
|
49
134
|
timestamp: new Date().toISOString(),
|
|
50
135
|
level,
|
|
51
136
|
service: this.service,
|
|
52
137
|
message,
|
|
53
138
|
...(metadata && { metadata }),
|
|
54
139
|
};
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
case "trace":
|
|
64
|
-
case "debug":
|
|
65
|
-
case "info":
|
|
66
|
-
console.log(prefix, style, msg, ...(metadata ? [metadata] : []));
|
|
67
|
-
break;
|
|
68
|
-
case "warn":
|
|
69
|
-
console.warn(prefix, style, msg, ...(metadata ? [metadata] : []));
|
|
70
|
-
break;
|
|
71
|
-
case "error":
|
|
72
|
-
case "fatal":
|
|
73
|
-
console.error(prefix, style, msg, ...(metadata ? [metadata] : []));
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
140
|
+
// Store in global log store
|
|
141
|
+
globalLogStore.push(entry);
|
|
142
|
+
// Notify subscribers
|
|
143
|
+
notifyLogSubscribers(entry);
|
|
144
|
+
// Write to file in Node.js (for logs tab to read)
|
|
145
|
+
// Lazily set up file logging if globalLogsDir was configured after this logger was created
|
|
146
|
+
if (isNode && !this.logFilePath && globalLogsDir) {
|
|
147
|
+
this.setupFileLogging();
|
|
76
148
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
149
|
+
if (isNode && this.logFilePath) {
|
|
150
|
+
// Write as JSON without the id field (to match expected format)
|
|
151
|
+
const fileEntry = {
|
|
152
|
+
timestamp: entry.timestamp,
|
|
153
|
+
level: entry.level,
|
|
154
|
+
service: entry.service,
|
|
155
|
+
message: entry.message,
|
|
156
|
+
...(entry.metadata && { metadata: entry.metadata }),
|
|
157
|
+
};
|
|
158
|
+
this.writeToFile(JSON.stringify(fileEntry)).catch(() => {
|
|
159
|
+
// Silently fail
|
|
160
|
+
});
|
|
80
161
|
}
|
|
162
|
+
// No console output - logs are only captured and displayed in UI
|
|
163
|
+
// This prevents logs from polluting stdout/stderr in TUI mode
|
|
81
164
|
}
|
|
82
165
|
trace(message, metadata) {
|
|
83
166
|
this.log("trace", message, metadata);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { subscribeToLogs } from "../../core/lib/logger.js";
|
|
5
|
+
// Color mapping for log levels
|
|
6
|
+
const LOG_LEVEL_COLORS = {
|
|
7
|
+
trace: "gray",
|
|
8
|
+
debug: "blue",
|
|
9
|
+
info: "green",
|
|
10
|
+
warn: "yellow",
|
|
11
|
+
error: "red",
|
|
12
|
+
fatal: "red",
|
|
13
|
+
};
|
|
14
|
+
export function LogsPanel({ logs: initialLogs }) {
|
|
15
|
+
const [logs, setLogs] = useState(initialLogs);
|
|
16
|
+
// Subscribe to new logs
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const unsubscribe = subscribeToLogs((entry) => {
|
|
19
|
+
setLogs((prev) => [...prev, entry]);
|
|
20
|
+
});
|
|
21
|
+
return unsubscribe;
|
|
22
|
+
}, []);
|
|
23
|
+
if (logs.length === 0) {
|
|
24
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: _jsx(Text, { dimColor: true, children: "No logs yet..." }) }));
|
|
25
|
+
}
|
|
26
|
+
// Show last 100 logs
|
|
27
|
+
const displayLogs = logs.slice(-100);
|
|
28
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: 1, children: displayLogs.map((log) => (_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { dimColor: true, children: new Date(log.timestamp).toLocaleTimeString() }), _jsxs(Text, { color: LOG_LEVEL_COLORS[log.level], bold: true, children: ["[", log.level.toUpperCase(), "]"] }), _jsxs(Text, { dimColor: true, children: ["[", log.service, "]"] }), _jsx(Text, { children: log.message }), log.metadata && (_jsx(Text, { dimColor: true, children: JSON.stringify(log.metadata) }))] }, log.id))) }));
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@agentclientprotocol/sdk": "^0.5.1",
|
|
43
|
-
"@townco/core": "0.0.
|
|
43
|
+
"@townco/core": "0.0.18",
|
|
44
44
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
45
45
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
46
46
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"lucide-react": "^0.552.0",
|
|
55
55
|
"react-markdown": "^10.1.0",
|
|
56
56
|
"react-resizable-panels": "^3.0.6",
|
|
57
|
+
"@radix-ui/react-tooltip": "^1.2.8",
|
|
57
58
|
"remark-gfm": "^4.0.1",
|
|
58
59
|
"sonner": "^2.0.7",
|
|
59
60
|
"tailwind-merge": "^3.3.1",
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@tailwindcss/postcss": "^4.1.17",
|
|
65
|
-
"@townco/tsconfig": "0.1.
|
|
66
|
+
"@townco/tsconfig": "0.1.37",
|
|
66
67
|
"@types/node": "^24.10.0",
|
|
67
68
|
"@types/react": "^19.2.2",
|
|
68
69
|
"ink": "^6.4.0",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { AcpClient } from "../../sdk/client/acp-client.js";
|
|
2
|
-
import type { ToolCall } from "../../core/schemas/tool-call.js";
|
|
3
|
-
export interface InlineToolCallSummaryProps {
|
|
4
|
-
/** The tool call to summarize */
|
|
5
|
-
toolCall: ToolCall;
|
|
6
|
-
/** ACP client for making requests */
|
|
7
|
-
client: AcpClient | null;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Inline tool call summary that appears beneath completed tool calls
|
|
11
|
-
* Shows a brief AI-generated summary of what the tool accomplished
|
|
12
|
-
* Uses the ACP client to make the request
|
|
13
|
-
*/
|
|
14
|
-
export declare function InlineToolCallSummary({ toolCall, client, }: InlineToolCallSummaryProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { CheckCircle, Sparkles } from "lucide-react";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
/**
|
|
5
|
-
* Inline tool call summary that appears beneath completed tool calls
|
|
6
|
-
* Shows a brief AI-generated summary of what the tool accomplished
|
|
7
|
-
* Uses the ACP client to make the request
|
|
8
|
-
*/
|
|
9
|
-
export function InlineToolCallSummary({ toolCall, client, }) {
|
|
10
|
-
const [summary, setSummary] = useState(null);
|
|
11
|
-
const [loading, setLoading] = useState(false);
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
// Only generate summary for completed tool calls
|
|
14
|
-
if (toolCall.status !== "completed" || !client) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
// Don't fetch if we already have a summary
|
|
18
|
-
if (summary) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const fetchSummary = async () => {
|
|
22
|
-
setLoading(true);
|
|
23
|
-
try {
|
|
24
|
-
// Create a minimal Anthropic-format request with just this tool call
|
|
25
|
-
const request = {
|
|
26
|
-
messages: [
|
|
27
|
-
{
|
|
28
|
-
role: "assistant",
|
|
29
|
-
content: [
|
|
30
|
-
{
|
|
31
|
-
type: "tool_use",
|
|
32
|
-
id: toolCall.id,
|
|
33
|
-
name: toolCall.title.toLowerCase().replace(/\s+/g, "_"),
|
|
34
|
-
input: toolCall.rawInput || {},
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
role: "user",
|
|
40
|
-
content: [
|
|
41
|
-
{
|
|
42
|
-
type: "tool_result",
|
|
43
|
-
tool_use_id: toolCall.id,
|
|
44
|
-
content: extractToolOutput(toolCall),
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
};
|
|
50
|
-
// Make ACP RPC request to agent/summarize method
|
|
51
|
-
const response = await client.request("agent/summarize", request);
|
|
52
|
-
// Extract the description from the first tool call
|
|
53
|
-
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
54
|
-
const firstToolCall = response.toolCalls[0];
|
|
55
|
-
if (firstToolCall) {
|
|
56
|
-
setSummary(firstToolCall.description);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
// Silently fail - summaries are optional enhancements
|
|
62
|
-
console.debug("Failed to fetch tool call summary via ACP:", error);
|
|
63
|
-
}
|
|
64
|
-
finally {
|
|
65
|
-
setLoading(false);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
fetchSummary();
|
|
69
|
-
}, [toolCall, client, summary]);
|
|
70
|
-
// Don't show anything if not completed or still loading
|
|
71
|
-
if (toolCall.status !== "completed" || loading) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
// Don't show if no summary
|
|
75
|
-
if (!summary) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
return (_jsxs("div", { className: "flex items-start gap-2 mt-2 px-3 py-2 rounded-lg bg-emerald-50/50 border border-emerald-100", children: [_jsx(CheckCircle, { className: "h-3.5 w-3.5 text-emerald-600 shrink-0 mt-0.5" }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx("p", { className: "text-[11px] text-emerald-900 font-medium", children: summary }) }), _jsx(Sparkles, { className: "h-3 w-3 text-emerald-600/50 shrink-0 mt-0.5" })] }));
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Extract output text from a tool call for summarization
|
|
82
|
-
*/
|
|
83
|
-
function extractToolOutput(toolCall) {
|
|
84
|
-
// Try to extract text from content blocks
|
|
85
|
-
if (toolCall.content && toolCall.content.length > 0) {
|
|
86
|
-
const textBlocks = [];
|
|
87
|
-
for (const block of toolCall.content) {
|
|
88
|
-
if (block.type === "text" && "text" in block) {
|
|
89
|
-
textBlocks.push(block.text);
|
|
90
|
-
}
|
|
91
|
-
else if (block.type === "content" && "content" in block) {
|
|
92
|
-
const innerContent = block.content;
|
|
93
|
-
if (innerContent.type === "text" && innerContent.text) {
|
|
94
|
-
textBlocks.push(innerContent.text);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (textBlocks.length > 0) {
|
|
99
|
-
// Truncate very long outputs for summary purposes
|
|
100
|
-
const output = textBlocks.join("\n");
|
|
101
|
-
return output.length > 500 ? `${output.slice(0, 500)}...` : output;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// Fallback to raw output
|
|
105
|
-
if (toolCall.rawOutput) {
|
|
106
|
-
const output = JSON.stringify(toolCall.rawOutput);
|
|
107
|
-
return output.length > 500 ? `${output.slice(0, 500)}...` : output;
|
|
108
|
-
}
|
|
109
|
-
return "No output";
|
|
110
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { AcpClient } from "../../sdk/client/acp-client.js";
|
|
2
|
-
import type { ToolCall } from "../../core/schemas/tool-call.js";
|
|
3
|
-
export interface InlineToolCallSummaryACPProps {
|
|
4
|
-
/** The tool call to summarize */
|
|
5
|
-
toolCall: ToolCall;
|
|
6
|
-
/** ACP client for making requests */
|
|
7
|
-
client: AcpClient | null;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Inline tool call summary using ACP protocol
|
|
11
|
-
*
|
|
12
|
-
* This version uses the ACP client to make a custom method call
|
|
13
|
-
* instead of direct HTTP fetch
|
|
14
|
-
*/
|
|
15
|
-
export declare function InlineToolCallSummaryACP({ toolCall, client, }: InlineToolCallSummaryACPProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { CheckCircle, Sparkles } from "lucide-react";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
/**
|
|
5
|
-
* Inline tool call summary using ACP protocol
|
|
6
|
-
*
|
|
7
|
-
* This version uses the ACP client to make a custom method call
|
|
8
|
-
* instead of direct HTTP fetch
|
|
9
|
-
*/
|
|
10
|
-
export function InlineToolCallSummaryACP({ toolCall, client, }) {
|
|
11
|
-
const [summary, setSummary] = useState(null);
|
|
12
|
-
const [loading, setLoading] = useState(false);
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
if (toolCall.status !== "completed" || !client || summary) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const fetchSummary = async () => {
|
|
18
|
-
setLoading(true);
|
|
19
|
-
try {
|
|
20
|
-
// This would require adding a custom ACP method to the server
|
|
21
|
-
// For now, this shows the concept - you'd need to implement
|
|
22
|
-
// the corresponding handler in the ACP adapter
|
|
23
|
-
// Example of what it might look like:
|
|
24
|
-
// const response = await client.request({
|
|
25
|
-
// method: "agent/summarize",
|
|
26
|
-
// params: {
|
|
27
|
-
// messages: [
|
|
28
|
-
// {
|
|
29
|
-
// role: "assistant",
|
|
30
|
-
// content: [{
|
|
31
|
-
// type: "tool_use",
|
|
32
|
-
// id: toolCall.id,
|
|
33
|
-
// name: toolCall.title,
|
|
34
|
-
// input: toolCall.rawInput || {}
|
|
35
|
-
// }]
|
|
36
|
-
// },
|
|
37
|
-
// {
|
|
38
|
-
// role: "user",
|
|
39
|
-
// content: [{
|
|
40
|
-
// type: "tool_result",
|
|
41
|
-
// tool_use_id: toolCall.id,
|
|
42
|
-
// content: extractToolOutput(toolCall)
|
|
43
|
-
// }]
|
|
44
|
-
// }
|
|
45
|
-
// ]
|
|
46
|
-
// }
|
|
47
|
-
// });
|
|
48
|
-
// For now, fall back to direct fetch since the ACP method
|
|
49
|
-
// isn't implemented
|
|
50
|
-
console.log("ACP-based summary not yet implemented");
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
console.debug("Failed to fetch tool call summary via ACP:", error);
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
setLoading(false);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
fetchSummary();
|
|
60
|
-
}, [toolCall, client, summary]);
|
|
61
|
-
if (toolCall.status !== "completed" || loading || !summary) {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
return (_jsxs("div", { className: "flex items-start gap-2 mt-2 px-3 py-2 rounded-lg bg-emerald-50/50 border border-emerald-100", children: [_jsx(CheckCircle, { className: "h-3.5 w-3.5 text-emerald-600 shrink-0 mt-0.5" }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx("p", { className: "text-[11px] text-emerald-900 font-medium", children: summary }) }), _jsx(Sparkles, { className: "h-3 w-3 text-emerald-600/50 shrink-0 mt-0.5" })] }));
|
|
65
|
-
}
|
|
66
|
-
function extractToolOutput(toolCall) {
|
|
67
|
-
if (toolCall.content && toolCall.content.length > 0) {
|
|
68
|
-
const textBlocks = [];
|
|
69
|
-
for (const block of toolCall.content) {
|
|
70
|
-
if (block.type === "text" && "text" in block) {
|
|
71
|
-
textBlocks.push(block.text);
|
|
72
|
-
}
|
|
73
|
-
else if (block.type === "content" && "content" in block) {
|
|
74
|
-
const innerContent = block.content;
|
|
75
|
-
if (innerContent.type === "text" && innerContent.text) {
|
|
76
|
-
textBlocks.push(innerContent.text);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (textBlocks.length > 0) {
|
|
81
|
-
const output = textBlocks.join("\n");
|
|
82
|
-
return output.length > 500 ? `${output.slice(0, 500)}...` : output;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (toolCall.rawOutput) {
|
|
86
|
-
const output = JSON.stringify(toolCall.rawOutput);
|
|
87
|
-
return output.length > 500 ? `${output.slice(0, 500)}...` : output;
|
|
88
|
-
}
|
|
89
|
-
return "No output";
|
|
90
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool call summary data from the /summarize endpoint
|
|
3
|
-
*/
|
|
4
|
-
export interface ToolCallSummary {
|
|
5
|
-
id: string;
|
|
6
|
-
name: string;
|
|
7
|
-
description: string;
|
|
8
|
-
input: Record<string, unknown>;
|
|
9
|
-
output?: string | undefined;
|
|
10
|
-
status: "pending" | "completed" | "error";
|
|
11
|
-
error?: string | undefined;
|
|
12
|
-
}
|
|
13
|
-
export interface SummaryResponse {
|
|
14
|
-
summary: {
|
|
15
|
-
total: number;
|
|
16
|
-
byStatus: Record<string, number>;
|
|
17
|
-
byTool: Record<string, number>;
|
|
18
|
-
};
|
|
19
|
-
toolCalls: ToolCallSummary[];
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Props for ToolCallSummaryDisplay component
|
|
23
|
-
*/
|
|
24
|
-
export interface ToolCallSummaryDisplayProps {
|
|
25
|
-
/** ACP server URL */
|
|
26
|
-
serverUrl: string;
|
|
27
|
-
/** Anthropic API request (messages with tool calls) */
|
|
28
|
-
request: {
|
|
29
|
-
model?: string;
|
|
30
|
-
messages: Array<{
|
|
31
|
-
role: "user" | "assistant";
|
|
32
|
-
content: string | Array<{
|
|
33
|
-
type: string;
|
|
34
|
-
[key: string]: unknown;
|
|
35
|
-
}>;
|
|
36
|
-
}>;
|
|
37
|
-
};
|
|
38
|
-
/** Custom className for styling */
|
|
39
|
-
className?: string;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Display a summary of tool calls from an Anthropic API request
|
|
43
|
-
*/
|
|
44
|
-
export declare function ToolCallSummaryDisplay({ serverUrl, request, className, }: ToolCallSummaryDisplayProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { createLogger } from "@townco/core";
|
|
3
|
-
import { AlertCircle, CheckCircle, Clock, Loader2, RefreshCw, } from "lucide-react";
|
|
4
|
-
import { useEffect, useState } from "react";
|
|
5
|
-
import { cn } from "../lib/utils";
|
|
6
|
-
const logger = createLogger("tool-call-summary");
|
|
7
|
-
/**
|
|
8
|
-
* Display a summary of tool calls from an Anthropic API request
|
|
9
|
-
*/
|
|
10
|
-
export function ToolCallSummaryDisplay({ serverUrl, request, className, }) {
|
|
11
|
-
const [summary, setSummary] = useState(null);
|
|
12
|
-
const [loading, setLoading] = useState(false);
|
|
13
|
-
const [error, setError] = useState(null);
|
|
14
|
-
const fetchSummary = async () => {
|
|
15
|
-
setLoading(true);
|
|
16
|
-
setError(null);
|
|
17
|
-
try {
|
|
18
|
-
const response = await fetch(`${serverUrl}/summarize`, {
|
|
19
|
-
method: "POST",
|
|
20
|
-
headers: {
|
|
21
|
-
"Content-Type": "application/json",
|
|
22
|
-
},
|
|
23
|
-
body: JSON.stringify(request),
|
|
24
|
-
});
|
|
25
|
-
if (!response.ok) {
|
|
26
|
-
const errorData = await response.json();
|
|
27
|
-
throw new Error(errorData.error || "Failed to fetch summary");
|
|
28
|
-
}
|
|
29
|
-
const data = await response.json();
|
|
30
|
-
setSummary(data);
|
|
31
|
-
logger.info("Tool call summary fetched", {
|
|
32
|
-
total: data.summary.total,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
37
|
-
setError(message);
|
|
38
|
-
logger.error("Failed to fetch tool call summary", { error: message });
|
|
39
|
-
}
|
|
40
|
-
finally {
|
|
41
|
-
setLoading(false);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
// Fetch summary on mount and when request changes
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
fetchSummary();
|
|
47
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
48
|
-
}, [serverUrl, request]);
|
|
49
|
-
if (loading && !summary) {
|
|
50
|
-
return (_jsxs("div", { className: cn("flex items-center justify-center p-4 text-muted-foreground", className), children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), _jsx("span", { className: "text-paragraph-sm", children: "Loading summary..." })] }));
|
|
51
|
-
}
|
|
52
|
-
if (error) {
|
|
53
|
-
return (_jsx("div", { className: cn("rounded-lg border border-destructive/20 p-4", className), children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive shrink-0 mt-0.5" }), _jsxs("div", { className: "flex-1", children: [_jsx("p", { className: "text-paragraph-sm font-medium text-destructive", children: "Error loading summary" }), _jsx("p", { className: "text-paragraph-sm text-muted-foreground mt-1", children: error })] }), _jsx("button", { type: "button", onClick: fetchSummary, className: "text-muted-foreground hover:text-foreground transition-colors", children: _jsx(RefreshCw, { className: "h-4 w-4" }) })] }) }));
|
|
54
|
-
}
|
|
55
|
-
if (!summary || summary.summary.total === 0) {
|
|
56
|
-
return (_jsx("div", { className: cn("flex items-center justify-center p-4 text-muted-foreground", className), children: _jsx("span", { className: "text-paragraph-sm", children: "No tool calls found" }) }));
|
|
57
|
-
}
|
|
58
|
-
return (_jsxs("div", { className: cn("space-y-4", className), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("h3", { className: "text-heading-5 font-semibold", children: ["Tool Calls (", summary.summary.total, ")"] }), _jsx("button", { type: "button", onClick: fetchSummary, disabled: loading, className: "text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50", children: _jsx(RefreshCw, { className: cn("h-4 w-4", loading && "animate-spin") }) })] }), _jsxs("div", { className: "flex gap-4", children: [(summary.summary.byStatus.completed ?? 0) > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(CheckCircle, { className: "h-4 w-4 text-success" }), _jsxs("span", { className: "text-paragraph-sm text-muted-foreground", children: [summary.summary.byStatus.completed, " completed"] })] })), (summary.summary.byStatus.pending ?? 0) > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Clock, { className: "h-4 w-4 text-warning" }), _jsxs("span", { className: "text-paragraph-sm text-muted-foreground", children: [summary.summary.byStatus.pending, " pending"] })] })), (summary.summary.byStatus.error ?? 0) > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" }), _jsxs("span", { className: "text-paragraph-sm text-muted-foreground", children: [summary.summary.byStatus.error, " errors"] })] }))] }), _jsx("div", { className: "space-y-2", children: summary.toolCalls.map((toolCall) => (_jsx(ToolCallSummaryItem, { toolCall: toolCall }, toolCall.id))) })] }));
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Display a single tool call summary
|
|
62
|
-
*/
|
|
63
|
-
function ToolCallSummaryItem({ toolCall }) {
|
|
64
|
-
const [expanded, setExpanded] = useState(false);
|
|
65
|
-
const statusIcon = toolCall.status === "completed" ? (_jsx(CheckCircle, { className: "h-4 w-4 text-success" })) : toolCall.status === "error" ? (_jsx(AlertCircle, { className: "h-4 w-4 text-destructive" })) : (_jsx(Clock, { className: "h-4 w-4 text-warning" }));
|
|
66
|
-
return (_jsxs("div", { className: "rounded-lg border border-border bg-card p-3 space-y-2", children: [_jsxs("button", { type: "button", onClick: () => setExpanded(!expanded), className: "flex items-start gap-2 w-full text-left", children: [statusIcon, _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-paragraph-sm font-medium text-foreground truncate", children: toolCall.description }), _jsx("p", { className: "text-paragraph-xs text-muted-foreground", children: toolCall.name })] })] }), expanded && (_jsxs("div", { className: "pl-6 space-y-2", children: [Object.keys(toolCall.input).length > 0 && (_jsxs("div", { children: [_jsx("p", { className: "text-paragraph-xs font-medium text-muted-foreground mb-1", children: "Input:" }), _jsx("pre", { className: "text-paragraph-xs bg-muted p-2 rounded overflow-x-auto", children: JSON.stringify(toolCall.input, null, 2) })] })), toolCall.output && (_jsxs("div", { children: [_jsx("p", { className: "text-paragraph-xs font-medium text-muted-foreground mb-1", children: "Output:" }), _jsx("pre", { className: "text-paragraph-xs bg-muted p-2 rounded overflow-x-auto", children: toolCall.output })] })), toolCall.error && (_jsxs("div", { children: [_jsx("p", { className: "text-paragraph-xs font-medium text-destructive mb-1", children: "Error:" }), _jsx("p", { className: "text-paragraph-xs text-destructive bg-destructive/10 p-2 rounded", children: toolCall.error })] }))] }))] }));
|
|
67
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* FileSystemDemo - A demo page showcasing the FileSystemView component
|
|
4
|
-
* Can be integrated into the GUI app for testing and demonstration
|
|
5
|
-
*/
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
import { FileSystemView } from "../components/FileSystemView.js";
|
|
8
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/Card.js";
|
|
9
|
-
export function FileSystemDemo() {
|
|
10
|
-
const [selectedItem, setSelectedItem] = React.useState(null);
|
|
11
|
-
const handleDownload = (item) => {
|
|
12
|
-
console.log("Download:", item.name);
|
|
13
|
-
// Implement download logic
|
|
14
|
-
};
|
|
15
|
-
const handleRename = (item) => {
|
|
16
|
-
console.log("Rename:", item.name);
|
|
17
|
-
// Implement rename logic
|
|
18
|
-
};
|
|
19
|
-
const handleDelete = (item) => {
|
|
20
|
-
console.log("Delete:", item.name);
|
|
21
|
-
// Implement delete logic
|
|
22
|
-
};
|
|
23
|
-
return (_jsx("div", { className: "w-full h-full p-8 bg-background", children: _jsxs("div", { className: "max-w-7xl mx-auto space-y-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold tracking-tight", children: "File Manager" }), _jsx("p", { className: "text-muted-foreground mt-2", children: "A hierarchical file system view with stub data. Replace the MockFileSystemProvider with a real implementation to connect to actual filesystem data." })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-6", children: [_jsxs(Card, { className: "lg:col-span-2", children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Files" }), _jsx(CardDescription, { children: "Browse files and folders. Click folders to expand/collapse." })] }), _jsx(CardContent, { className: "p-0", children: _jsx(FileSystemView, { onItemSelect: setSelectedItem, onDownload: handleDownload, onRename: handleRename, onDelete: handleDelete, className: "max-h-[600px] overflow-y-auto" }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Details" }), _jsx(CardDescription, { children: "Information about the selected item" })] }), _jsx(CardContent, { children: selectedItem ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Name" }), _jsx("p", { className: "text-base font-mono", children: selectedItem.name })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Type" }), _jsx("p", { className: "text-base capitalize", children: selectedItem.type })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "ID" }), _jsx("p", { className: "text-base font-mono text-xs", children: selectedItem.id })] }), selectedItem.extension && (_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Extension" }), _jsxs("p", { className: "text-base", children: [".", selectedItem.extension] })] })), selectedItem.children && (_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Children" }), _jsxs("p", { className: "text-base", children: [selectedItem.children.length, " item(s)"] })] })), selectedItem.path && (_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Path" }), _jsx("p", { className: "text-base font-mono text-xs", children: selectedItem.path })] }))] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Select a file or folder to view details" })) })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Implementation Notes" }) }), _jsxs(CardContent, { className: "space-y-2 text-sm", children: [_jsxs("p", { children: [_jsx("strong", { children: "Stub System:" }), " Currently using ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "MockFileSystemProvider" }), " with hardcoded data."] }), _jsxs("p", { children: [_jsx("strong", { children: "Easy Replacement:" }), " Implement the ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "FileSystemProvider" }), " ", "interface to connect to real data sources."] }), _jsxs("p", { children: [_jsx("strong", { children: "Design:" }), " Based on shadcn file manager components and Figma specifications."] })] })] })] }) }));
|
|
24
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example usage of FileSystemView component
|
|
3
|
-
* This demonstrates how to use the stub filesystem and how to replace it with real data
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Basic usage with default mock data
|
|
7
|
-
*/
|
|
8
|
-
export declare function BasicFileSystemExample(): import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
/**
|
|
10
|
-
* Custom mock data example
|
|
11
|
-
* Shows how to create custom stub data
|
|
12
|
-
*/
|
|
13
|
-
export declare function CustomDataExample(): import("react/jsx-runtime").JSX.Element;
|
|
14
|
-
/**
|
|
15
|
-
* Example with real provider (commented out to avoid actual API calls)
|
|
16
|
-
*/
|
|
17
|
-
export declare function RealDataExample(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Example usage of FileSystemView component
|
|
4
|
-
* This demonstrates how to use the stub filesystem and how to replace it with real data
|
|
5
|
-
*/
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
import { FileSystemView } from "../components/FileSystemView.js";
|
|
8
|
-
import { MockFileSystemProvider } from "../data/mockFileSystemData.js";
|
|
9
|
-
/**
|
|
10
|
-
* Basic usage with default mock data
|
|
11
|
-
*/
|
|
12
|
-
export function BasicFileSystemExample() {
|
|
13
|
-
return (_jsx("div", { className: "w-full h-full bg-background", children: _jsx(FileSystemView, { onItemSelect: (item) => {
|
|
14
|
-
console.log("Selected:", item);
|
|
15
|
-
} }) }));
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Custom mock data example
|
|
19
|
-
* Shows how to create custom stub data
|
|
20
|
-
*/
|
|
21
|
-
export function CustomDataExample() {
|
|
22
|
-
const customData = [
|
|
23
|
-
{
|
|
24
|
-
id: "src",
|
|
25
|
-
name: "src",
|
|
26
|
-
type: "folder",
|
|
27
|
-
children: [
|
|
28
|
-
{
|
|
29
|
-
id: "components",
|
|
30
|
-
name: "components",
|
|
31
|
-
type: "folder",
|
|
32
|
-
children: [
|
|
33
|
-
{ id: "button", name: "Button.tsx", type: "file", extension: "tsx" },
|
|
34
|
-
{ id: "card", name: "Card.tsx", type: "file", extension: "tsx" },
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
{ id: "app", name: "App.tsx", type: "file", extension: "tsx" },
|
|
38
|
-
{ id: "index", name: "index.ts", type: "file", extension: "ts" },
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
id: "public",
|
|
43
|
-
name: "public",
|
|
44
|
-
type: "folder",
|
|
45
|
-
children: [
|
|
46
|
-
{ id: "favicon", name: "favicon.ico", type: "file" },
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
const customProvider = new MockFileSystemProvider(customData);
|
|
51
|
-
return (_jsx("div", { className: "w-full h-full bg-background", children: _jsx(FileSystemView, { provider: customProvider, onItemSelect: (item) => {
|
|
52
|
-
console.log("Selected:", item);
|
|
53
|
-
} }) }));
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Example of how to implement a real FileSystemProvider
|
|
57
|
-
* Replace MockFileSystemProvider with this when connecting to real data
|
|
58
|
-
*/
|
|
59
|
-
class RealFileSystemProvider {
|
|
60
|
-
apiUrl;
|
|
61
|
-
constructor(apiUrl) {
|
|
62
|
-
this.apiUrl = apiUrl;
|
|
63
|
-
}
|
|
64
|
-
async getRootItems() {
|
|
65
|
-
// Replace with actual API call
|
|
66
|
-
const response = await fetch(`${this.apiUrl}/filesystem/root`);
|
|
67
|
-
return response.json();
|
|
68
|
-
}
|
|
69
|
-
async getItemChildren(itemId) {
|
|
70
|
-
// Replace with actual API call
|
|
71
|
-
const response = await fetch(`${this.apiUrl}/filesystem/items/${itemId}/children`);
|
|
72
|
-
return response.json();
|
|
73
|
-
}
|
|
74
|
-
async getItemDetails(itemId) {
|
|
75
|
-
// Replace with actual API call
|
|
76
|
-
const response = await fetch(`${this.apiUrl}/filesystem/items/${itemId}`);
|
|
77
|
-
return response.json();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Example with real provider (commented out to avoid actual API calls)
|
|
82
|
-
*/
|
|
83
|
-
export function RealDataExample() {
|
|
84
|
-
// Uncomment and configure when ready to use real data:
|
|
85
|
-
// const realProvider = new RealFileSystemProvider('https://your-api.com');
|
|
86
|
-
return (_jsx("div", { className: "w-full h-full bg-background", children: _jsx(FileSystemView
|
|
87
|
-
// provider={realProvider}
|
|
88
|
-
, {
|
|
89
|
-
// provider={realProvider}
|
|
90
|
-
onItemSelect: (item) => {
|
|
91
|
-
console.log("Selected:", item);
|
|
92
|
-
// Handle file selection - open file, show details, etc.
|
|
93
|
-
} }) }));
|
|
94
|
-
}
|