mistagent 0.1.19 → 0.1.21
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/src/api/quota.d.ts +13 -0
- package/dist/src/api/quota.js +7 -0
- package/dist/src/components/App.d.ts +2 -0
- package/dist/src/components/App.js +23 -5
- package/dist/src/components/Header.js +74 -166
- package/dist/src/components/MainContent.js +3 -14
- package/dist/src/components/shared/MarkdownRenderer.js +2 -17
- package/dist/src/components/shared/TextInput.js +62 -3
- package/dist/src/hooks/useChat.js +23 -2
- package/dist/src/hooks/useSlashCommand.js +45 -0
- package/dist/src/main.js +63 -4
- package/dist/src/types/api.d.ts +4 -0
- package/dist/src/utils/config.d.ts +2 -0
- package/dist/src/utils/config.js +23 -0
- package/dist/src/utils/constants.d.ts +1 -1
- package/dist/src/utils/constants.js +1 -1
- package/dist/src/utils/markdown.d.ts +10 -0
- package/dist/src/utils/markdown.js +223 -0
- package/dist/src/utils/updateChecker.js +10 -4
- package/package.json +3 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/src/api/auth.d.ts.map +0 -1
- package/dist/src/api/auth.js.map +0 -1
- package/dist/src/api/chat.d.ts.map +0 -1
- package/dist/src/api/chat.js.map +0 -1
- package/dist/src/api/client.d.ts.map +0 -1
- package/dist/src/api/client.js.map +0 -1
- package/dist/src/api/models.d.ts.map +0 -1
- package/dist/src/api/models.js.map +0 -1
- package/dist/src/api/sessions.d.ts.map +0 -1
- package/dist/src/api/sessions.js.map +0 -1
- package/dist/src/api/skills.d.ts.map +0 -1
- package/dist/src/api/skills.js.map +0 -1
- package/dist/src/api/tools.d.ts.map +0 -1
- package/dist/src/api/tools.js.map +0 -1
- package/dist/src/api/tunnel.d.ts.map +0 -1
- package/dist/src/api/tunnel.js.map +0 -1
- package/dist/src/components/App.d.ts.map +0 -1
- package/dist/src/components/App.js.map +0 -1
- package/dist/src/components/AppLayout.d.ts.map +0 -1
- package/dist/src/components/AppLayout.js.map +0 -1
- package/dist/src/components/Composer.d.ts.map +0 -1
- package/dist/src/components/Composer.js.map +0 -1
- package/dist/src/components/Footer.d.ts.map +0 -1
- package/dist/src/components/Footer.js.map +0 -1
- package/dist/src/components/Header.d.ts.map +0 -1
- package/dist/src/components/Header.js.map +0 -1
- package/dist/src/components/HistoryItemDisplay.d.ts.map +0 -1
- package/dist/src/components/HistoryItemDisplay.js.map +0 -1
- package/dist/src/components/InputPrompt.d.ts.map +0 -1
- package/dist/src/components/InputPrompt.js.map +0 -1
- package/dist/src/components/LoadingIndicator.d.ts.map +0 -1
- package/dist/src/components/LoadingIndicator.js.map +0 -1
- package/dist/src/components/LoginPrompt.d.ts.map +0 -1
- package/dist/src/components/LoginPrompt.js.map +0 -1
- package/dist/src/components/MainContent.d.ts.map +0 -1
- package/dist/src/components/MainContent.js.map +0 -1
- package/dist/src/components/ModelPicker.d.ts.map +0 -1
- package/dist/src/components/ModelPicker.js.map +0 -1
- package/dist/src/components/SessionPicker.d.ts.map +0 -1
- package/dist/src/components/SessionPicker.js.map +0 -1
- package/dist/src/components/SuggestionsDisplay.d.ts.map +0 -1
- package/dist/src/components/SuggestionsDisplay.js.map +0 -1
- package/dist/src/components/ThemePicker.d.ts.map +0 -1
- package/dist/src/components/ThemePicker.js.map +0 -1
- package/dist/src/components/messages/AssistantMessage.d.ts.map +0 -1
- package/dist/src/components/messages/AssistantMessage.js.map +0 -1
- package/dist/src/components/messages/CommandResult.d.ts.map +0 -1
- package/dist/src/components/messages/CommandResult.js.map +0 -1
- package/dist/src/components/messages/ErrorMessage.d.ts.map +0 -1
- package/dist/src/components/messages/ErrorMessage.js.map +0 -1
- package/dist/src/components/messages/InfoMessage.d.ts.map +0 -1
- package/dist/src/components/messages/InfoMessage.js.map +0 -1
- package/dist/src/components/messages/ModelMessage.d.ts.map +0 -1
- package/dist/src/components/messages/ModelMessage.js.map +0 -1
- package/dist/src/components/messages/SessionMessage.d.ts.map +0 -1
- package/dist/src/components/messages/SessionMessage.js.map +0 -1
- package/dist/src/components/messages/ToolCallMessage.d.ts.map +0 -1
- package/dist/src/components/messages/ToolCallMessage.js.map +0 -1
- package/dist/src/components/messages/UserMessage.d.ts.map +0 -1
- package/dist/src/components/messages/UserMessage.js.map +0 -1
- package/dist/src/components/shared/HorizontalLine.d.ts.map +0 -1
- package/dist/src/components/shared/HorizontalLine.js.map +0 -1
- package/dist/src/components/shared/MarkdownRenderer.d.ts.map +0 -1
- package/dist/src/components/shared/MarkdownRenderer.js.map +0 -1
- package/dist/src/components/shared/Spinner.d.ts.map +0 -1
- package/dist/src/components/shared/Spinner.js.map +0 -1
- package/dist/src/components/shared/TextInput.d.ts.map +0 -1
- package/dist/src/components/shared/TextInput.js.map +0 -1
- package/dist/src/contexts/AppContext.d.ts.map +0 -1
- package/dist/src/contexts/AppContext.js.map +0 -1
- package/dist/src/contexts/ChatContext.d.ts.map +0 -1
- package/dist/src/contexts/ChatContext.js.map +0 -1
- package/dist/src/contexts/KeypressContext.d.ts.map +0 -1
- package/dist/src/contexts/KeypressContext.js.map +0 -1
- package/dist/src/contexts/ModelContext.d.ts.map +0 -1
- package/dist/src/contexts/ModelContext.js.map +0 -1
- package/dist/src/contexts/SessionContext.d.ts.map +0 -1
- package/dist/src/contexts/SessionContext.js.map +0 -1
- package/dist/src/contexts/UIContext.d.ts.map +0 -1
- package/dist/src/contexts/UIContext.js.map +0 -1
- package/dist/src/hooks/useChat.d.ts.map +0 -1
- package/dist/src/hooks/useChat.js.map +0 -1
- package/dist/src/hooks/useFileCompletion.d.ts.map +0 -1
- package/dist/src/hooks/useFileCompletion.js.map +0 -1
- package/dist/src/hooks/useInputHistory.d.ts.map +0 -1
- package/dist/src/hooks/useInputHistory.js.map +0 -1
- package/dist/src/hooks/useKeypress.d.ts.map +0 -1
- package/dist/src/hooks/useKeypress.js.map +0 -1
- package/dist/src/hooks/useLoadingIndicator.d.ts.map +0 -1
- package/dist/src/hooks/useLoadingIndicator.js.map +0 -1
- package/dist/src/hooks/usePasteBuffer.d.ts.map +0 -1
- package/dist/src/hooks/usePasteBuffer.js.map +0 -1
- package/dist/src/hooks/useSlashCommand.d.ts.map +0 -1
- package/dist/src/hooks/useSlashCommand.js.map +0 -1
- package/dist/src/hooks/useStdinInterceptor.d.ts.map +0 -1
- package/dist/src/hooks/useStdinInterceptor.js.map +0 -1
- package/dist/src/hooks/useSymbolCompletion.d.ts.map +0 -1
- package/dist/src/hooks/useSymbolCompletion.js.map +0 -1
- package/dist/src/hooks/useTextBuffer.d.ts.map +0 -1
- package/dist/src/hooks/useTextBuffer.js.map +0 -1
- package/dist/src/main.d.ts.map +0 -1
- package/dist/src/main.js.map +0 -1
- package/dist/src/tools/code-analyzer/config/ignore-service.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/config/ignore-service.js.map +0 -1
- package/dist/src/tools/code-analyzer/config/supported-languages.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/config/supported-languages.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/graph.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/graph.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/types.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/types.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/call-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/call-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/community-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/community-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/import-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/import-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/pipeline.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/pipeline.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/process-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/process-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/utils.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/utils.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.js.map +0 -1
- package/dist/src/tools/code-analyzer/index.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/index.js.map +0 -1
- package/dist/src/tools/code-analyzer/lib/utils.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/lib/utils.js.map +0 -1
- package/dist/src/tools/code-analyzer/types/pipeline.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/types/pipeline.js.map +0 -1
- package/dist/src/types/api.d.ts.map +0 -1
- package/dist/src/types/api.js.map +0 -1
- package/dist/src/types/history.d.ts.map +0 -1
- package/dist/src/types/history.js.map +0 -1
- package/dist/src/utils/colors.d.ts.map +0 -1
- package/dist/src/utils/colors.js.map +0 -1
- package/dist/src/utils/config.d.ts.map +0 -1
- package/dist/src/utils/config.js.map +0 -1
- package/dist/src/utils/constants.d.ts.map +0 -1
- package/dist/src/utils/constants.js.map +0 -1
- package/dist/src/utils/fileRef.d.ts.map +0 -1
- package/dist/src/utils/fileRef.js.map +0 -1
- package/dist/src/utils/fileTunnel.d.ts.map +0 -1
- package/dist/src/utils/fileTunnel.js.map +0 -1
- package/dist/src/utils/formatters.d.ts.map +0 -1
- package/dist/src/utils/formatters.js.map +0 -1
- package/dist/src/utils/pasteUtils.d.ts.map +0 -1
- package/dist/src/utils/pasteUtils.js.map +0 -1
- package/dist/src/utils/skillScanner.d.ts.map +0 -1
- package/dist/src/utils/skillScanner.js.map +0 -1
- package/dist/src/utils/textUtils.d.ts.map +0 -1
- package/dist/src/utils/textUtils.js.map +0 -1
- package/dist/src/utils/updateChecker.d.ts.map +0 -1
- package/dist/src/utils/updateChecker.js.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface QuotaBalance {
|
|
2
|
+
usd_balance: number;
|
|
3
|
+
usd_total_granted: number;
|
|
4
|
+
usd_total_consumed: number;
|
|
5
|
+
usage_today_tokens: number;
|
|
6
|
+
usage_this_month_tokens: number;
|
|
7
|
+
cost_today_usd: number;
|
|
8
|
+
cost_this_month_usd: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const quotaApi: {
|
|
11
|
+
balance(): Promise<QuotaBalance>;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=quota.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback } from 'react';
|
|
2
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
3
|
+
import { useStdout } from 'ink';
|
|
3
4
|
import { AppStateContext, AppActionsContext, } from '../contexts/AppContext.js';
|
|
4
5
|
import { ChatStateContext, ChatDispatchContext, useChatReducer, } from '../contexts/ChatContext.js';
|
|
5
6
|
import { UIStateContext, UIActionsContext, } from '../contexts/UIContext.js';
|
|
@@ -30,15 +31,32 @@ const AppInner = ({ onLogin, loginError, isAuthenticated, authEnabled }) => {
|
|
|
30
31
|
}
|
|
31
32
|
return _jsx(AppLayout, { onSubmit: handleSubmit, onCancel: cancelStream, onConfirmPlan: confirmPlan, onResolveToolApproval: resolveToolApproval });
|
|
32
33
|
};
|
|
33
|
-
export const App = ({ serverUrl, token, username, authEnabled, version, healthData, availableCommands, availableTools, initialModels, initialCurrentModel, initialSessions, onLogin, onLogout, loginError, isAuthenticated, initialTheme, }) => {
|
|
34
|
+
export const App = ({ serverUrl, token, username, authEnabled, version, healthData, availableCommands, availableTools, initialModels, initialCurrentModel, initialSessions, onLogin, onLogout, loginError, isAuthenticated, initialTheme, terminalWidth: initialTerminalWidth, terminalHeight: initialTerminalHeight, }) => {
|
|
35
|
+
const { stdout } = useStdout();
|
|
34
36
|
const [appToken, setAppToken] = useState(token);
|
|
35
37
|
const [appUsername, setAppUsername] = useState(username);
|
|
36
38
|
const [chatState, chatDispatch] = useChatReducer();
|
|
37
|
-
// Terminal size
|
|
38
|
-
const [terminalWidth] = useState(process.stdout.columns || 80);
|
|
39
|
-
const [terminalHeight] = useState(process.stdout.rows || 24);
|
|
40
39
|
const [activeDialog, setActiveDialog] = useState('none');
|
|
41
40
|
const [theme, setThemeState] = useState(initialTheme);
|
|
41
|
+
// Track terminal size — updated by both:
|
|
42
|
+
// 1. stdout 'resize' events (primary: fired by the OS → Node TTY → Ink's stdout)
|
|
43
|
+
// 2. A polling fallback every 200ms (catches any missed events)
|
|
44
|
+
const [terminalWidth, setTerminalWidth] = useState(() => stdout.columns || process.stdout.columns || initialTerminalWidth);
|
|
45
|
+
const [terminalHeight, setTerminalHeight] = useState(() => stdout.rows || process.stdout.rows || initialTerminalHeight);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const readSize = () => {
|
|
48
|
+
setTerminalWidth(stdout.columns || process.stdout.columns || 80);
|
|
49
|
+
setTerminalHeight(stdout.rows || process.stdout.rows || 24);
|
|
50
|
+
};
|
|
51
|
+
stdout.on('resize', readSize);
|
|
52
|
+
process.stdout.on('resize', readSize);
|
|
53
|
+
const poll = setInterval(readSize, 200);
|
|
54
|
+
return () => {
|
|
55
|
+
stdout.off('resize', readSize);
|
|
56
|
+
process.stdout.off('resize', readSize);
|
|
57
|
+
clearInterval(poll);
|
|
58
|
+
};
|
|
59
|
+
}, [stdout]);
|
|
42
60
|
const appState = {
|
|
43
61
|
serverUrl,
|
|
44
62
|
token: appToken,
|
|
@@ -8,12 +8,7 @@ import { useSessionState } from '../contexts/SessionContext.js';
|
|
|
8
8
|
import { useChatState } from '../contexts/ChatContext.js';
|
|
9
9
|
import { useAppState } from '../contexts/AppContext.js';
|
|
10
10
|
import { MASCOT_LINES, VERSION, TIPS, getMascotGradient } from '../utils/constants.js';
|
|
11
|
-
// ── Catppuccin tokens (matching pencil variables) ──
|
|
12
11
|
const C = {
|
|
13
|
-
bg: '#1E1E2E',
|
|
14
|
-
bgDark: '#181825',
|
|
15
|
-
bgDeep: '#11111B',
|
|
16
|
-
surface1: '#313244',
|
|
17
12
|
border: '#585B70',
|
|
18
13
|
dimtext: '#6C7086',
|
|
19
14
|
subtext: '#A6ADC8',
|
|
@@ -24,185 +19,98 @@ const C = {
|
|
|
24
19
|
teal: '#94E2D5',
|
|
25
20
|
green: '#A6E3A1',
|
|
26
21
|
red: '#F38BA8',
|
|
27
|
-
peach: '#FAB387',
|
|
28
|
-
rose: '#E05A4E',
|
|
29
22
|
yellow: '#F9E2AF',
|
|
30
23
|
};
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// ── Helper functions ──
|
|
40
|
-
const pad = (s, width) => {
|
|
41
|
-
const len = stringWidth(s);
|
|
42
|
-
if (len >= width)
|
|
43
|
-
return s;
|
|
44
|
-
return s + ' '.repeat(width - len);
|
|
45
|
-
};
|
|
46
|
-
const centerPad = (s, width) => {
|
|
47
|
-
const len = stringWidth(s);
|
|
48
|
-
if (len >= width)
|
|
49
|
-
return s;
|
|
50
|
-
const left = Math.floor((width - len) / 2);
|
|
51
|
-
const right = width - len - left;
|
|
52
|
-
return ' '.repeat(left) + s + ' '.repeat(right);
|
|
53
|
-
};
|
|
54
|
-
const truncate = (s, maxLen) => {
|
|
55
|
-
if (maxLen <= 0)
|
|
24
|
+
// Three-tier breakpoints
|
|
25
|
+
// col1(mascot)=35 + div=1 + col2(stats)=26 + div=1 + col3(min~10) + border+pad~6 = ~79 minimum
|
|
26
|
+
// Use 90 as safe threshold so the 3-column layout never gets squeezed
|
|
27
|
+
const CONDENSED_MAX = 40; // <40 → single line
|
|
28
|
+
const COMPACT_MAX = 90; // <90 → stacked compact
|
|
29
|
+
// ≥90 → full 3-column
|
|
30
|
+
function truncate(s, max) {
|
|
31
|
+
if (max <= 0)
|
|
56
32
|
return '';
|
|
57
|
-
if (stringWidth(s) <=
|
|
33
|
+
if (stringWidth(s) <= max)
|
|
58
34
|
return s;
|
|
59
|
-
let
|
|
35
|
+
let out = '';
|
|
60
36
|
for (const ch of s) {
|
|
61
|
-
if (stringWidth(
|
|
37
|
+
if (stringWidth(out + ch) > max - 1)
|
|
62
38
|
break;
|
|
63
|
-
|
|
39
|
+
out += ch;
|
|
64
40
|
}
|
|
65
|
-
return
|
|
41
|
+
return out + '…';
|
|
42
|
+
}
|
|
43
|
+
// ─────────────────────────────────────────────
|
|
44
|
+
// Tier 1 — Condensed (<40 cols)
|
|
45
|
+
// Single row: ❯ MIST v0.x model ●
|
|
46
|
+
// ─────────────────────────────────────────────
|
|
47
|
+
const CondensedHeader = ({ modelName, isConnected, version }) => (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: C.blue, bold: true, children: '❯' }), _jsx(Text, { color: C.text, bold: true, children: 'MIST' }), _jsx(Text, { color: C.dimtext, children: `v${version}` }), _jsx(Text, { color: C.sapphire, children: modelName }), _jsx(Text, { color: isConnected ? C.green : C.red, children: '●' })] }));
|
|
48
|
+
// ─────────────────────────────────────────────
|
|
49
|
+
// Tier 2 — Compact (40–69 cols)
|
|
50
|
+
// Stacked: brand + model + status + shortcuts
|
|
51
|
+
// ─────────────────────────────────────────────
|
|
52
|
+
const CompactHeader = ({ modelName, isConnected, modeLabel, theme, version }) => {
|
|
53
|
+
const themeIcon = theme === 'dark' ? '◐' : '◑';
|
|
54
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: C.border, paddingX: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Text, { color: C.blue, bold: true, children: `❯ MIST v${version}` }), _jsx(Text, { color: isConnected ? C.green : C.red, children: isConnected ? '● ON' : '○ OFF' })] }), _jsx(Text, { color: C.sapphire, children: `model: ${modelName}` }), _jsx(Text, { color: C.dimtext, children: `mode: ${modeLabel} ${themeIcon} ${theme}` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: C.dimtext, children: '[/] cmd [@] file [Shift+Tab] mode' }) })] }));
|
|
55
|
+
};
|
|
56
|
+
// ─────────────────────────────────────────────
|
|
57
|
+
// Tier 3 — Horizontal (≥70 cols)
|
|
58
|
+
// Full 3-column with Ink flexbox (no manual widths)
|
|
59
|
+
// ─────────────────────────────────────────────
|
|
60
|
+
const MascotLogo = () => {
|
|
61
|
+
const gradient = getMascotGradient();
|
|
62
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", children: [MASCOT_LINES.map((line, i) => (_jsx(Text, { color: gradient[i] ?? gradient[gradient.length - 1], bold: true, children: line }, i))), _jsx(Text, { color: C.dimtext, children: 'A G E N T' })] }));
|
|
66
63
|
};
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
const StatsPanel = ({ modelName, sessionCount, modeLabel, theme }) => {
|
|
65
|
+
const themeIcon = theme === 'dark' ? '◐' : '◑';
|
|
66
|
+
const rows = [
|
|
67
|
+
{ label: 'model', value: truncate(modelName, 18), color: C.sapphire },
|
|
68
|
+
{ label: 'sessions', value: String(sessionCount), color: C.teal },
|
|
69
|
+
{ label: 'mode', value: modeLabel, color: modeLabel === 'plan' ? C.mauve : C.green },
|
|
70
|
+
{ label: 'theme', value: `${themeIcon} ${theme}`, color: C.mauve },
|
|
71
|
+
];
|
|
72
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: C.subtext, children: '// stats' }), rows.map(r => (_jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Box, { width: 10, children: _jsx(Text, { color: C.dimtext, children: r.label }) }), _jsx(Text, { color: r.color, bold: true, children: r.value })] }, r.label)))] }));
|
|
73
|
+
};
|
|
74
|
+
const ActivityPanel = ({ tip, sessions }) => {
|
|
75
|
+
const dotColors = [C.green, C.blue, C.mauve];
|
|
76
|
+
const recent = sessions.slice(0, 3);
|
|
77
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: C.subtext, children: '// tip' }), _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Text, { color: C.green, bold: true, children: '$ ' }), _jsx(Text, { color: C.text, children: tip })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: C.subtext, children: `// recent (${sessions.length})` }) }), recent.length === 0 ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: C.dimtext, children: 'No recent activity' }) })) : (recent.map((s, i) => {
|
|
78
|
+
const d = new Date(s.last_activity);
|
|
79
|
+
const t = d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
|
|
80
|
+
return (_jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Text, { color: C.dimtext, children: `${t} ` }), _jsx(Text, { color: dotColors[i % dotColors.length], children: '● ' }), _jsx(Text, { color: C.subtext, children: truncate(s.title || 'New Chat', 25) })] }, i));
|
|
81
|
+
}))] }));
|
|
75
82
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
const SHORTCUTS = [
|
|
84
|
+
{ key: '/', desc: '命令' },
|
|
85
|
+
{ key: '@', desc: '文件' },
|
|
86
|
+
{ key: '@@', desc: '符号' },
|
|
87
|
+
{ key: 'Shift+Tab', desc: '模式' },
|
|
88
|
+
{ key: 'Shift+↵', desc: '多行' },
|
|
89
|
+
];
|
|
90
|
+
const HorizontalHeader = ({ modelName, isConnected, sessionCount, modeLabel, theme, tip, sessions, version }) => {
|
|
91
|
+
const netColor = isConnected ? C.green : C.red;
|
|
92
|
+
const netBadge = isConnected ? '● CONNECTED' : '○ OFFLINE';
|
|
93
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: C.border, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: C.blue, bold: true, children: '❯ ' }), _jsx(Text, { color: C.text, bold: true, children: 'MIST AGENT ' }), _jsx(Text, { color: C.mauve, bold: true, children: `[v${version}]` })] }), _jsx(Text, { color: netColor, children: `[ ${netBadge} ]` })] }), _jsx(Box, { borderStyle: "classic", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: C.border }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, paddingX: 2, paddingY: 1, children: _jsx(MascotLogo, {}) }), _jsx(Box, { borderStyle: "classic", borderLeft: true, borderTop: false, borderBottom: false, borderRight: false, borderColor: C.border }), _jsx(Box, { flexShrink: 0, width: 26, paddingX: 1, paddingY: 1, children: _jsx(StatsPanel, { modelName: modelName, sessionCount: sessionCount, modeLabel: modeLabel, theme: theme }) }), _jsx(Box, { borderStyle: "classic", borderLeft: true, borderTop: false, borderBottom: false, borderRight: false, borderColor: C.border }), _jsx(Box, { flexGrow: 1, flexShrink: 1, paddingX: 1, paddingY: 1, overflow: "hidden", children: _jsx(ActivityPanel, { tip: tip, sessions: sessions }) })] }), _jsx(Box, { borderStyle: "classic", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: C.border }), _jsx(Box, { flexDirection: "row", justifyContent: "center", gap: 3, paddingX: 1, paddingY: 1, children: SHORTCUTS.map(s => (_jsxs(Text, { color: C.dimtext, children: [_jsx(Text, { color: C.subtext, children: `[${s.key}]` }), ` ${s.desc}`] }, s.key))) })] }));
|
|
94
|
+
};
|
|
95
|
+
// ─────────────────────────────────────────────
|
|
96
|
+
// Root export — picks tier based on terminalWidth
|
|
97
|
+
// ─────────────────────────────────────────────
|
|
80
98
|
export const Header = () => {
|
|
81
99
|
const { currentModel } = useModelState();
|
|
82
100
|
const { terminalWidth, theme } = useUIState();
|
|
83
101
|
const { sessions } = useSessionState();
|
|
84
102
|
const { forceMode } = useChatState();
|
|
85
103
|
const { healthData } = useAppState();
|
|
86
|
-
const modelName = currentModel?.model ?? 'MistAgent
|
|
104
|
+
const modelName = currentModel?.model ?? 'MistAgent';
|
|
87
105
|
const isConnected = !!healthData;
|
|
88
|
-
const innerWidth = terminalWidth - 2;
|
|
89
|
-
// ── Column widths ──
|
|
90
|
-
const mascotWidth = Math.max(...MASCOT_LINES.map(l => stringWidth(l)));
|
|
91
|
-
const logoColWidth = mascotWidth + 8;
|
|
92
|
-
const statsColWidth = 28;
|
|
93
|
-
const infoColWidth = Math.max(10, innerWidth - logoColWidth - statsColWidth - 2);
|
|
94
|
-
// ── Random tip (stable per mount) ──
|
|
95
|
-
const [tipText] = React.useState(() => TIPS[Math.floor(Math.random() * TIPS.length)]);
|
|
96
|
-
const mascotGradient = getMascotGradient();
|
|
97
|
-
// ── Stats data ──
|
|
98
106
|
const modeLabel = forceMode === 'plan' ? 'plan' : 'chat';
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
{ label: 'sessions', value: String(sessions.length), valueColor: C.teal },
|
|
103
|
-
{ label: 'mode', value: modeLabel, valueColor: forceMode === 'plan' ? C.mauve : C.green },
|
|
104
|
-
{ label: 'theme', value: `${themeIcon} ${theme}`, valueColor: C.mauve },
|
|
105
|
-
];
|
|
106
|
-
// ── Recent sessions ──
|
|
107
|
-
const recentSessions = sessions.slice(0, 3);
|
|
108
|
-
const dotColors = [C.green, C.blue, C.mauve];
|
|
109
|
-
const emptyLogo = () => _jsx(Text, { children: ' '.repeat(logoColWidth) });
|
|
110
|
-
const emptyStats = () => _jsx(Text, { children: ' '.repeat(statsColWidth) });
|
|
111
|
-
const emptyInfo = () => _jsx(Text, { children: ' '.repeat(infoColWidth) });
|
|
112
|
-
// Col 1: Logo — top padding(2) + mascot(6) + gap + subtitle + bottom padding(2)
|
|
113
|
-
const logoRows = [
|
|
114
|
-
emptyLogo, // top padding 1
|
|
115
|
-
emptyLogo, // top padding 2
|
|
116
|
-
...MASCOT_LINES.map((line, i) => () => {
|
|
117
|
-
const color = mascotGradient[i] ?? mascotGradient[mascotGradient.length - 1];
|
|
118
|
-
return _jsx(Text, { color: color, bold: true, children: centerPad(line, logoColWidth) });
|
|
119
|
-
}),
|
|
120
|
-
emptyLogo, // gap before subtitle
|
|
121
|
-
() => _jsx(Text, { color: C.dimtext, children: centerPad('A G E N T', logoColWidth) }),
|
|
122
|
-
emptyLogo, // bottom padding 1
|
|
123
|
-
emptyLogo, // bottom padding 2
|
|
124
|
-
];
|
|
125
|
-
// Col 2: Stats — top padding(2) + header + gap + 4 rows (with gaps) + bottom padding
|
|
126
|
-
const statsRows = [
|
|
127
|
-
emptyStats, // top padding 1
|
|
128
|
-
emptyStats, // top padding 2
|
|
129
|
-
() => _jsx(Text, { color: C.subtext, children: pad(' // quick stats', statsColWidth) }),
|
|
130
|
-
emptyStats, // gap after header
|
|
131
|
-
...statsData.flatMap((s, i) => {
|
|
132
|
-
const row = () => {
|
|
133
|
-
const lbl = ` ${s.label}`;
|
|
134
|
-
const val = `${s.value} `;
|
|
135
|
-
const gap = Math.max(1, statsColWidth - stringWidth(lbl) - stringWidth(val));
|
|
136
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: C.dimtext, children: lbl }), _jsx(Text, { children: ' '.repeat(gap) }), _jsx(Text, { color: s.valueColor, bold: true, children: val })] }));
|
|
137
|
-
};
|
|
138
|
-
// Add empty row between stat rows (not after the last one)
|
|
139
|
-
return i < statsData.length - 1 ? [row, emptyStats] : [row];
|
|
140
|
-
}),
|
|
141
|
-
emptyStats, // bottom padding 1
|
|
142
|
-
emptyStats, // bottom padding 2
|
|
143
|
-
];
|
|
144
|
-
// Col 3: Info — top padding(2) + tip section + gap + recent section + bottom padding
|
|
145
|
-
const infoRows = [
|
|
146
|
-
emptyInfo, // top padding
|
|
147
|
-
() => _jsx(Text, { color: C.subtext, children: pad(' // daily tip', infoColWidth) }),
|
|
148
|
-
emptyInfo, // gap after tip label
|
|
149
|
-
() => {
|
|
150
|
-
const tipPrefix = ' $ ';
|
|
151
|
-
const tipContent = truncate(tipText, infoColWidth - stringWidth(tipPrefix));
|
|
152
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: C.green, bold: true, children: tipPrefix }), _jsx(Text, { color: C.text, children: pad(tipContent, infoColWidth - stringWidth(tipPrefix)) })] }));
|
|
153
|
-
},
|
|
154
|
-
emptyInfo, // gap between sections
|
|
155
|
-
emptyInfo,
|
|
156
|
-
() => _jsx(Text, { color: C.subtext, children: pad(' // recent activity (' + sessions.length + ')', infoColWidth) }),
|
|
157
|
-
emptyInfo, // gap after recent label
|
|
158
|
-
];
|
|
159
|
-
if (recentSessions.length > 0) {
|
|
160
|
-
for (const [idx, s] of recentSessions.entries()) {
|
|
161
|
-
const dotColor = dotColors[idx % dotColors.length];
|
|
162
|
-
infoRows.push(() => {
|
|
163
|
-
const date = new Date(s.last_activity);
|
|
164
|
-
const timeStr = date.toLocaleString('zh-CN', {
|
|
165
|
-
month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
|
|
166
|
-
});
|
|
167
|
-
const title = s.title || 'New Chat';
|
|
168
|
-
const timePrefix = ` ${timeStr} `;
|
|
169
|
-
const dotStr = '● ';
|
|
170
|
-
const usedWidth = stringWidth(timePrefix) + stringWidth(dotStr);
|
|
171
|
-
const titleWidth = Math.max(0, infoColWidth - usedWidth);
|
|
172
|
-
const displayTitle = pad(truncate(title, titleWidth), titleWidth);
|
|
173
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: C.dimtext, children: timePrefix }), _jsx(Text, { color: dotColor, children: dotStr }), _jsx(Text, { color: C.subtext, children: displayTitle })] }));
|
|
174
|
-
});
|
|
175
|
-
// Add empty row between recent items (not after the last one)
|
|
176
|
-
if (idx < recentSessions.length - 1) {
|
|
177
|
-
infoRows.push(emptyInfo);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
107
|
+
const [tip] = React.useState(() => TIPS[Math.floor(Math.random() * TIPS.length)]);
|
|
108
|
+
if (terminalWidth < CONDENSED_MAX) {
|
|
109
|
+
return _jsx(CondensedHeader, { modelName: modelName, isConnected: isConnected, version: VERSION });
|
|
180
110
|
}
|
|
181
|
-
|
|
182
|
-
|
|
111
|
+
if (terminalWidth < COMPACT_MAX) {
|
|
112
|
+
return (_jsx(CompactHeader, { modelName: modelName, isConnected: isConnected, modeLabel: modeLabel, theme: theme, version: VERSION }));
|
|
183
113
|
}
|
|
184
|
-
|
|
185
|
-
const maxRows = Math.max(logoRows.length, statsRows.length, infoRows.length);
|
|
186
|
-
while (logoRows.length < maxRows)
|
|
187
|
-
logoRows.push(emptyLogo);
|
|
188
|
-
while (statsRows.length < maxRows)
|
|
189
|
-
statsRows.push(emptyStats);
|
|
190
|
-
while (infoRows.length < maxRows)
|
|
191
|
-
infoRows.push(emptyInfo);
|
|
192
|
-
// ── Top bar layout ──
|
|
193
|
-
const netColor = isConnected ? C.green : C.red;
|
|
194
|
-
return (_jsxs(Box, { flexDirection: "column", width: terminalWidth, marginTop: 1, children: [_jsx(Text, { children: _jsxs(Text, { color: C.yellow, children: [B.topLeft, B.h.repeat(innerWidth), B.topRight] }) }), _jsx(EmptyRow, { width: innerWidth }), (() => {
|
|
195
|
-
const leftPad = ' ';
|
|
196
|
-
const prompt = '❯';
|
|
197
|
-
const brand = ' M I S T A G E N T ';
|
|
198
|
-
const verBadge = `[ v${VERSION} ]`;
|
|
199
|
-
const netBadge = isConnected ? '[ ● CONNECTED ]' : '[ ○ OFFLINE ]';
|
|
200
|
-
const leftStr = `${leftPad}${prompt}${brand}${verBadge}`;
|
|
201
|
-
const rightStr = `${netBadge} `;
|
|
202
|
-
const gap = Math.max(1, innerWidth - stringWidth(leftStr) - stringWidth(rightStr));
|
|
203
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), _jsx(Text, { children: leftPad }), _jsx(Text, { color: C.blue, bold: true, children: prompt }), _jsx(Text, { color: C.text, bold: true, children: brand }), _jsx(Text, { color: C.mauve, bold: true, children: verBadge }), _jsx(Text, { children: ' '.repeat(gap) }), _jsx(Text, { color: netColor, children: netBadge }), _jsx(Text, { children: ' ' }), _jsx(Text, { color: C.yellow, children: B.v })] }));
|
|
204
|
-
})(), _jsx(EmptyRow, { width: innerWidth }), _jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), _jsx(DashedDivider, { width: innerWidth }), _jsx(Text, { color: C.yellow, children: B.v })] }), Array.from({ length: maxRows }).map((_, i) => {
|
|
205
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), logoRows[i](), _jsx(Text, { color: C.yellow, children: "\u2502" }), statsRows[i](), _jsx(Text, { color: C.yellow, children: "\u2502" }), infoRows[i](), _jsx(Text, { color: C.yellow, children: B.v })] }, i));
|
|
206
|
-
}), _jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), _jsx(DashedDivider, { width: innerWidth }), _jsx(Text, { color: C.yellow, children: B.v })] }), _jsx(EmptyRow, { width: innerWidth }), _jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), _jsx(Text, { children: centerPad(SHORTCUTS.map(s => `[${s.key}] ${s.desc}`).join(' '), innerWidth) }), _jsx(Text, { color: C.yellow, children: B.v })] }), _jsx(EmptyRow, { width: innerWidth }), _jsx(Text, { children: _jsxs(Text, { color: C.yellow, children: [B.bottomLeft, B.h.repeat(innerWidth), B.bottomRight] }) })] }));
|
|
114
|
+
return (_jsx(HorizontalHeader, { modelName: modelName, isConnected: isConnected, sessionCount: sessions.length, modeLabel: modeLabel, theme: theme, tip: tip, sessions: sessions, version: VERSION }));
|
|
207
115
|
};
|
|
208
116
|
//# sourceMappingURL=Header.js.map
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { Static, Box } from 'ink';
|
|
2
|
+
import { Box } from 'ink';
|
|
4
3
|
import { useChatState, StreamingState } from '../contexts/ChatContext.js';
|
|
5
4
|
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
|
|
6
5
|
import { AssistantMessage } from './messages/AssistantMessage.js';
|
|
7
6
|
import { Header } from './Header.js';
|
|
8
|
-
// Stable sentinel object — always the first item so <Static> renders
|
|
9
|
-
// the Header once, and subsequent history items are appended after it.
|
|
10
|
-
const HEADER_SENTINEL = { __header: true, id: '__header__' };
|
|
11
7
|
export const MainContent = () => {
|
|
12
|
-
const { history, pendingContent, streamingState
|
|
13
|
-
|
|
14
|
-
const staticItems = useMemo(() => [HEADER_SENTINEL, ...history], [history]);
|
|
15
|
-
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Static, { items: staticItems, children: (item) => {
|
|
16
|
-
if ('__header' in item) {
|
|
17
|
-
return (_jsx(Box, { children: _jsx(Header, {}) }, "__header__"));
|
|
18
|
-
}
|
|
19
|
-
return (_jsx(Box, { paddingX: 1, children: _jsx(HistoryItemDisplay, { item: item }) }, item.id));
|
|
20
|
-
} }, sessionVersion), streamingState !== StreamingState.Idle && (_jsx(Box, { paddingX: 1, children: _jsx(AssistantMessage, { text: pendingContent, isPending: true }) }))] }));
|
|
8
|
+
const { history, pendingContent, streamingState } = useChatState();
|
|
9
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, {}), history.map((item) => (_jsx(Box, { paddingX: 1, children: _jsx(HistoryItemDisplay, { item: item }) }, item.id))), streamingState !== StreamingState.Idle && (_jsx(Box, { paddingX: 1, children: _jsx(AssistantMessage, { text: pendingContent, isPending: true }) }))] }));
|
|
21
10
|
};
|
|
22
11
|
//# sourceMappingURL=MainContent.js.map
|
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React, { useMemo } from 'react';
|
|
3
3
|
import { Text } from 'ink';
|
|
4
|
-
import {
|
|
5
|
-
import { markedTerminal } from 'marked-terminal';
|
|
6
|
-
import { supportsLanguage } from 'cli-highlight';
|
|
7
|
-
const marked = new Marked(markedTerminal({
|
|
8
|
-
reflowText: true,
|
|
9
|
-
width: 80,
|
|
10
|
-
showSectionPrefix: false,
|
|
11
|
-
tab: 2,
|
|
12
|
-
}));
|
|
13
|
-
// Replace ```<unsupported-lang> with ``` to avoid console.warn from highlight.js
|
|
14
|
-
function silentParse(src) {
|
|
15
|
-
// Strip internal heading markers (e.g. {mist_session_heading:...})
|
|
16
|
-
let cleaned = src.replace(/\{mist_session_heading:[^}]*\}\s*/g, '');
|
|
17
|
-
cleaned = cleaned.replace(/^```(\w+)/gm, (_match, lang) => supportsLanguage(lang) ? '```' + lang : '```');
|
|
18
|
-
return marked.parse(cleaned);
|
|
19
|
-
}
|
|
4
|
+
import { applyMarkdown } from '../../utils/markdown.js';
|
|
20
5
|
export const MarkdownRenderer = React.memo(({ text, }) => {
|
|
21
6
|
const rendered = useMemo(() => {
|
|
22
7
|
if (!text)
|
|
23
8
|
return null;
|
|
24
9
|
try {
|
|
25
|
-
return
|
|
10
|
+
return applyMarkdown(text);
|
|
26
11
|
}
|
|
27
12
|
catch {
|
|
28
13
|
return text;
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { Box, Text, useCursor } from 'ink';
|
|
3
4
|
import chalk from 'chalk';
|
|
5
|
+
import stringWidth from 'string-width';
|
|
4
6
|
import { cpSlice, cpLen } from '../../utils/textUtils.js';
|
|
5
7
|
import { PASTE_PLACEHOLDER_REGEX } from '../../utils/pasteUtils.js';
|
|
6
8
|
import { palette } from '../../utils/colors.js';
|
|
9
|
+
/**
|
|
10
|
+
* Walk up the Yoga layout tree to compute absolute (x, y) of a DOM node.
|
|
11
|
+
* Same algorithm as Ink's internal `getAbsolutePosition` (layout.ts).
|
|
12
|
+
*/
|
|
13
|
+
function getAbsolutePosition(node) {
|
|
14
|
+
let x = 0;
|
|
15
|
+
let y = 0;
|
|
16
|
+
let cur = node;
|
|
17
|
+
while (cur?.parentNode) {
|
|
18
|
+
if (!cur.yogaNode)
|
|
19
|
+
return null;
|
|
20
|
+
x += cur.yogaNode.getComputedLeft();
|
|
21
|
+
y += cur.yogaNode.getComputedTop();
|
|
22
|
+
cur = cur.parentNode;
|
|
23
|
+
}
|
|
24
|
+
return { x, y };
|
|
25
|
+
}
|
|
7
26
|
/**
|
|
8
27
|
* Split a line into segments, marking paste placeholders for accent rendering.
|
|
9
28
|
* Returns array of { text, isPaste } segments.
|
|
@@ -93,10 +112,50 @@ export const TextInput = ({ buffer, placeholder = 'Ask anything...', isActive, }
|
|
|
93
112
|
const { viewportVisualLines, visualCursor } = buffer;
|
|
94
113
|
const [cursorRow, cursorCol] = visualCursor;
|
|
95
114
|
const showPlaceholder = buffer.text.length === 0;
|
|
115
|
+
// ── Terminal cursor positioning for IME ──
|
|
116
|
+
// Park the real terminal cursor at the text caret so CJK IME candidate
|
|
117
|
+
// windows appear inline instead of at the end of the line.
|
|
118
|
+
const { setCursorPosition } = useCursor();
|
|
119
|
+
const boxRef = useRef(null);
|
|
120
|
+
if (!isActive) {
|
|
121
|
+
setCursorPosition(undefined);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const node = boxRef.current;
|
|
125
|
+
if (node) {
|
|
126
|
+
const abs = getAbsolutePosition(node);
|
|
127
|
+
if (abs) {
|
|
128
|
+
const indent = cursorRow === 0 ? 0 : 2;
|
|
129
|
+
const lineText = viewportVisualLines[cursorRow] ?? '';
|
|
130
|
+
const textBeforeCursor = cpSlice(lineText, 0, cursorCol);
|
|
131
|
+
const displayCol = stringWidth(textBeforeCursor);
|
|
132
|
+
// Ink's buildCursorSuffix computes: moveUp = visibleLineCount - y.
|
|
133
|
+
// When output fits in terminal, the output string ends with '\n'
|
|
134
|
+
// and visibleLineCount = lines.length - 1 (= Yoga rootH). The
|
|
135
|
+
// cursor sits one line below the last visible line, so Yoga's
|
|
136
|
+
// absolute Y maps directly.
|
|
137
|
+
//
|
|
138
|
+
// When output overflows (rootH > termRows), Ink's incremental
|
|
139
|
+
// renderer omits the trailing '\n', so visibleLineCount = lines.length
|
|
140
|
+
// (= rootH + 1 effectively in the moveUp math). The cursor sits at
|
|
141
|
+
// the END of the last line, not a line below it. This shifts the
|
|
142
|
+
// moveUp origin by one row, requiring y + 1 to compensate.
|
|
143
|
+
let rootNode = node;
|
|
144
|
+
while (rootNode?.parentNode)
|
|
145
|
+
rootNode = rootNode.parentNode;
|
|
146
|
+
const rootH = rootNode?.yogaNode?.getComputedHeight() ?? 0;
|
|
147
|
+
const overflows = rootH > (process.stdout.rows ?? 24);
|
|
148
|
+
setCursorPosition({
|
|
149
|
+
x: abs.x + indent + displayCol,
|
|
150
|
+
y: abs.y + cursorRow + (overflows ? 1 : 0),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
96
155
|
if (showPlaceholder) {
|
|
97
|
-
return (_jsx(Box, { children: isActive ? (_jsxs(Text, { children: [chalk.inverse(placeholder[0] || ' '), _jsx(Text, { color: palette.textDim, children: placeholder.slice(1) })] })) : (_jsx(Text, { color: palette.textDim, children: placeholder })) }));
|
|
156
|
+
return (_jsx(Box, { ref: boxRef, children: isActive ? (_jsxs(Text, { children: [chalk.inverse(placeholder[0] || ' '), _jsx(Text, { color: palette.textDim, children: placeholder.slice(1) })] })) : (_jsx(Text, { color: palette.textDim, children: placeholder })) }));
|
|
98
157
|
}
|
|
99
|
-
return (_jsx(Box, { flexDirection: "column", children: viewportVisualLines.map((lineText, idx) => {
|
|
158
|
+
return (_jsx(Box, { ref: boxRef, flexDirection: "column", children: viewportVisualLines.map((lineText, idx) => {
|
|
100
159
|
const isCursorLine = isActive && idx === cursorRow;
|
|
101
160
|
// First line has no indent (follows ❯ prompt), subsequent lines indent 2 spaces
|
|
102
161
|
const indent = idx === 0 ? '' : ' ';
|
|
@@ -3,6 +3,7 @@ import { createParser } from 'eventsource-parser';
|
|
|
3
3
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { chatApi } from '../api/chat.js';
|
|
6
|
+
import { ApiError } from '../api/client.js';
|
|
6
7
|
import { submitTunnelResult } from '../api/tunnel.js';
|
|
7
8
|
import { processFileReferences } from '../utils/fileRef.js';
|
|
8
9
|
import { executeFileOperation, isSensitiveOperation, isAlwaysConfirmRequired } from '../utils/fileTunnel.js';
|
|
@@ -292,7 +293,17 @@ export function useChat() {
|
|
|
292
293
|
}
|
|
293
294
|
catch (err) {
|
|
294
295
|
tokenBuf.dispose();
|
|
295
|
-
|
|
296
|
+
let errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
297
|
+
if (err instanceof ApiError) {
|
|
298
|
+
try {
|
|
299
|
+
const parsed = JSON.parse(err.body);
|
|
300
|
+
if (parsed.detail?.message)
|
|
301
|
+
errorMessage = parsed.detail.message;
|
|
302
|
+
else if (typeof parsed.detail === 'string')
|
|
303
|
+
errorMessage = parsed.detail;
|
|
304
|
+
}
|
|
305
|
+
catch { /* use default */ }
|
|
306
|
+
}
|
|
296
307
|
dispatch({ type: 'STREAM_ERROR', error: errorMessage });
|
|
297
308
|
}
|
|
298
309
|
finally {
|
|
@@ -421,7 +432,17 @@ export function useChat() {
|
|
|
421
432
|
await processConfirmStream(reader);
|
|
422
433
|
}
|
|
423
434
|
catch (err) {
|
|
424
|
-
|
|
435
|
+
let errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
436
|
+
if (err instanceof ApiError) {
|
|
437
|
+
try {
|
|
438
|
+
const parsed = JSON.parse(err.body);
|
|
439
|
+
if (parsed.detail?.message)
|
|
440
|
+
errorMessage = parsed.detail.message;
|
|
441
|
+
else if (typeof parsed.detail === 'string')
|
|
442
|
+
errorMessage = parsed.detail;
|
|
443
|
+
}
|
|
444
|
+
catch { /* use default */ }
|
|
445
|
+
}
|
|
425
446
|
dispatch({ type: 'STREAM_ERROR', error: errorMessage });
|
|
426
447
|
}
|
|
427
448
|
}, [state.threadId, state.pendingPlan, dispatch, processConfirmStream]);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { sessionsApi } from '../api/sessions.js';
|
|
3
|
+
import { quotaApi } from '../api/quota.js';
|
|
3
4
|
import { useChatState, useChatDispatch } from '../contexts/ChatContext.js';
|
|
4
5
|
import { useAppState, useAppActions } from '../contexts/AppContext.js';
|
|
5
6
|
import { useUIActions } from '../contexts/UIContext.js';
|
|
@@ -104,6 +105,50 @@ export function useSlashCommand() {
|
|
|
104
105
|
});
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
108
|
+
// /usage — show account balance and usage
|
|
109
|
+
if (trimmed === '/usage') {
|
|
110
|
+
dispatch({ type: 'SET_BUSY', busy: true });
|
|
111
|
+
try {
|
|
112
|
+
const b = await quotaApi.balance();
|
|
113
|
+
const fmt = (v) => `$${v.toFixed(4)}`;
|
|
114
|
+
const fmtTokens = (v) => v >= 1_000_000
|
|
115
|
+
? `${(v / 1_000_000).toFixed(1)}M`
|
|
116
|
+
: v >= 1_000
|
|
117
|
+
? `${(v / 1_000).toFixed(1)}K`
|
|
118
|
+
: String(v);
|
|
119
|
+
const lines = [
|
|
120
|
+
`**账户余额**`,
|
|
121
|
+
` 余额: ${fmt(b.usd_balance)}`,
|
|
122
|
+
` 总充值: ${fmt(b.usd_total_granted)}`,
|
|
123
|
+
` 总消耗: ${fmt(b.usd_total_consumed)}`,
|
|
124
|
+
``,
|
|
125
|
+
`**今日用量**`,
|
|
126
|
+
` Tokens: ${fmtTokens(b.usage_today_tokens)}`,
|
|
127
|
+
` 费用: ${fmt(b.cost_today_usd)}`,
|
|
128
|
+
``,
|
|
129
|
+
`**本月用量**`,
|
|
130
|
+
` Tokens: ${fmtTokens(b.usage_this_month_tokens)}`,
|
|
131
|
+
` 费用: ${fmt(b.cost_this_month_usd)}`,
|
|
132
|
+
];
|
|
133
|
+
dispatch({
|
|
134
|
+
type: 'ADD_ITEM',
|
|
135
|
+
item: { type: 'command', command: '/usage', result: lines.join('\n') },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
dispatch({
|
|
140
|
+
type: 'ADD_ITEM',
|
|
141
|
+
item: {
|
|
142
|
+
type: 'error',
|
|
143
|
+
text: `查询用量失败: ${err instanceof Error ? err.message : String(err)}`,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
dispatch({ type: 'SET_BUSY', busy: false });
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
107
152
|
// /quit or /exit
|
|
108
153
|
if (trimmed === '/quit' || trimmed === '/exit') {
|
|
109
154
|
dispatch({
|