@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 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
+ };