anyclaude-react 0.1.0 → 0.2.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/dist/client.d.ts +27 -2
- package/dist/client.js +41 -8
- package/dist/components/AskUser.d.ts +21 -0
- package/dist/components/AskUser.js +16 -0
- package/dist/components/ChatPanel.d.ts +17 -0
- package/dist/components/ChatPanel.js +14 -0
- package/dist/components/CodeEditor.d.ts +17 -0
- package/dist/components/CodeEditor.js +55 -0
- package/dist/components/FileExplorer.d.ts +21 -0
- package/dist/components/FileExplorer.js +54 -0
- package/dist/components/Message.d.ts +2 -2
- package/dist/components/Message.js +3 -3
- package/dist/components/Terminal.d.ts +28 -0
- package/dist/components/Terminal.js +71 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +6 -0
- package/dist/useAgent.d.ts +3 -1
- package/dist/useAgent.js +3 -3
- package/package.json +88 -10
- package/styles.css +52 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
import type { SDKMessage } from 'anyclaude-sdk';
|
|
2
|
+
export interface ClientToolResult {
|
|
3
|
+
tool_use_id: string;
|
|
4
|
+
content: string | unknown;
|
|
5
|
+
is_error?: boolean;
|
|
6
|
+
}
|
|
2
7
|
export interface RunOptions {
|
|
3
8
|
prompt: string;
|
|
4
9
|
sessionId: string;
|
|
5
10
|
continueRun?: boolean;
|
|
11
|
+
/** Results of host-executed client tools, carried into a continuation run. */
|
|
12
|
+
clientToolResults?: ClientToolResult[];
|
|
6
13
|
}
|
|
7
14
|
/** Produces the raw SDKMessage stream for one underlying run (in-process or remote). */
|
|
8
15
|
export type RunFn = (opts: RunOptions) => AsyncIterable<SDKMessage>;
|
|
16
|
+
/** Executor for a host/client-side tool (e.g. run `bash` on a WebContainer). */
|
|
17
|
+
export type ClientToolExecutor = (input: Record<string, unknown>, req: {
|
|
18
|
+
tool_use_id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
}) => Promise<{
|
|
21
|
+
content: string | unknown;
|
|
22
|
+
is_error?: boolean;
|
|
23
|
+
}> | {
|
|
24
|
+
content: string | unknown;
|
|
25
|
+
is_error?: boolean;
|
|
26
|
+
};
|
|
27
|
+
/** Map of tool name → host executor. */
|
|
28
|
+
export type ClientToolMap = Record<string, ClientToolExecutor>;
|
|
9
29
|
export interface AgentClient {
|
|
10
30
|
/** Stream one logical run for `prompt` under `sessionId`, survivor-stitched. */
|
|
11
31
|
send(prompt: string, sessionId: string): AsyncIterable<SDKMessage>;
|
|
@@ -13,10 +33,13 @@ export interface AgentClient {
|
|
|
13
33
|
/**
|
|
14
34
|
* Build an AgentClient from a `run` function. `run` does ONE underlying run —
|
|
15
35
|
* e.g. wrapping the SDK's `query()` in-process, or a fetch to a serverless
|
|
16
|
-
* endpoint.
|
|
36
|
+
* endpoint. This stitches across `paused` boundaries (survivor) AND executes
|
|
37
|
+
* client-side tools: on a `client_tool_request`, it runs the matching
|
|
38
|
+
* `clientTools` executor and feeds the result into the continuation.
|
|
17
39
|
*/
|
|
18
|
-
export declare function createAgentClient({ run }: {
|
|
40
|
+
export declare function createAgentClient({ run, clientTools }: {
|
|
19
41
|
run: RunFn;
|
|
42
|
+
clientTools?: ClientToolMap;
|
|
20
43
|
}): AgentClient;
|
|
21
44
|
export interface EndpointClientOptions {
|
|
22
45
|
/** URL of a serverless function that streams NDJSON SDKMessages. */
|
|
@@ -24,6 +47,8 @@ export interface EndpointClientOptions {
|
|
|
24
47
|
headers?: Record<string, string>;
|
|
25
48
|
/** Extra fields merged into the POST body (e.g. model, auth context). */
|
|
26
49
|
body?: Record<string, unknown>;
|
|
50
|
+
/** Host executors for client-side tools (e.g. run `bash` on a WebContainer). */
|
|
51
|
+
clientTools?: ClientToolMap;
|
|
27
52
|
}
|
|
28
53
|
/**
|
|
29
54
|
* AgentClient backed by a serverless function. POSTs `{ prompt, sessionId,
|
package/dist/client.js
CHANGED
|
@@ -1,25 +1,58 @@
|
|
|
1
1
|
function isPaused(m) {
|
|
2
2
|
return m.type === 'system' && m.subtype === 'paused';
|
|
3
3
|
}
|
|
4
|
+
function clientToolReq(m) {
|
|
5
|
+
if (m.type === 'system' && m.subtype === 'client_tool_request') {
|
|
6
|
+
return m.request ?? null;
|
|
7
|
+
}
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
4
10
|
/**
|
|
5
11
|
* Build an AgentClient from a `run` function. `run` does ONE underlying run —
|
|
6
12
|
* e.g. wrapping the SDK's `query()` in-process, or a fetch to a serverless
|
|
7
|
-
* endpoint.
|
|
13
|
+
* endpoint. This stitches across `paused` boundaries (survivor) AND executes
|
|
14
|
+
* client-side tools: on a `client_tool_request`, it runs the matching
|
|
15
|
+
* `clientTools` executor and feeds the result into the continuation.
|
|
8
16
|
*/
|
|
9
|
-
export function createAgentClient({ run }) {
|
|
17
|
+
export function createAgentClient({ run, clientTools }) {
|
|
10
18
|
return {
|
|
11
19
|
async *send(prompt, sessionId) {
|
|
12
20
|
let continueRun = false;
|
|
21
|
+
let pending;
|
|
13
22
|
for (;;) {
|
|
14
23
|
let paused = false;
|
|
15
|
-
|
|
24
|
+
const requests = [];
|
|
25
|
+
for await (const m of run({ prompt: continueRun ? '' : prompt, sessionId, continueRun, clientToolResults: pending })) {
|
|
26
|
+
const req = clientToolReq(m);
|
|
27
|
+
if (req)
|
|
28
|
+
requests.push(req);
|
|
16
29
|
if (isPaused(m))
|
|
17
30
|
paused = true;
|
|
18
|
-
yield m; // forward everything (incl.
|
|
31
|
+
yield m; // forward everything (incl. paused + client_tool_request) so the UI can react
|
|
19
32
|
}
|
|
20
33
|
if (!paused)
|
|
21
34
|
break;
|
|
22
|
-
continueRun = true;
|
|
35
|
+
continueRun = true;
|
|
36
|
+
// Execute any client-side tool requests, carry the results into the next run.
|
|
37
|
+
pending = undefined;
|
|
38
|
+
if (requests.length && clientTools) {
|
|
39
|
+
pending = [];
|
|
40
|
+
for (const req of requests) {
|
|
41
|
+
const exec = clientTools[req.name];
|
|
42
|
+
try {
|
|
43
|
+
if (!exec) {
|
|
44
|
+
pending.push({ tool_use_id: req.tool_use_id, content: `No client executor for tool "${req.name}".`, is_error: true });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const r = await exec(req.input, { tool_use_id: req.tool_use_id, name: req.name });
|
|
48
|
+
pending.push({ tool_use_id: req.tool_use_id, content: r.content, is_error: r.is_error });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
pending.push({ tool_use_id: req.tool_use_id, content: `Error: ${e instanceof Error ? e.message : String(e)}`, is_error: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
23
56
|
}
|
|
24
57
|
},
|
|
25
58
|
};
|
|
@@ -30,11 +63,11 @@ export function createAgentClient({ run }) {
|
|
|
30
63
|
* SDKMessages. Survivor-stitched automatically.
|
|
31
64
|
*/
|
|
32
65
|
export function createEndpointClient(opts) {
|
|
33
|
-
const run = async function* ({ prompt, sessionId, continueRun }) {
|
|
66
|
+
const run = async function* ({ prompt, sessionId, continueRun, clientToolResults }) {
|
|
34
67
|
const res = await fetch(opts.endpoint, {
|
|
35
68
|
method: 'POST',
|
|
36
69
|
headers: { 'content-type': 'application/json', ...opts.headers },
|
|
37
|
-
body: JSON.stringify({ prompt, sessionId, continueRun, ...opts.body }),
|
|
70
|
+
body: JSON.stringify({ prompt, sessionId, continueRun, clientToolResults, ...opts.body }),
|
|
38
71
|
});
|
|
39
72
|
if (!res.body)
|
|
40
73
|
return;
|
|
@@ -70,5 +103,5 @@ export function createEndpointClient(opts) {
|
|
|
70
103
|
}
|
|
71
104
|
}
|
|
72
105
|
};
|
|
73
|
-
return createAgentClient({ run });
|
|
106
|
+
return createAgentClient({ run, clientTools: opts.clientTools });
|
|
74
107
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface AskUserQuestion {
|
|
2
|
+
question: string;
|
|
3
|
+
header?: string;
|
|
4
|
+
options: Array<{
|
|
5
|
+
label: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
}>;
|
|
8
|
+
multiSelect?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface AskUserProps {
|
|
11
|
+
question: AskUserQuestion;
|
|
12
|
+
/** Called with the chosen label (single) or labels (multiSelect). */
|
|
13
|
+
onAnswer: (answer: string | string[]) => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Renders an `ask_user_question` prompt as option buttons and resolves the
|
|
18
|
+
* answer. Wire it to the SDK's `onAskUser` handler: store the question + a
|
|
19
|
+
* resolver in state, render <AskUser>, and call the resolver from onAnswer.
|
|
20
|
+
*/
|
|
21
|
+
export declare function AskUser({ question, onAnswer, className }: AskUserProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Renders an `ask_user_question` prompt as option buttons and resolves the
|
|
5
|
+
* answer. Wire it to the SDK's `onAskUser` handler: store the question + a
|
|
6
|
+
* resolver in state, render <AskUser>, and call the resolver from onAnswer.
|
|
7
|
+
*/
|
|
8
|
+
export function AskUser({ question, onAnswer, className }) {
|
|
9
|
+
const [selected, setSelected] = useState([]);
|
|
10
|
+
const multi = !!question.multiSelect;
|
|
11
|
+
const toggle = (label) => setSelected((s) => (s.includes(label) ? s.filter((l) => l !== label) : [...s, label]));
|
|
12
|
+
return (_jsxs("div", { className: `ac-askuser${className ? ' ' + className : ''}`, children: [question.header && _jsx("span", { className: "ac-askuser-header", children: question.header }), _jsx("div", { className: "ac-askuser-question", children: question.question }), _jsx("div", { className: "ac-askuser-options", children: question.options.map((o) => {
|
|
13
|
+
const isSel = selected.includes(o.label);
|
|
14
|
+
return (_jsxs("button", { className: 'ac-askuser-option' + (isSel ? ' ac-selected' : ''), onClick: () => (multi ? toggle(o.label) : onAnswer(o.label)), children: [_jsx("span", { className: "ac-askuser-label", children: o.label }), o.description && _jsx("span", { className: "ac-askuser-desc", children: o.description })] }, o.label));
|
|
15
|
+
}) }), multi && (_jsxs("button", { className: "ac-askuser-submit", disabled: !selected.length, onClick: () => onAnswer(selected), children: ["Submit", selected.length ? ` (${selected.length})` : ''] }))] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type UseAgentOptions } from '../useAgent.js';
|
|
3
|
+
export interface ChatPanelProps extends UseAgentOptions {
|
|
4
|
+
className?: string;
|
|
5
|
+
/** Panel title shown in the header. */
|
|
6
|
+
title?: ReactNode;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
workingLabel?: string;
|
|
9
|
+
renderMarkdown?: (text: string) => ReactNode;
|
|
10
|
+
/** Show the tokens · cost · status line in the header. Default true. */
|
|
11
|
+
showStats?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A polished chat panel: header (title + live status/tokens/cost) + Transcript +
|
|
15
|
+
* Working + Composer, wired to useAgent. Like <AgentChat> but with a header bar.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ChatPanel({ className, title, placeholder, workingLabel, renderMarkdown, showStats, ...agentOpts }: ChatPanelProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAgent } from '../useAgent.js';
|
|
3
|
+
import { Transcript } from './Transcript.js';
|
|
4
|
+
import { Composer } from './Composer.js';
|
|
5
|
+
import { Working } from './Working.js';
|
|
6
|
+
/**
|
|
7
|
+
* A polished chat panel: header (title + live status/tokens/cost) + Transcript +
|
|
8
|
+
* Working + Composer, wired to useAgent. Like <AgentChat> but with a header bar.
|
|
9
|
+
*/
|
|
10
|
+
export function ChatPanel({ className, title = 'Agent', placeholder, workingLabel, renderMarkdown, showStats = true, ...agentOpts }) {
|
|
11
|
+
const { messages, streamingText, status, tokens, cost, send } = useAgent(agentOpts);
|
|
12
|
+
const running = status !== 'idle';
|
|
13
|
+
return (_jsxs("div", { className: `ac-chatpanel${className ? ' ' + className : ''}`, children: [_jsxs("div", { className: "ac-chatpanel-head", children: [_jsx("span", { className: "ac-chatpanel-title", children: title }), showStats && (_jsxs("span", { className: "ac-chatpanel-stats", children: [_jsx("span", { className: 'ac-status ac-status-' + status, children: status }), tokens ? _jsxs("span", { className: "ac-stat", children: [" \u00B7 ", tokens.toLocaleString(), " tok"] }) : null, cost ? _jsxs("span", { className: "ac-stat", children: [" \u00B7 $", cost.toFixed(4)] }) : null] }))] }), _jsx(Transcript, { messages: messages, streamingText: streamingText, renderMarkdown: renderMarkdown }), _jsx(Working, { active: running, label: workingLabel, paused: status === 'paused' }), _jsx(Composer, { onSend: send, placeholder: placeholder, disabled: running })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Extension } from '@codemirror/state';
|
|
2
|
+
export interface CodeEditorProps {
|
|
3
|
+
/** The document text (controlled). */
|
|
4
|
+
value: string;
|
|
5
|
+
onChange?: (value: string) => void;
|
|
6
|
+
/** Language for highlighting. Currently 'javascript'/'typescript' supported out of the box. */
|
|
7
|
+
language?: string;
|
|
8
|
+
readOnly?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
/** Extra CodeMirror extensions to append. */
|
|
11
|
+
extensions?: Extension[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A CodeMirror 6 editor. Controlled via `value`/`onChange`. Requires the optional
|
|
15
|
+
* peer deps `codemirror` + `@codemirror/*`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function CodeEditor({ value, onChange, language, readOnly, className, extensions }: CodeEditorProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { EditorView, keymap, lineNumbers, highlightActiveLine } from '@codemirror/view';
|
|
4
|
+
import { EditorState } from '@codemirror/state';
|
|
5
|
+
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
|
6
|
+
import { javascript } from '@codemirror/lang-javascript';
|
|
7
|
+
/**
|
|
8
|
+
* A CodeMirror 6 editor. Controlled via `value`/`onChange`. Requires the optional
|
|
9
|
+
* peer deps `codemirror` + `@codemirror/*`.
|
|
10
|
+
*/
|
|
11
|
+
export function CodeEditor({ value, onChange, language = 'typescript', readOnly = false, className, extensions = [] }) {
|
|
12
|
+
const hostRef = useRef(null);
|
|
13
|
+
const viewRef = useRef(null);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!hostRef.current)
|
|
16
|
+
return;
|
|
17
|
+
const langExt = language === 'javascript' ? javascript({ jsx: true }) : javascript({ typescript: true, jsx: true });
|
|
18
|
+
const view = new EditorView({
|
|
19
|
+
parent: hostRef.current,
|
|
20
|
+
state: EditorState.create({
|
|
21
|
+
doc: value,
|
|
22
|
+
extensions: [
|
|
23
|
+
lineNumbers(),
|
|
24
|
+
highlightActiveLine(),
|
|
25
|
+
history(),
|
|
26
|
+
langExt,
|
|
27
|
+
keymap.of([...defaultKeymap, ...historyKeymap]),
|
|
28
|
+
EditorView.editable.of(!readOnly),
|
|
29
|
+
EditorView.updateListener.of((u) => {
|
|
30
|
+
if (u.docChanged && onChange)
|
|
31
|
+
onChange(u.state.doc.toString());
|
|
32
|
+
}),
|
|
33
|
+
EditorView.theme({ '&': { height: '100%', fontSize: '13px' }, '.cm-scroller': { fontFamily: 'ui-monospace, monospace' } }),
|
|
34
|
+
...extensions,
|
|
35
|
+
],
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
viewRef.current = view;
|
|
39
|
+
return () => {
|
|
40
|
+
view.destroy();
|
|
41
|
+
viewRef.current = null;
|
|
42
|
+
};
|
|
43
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
+
}, [language, readOnly]);
|
|
45
|
+
// Sync external value changes into the editor (without clobbering local edits).
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const view = viewRef.current;
|
|
48
|
+
if (!view)
|
|
49
|
+
return;
|
|
50
|
+
if (value !== view.state.doc.toString()) {
|
|
51
|
+
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: value } });
|
|
52
|
+
}
|
|
53
|
+
}, [value]);
|
|
54
|
+
return _jsx("div", { className: `ac-editor-host${className ? ' ' + className : ''}`, ref: hostRef });
|
|
55
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface FileEntry {
|
|
2
|
+
name: string;
|
|
3
|
+
isDir: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface FileExplorerProps {
|
|
6
|
+
/** List a directory's immediate children. Adapt any FS (WebContainer, SDK FileSystem, …). */
|
|
7
|
+
list: (dir: string) => Promise<FileEntry[]>;
|
|
8
|
+
/** Root directory to show. Default '/'. */
|
|
9
|
+
root?: string;
|
|
10
|
+
/** Currently-open file path (highlighted). */
|
|
11
|
+
openPath?: string | null;
|
|
12
|
+
onOpen: (path: string) => void;
|
|
13
|
+
/** Bump to force a re-read (e.g. after the agent writes files). */
|
|
14
|
+
refreshKey?: number;
|
|
15
|
+
className?: string;
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Directory names to skip. Default: node_modules, .git, .bcs. */
|
|
18
|
+
ignore?: string[];
|
|
19
|
+
}
|
|
20
|
+
/** A collapsible file tree over any filesystem (provide a `list(dir)` adapter). */
|
|
21
|
+
export declare function FileExplorer({ list, root, openPath, onOpen, refreshKey, className, title, ignore }: FileExplorerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
/** A collapsible file tree over any filesystem (provide a `list(dir)` adapter). */
|
|
4
|
+
export function FileExplorer({ list, root = '/', openPath, onOpen, refreshKey = 0, className, title = 'Files', ignore }) {
|
|
5
|
+
const [nodes, setNodes] = useState([]);
|
|
6
|
+
const ignoreSet = new Set(ignore ?? ['node_modules', '.git', '.bcs']);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
let cancelled = false;
|
|
9
|
+
list(root)
|
|
10
|
+
.then((entries) => {
|
|
11
|
+
if (cancelled)
|
|
12
|
+
return;
|
|
13
|
+
const mapped = entries
|
|
14
|
+
.filter((e) => !ignoreSet.has(e.name))
|
|
15
|
+
.map((e) => ({ ...e, path: root === '/' ? `/${e.name}` : `${root}/${e.name}` }))
|
|
16
|
+
.sort((a, b) => (a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1));
|
|
17
|
+
setNodes(mapped);
|
|
18
|
+
})
|
|
19
|
+
.catch(() => setNodes([]));
|
|
20
|
+
return () => {
|
|
21
|
+
cancelled = true;
|
|
22
|
+
};
|
|
23
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
24
|
+
}, [root, refreshKey]);
|
|
25
|
+
return (_jsxs("div", { className: `ac-explorer${className ? ' ' + className : ''}`, children: [_jsx("div", { className: "ac-explorer-head", children: title }), _jsx("div", { className: "ac-tree", children: nodes.map((n) => (_jsx(TreeNode, { node: n, depth: 0, list: list, openPath: openPath ?? null, onOpen: onOpen, ignore: ignoreSet, refreshKey: refreshKey }, n.path))) })] }));
|
|
26
|
+
}
|
|
27
|
+
function TreeNode({ node, depth, list, openPath, onOpen, ignore, refreshKey }) {
|
|
28
|
+
const [open, setOpen] = useState(depth < 1);
|
|
29
|
+
const [children, setChildren] = useState(null);
|
|
30
|
+
const pad = { paddingLeft: 6 + depth * 12 };
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!node.isDir || !open)
|
|
33
|
+
return;
|
|
34
|
+
let cancelled = false;
|
|
35
|
+
list(node.path)
|
|
36
|
+
.then((entries) => {
|
|
37
|
+
if (cancelled)
|
|
38
|
+
return;
|
|
39
|
+
setChildren(entries
|
|
40
|
+
.filter((e) => !ignore.has(e.name))
|
|
41
|
+
.map((e) => ({ ...e, path: `${node.path}/${e.name}` }))
|
|
42
|
+
.sort((a, b) => (a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1)));
|
|
43
|
+
})
|
|
44
|
+
.catch(() => setChildren([]));
|
|
45
|
+
return () => {
|
|
46
|
+
cancelled = true;
|
|
47
|
+
};
|
|
48
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
49
|
+
}, [open, node.path, refreshKey]);
|
|
50
|
+
if (node.isDir) {
|
|
51
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "ac-tree-row ac-dir", style: pad, onClick: () => setOpen((o) => !o), children: [_jsx("span", { className: "ac-tree-caret", children: open ? '▾' : '▸' }), " ", node.name] }), open && children?.map((c) => _jsx(TreeNode, { node: c, depth: depth + 1, list: list, openPath: openPath, onOpen: onOpen, ignore: ignore, refreshKey: refreshKey }, c.path))] }));
|
|
52
|
+
}
|
|
53
|
+
return (_jsx("div", { className: 'ac-tree-row ac-file' + (openPath === node.path ? ' ac-active' : ''), style: pad, onClick: () => onOpen(node.path), children: node.name }));
|
|
54
|
+
}
|
|
@@ -10,8 +10,8 @@ export interface MarkdownMessageProps {
|
|
|
10
10
|
text: string;
|
|
11
11
|
role?: 'user' | 'assistant';
|
|
12
12
|
className?: string;
|
|
13
|
-
/** Override the markdown renderer (default:
|
|
13
|
+
/** Override the markdown renderer (default: streamdown, streaming-aware). */
|
|
14
14
|
render?: (text: string) => ReactNode;
|
|
15
15
|
}
|
|
16
|
-
/** An assistant bubble whose text is rendered as markdown. */
|
|
16
|
+
/** An assistant bubble whose text is rendered as markdown via streamdown. */
|
|
17
17
|
export declare function MarkdownMessage({ text, role, className, render }: MarkdownMessageProps): import("react").JSX.Element;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Streamdown } from 'streamdown';
|
|
3
3
|
/** A plain chat bubble. */
|
|
4
4
|
export function Message({ role, children, className }) {
|
|
5
5
|
return (_jsxs("div", { className: `ac-msg ac-msg-${role}${className ? ' ' + className : ''}`, "data-role": role, children: [role === 'user' && _jsx("span", { className: "ac-msg-prefix", "aria-hidden": true, children: "\u203A" }), _jsx("div", { className: "ac-msg-body", children: children })] }));
|
|
6
6
|
}
|
|
7
|
-
/** An assistant bubble whose text is rendered as markdown. */
|
|
7
|
+
/** An assistant bubble whose text is rendered as markdown via streamdown. */
|
|
8
8
|
export function MarkdownMessage({ text, role = 'assistant', className, render }) {
|
|
9
|
-
return (_jsx(Message, { role: role, className: `ac-msg-md${className ? ' ' + className : ''}`, children:
|
|
9
|
+
return (_jsx(Message, { role: role, className: `ac-msg-md${className ? ' ' + className : ''}`, children: render ? render(text) : _jsx(Streamdown, { children: text }) }));
|
|
10
10
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import '@xterm/xterm/css/xterm.css';
|
|
2
|
+
/** A live shell process (structurally compatible with a WebContainer process). */
|
|
3
|
+
export interface ShellProcess {
|
|
4
|
+
output: ReadableStream<string>;
|
|
5
|
+
input: WritableStream<string>;
|
|
6
|
+
resize?(size: {
|
|
7
|
+
cols: number;
|
|
8
|
+
rows: number;
|
|
9
|
+
}): void;
|
|
10
|
+
kill?(): void;
|
|
11
|
+
}
|
|
12
|
+
export interface TerminalProps {
|
|
13
|
+
/** Spawn a shell process for the given terminal size (e.g. wc.spawn('jsh', {terminal})). */
|
|
14
|
+
spawn: (size: {
|
|
15
|
+
cols: number;
|
|
16
|
+
rows: number;
|
|
17
|
+
}) => Promise<ShellProcess>;
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Header label; pass null to hide the header. */
|
|
20
|
+
title?: string | null;
|
|
21
|
+
fontSize?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* An interactive xterm.js terminal bound to a streaming shell process. Backend-
|
|
25
|
+
* agnostic — pass any `spawn` that returns a {output, input, resize?, kill?}.
|
|
26
|
+
* Requires the optional peer deps `@xterm/xterm` + `@xterm/addon-fit`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function Terminal({ spawn, className, title, fontSize }: TerminalProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { Terminal as XTerm } from '@xterm/xterm';
|
|
4
|
+
import { FitAddon } from '@xterm/addon-fit';
|
|
5
|
+
import '@xterm/xterm/css/xterm.css';
|
|
6
|
+
/**
|
|
7
|
+
* An interactive xterm.js terminal bound to a streaming shell process. Backend-
|
|
8
|
+
* agnostic — pass any `spawn` that returns a {output, input, resize?, kill?}.
|
|
9
|
+
* Requires the optional peer deps `@xterm/xterm` + `@xterm/addon-fit`.
|
|
10
|
+
*/
|
|
11
|
+
export function Terminal({ spawn, className, title = 'Terminal', fontSize = 13 }) {
|
|
12
|
+
const hostRef = useRef(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!hostRef.current)
|
|
15
|
+
return;
|
|
16
|
+
let disposed = false;
|
|
17
|
+
let shell = null;
|
|
18
|
+
let writer = null;
|
|
19
|
+
const term = new XTerm({
|
|
20
|
+
convertEol: true,
|
|
21
|
+
cursorBlink: true,
|
|
22
|
+
fontSize,
|
|
23
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
24
|
+
theme: { background: '#0b0e14', foreground: '#cbd5e1' },
|
|
25
|
+
});
|
|
26
|
+
const fit = new FitAddon();
|
|
27
|
+
term.loadAddon(fit);
|
|
28
|
+
term.open(hostRef.current);
|
|
29
|
+
const safeFit = () => {
|
|
30
|
+
const el = hostRef.current;
|
|
31
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0)
|
|
32
|
+
return;
|
|
33
|
+
try {
|
|
34
|
+
fit.fit();
|
|
35
|
+
shell?.resize?.({ cols: term.cols, rows: term.rows });
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
/* not ready */
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const raf = requestAnimationFrame(safeFit);
|
|
42
|
+
void (async () => {
|
|
43
|
+
shell = await spawn({ cols: term.cols, rows: term.rows });
|
|
44
|
+
if (disposed) {
|
|
45
|
+
shell.kill?.();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
shell.output
|
|
49
|
+
.pipeTo(new WritableStream({ write: (chunk) => term.write(chunk) }))
|
|
50
|
+
.catch(() => { });
|
|
51
|
+
writer = shell.input.getWriter();
|
|
52
|
+
term.onData((d) => void writer?.write(d));
|
|
53
|
+
})();
|
|
54
|
+
const ro = new ResizeObserver(() => safeFit());
|
|
55
|
+
ro.observe(hostRef.current);
|
|
56
|
+
return () => {
|
|
57
|
+
disposed = true;
|
|
58
|
+
cancelAnimationFrame(raf);
|
|
59
|
+
ro.disconnect();
|
|
60
|
+
try {
|
|
61
|
+
writer?.releaseLock();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* ignore */
|
|
65
|
+
}
|
|
66
|
+
shell?.kill?.();
|
|
67
|
+
term.dispose();
|
|
68
|
+
};
|
|
69
|
+
}, [spawn, fontSize]);
|
|
70
|
+
return (_jsxs("div", { className: `ac-terminal${className ? ' ' + className : ''}`, children: [title !== null && _jsx("div", { className: "ac-terminal-head", children: title }), _jsx("div", { className: "ac-terminal-host", ref: hostRef })] }));
|
|
71
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createAgentClient, createEndpointClient } from './client.js';
|
|
2
|
-
export type { AgentClient, RunFn, RunOptions, EndpointClientOptions } from './client.js';
|
|
2
|
+
export type { AgentClient, RunFn, RunOptions, EndpointClientOptions, ClientToolMap, ClientToolExecutor, ClientToolResult, } from './client.js';
|
|
3
3
|
export { useAgent } from './useAgent.js';
|
|
4
4
|
export type { UseAgentOptions, UseAgentResult, AgentStatus } from './useAgent.js';
|
|
5
5
|
export { renderMarkdown } from './markdown.js';
|
|
@@ -15,3 +15,13 @@ export { Transcript } from './components/Transcript.js';
|
|
|
15
15
|
export type { TranscriptProps } from './components/Transcript.js';
|
|
16
16
|
export { AgentChat } from './components/AgentChat.js';
|
|
17
17
|
export type { AgentChatProps } from './components/AgentChat.js';
|
|
18
|
+
export { ChatPanel } from './components/ChatPanel.js';
|
|
19
|
+
export type { ChatPanelProps } from './components/ChatPanel.js';
|
|
20
|
+
export { Terminal } from './components/Terminal.js';
|
|
21
|
+
export type { TerminalProps, ShellProcess } from './components/Terminal.js';
|
|
22
|
+
export { FileExplorer } from './components/FileExplorer.js';
|
|
23
|
+
export type { FileExplorerProps, FileEntry } from './components/FileExplorer.js';
|
|
24
|
+
export { CodeEditor } from './components/CodeEditor.js';
|
|
25
|
+
export type { CodeEditorProps } from './components/CodeEditor.js';
|
|
26
|
+
export { AskUser } from './components/AskUser.js';
|
|
27
|
+
export type { AskUserProps, AskUserQuestion } from './components/AskUser.js';
|
package/dist/index.js
CHANGED
|
@@ -8,3 +8,9 @@ export { Composer } from './components/Composer.js';
|
|
|
8
8
|
export { Working } from './components/Working.js';
|
|
9
9
|
export { Transcript } from './components/Transcript.js';
|
|
10
10
|
export { AgentChat } from './components/AgentChat.js';
|
|
11
|
+
export { ChatPanel } from './components/ChatPanel.js';
|
|
12
|
+
// IDE-grade components (optional peer deps: @xterm/*, @codemirror/*)
|
|
13
|
+
export { Terminal } from './components/Terminal.js';
|
|
14
|
+
export { FileExplorer } from './components/FileExplorer.js';
|
|
15
|
+
export { CodeEditor } from './components/CodeEditor.js';
|
|
16
|
+
export { AskUser } from './components/AskUser.js';
|
package/dist/useAgent.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SDKMessage } from 'anyclaude-sdk';
|
|
2
|
-
import { type AgentClient, type RunFn } from './client.js';
|
|
2
|
+
import { type AgentClient, type RunFn, type ClientToolMap } from './client.js';
|
|
3
3
|
export type AgentStatus = 'idle' | 'running' | 'paused';
|
|
4
4
|
export interface UseAgentOptions {
|
|
5
5
|
/** Provide ONE of these. */
|
|
@@ -7,6 +7,8 @@ export interface UseAgentOptions {
|
|
|
7
7
|
run?: RunFn;
|
|
8
8
|
endpoint?: string;
|
|
9
9
|
headers?: Record<string, string>;
|
|
10
|
+
/** Host executors for client-side tools (e.g. run `bash` on a WebContainer). */
|
|
11
|
+
clientTools?: ClientToolMap;
|
|
10
12
|
/** Stable id for this conversation (survivor continuation reuses it). Auto if omitted. */
|
|
11
13
|
sessionId?: string;
|
|
12
14
|
}
|
package/dist/useAgent.js
CHANGED
|
@@ -10,9 +10,9 @@ function resolveClient(opts) {
|
|
|
10
10
|
if (opts.client)
|
|
11
11
|
return opts.client;
|
|
12
12
|
if (opts.run)
|
|
13
|
-
return createAgentClient({ run: opts.run });
|
|
13
|
+
return createAgentClient({ run: opts.run, clientTools: opts.clientTools });
|
|
14
14
|
if (opts.endpoint)
|
|
15
|
-
return createEndpointClient({ endpoint: opts.endpoint, headers: opts.headers });
|
|
15
|
+
return createEndpointClient({ endpoint: opts.endpoint, headers: opts.headers, clientTools: opts.clientTools });
|
|
16
16
|
throw new Error('useAgent: provide one of `client`, `run`, or `endpoint`.');
|
|
17
17
|
}
|
|
18
18
|
export function useAgent(opts) {
|
|
@@ -26,7 +26,7 @@ export function useAgent(opts) {
|
|
|
26
26
|
const runningRef = useRef(false);
|
|
27
27
|
const client = useMemo(() => resolveClient(opts),
|
|
28
28
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
-
[opts.client, opts.run, opts.endpoint, opts.headers]);
|
|
29
|
+
[opts.client, opts.run, opts.endpoint, opts.headers, opts.clientTools]);
|
|
30
30
|
const send = useCallback((text) => {
|
|
31
31
|
const t = text.trim();
|
|
32
32
|
if (!t || runningRef.current)
|
package/package.json
CHANGED
|
@@ -1,22 +1,100 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anyclaude-react",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "React UI kit for anyclaude-sdk — restylable hooks + components (useAgent, Transcript, Composer, AgentChat) with built-in serverless 'survivor' stream-stitching. Build chatbots, agents, research assistants.",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "React UI kit for anyclaude-sdk — restylable hooks + components (useAgent, Transcript, Composer, AgentChat, Terminal, FileExplorer, CodeEditor, ChatPanel, AskUser) with built-in serverless 'survivor' stream-stitching. Build chatbots, agents, research assistants, browser IDEs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
|
-
".": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
11
14
|
"./styles.css": "./styles.css"
|
|
12
15
|
},
|
|
13
|
-
"files": [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"styles.css"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": [
|
|
21
|
+
"*.css",
|
|
22
|
+
"@xterm/xterm/css/xterm.css"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.json",
|
|
26
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"anyclaude",
|
|
31
|
+
"claude",
|
|
32
|
+
"agent",
|
|
33
|
+
"react",
|
|
34
|
+
"ai-chat",
|
|
35
|
+
"chatbot",
|
|
36
|
+
"ui",
|
|
37
|
+
"hooks",
|
|
38
|
+
"terminal",
|
|
39
|
+
"ide",
|
|
40
|
+
"codemirror",
|
|
41
|
+
"xterm"
|
|
42
|
+
],
|
|
17
43
|
"license": "MIT",
|
|
18
44
|
"author": "Hans Ade <anye.happiness@swisslinkedu.com>",
|
|
19
|
-
"repository": {
|
|
20
|
-
|
|
21
|
-
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/pipilot-dev/anyclaude-sdk.git",
|
|
48
|
+
"directory": "anyclaude-react"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@codemirror/commands": ">=6.0.0",
|
|
52
|
+
"@codemirror/lang-javascript": ">=6.0.0",
|
|
53
|
+
"@codemirror/state": ">=6.0.0",
|
|
54
|
+
"@codemirror/view": ">=6.0.0",
|
|
55
|
+
"@xterm/addon-fit": ">=0.10.0",
|
|
56
|
+
"@xterm/xterm": ">=5.3.0",
|
|
57
|
+
"anyclaude-sdk": ">=0.1.0",
|
|
58
|
+
"codemirror": ">=6.0.0",
|
|
59
|
+
"react": ">=18"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@xterm/xterm": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"@xterm/addon-fit": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"codemirror": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"@codemirror/view": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@codemirror/state": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@codemirror/commands": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"@codemirror/lang-javascript": {
|
|
81
|
+
"optional": true
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@codemirror/commands": "^6.7.1",
|
|
86
|
+
"@codemirror/lang-javascript": "^6.2.2",
|
|
87
|
+
"@codemirror/state": "^6.4.1",
|
|
88
|
+
"@codemirror/view": "^6.34.0",
|
|
89
|
+
"@types/react": "^18.3.0",
|
|
90
|
+
"@types/react-dom": "^18.3.0",
|
|
91
|
+
"@xterm/addon-fit": "^0.10.0",
|
|
92
|
+
"@xterm/xterm": "^5.5.0",
|
|
93
|
+
"react": "^18.3.1",
|
|
94
|
+
"react-dom": "^18.3.1",
|
|
95
|
+
"typescript": "^5.4.0"
|
|
96
|
+
},
|
|
97
|
+
"dependencies": {
|
|
98
|
+
"streamdown": "^2.5.0"
|
|
99
|
+
}
|
|
22
100
|
}
|
package/styles.css
CHANGED
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
.ac-transcript { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; padding: 14px; }
|
|
25
|
+
/* The transcript is a flex column; children must NOT shrink. Tool pills use
|
|
26
|
+
overflow:hidden (→ flex min-height:0), so without this they collapse to a
|
|
27
|
+
sliver and the error border reads as a stray red line. They should keep their
|
|
28
|
+
natural height and let the transcript scroll. */
|
|
29
|
+
.ac-transcript > * { flex-shrink: 0; }
|
|
25
30
|
|
|
26
31
|
.ac-msg { display: flex; gap: 8px; }
|
|
27
32
|
.ac-msg-user { color: var(--ac-user); }
|
|
@@ -37,7 +42,7 @@
|
|
|
37
42
|
.ac-code-inline { font-family: var(--ac-mono); background: var(--ac-panel); border: 1px solid var(--ac-border); border-radius: 6px; padding: 1px 5px; font-size: 0.9em; }
|
|
38
43
|
.ac-code-block { font-family: var(--ac-mono); background: var(--ac-panel); border: 1px solid var(--ac-border); border-radius: var(--ac-radius); padding: 12px 14px; overflow-x: auto; margin: 10px 0; }
|
|
39
44
|
|
|
40
|
-
.ac-tool { border: 1px solid var(--ac-border); border-radius: 10px; background: var(--ac-panel); margin: 2px 0; overflow: hidden; }
|
|
45
|
+
.ac-tool { border: 1px solid var(--ac-border); border-radius: 10px; background: var(--ac-panel); margin: 2px 0; overflow: hidden; flex-shrink: 0; }
|
|
41
46
|
.ac-tool-error { border-color: #b3403a; }
|
|
42
47
|
.ac-tool-head { display: flex; align-items: center; gap: 8px; width: 100%; background: none; border: 0; color: var(--ac-accent); padding: 8px 12px; cursor: pointer; font: inherit; text-align: left; }
|
|
43
48
|
.ac-tool-caret { color: var(--ac-muted); }
|
|
@@ -58,3 +63,49 @@
|
|
|
58
63
|
.ac-composer-input:focus { outline: none; border-color: var(--ac-accent); }
|
|
59
64
|
.ac-composer-send { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px; background: var(--ac-accent); color: #00121a; border: 0; cursor: pointer; flex: none; }
|
|
60
65
|
.ac-composer-send:disabled { opacity: 0.4; cursor: default; }
|
|
66
|
+
|
|
67
|
+
/* ---- shared vars (so IDE components work outside .ac-chat) ---- */
|
|
68
|
+
:root {
|
|
69
|
+
--ac-bg: #0d1117; --ac-fg: #e6edf3; --ac-muted: #8b949e; --ac-border: #232c3d;
|
|
70
|
+
--ac-accent: #4dd0e1; --ac-user: #5fd75f; --ac-panel: #161b22; --ac-radius: 12px;
|
|
71
|
+
--ac-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ---- ChatPanel ---- */
|
|
75
|
+
.ac-chatpanel { display: flex; flex-direction: column; height: 100%; background: var(--ac-bg); color: var(--ac-fg); font: 15px/1.55 system-ui, sans-serif; }
|
|
76
|
+
.ac-chatpanel-head { display: flex; align-items: center; justify-content: space-between; padding: 9px 14px; border-bottom: 1px solid var(--ac-border); font-size: 14px; }
|
|
77
|
+
.ac-chatpanel-title { font-weight: 600; }
|
|
78
|
+
.ac-chatpanel-stats { color: var(--ac-muted); font-family: var(--ac-mono); font-size: 12.5px; }
|
|
79
|
+
.ac-status { text-transform: capitalize; }
|
|
80
|
+
.ac-status-running { color: var(--ac-accent); }
|
|
81
|
+
.ac-status-paused { color: #ffd75f; }
|
|
82
|
+
|
|
83
|
+
/* ---- Terminal ---- */
|
|
84
|
+
.ac-terminal { display: flex; flex-direction: column; height: 100%; background: #0b0e14; border: 1px solid var(--ac-border); border-radius: var(--ac-radius); overflow: hidden; }
|
|
85
|
+
.ac-terminal-head { padding: 6px 12px; font-family: var(--ac-mono); font-size: 12px; color: var(--ac-muted); border-bottom: 1px solid var(--ac-border); }
|
|
86
|
+
.ac-terminal-host { flex: 1; min-height: 0; padding: 6px; }
|
|
87
|
+
|
|
88
|
+
/* ---- FileExplorer ---- */
|
|
89
|
+
.ac-explorer { background: var(--ac-panel); border: 1px solid var(--ac-border); border-radius: var(--ac-radius); overflow: auto; font-size: 13px; color: var(--ac-fg); }
|
|
90
|
+
.ac-explorer-head { padding: 8px 12px; font-weight: 600; color: var(--ac-muted); border-bottom: 1px solid var(--ac-border); }
|
|
91
|
+
.ac-tree-row { padding: 4px 8px; cursor: pointer; white-space: nowrap; font-family: var(--ac-mono); }
|
|
92
|
+
.ac-tree-row:hover { background: rgba(255,255,255,0.05); }
|
|
93
|
+
.ac-tree-row.ac-active { background: rgba(77,208,225,0.16); color: var(--ac-accent); }
|
|
94
|
+
.ac-tree-caret { opacity: 0.7; }
|
|
95
|
+
.ac-dir { color: var(--ac-fg); }
|
|
96
|
+
|
|
97
|
+
/* ---- CodeEditor ---- */
|
|
98
|
+
.ac-editor-host { height: 100%; overflow: hidden; border: 1px solid var(--ac-border); border-radius: var(--ac-radius); }
|
|
99
|
+
.ac-editor-host .cm-editor { height: 100%; }
|
|
100
|
+
|
|
101
|
+
/* ---- AskUser ---- */
|
|
102
|
+
.ac-askuser { border: 1px solid var(--ac-accent); border-radius: var(--ac-radius); background: var(--ac-panel); padding: 12px 14px; margin: 8px 0; }
|
|
103
|
+
.ac-askuser-header { display: inline-block; font-family: var(--ac-mono); font-size: 11px; text-transform: uppercase; color: var(--ac-accent); margin-bottom: 4px; }
|
|
104
|
+
.ac-askuser-question { font-weight: 600; margin-bottom: 10px; }
|
|
105
|
+
.ac-askuser-options { display: flex; flex-direction: column; gap: 6px; }
|
|
106
|
+
.ac-askuser-option { text-align: left; background: var(--ac-bg); border: 1px solid var(--ac-border); border-radius: 8px; padding: 8px 10px; color: var(--ac-fg); cursor: pointer; display: flex; flex-direction: column; gap: 2px; }
|
|
107
|
+
.ac-askuser-option:hover, .ac-askuser-option.ac-selected { border-color: var(--ac-accent); }
|
|
108
|
+
.ac-askuser-label { font-weight: 600; }
|
|
109
|
+
.ac-askuser-desc { color: var(--ac-muted); font-size: 13px; }
|
|
110
|
+
.ac-askuser-submit { margin-top: 10px; background: var(--ac-accent); color: #001016; border: none; border-radius: 8px; padding: 8px 14px; font-weight: 600; cursor: pointer; }
|
|
111
|
+
.ac-askuser-submit:disabled { opacity: 0.5; cursor: default; }
|