@radnine/storybook-addon-claude 0.1.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/LICENSE +31 -0
- package/dist/Panel.js +167 -0
- package/dist/WebSocketClient.js +247 -0
- package/dist/components/ConnectionStatus.js +109 -0
- package/dist/components/MessageInput.js +104 -0
- package/dist/components/MessageList.js +533 -0
- package/dist/components/TokenInput.js +148 -0
- package/dist/constants.js +17 -0
- package/dist/index.js +64 -0
- package/dist/manager.js +15 -0
- package/dist/preset.js +8 -0
- package/dist/useClaudeSession.js +139 -0
- package/dist/useStoryContext.js +96 -0
- package/package.json +49 -0
- package/preset.js +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Proprietary Software License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Rad Nine Software Inc.. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
proprietary and confidential property of Rad Nine Software Inc..
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted to authorized licensees to use the Software
|
|
9
|
+
solely for their own internal purposes, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
1. The Software may not be copied, modified, merged, published, distributed,
|
|
12
|
+
sublicensed, sold, or otherwise transferred to any third party without
|
|
13
|
+
the prior written consent of Rad Nine Software Inc..
|
|
14
|
+
|
|
15
|
+
2. The Software may not be reverse-engineered, decompiled, or disassembled,
|
|
16
|
+
except to the extent that such activity is expressly permitted by
|
|
17
|
+
applicable law.
|
|
18
|
+
|
|
19
|
+
3. This license does not grant any ownership rights in the Software. All
|
|
20
|
+
intellectual property rights in the Software remain with Rad Nine Software Inc..
|
|
21
|
+
|
|
22
|
+
4. This license may be terminated by Rad Nine Software Inc. at any time upon written
|
|
23
|
+
notice. Upon termination, the licensee must cease all use of the Software
|
|
24
|
+
and destroy all copies.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
28
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
29
|
+
RADIXHOUND BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
30
|
+
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
31
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/Panel.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ClaudePanel = ClaudePanel;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _managerApi = require("storybook/manager-api");
|
|
9
|
+
var _ConnectionStatus = require("./components/ConnectionStatus");
|
|
10
|
+
var _MessageList = require("./components/MessageList");
|
|
11
|
+
var _MessageInput = require("./components/MessageInput");
|
|
12
|
+
var _TokenInput = require("./components/TokenInput");
|
|
13
|
+
var _useClaudeSession = require("./useClaudeSession");
|
|
14
|
+
var _useStoryContext = require("./useStoryContext");
|
|
15
|
+
var _constants = require("./constants");
|
|
16
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
17
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
18
|
+
/**
|
|
19
|
+
* Main Storybook addon panel for Claude.
|
|
20
|
+
*
|
|
21
|
+
* Manages the auth token, connects to the standalone daemon,
|
|
22
|
+
* and provides the chat interface with story-context awareness.
|
|
23
|
+
*/
|
|
24
|
+
function ClaudePanel({
|
|
25
|
+
active
|
|
26
|
+
}) {
|
|
27
|
+
// Persisted addon state for the token and port
|
|
28
|
+
const [addonState, setAddonState] = (0, _managerApi.useAddonState)(_constants.ADDON_ID, {
|
|
29
|
+
token: getInitialToken(),
|
|
30
|
+
port: _constants.DEFAULT_PORT,
|
|
31
|
+
contextEnabled: true
|
|
32
|
+
});
|
|
33
|
+
const token = addonState?.token || null;
|
|
34
|
+
const port = addonState?.port || _constants.DEFAULT_PORT;
|
|
35
|
+
const contextEnabled = addonState?.contextEnabled ?? true;
|
|
36
|
+
|
|
37
|
+
// Story context
|
|
38
|
+
const storyContext = (0, _useStoryContext.useStoryContext)();
|
|
39
|
+
|
|
40
|
+
// Claude session
|
|
41
|
+
const {
|
|
42
|
+
connectionState,
|
|
43
|
+
messages,
|
|
44
|
+
sessionId,
|
|
45
|
+
isProcessing,
|
|
46
|
+
sendMessage,
|
|
47
|
+
startSession,
|
|
48
|
+
disconnect,
|
|
49
|
+
isConnected
|
|
50
|
+
} = (0, _useClaudeSession.useClaudeSession)({
|
|
51
|
+
host: _constants.DEFAULT_HOST,
|
|
52
|
+
port,
|
|
53
|
+
token
|
|
54
|
+
});
|
|
55
|
+
const handleTokenSubmit = (0, _react.useCallback)((newToken, newPort) => {
|
|
56
|
+
setAddonState({
|
|
57
|
+
...addonState,
|
|
58
|
+
token: newToken,
|
|
59
|
+
port: newPort || _constants.DEFAULT_PORT
|
|
60
|
+
});
|
|
61
|
+
}, [addonState, setAddonState]);
|
|
62
|
+
const handleDisconnect = (0, _react.useCallback)(() => {
|
|
63
|
+
disconnect();
|
|
64
|
+
setAddonState({
|
|
65
|
+
...addonState,
|
|
66
|
+
token: null
|
|
67
|
+
});
|
|
68
|
+
}, [addonState, setAddonState, disconnect]);
|
|
69
|
+
const handleSend = (0, _react.useCallback)(text => {
|
|
70
|
+
if (!sessionId) {
|
|
71
|
+
// Auto-start a session on first message
|
|
72
|
+
startSession();
|
|
73
|
+
}
|
|
74
|
+
const prefix = contextEnabled ? (0, _useStoryContext.buildContextPrefix)(storyContext) : null;
|
|
75
|
+
sendMessage(text, prefix);
|
|
76
|
+
}, [sessionId, startSession, sendMessage, contextEnabled, storyContext]);
|
|
77
|
+
const toggleContext = (0, _react.useCallback)(() => {
|
|
78
|
+
setAddonState({
|
|
79
|
+
...addonState,
|
|
80
|
+
contextEnabled: !contextEnabled
|
|
81
|
+
});
|
|
82
|
+
}, [addonState, contextEnabled, setAddonState]);
|
|
83
|
+
|
|
84
|
+
// Don't render if panel is not active
|
|
85
|
+
if (!active) return null;
|
|
86
|
+
|
|
87
|
+
// No token — show token input
|
|
88
|
+
if (!token) {
|
|
89
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
90
|
+
style: styles.panel,
|
|
91
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_TokenInput.TokenInput, {
|
|
92
|
+
onSubmit: handleTokenSubmit
|
|
93
|
+
})
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
97
|
+
style: styles.panel,
|
|
98
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_ConnectionStatus.ConnectionStatus, {
|
|
99
|
+
state: connectionState,
|
|
100
|
+
sessionId: sessionId,
|
|
101
|
+
onDisconnect: handleDisconnect
|
|
102
|
+
}), storyContext.componentPath && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
103
|
+
style: styles.contextBar,
|
|
104
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("label", {
|
|
105
|
+
style: styles.contextLabel,
|
|
106
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("input", {
|
|
107
|
+
type: "checkbox",
|
|
108
|
+
checked: contextEnabled,
|
|
109
|
+
onChange: toggleContext,
|
|
110
|
+
style: styles.contextCheckbox
|
|
111
|
+
}), "Context: ", /*#__PURE__*/(0, _jsxRuntime.jsx)("code", {
|
|
112
|
+
style: styles.contextPath,
|
|
113
|
+
children: storyContext.componentPath
|
|
114
|
+
})]
|
|
115
|
+
})
|
|
116
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_MessageList.MessageList, {
|
|
117
|
+
messages: messages
|
|
118
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_MessageInput.MessageInput, {
|
|
119
|
+
onSend: handleSend,
|
|
120
|
+
disabled: !isConnected,
|
|
121
|
+
isProcessing: isProcessing
|
|
122
|
+
})]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Read initial token from environment variable if available.
|
|
128
|
+
*/
|
|
129
|
+
function getInitialToken() {
|
|
130
|
+
try {
|
|
131
|
+
// Storybook exposes env vars prefixed with STORYBOOK_
|
|
132
|
+
return typeof process !== 'undefined' && process.env?.STORYBOOK_CLAUDE_DAEMON_TOKEN || typeof process !== 'undefined' && process.env?.CLAUDE_DAEMON_TOKEN || null;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const styles = {
|
|
138
|
+
panel: {
|
|
139
|
+
display: 'flex',
|
|
140
|
+
flexDirection: 'column',
|
|
141
|
+
height: '100%',
|
|
142
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
|
|
143
|
+
},
|
|
144
|
+
contextBar: {
|
|
145
|
+
padding: '4px 12px',
|
|
146
|
+
borderBottom: '1px solid rgba(0,0,0,0.1)',
|
|
147
|
+
backgroundColor: '#fafafa',
|
|
148
|
+
fontSize: '12px'
|
|
149
|
+
},
|
|
150
|
+
contextLabel: {
|
|
151
|
+
display: 'flex',
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
gap: '4px',
|
|
154
|
+
color: '#666',
|
|
155
|
+
cursor: 'pointer'
|
|
156
|
+
},
|
|
157
|
+
contextCheckbox: {
|
|
158
|
+
margin: 0
|
|
159
|
+
},
|
|
160
|
+
contextPath: {
|
|
161
|
+
fontFamily: 'monospace',
|
|
162
|
+
fontSize: '11px',
|
|
163
|
+
backgroundColor: '#eee',
|
|
164
|
+
padding: '1px 4px',
|
|
165
|
+
borderRadius: '3px'
|
|
166
|
+
}
|
|
167
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.WebSocketClient = void 0;
|
|
7
|
+
var _constants = require("./constants");
|
|
8
|
+
/**
|
|
9
|
+
* Browser WebSocket client for the standalone daemon (Phase 2 protocol).
|
|
10
|
+
*
|
|
11
|
+
* Handles connection lifecycle, authentication, auto-reconnect with
|
|
12
|
+
* exponential backoff, and the activate/command/output message protocol.
|
|
13
|
+
*/
|
|
14
|
+
class WebSocketClient {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this._host = options.host || _constants.DEFAULT_HOST;
|
|
17
|
+
this._port = options.port || _constants.DEFAULT_PORT;
|
|
18
|
+
this._token = options.token || null;
|
|
19
|
+
this._ws = null;
|
|
20
|
+
this._state = _constants.CONNECTION_STATES.DISCONNECTED;
|
|
21
|
+
this._listeners = new Map();
|
|
22
|
+
this._reconnectAttempt = 0;
|
|
23
|
+
this._maxReconnectAttempts = options.maxReconnectAttempts || 10;
|
|
24
|
+
this._baseDelay = options.baseDelay || 1000;
|
|
25
|
+
this._maxDelay = options.maxDelay || 30000;
|
|
26
|
+
this._reconnectTimer = null;
|
|
27
|
+
this._pingInterval = null;
|
|
28
|
+
this._pingIntervalMs = options.pingIntervalMs || 30000;
|
|
29
|
+
}
|
|
30
|
+
get state() {
|
|
31
|
+
return this._state;
|
|
32
|
+
}
|
|
33
|
+
get isConnected() {
|
|
34
|
+
return this._state === _constants.CONNECTION_STATES.CONNECTED;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Register an event listener.
|
|
39
|
+
* Events: 'state_change', 'session_activated', 'output', 'complete', 'error', 'pong'
|
|
40
|
+
*/
|
|
41
|
+
on(event, callback) {
|
|
42
|
+
if (!this._listeners.has(event)) {
|
|
43
|
+
this._listeners.set(event, new Set());
|
|
44
|
+
}
|
|
45
|
+
this._listeners.get(event).add(callback);
|
|
46
|
+
return () => this._listeners.get(event)?.delete(callback);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Connect to the standalone daemon.
|
|
51
|
+
*/
|
|
52
|
+
connect() {
|
|
53
|
+
if (this._ws) {
|
|
54
|
+
this._ws.close();
|
|
55
|
+
}
|
|
56
|
+
if (!this._token) {
|
|
57
|
+
this._emit('error', {
|
|
58
|
+
code: 'NO_TOKEN',
|
|
59
|
+
message: 'Auth token is required'
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Preserve RECONNECTING state so failed reconnect attempts still trigger
|
|
65
|
+
// _scheduleReconnect in the onclose handler.
|
|
66
|
+
if (this._state !== _constants.CONNECTION_STATES.RECONNECTING) {
|
|
67
|
+
this._setState(_constants.CONNECTION_STATES.CONNECTING);
|
|
68
|
+
}
|
|
69
|
+
const url = `ws://${this._host}:${this._port}/?token=${encodeURIComponent(this._token)}`;
|
|
70
|
+
try {
|
|
71
|
+
this._ws = new WebSocket(url);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
this._setState(_constants.CONNECTION_STATES.DISCONNECTED);
|
|
74
|
+
this._emit('error', {
|
|
75
|
+
code: 'CONNECT_FAILED',
|
|
76
|
+
message: err.message
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this._ws.onopen = () => {
|
|
81
|
+
this._setState(_constants.CONNECTION_STATES.CONNECTED);
|
|
82
|
+
this._reconnectAttempt = 0;
|
|
83
|
+
this._startPing();
|
|
84
|
+
};
|
|
85
|
+
this._ws.onmessage = event => {
|
|
86
|
+
this._handleMessage(event.data);
|
|
87
|
+
};
|
|
88
|
+
this._ws.onclose = event => {
|
|
89
|
+
this._stopPing();
|
|
90
|
+
const wasConnected = this._state === _constants.CONNECTION_STATES.CONNECTED;
|
|
91
|
+
this._ws = null;
|
|
92
|
+
|
|
93
|
+
// Auth failure — don't reconnect
|
|
94
|
+
if (event.code === 4001) {
|
|
95
|
+
this._setState(_constants.CONNECTION_STATES.DISCONNECTED);
|
|
96
|
+
this._emit('error', {
|
|
97
|
+
code: 'AUTH_FAILED',
|
|
98
|
+
message: 'Authentication failed'
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// If we were connected, try to reconnect
|
|
104
|
+
if (wasConnected || this._state === _constants.CONNECTION_STATES.RECONNECTING) {
|
|
105
|
+
this._scheduleReconnect();
|
|
106
|
+
} else {
|
|
107
|
+
this._setState(_constants.CONNECTION_STATES.DISCONNECTED);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
this._ws.onerror = () => {
|
|
111
|
+
// The close event will fire after this — reconnection is handled there
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Disconnect and stop reconnection attempts.
|
|
117
|
+
*/
|
|
118
|
+
disconnect() {
|
|
119
|
+
this._clearReconnect();
|
|
120
|
+
this._stopPing();
|
|
121
|
+
if (this._ws) {
|
|
122
|
+
this._ws.close(1000, 'Client disconnecting');
|
|
123
|
+
this._ws = null;
|
|
124
|
+
}
|
|
125
|
+
this._setState(_constants.CONNECTION_STATES.DISCONNECTED);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Update the auth token (e.g., when user pastes it in UI).
|
|
130
|
+
*/
|
|
131
|
+
setToken(token) {
|
|
132
|
+
this._token = token;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Activate a session. Must be called after connecting.
|
|
137
|
+
*/
|
|
138
|
+
activate(sessionId, workingDir) {
|
|
139
|
+
this._send({
|
|
140
|
+
type: 'activate',
|
|
141
|
+
sessionId,
|
|
142
|
+
workingDir
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Send a command (user message) to Claude.
|
|
148
|
+
*/
|
|
149
|
+
sendCommand(sessionId, text, commandId) {
|
|
150
|
+
this._send({
|
|
151
|
+
type: 'command',
|
|
152
|
+
sessionId,
|
|
153
|
+
commandId: commandId || crypto.randomUUID(),
|
|
154
|
+
text
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Internal
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
_send(msg) {
|
|
163
|
+
if (this._ws && this._ws.readyState === WebSocket.OPEN) {
|
|
164
|
+
this._ws.send(JSON.stringify(msg));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
_handleMessage(raw) {
|
|
168
|
+
let msg;
|
|
169
|
+
try {
|
|
170
|
+
msg = JSON.parse(raw);
|
|
171
|
+
} catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
switch (msg.type) {
|
|
175
|
+
case 'session_activated':
|
|
176
|
+
case 'output':
|
|
177
|
+
case 'complete':
|
|
178
|
+
case 'error':
|
|
179
|
+
case 'pong':
|
|
180
|
+
this._emit(msg.type, msg);
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
// Unknown message type — emit as generic event
|
|
184
|
+
this._emit('message', msg);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
_setState(newState) {
|
|
188
|
+
if (this._state !== newState) {
|
|
189
|
+
const oldState = this._state;
|
|
190
|
+
this._state = newState;
|
|
191
|
+
this._emit('state_change', {
|
|
192
|
+
oldState,
|
|
193
|
+
newState
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
_emit(event, data) {
|
|
198
|
+
const listeners = this._listeners.get(event);
|
|
199
|
+
if (listeners) {
|
|
200
|
+
for (const cb of listeners) {
|
|
201
|
+
try {
|
|
202
|
+
cb(data);
|
|
203
|
+
} catch {
|
|
204
|
+
// Don't let listener errors break the client
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
_scheduleReconnect() {
|
|
210
|
+
if (this._reconnectAttempt >= this._maxReconnectAttempts) {
|
|
211
|
+
this._setState(_constants.CONNECTION_STATES.DISCONNECTED);
|
|
212
|
+
this._emit('error', {
|
|
213
|
+
code: 'MAX_RECONNECT',
|
|
214
|
+
message: 'Max reconnection attempts reached'
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this._setState(_constants.CONNECTION_STATES.RECONNECTING);
|
|
219
|
+
const delay = Math.min(this._baseDelay * Math.pow(2, this._reconnectAttempt), this._maxDelay);
|
|
220
|
+
this._reconnectAttempt++;
|
|
221
|
+
this._reconnectTimer = setTimeout(() => {
|
|
222
|
+
this.connect();
|
|
223
|
+
}, delay);
|
|
224
|
+
}
|
|
225
|
+
_clearReconnect() {
|
|
226
|
+
if (this._reconnectTimer) {
|
|
227
|
+
clearTimeout(this._reconnectTimer);
|
|
228
|
+
this._reconnectTimer = null;
|
|
229
|
+
}
|
|
230
|
+
this._reconnectAttempt = 0;
|
|
231
|
+
}
|
|
232
|
+
_startPing() {
|
|
233
|
+
this._stopPing();
|
|
234
|
+
this._pingInterval = setInterval(() => {
|
|
235
|
+
this._send({
|
|
236
|
+
type: 'ping'
|
|
237
|
+
});
|
|
238
|
+
}, this._pingIntervalMs);
|
|
239
|
+
}
|
|
240
|
+
_stopPing() {
|
|
241
|
+
if (this._pingInterval) {
|
|
242
|
+
clearInterval(this._pingInterval);
|
|
243
|
+
this._pingInterval = null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
exports.WebSocketClient = WebSocketClient;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ConnectionStatus = ConnectionStatus;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _constants = require("../constants");
|
|
9
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
const STATUS_CONFIG = {
|
|
12
|
+
[_constants.CONNECTION_STATES.CONNECTED]: {
|
|
13
|
+
label: 'Connected',
|
|
14
|
+
color: '#4caf50'
|
|
15
|
+
},
|
|
16
|
+
[_constants.CONNECTION_STATES.CONNECTING]: {
|
|
17
|
+
label: 'Connecting...',
|
|
18
|
+
color: '#ff9800'
|
|
19
|
+
},
|
|
20
|
+
[_constants.CONNECTION_STATES.RECONNECTING]: {
|
|
21
|
+
label: 'Reconnecting...',
|
|
22
|
+
color: '#ff9800'
|
|
23
|
+
},
|
|
24
|
+
[_constants.CONNECTION_STATES.DISCONNECTED]: {
|
|
25
|
+
label: 'Disconnected',
|
|
26
|
+
color: '#f44336'
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function ConnectionStatus({
|
|
30
|
+
state,
|
|
31
|
+
sessionId,
|
|
32
|
+
onDisconnect
|
|
33
|
+
}) {
|
|
34
|
+
const config = STATUS_CONFIG[state] || STATUS_CONFIG[_constants.CONNECTION_STATES.DISCONNECTED];
|
|
35
|
+
const isConnected = state === _constants.CONNECTION_STATES.CONNECTED;
|
|
36
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
37
|
+
style: styles.container,
|
|
38
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
39
|
+
style: styles.indicator,
|
|
40
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
41
|
+
style: {
|
|
42
|
+
...styles.dot,
|
|
43
|
+
backgroundColor: config.color
|
|
44
|
+
}
|
|
45
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
46
|
+
style: styles.label,
|
|
47
|
+
children: config.label
|
|
48
|
+
})]
|
|
49
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
50
|
+
style: styles.right,
|
|
51
|
+
children: [sessionId && /*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
|
|
52
|
+
style: styles.sessionId,
|
|
53
|
+
title: sessionId,
|
|
54
|
+
children: [sessionId.slice(0, 8), "..."]
|
|
55
|
+
}), onDisconnect && /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
|
|
56
|
+
onClick: onDisconnect,
|
|
57
|
+
style: styles.disconnectButton,
|
|
58
|
+
title: isConnected ? 'Disconnect' : 'Change Token',
|
|
59
|
+
"aria-label": isConnected ? 'Disconnect' : 'Change Token',
|
|
60
|
+
children: isConnected ? 'Disconnect' : 'Change Token'
|
|
61
|
+
})]
|
|
62
|
+
})]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const styles = {
|
|
66
|
+
container: {
|
|
67
|
+
display: 'flex',
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
justifyContent: 'space-between',
|
|
70
|
+
padding: '6px 12px',
|
|
71
|
+
borderBottom: '1px solid rgba(0,0,0,0.1)',
|
|
72
|
+
fontSize: '12px',
|
|
73
|
+
fontFamily: 'inherit'
|
|
74
|
+
},
|
|
75
|
+
indicator: {
|
|
76
|
+
display: 'flex',
|
|
77
|
+
alignItems: 'center',
|
|
78
|
+
gap: '6px'
|
|
79
|
+
},
|
|
80
|
+
dot: {
|
|
81
|
+
width: '8px',
|
|
82
|
+
height: '8px',
|
|
83
|
+
borderRadius: '50%',
|
|
84
|
+
display: 'inline-block'
|
|
85
|
+
},
|
|
86
|
+
label: {
|
|
87
|
+
color: '#666'
|
|
88
|
+
},
|
|
89
|
+
right: {
|
|
90
|
+
display: 'flex',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
gap: '8px'
|
|
93
|
+
},
|
|
94
|
+
sessionId: {
|
|
95
|
+
color: '#999',
|
|
96
|
+
fontFamily: 'monospace',
|
|
97
|
+
fontSize: '11px'
|
|
98
|
+
},
|
|
99
|
+
disconnectButton: {
|
|
100
|
+
padding: '2px 8px',
|
|
101
|
+
border: '1px solid #ccc',
|
|
102
|
+
borderRadius: '4px',
|
|
103
|
+
backgroundColor: 'transparent',
|
|
104
|
+
color: '#666',
|
|
105
|
+
fontSize: '11px',
|
|
106
|
+
cursor: 'pointer',
|
|
107
|
+
lineHeight: '1.4'
|
|
108
|
+
}
|
|
109
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.MessageInput = MessageInput;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
9
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
10
|
+
/**
|
|
11
|
+
* Text input with send button for the Claude chat panel.
|
|
12
|
+
* Supports Enter to send, Shift+Enter for newline.
|
|
13
|
+
*/
|
|
14
|
+
function MessageInput({
|
|
15
|
+
onSend,
|
|
16
|
+
disabled,
|
|
17
|
+
isProcessing
|
|
18
|
+
}) {
|
|
19
|
+
const [text, setText] = (0, _react.useState)('');
|
|
20
|
+
const textareaRef = (0, _react.useRef)(null);
|
|
21
|
+
const handleSend = () => {
|
|
22
|
+
const trimmed = text.trim();
|
|
23
|
+
if (!trimmed || disabled) return;
|
|
24
|
+
onSend(trimmed);
|
|
25
|
+
setText('');
|
|
26
|
+
// Reset textarea height
|
|
27
|
+
if (textareaRef.current) {
|
|
28
|
+
textareaRef.current.style.height = 'auto';
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const handleKeyDown = e => {
|
|
32
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
handleSend();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const handleInput = e => {
|
|
38
|
+
setText(e.target.value);
|
|
39
|
+
// Auto-resize textarea
|
|
40
|
+
const el = e.target;
|
|
41
|
+
el.style.height = 'auto';
|
|
42
|
+
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
|
|
43
|
+
};
|
|
44
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
45
|
+
style: styles.container,
|
|
46
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("textarea", {
|
|
47
|
+
ref: textareaRef,
|
|
48
|
+
style: styles.textarea,
|
|
49
|
+
value: text,
|
|
50
|
+
onChange: handleInput,
|
|
51
|
+
onKeyDown: handleKeyDown,
|
|
52
|
+
placeholder: disabled ? 'Connect to daemon first...' : 'Ask Claude...',
|
|
53
|
+
disabled: disabled,
|
|
54
|
+
rows: 1
|
|
55
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
|
|
56
|
+
style: {
|
|
57
|
+
...styles.button,
|
|
58
|
+
...(disabled || !text.trim() ? styles.buttonDisabled : {})
|
|
59
|
+
},
|
|
60
|
+
onClick: handleSend,
|
|
61
|
+
disabled: disabled || !text.trim(),
|
|
62
|
+
title: "Send message (Enter)",
|
|
63
|
+
children: isProcessing ? '...' : 'Send'
|
|
64
|
+
})]
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const styles = {
|
|
68
|
+
container: {
|
|
69
|
+
display: 'flex',
|
|
70
|
+
alignItems: 'flex-end',
|
|
71
|
+
gap: '8px',
|
|
72
|
+
padding: '8px 12px',
|
|
73
|
+
borderTop: '1px solid rgba(0,0,0,0.1)'
|
|
74
|
+
},
|
|
75
|
+
textarea: {
|
|
76
|
+
flex: 1,
|
|
77
|
+
padding: '8px 10px',
|
|
78
|
+
border: '1px solid #ddd',
|
|
79
|
+
borderRadius: '8px',
|
|
80
|
+
fontSize: '13px',
|
|
81
|
+
fontFamily: 'inherit',
|
|
82
|
+
lineHeight: '1.4',
|
|
83
|
+
resize: 'none',
|
|
84
|
+
outline: 'none',
|
|
85
|
+
minHeight: '36px',
|
|
86
|
+
maxHeight: '120px'
|
|
87
|
+
},
|
|
88
|
+
button: {
|
|
89
|
+
padding: '8px 16px',
|
|
90
|
+
border: 'none',
|
|
91
|
+
borderRadius: '8px',
|
|
92
|
+
backgroundColor: '#1ea7fd',
|
|
93
|
+
color: '#fff',
|
|
94
|
+
fontSize: '13px',
|
|
95
|
+
fontWeight: 600,
|
|
96
|
+
cursor: 'pointer',
|
|
97
|
+
whiteSpace: 'nowrap',
|
|
98
|
+
minHeight: '36px'
|
|
99
|
+
},
|
|
100
|
+
buttonDisabled: {
|
|
101
|
+
backgroundColor: '#ccc',
|
|
102
|
+
cursor: 'not-allowed'
|
|
103
|
+
}
|
|
104
|
+
};
|