clarity-ai 7.0.0 → 7.1.0
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/CHANGELOG.md +10 -0
- package/package.json +1 -2
- package/src/app.js +27 -11
- package/src/components/Banner.js +41 -0
- package/src/components/Footer.js +7 -6
- package/src/components/InputPanel.js +39 -0
- package/src/components/StatusBar.js +45 -0
- package/src/components/StreamView.js +64 -38
- package/src/config/theme.js +17 -13
- package/src/components/Header.js +0 -11
- package/src/components/PromptCard.js +0 -50
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## 7.1.0 (2026-06-06)
|
|
6
|
+
|
|
7
|
+
### Premium UI/UX overhaul — high-texture capsule design with live streaming
|
|
8
|
+
- **Massive ASCII art banner**: 6-line figlet-style "CLARITY" in orange→purple→blue gradient (auto-falls back for narrow terminals)
|
|
9
|
+
- **Solid Ink capsule cards**: zero wire borders (`|`, `[`, `]`, `-` banned). All containers use `backgroundColor` blocks with graphite (#1C1C1C) surfaces
|
|
10
|
+
- **Orange accent selection**: `#FF9F43` full-width highlight bars with black text for all active/focus elements
|
|
11
|
+
- **State machine**: `IDLE` → `THINKING` (live elapsed counter in ms) → `STREAMING` — each with distinct badge coloring
|
|
12
|
+
- **Live token streaming**: every chunk runs through `wrap-ansi` + `string-width` against current terminal width; no pre-packaged truncation
|
|
13
|
+
- **Dynamic viewport hardening**: `process.stdout.on('resize')` tracks live dimensions; auto-scrolls old content up while keeping input anchored
|
|
14
|
+
|
|
5
15
|
## 7.0.0 (2026-06-06)
|
|
6
16
|
|
|
7
17
|
### Ground-up architectural rebuild — centered minimalist terminal platform
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarity-ai",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"description": "CLARITY — terminal AI agent with local GGUF inference on HF Spaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"gradient-string": "^3",
|
|
29
29
|
"ink": "^5",
|
|
30
30
|
"ink-spinner": "^5",
|
|
31
|
-
"ink-text-input": "^6.0.0",
|
|
32
31
|
"react": "^18",
|
|
33
32
|
"string-width": "^7",
|
|
34
33
|
"wrap-ansi": "^9"
|
package/src/app.js
CHANGED
|
@@ -3,9 +3,10 @@ import { Box, Text, useStdout } from 'ink';
|
|
|
3
3
|
import { createChatState, handleSend, handleCommand } from './chat.js';
|
|
4
4
|
import { hex } from './config/theme.js';
|
|
5
5
|
import { getLayout } from './config/layout.js';
|
|
6
|
-
import {
|
|
6
|
+
import { Banner } from './components/Banner.js';
|
|
7
|
+
import { StatusBar } from './components/StatusBar.js';
|
|
7
8
|
import { StreamView } from './components/StreamView.js';
|
|
8
|
-
import {
|
|
9
|
+
import { InputPanel } from './components/InputPanel.js';
|
|
9
10
|
import { Footer } from './components/Footer.js';
|
|
10
11
|
const { createElement: h } = React;
|
|
11
12
|
|
|
@@ -26,11 +27,18 @@ export function cancelStream() {
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
function deriveStatus(state, streamContent) {
|
|
31
|
+
if (!state.thinking) return 'idle';
|
|
32
|
+
if (state.thinking && streamContent) return 'streaming';
|
|
33
|
+
return 'thinking';
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
export function App({ config }) {
|
|
30
37
|
const { stdout } = useStdout();
|
|
31
38
|
const [dims, setDims] = useState({ rows: stdout.rows || 30, cols: stdout.columns || 80 });
|
|
32
39
|
const [state, setState] = useState(() => createChatState());
|
|
33
40
|
const [streamContent, setStreamContent] = useState('');
|
|
41
|
+
const [thinkingStart, setThinkingStart] = useState(null);
|
|
34
42
|
const defaultModel = (config.model || 'groq/llama-3.3-70b-versatile').replace(/^[^/]+\//, '');
|
|
35
43
|
const [model, setModel] = useState(defaultModel);
|
|
36
44
|
const [provider, setProvider] = useState(config.provider || 'groq');
|
|
@@ -50,11 +58,18 @@ export function App({ config }) {
|
|
|
50
58
|
return () => process.stdout.removeListener('resize', onResize);
|
|
51
59
|
}, []);
|
|
52
60
|
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (state.thinking && !thinkingStart) {
|
|
63
|
+
setThinkingStart(Date.now());
|
|
64
|
+
} else if (!state.thinking) {
|
|
65
|
+
setThinkingStart(null);
|
|
66
|
+
}
|
|
67
|
+
}, [state.thinking]);
|
|
68
|
+
|
|
53
69
|
const cols = dims.cols;
|
|
54
70
|
const rows = dims.rows;
|
|
55
|
-
const
|
|
56
|
-
const cardWidth = Math.min(cols - 4,
|
|
57
|
-
const contentPad = isCompact ? 1 : 2;
|
|
71
|
+
const status = deriveStatus(state, streamContent);
|
|
72
|
+
const cardWidth = Math.min(cols - 4, 56);
|
|
58
73
|
|
|
59
74
|
const onSubmit = useCallback(async (input) => {
|
|
60
75
|
if (input === '/exit') { process.exit(0); return; }
|
|
@@ -86,17 +101,18 @@ export function App({ config }) {
|
|
|
86
101
|
|
|
87
102
|
return h(Box, { width: '100%', height: '100%', flexDirection: 'column', alignItems: 'center', backgroundColor: hex.bg },
|
|
88
103
|
h(Box, { flexGrow: 1, minHeight: 1 }),
|
|
89
|
-
h(
|
|
90
|
-
h(
|
|
91
|
-
|
|
104
|
+
h(Banner, { cols }),
|
|
105
|
+
h(StatusBar, { status, thinkingStart }),
|
|
106
|
+
h(Box, { flexGrow: 2, width: cardWidth, minHeight: 2, flexDirection: 'column' },
|
|
107
|
+
h(StreamView, { messages: state.messages, streamContent, status, width: cardWidth })
|
|
92
108
|
),
|
|
93
|
-
h(
|
|
109
|
+
h(Box, { height: 1 }),
|
|
110
|
+
h(InputPanel, {
|
|
94
111
|
width: cardWidth,
|
|
95
|
-
compact: isCompact,
|
|
96
112
|
provider,
|
|
97
113
|
model,
|
|
98
114
|
agentMode: state.agentMode,
|
|
99
|
-
|
|
115
|
+
status,
|
|
100
116
|
onSubmit: handleInputSubmit,
|
|
101
117
|
}),
|
|
102
118
|
h(Box, { flexGrow: 1, minHeight: 1 }),
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { appGradient } from '../config/theme.js';
|
|
4
|
+
const { createElement: h } = React;
|
|
5
|
+
|
|
6
|
+
const BANNER_FULL = [
|
|
7
|
+
' ██████╗██╗ █████╗ ██████╗ ██╗████████╗██╗ ██╗',
|
|
8
|
+
'██╔════╝██║ ██╔══██╗██╔══██╗██║╚══██╔══╝╚██╗ ██╔╝',
|
|
9
|
+
'██║ ███╗██║ ███████║██████╔╝██║ ██║ ╚████╔╝ ',
|
|
10
|
+
'██║ ██║██║ ██╔══██║██╔══██╗██║ ██║ ╚██╔╝ ',
|
|
11
|
+
'╚██████╔╝███████╗██║ ██║██║ ██║██║ ██║ ██║ ',
|
|
12
|
+
' ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const BANNER_MED = [
|
|
16
|
+
'██████╗ ██╗ █████╗ ██████╗ ██╗████████╗██╗ ██╗',
|
|
17
|
+
'██╔════╝ ██║ ██╔══██╗██╔══██╗██║╚══██╔══╝╚██╗ ██╔╝',
|
|
18
|
+
'███████╗ ██║ ███████║██████╔╝██║ ██║ ╚████╔╝ ',
|
|
19
|
+
'╚════██║ ██║ ██╔══██║██╔══██╗██║ ██║ ╚██╔╝ ',
|
|
20
|
+
'██████╔╝ ███████╗██║ ██║██║ ██║██║ ██║ ██║ ',
|
|
21
|
+
'╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function Banner({ cols }) {
|
|
25
|
+
if (cols < 50) {
|
|
26
|
+
return h(Box, { height: 2, width: '100%', alignItems: 'center', justifyContent: 'center' },
|
|
27
|
+
h(Text, { bold: true }, appGradient('CLARITY'))
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const lines = cols < 60 ? BANNER_MED : BANNER_FULL;
|
|
32
|
+
const bannerHeight = lines.length;
|
|
33
|
+
|
|
34
|
+
return h(Box, { height: bannerHeight, width: '100%', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' },
|
|
35
|
+
lines.map((line, i) =>
|
|
36
|
+
h(Box, { key: i, height: 1 },
|
|
37
|
+
h(Text, { bold: true }, appGradient.multiline(line))
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
}
|
package/src/components/Footer.js
CHANGED
|
@@ -4,19 +4,20 @@ import { hex } from '../config/theme.js';
|
|
|
4
4
|
const { createElement: h } = React;
|
|
5
5
|
|
|
6
6
|
export function Footer({ cols }) {
|
|
7
|
-
const showFull = cols >=
|
|
7
|
+
const showFull = cols >= 58;
|
|
8
8
|
const keybindText = 'tab agents ctrl+p commands';
|
|
9
|
-
const tipText = 'Tip: Use /help to view system commands and switch active models';
|
|
10
9
|
|
|
11
10
|
return h(Box, { width: '100%', flexDirection: 'column', backgroundColor: hex.bg },
|
|
12
|
-
h(Box, { height: 1, justifyContent: 'flex-end', paddingRight: 2 },
|
|
11
|
+
h(Box, { height: 1, justifyContent: 'flex-end', paddingRight: 2, backgroundColor: hex.bg },
|
|
13
12
|
h(Text, { color: hex.textMuted }, keybindText)
|
|
14
13
|
),
|
|
15
|
-
h(Box, { height: 1, paddingLeft: 2, paddingRight: 2 },
|
|
14
|
+
h(Box, { height: 1, paddingLeft: 2, paddingRight: 2, backgroundColor: hex.bg },
|
|
16
15
|
h(Text, { color: hex.textDim },
|
|
17
16
|
'\u2580 ',
|
|
18
|
-
h(Text, { color: hex.
|
|
19
|
-
h(Text, { color: hex.textDim }, ': ' + (showFull
|
|
17
|
+
h(Text, { color: hex.orange }, 'Tip'),
|
|
18
|
+
h(Text, { color: hex.textDim }, ': ' + (showFull
|
|
19
|
+
? 'Use /help to view system commands and switch active models'
|
|
20
|
+
: 'Use /help for commands'))
|
|
20
21
|
)
|
|
21
22
|
)
|
|
22
23
|
);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import { hex } from '../config/theme.js';
|
|
5
|
+
const { createElement: h } = React;
|
|
6
|
+
|
|
7
|
+
export function InputPanel({ width, provider, model, agentMode, status, onSubmit }) {
|
|
8
|
+
const [input, setInput] = useState('');
|
|
9
|
+
const isLocked = status !== 'idle';
|
|
10
|
+
const mShort = model.replace(/^[^/]+\//, '').slice(0, 18);
|
|
11
|
+
const innerW = Math.max(4, width - 4);
|
|
12
|
+
|
|
13
|
+
function handleSubmit(value) {
|
|
14
|
+
const trimmed = value.trim();
|
|
15
|
+
if (!trimmed) return;
|
|
16
|
+
onSubmit(trimmed);
|
|
17
|
+
setInput('');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return h(Box, { width, flexDirection: 'column', backgroundColor: hex.cardBg },
|
|
21
|
+
h(Box, { height: 1, backgroundColor: hex.orange }),
|
|
22
|
+
h(Box, { height: 1, paddingLeft: 2, paddingRight: 2, backgroundColor: hex.cardBg },
|
|
23
|
+
h(TextInput, {
|
|
24
|
+
value: input,
|
|
25
|
+
onChange: setInput,
|
|
26
|
+
onSubmit: handleSubmit,
|
|
27
|
+
placeholder: 'Ask anything...',
|
|
28
|
+
focus: !isLocked,
|
|
29
|
+
})
|
|
30
|
+
),
|
|
31
|
+
h(Box, { height: 1, paddingLeft: 2, paddingRight: 2, backgroundColor: hex.cardBg },
|
|
32
|
+
h(Text, { color: hex.textMuted },
|
|
33
|
+
provider + ' \u00B7 ' + mShort + (agentMode ? ' \u00B7 AGENT' : '') +
|
|
34
|
+
' '.repeat(Math.max(0, innerW - provider.length - mShort.length - (agentMode ? 10 : 4))) +
|
|
35
|
+
(isLocked ? '' : ''))
|
|
36
|
+
),
|
|
37
|
+
h(Box, { height: 1, backgroundColor: hex.cardBg }),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { hex } from '../config/theme.js';
|
|
4
|
+
const { createElement: h } = React;
|
|
5
|
+
|
|
6
|
+
export function StatusBar({ status, thinkingStart }) {
|
|
7
|
+
const [elapsed, setElapsed] = useState(0);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (status === 'idle') { setElapsed(0); return; }
|
|
11
|
+
const id = setInterval(() => {
|
|
12
|
+
setElapsed(thinkingStart ? Date.now() - thinkingStart : 0);
|
|
13
|
+
}, 100);
|
|
14
|
+
return () => clearInterval(id);
|
|
15
|
+
}, [status, thinkingStart]);
|
|
16
|
+
|
|
17
|
+
const isThinking = status === 'thinking';
|
|
18
|
+
const isStreaming = status === 'streaming';
|
|
19
|
+
|
|
20
|
+
if (status === 'idle') {
|
|
21
|
+
return h(Box, { height: 1, width: '100%', alignItems: 'center', justifyContent: 'center' },
|
|
22
|
+
h(Box, { backgroundColor: hex.surfaceAlt, paddingX: 1 },
|
|
23
|
+
h(Text, { color: hex.textMuted }, ' IDLE ')
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isThinking) {
|
|
29
|
+
return h(Box, { height: 1, width: '100%', alignItems: 'center', justifyContent: 'center' },
|
|
30
|
+
h(Box, { backgroundColor: hex.surfaceAlt, paddingX: 1 },
|
|
31
|
+
h(Text, { color: hex.orange }, ' \u25CF THINKING ' + elapsed + 'ms ')
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isStreaming) {
|
|
37
|
+
return h(Box, { height: 1, width: '100%', alignItems: 'center', justifyContent: 'center' },
|
|
38
|
+
h(Box, { backgroundColor: hex.surfaceAlt, paddingX: 1 },
|
|
39
|
+
h(Text, { color: hex.blue }, ' \u25CF STREAMING ' + elapsed + 'ms ')
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
@@ -1,77 +1,103 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
1
|
+
import React, { useMemo, useRef, useEffect } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { hex } from '../config/theme.js';
|
|
4
4
|
import wrapAnsi from 'wrap-ansi';
|
|
5
|
-
import
|
|
5
|
+
import stringWidth from 'string-width';
|
|
6
6
|
const { createElement: h } = React;
|
|
7
7
|
|
|
8
|
-
function
|
|
9
|
-
if (!text) return
|
|
10
|
-
return wrapAnsi(String(text), Math.max(
|
|
8
|
+
function wrapText(text, width) {
|
|
9
|
+
if (!text) return [];
|
|
10
|
+
return wrapAnsi(String(text), Math.max(4, width), { trim: false, hard: true }).split('\n');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
}
|
|
13
|
+
function MessageBlock({ msg, width }) {
|
|
14
|
+
const lines = useMemo(() => wrapText(msg.content, width), [msg.content, width]);
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
switch (line.role) {
|
|
19
|
-
case 'system':
|
|
20
|
-
return h(Box, { height: 1 },
|
|
21
|
-
h(Text, { color: hex.blue }, ' \u25C9 ' + trunc(line.content, width - 4))
|
|
22
|
-
);
|
|
16
|
+
switch (msg.role) {
|
|
23
17
|
case 'user':
|
|
24
|
-
return h(Box, {
|
|
25
|
-
h(
|
|
26
|
-
|
|
18
|
+
return h(Box, { flexDirection: 'column', marginBottom: 0 },
|
|
19
|
+
h(Box, { height: 1 },
|
|
20
|
+
h(Text, { color: hex.orange, bold: true }, ' \u276F '),
|
|
21
|
+
h(Text, { color: hex.text }, lines[0] || '')
|
|
22
|
+
),
|
|
23
|
+
lines.slice(1).map((l, i) =>
|
|
24
|
+
h(Box, { key: i, height: 1 },
|
|
25
|
+
h(Text, { color: hex.text }, ' ' + l)
|
|
26
|
+
)
|
|
27
|
+
)
|
|
27
28
|
);
|
|
28
29
|
case 'assistant':
|
|
30
|
+
return h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
31
|
+
h(Box, { height: 1 },
|
|
32
|
+
h(Text, { color: hex.purple, bold: true }, ' \u25C6 '),
|
|
33
|
+
h(Text, { color: hex.text }, lines[0] || '')
|
|
34
|
+
),
|
|
35
|
+
lines.slice(1).map((l, i) =>
|
|
36
|
+
h(Box, { key: i, height: 1 },
|
|
37
|
+
h(Text, { color: hex.text }, ' ' + l)
|
|
38
|
+
)
|
|
39
|
+
),
|
|
40
|
+
msg.duration
|
|
41
|
+
? h(Box, { height: 1 },
|
|
42
|
+
h(Text, { color: hex.textMuted }, ' ' + (msg.duration < 1000 ? msg.duration + 'ms' : (msg.duration / 1000).toFixed(1) + 's'))
|
|
43
|
+
)
|
|
44
|
+
: null
|
|
45
|
+
);
|
|
46
|
+
case 'system':
|
|
29
47
|
return h(Box, { height: 1 },
|
|
30
|
-
h(Text, { color: hex.
|
|
31
|
-
h(Text, { color: hex.text }, trunc(line.content, width - 4))
|
|
48
|
+
h(Text, { color: hex.blue }, ' \u25C9 ' + lines[0] || '')
|
|
32
49
|
);
|
|
33
50
|
case 'tool':
|
|
34
51
|
return h(Box, { height: 1 },
|
|
35
|
-
h(Text, { color: hex.green }, ' \u25C9 ' +
|
|
52
|
+
h(Text, { color: hex.green }, ' \u25C9 ' + (msg.toolName || 'tool') + (msg.duration ? ' ' + msg.duration + 'ms' : ''))
|
|
36
53
|
);
|
|
37
54
|
case 'error':
|
|
38
55
|
return h(Box, { height: 1 },
|
|
39
|
-
h(Text, { color: hex.red }, ' \u2716 ' +
|
|
56
|
+
h(Text, { color: hex.red }, ' \u2716 ' + lines[0] || msg.content)
|
|
40
57
|
);
|
|
41
58
|
default:
|
|
42
59
|
return null;
|
|
43
60
|
}
|
|
44
61
|
}
|
|
45
62
|
|
|
46
|
-
function
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
function StreamingBlock({ content, width }) {
|
|
64
|
+
const lines = useMemo(() => wrapText(content, width), [content, width]);
|
|
65
|
+
const visible = lines.slice(-100);
|
|
66
|
+
const first = visible[0] || '';
|
|
67
|
+
|
|
68
|
+
return h(Box, { flexDirection: 'column' },
|
|
69
|
+
h(Box, { height: 1 },
|
|
70
|
+
h(Text, { color: hex.blue, bold: true }, ' \u25CF '),
|
|
71
|
+
h(Text, { color: hex.text }, first)
|
|
72
|
+
),
|
|
73
|
+
visible.slice(1).map((l, i) =>
|
|
74
|
+
h(Box, { key: i, height: 1 },
|
|
75
|
+
h(Text, { color: hex.text }, ' ' + l)
|
|
76
|
+
)
|
|
77
|
+
)
|
|
52
78
|
);
|
|
53
79
|
}
|
|
54
80
|
|
|
55
|
-
export function StreamView({ messages, streamContent,
|
|
56
|
-
const contentW = Math.max(
|
|
81
|
+
export function StreamView({ messages, streamContent, status, width }) {
|
|
82
|
+
const contentW = Math.max(4, width - 2);
|
|
57
83
|
|
|
58
|
-
const
|
|
84
|
+
const displayMessages = useMemo(() => {
|
|
59
85
|
if (messages.length === 0) {
|
|
60
|
-
return [{ id: '
|
|
86
|
+
return [{ id: 'welcome', role: 'system', content: 'CLARITY AI ready \u00B7 /help for commands' }];
|
|
61
87
|
}
|
|
62
|
-
return messages
|
|
88
|
+
return messages;
|
|
63
89
|
}, [messages]);
|
|
64
90
|
|
|
65
91
|
return h(Box, { flexDirection: 'column', width: '100%', paddingLeft: 1, paddingRight: 1 },
|
|
66
|
-
|
|
67
|
-
h(
|
|
92
|
+
displayMessages.map(m =>
|
|
93
|
+
h(MessageBlock, { key: m.id, msg: m, width: contentW })
|
|
68
94
|
),
|
|
69
|
-
thinking && streamContent
|
|
70
|
-
? h(
|
|
95
|
+
(status === 'streaming' || status === 'thinking') && streamContent
|
|
96
|
+
? h(StreamingBlock, { content: streamContent, width: contentW })
|
|
71
97
|
: null,
|
|
72
|
-
thinking && !streamContent
|
|
98
|
+
status === 'thinking' && !streamContent
|
|
73
99
|
? h(Box, { height: 1 },
|
|
74
|
-
h(Text, { color: hex.
|
|
100
|
+
h(Text, { color: hex.textMuted }, ' \u25CF processing...')
|
|
75
101
|
)
|
|
76
102
|
: null,
|
|
77
103
|
);
|
package/src/config/theme.js
CHANGED
|
@@ -2,33 +2,37 @@ import chalk from 'chalk';
|
|
|
2
2
|
import gradient from 'gradient-string';
|
|
3
3
|
|
|
4
4
|
export const hex = {
|
|
5
|
-
bg: '#
|
|
6
|
-
surface: '#
|
|
7
|
-
surfaceAlt: '#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
bg: '#0D0D14',
|
|
6
|
+
surface: '#111118',
|
|
7
|
+
surfaceAlt: '#16161E',
|
|
8
|
+
cardBg: '#1C1C1C',
|
|
9
|
+
inputBg: '#1A1A22',
|
|
10
|
+
codeBg: '#0D0D14',
|
|
11
|
+
selectionBg: '#FF9F43',
|
|
12
|
+
selectionText: '#000000',
|
|
13
|
+
accent: '#FF9F43',
|
|
14
|
+
orange: '#FF9F43',
|
|
13
15
|
purple: '#A855F7',
|
|
14
16
|
green: '#22C55E',
|
|
15
17
|
red: '#EF4444',
|
|
16
18
|
gold: '#F59E0B',
|
|
17
19
|
blue: '#3B82F6',
|
|
18
20
|
cyan: '#22D3EE',
|
|
19
|
-
text: '#
|
|
20
|
-
textDim: '#
|
|
21
|
-
textMuted: '#
|
|
21
|
+
text: '#EEEEF0',
|
|
22
|
+
textDim: '#9999AA',
|
|
23
|
+
textMuted: '#555566',
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
export const color = {
|
|
25
27
|
bg: chalk.hex(hex.bg),
|
|
26
28
|
surface: chalk.hex(hex.surface),
|
|
27
29
|
surfaceAlt: chalk.hex(hex.surfaceAlt),
|
|
28
|
-
|
|
30
|
+
cardBg: chalk.hex(hex.cardBg),
|
|
31
|
+
inputBg: chalk.hex(hex.inputBg),
|
|
29
32
|
selectionBg: chalk.hex(hex.selectionBg),
|
|
30
33
|
selectionText: chalk.hex(hex.selectionText),
|
|
31
34
|
accent: chalk.hex(hex.accent),
|
|
35
|
+
orange: chalk.hex(hex.orange),
|
|
32
36
|
purple: chalk.hex(hex.purple),
|
|
33
37
|
green: chalk.hex(hex.green),
|
|
34
38
|
red: chalk.hex(hex.red),
|
|
@@ -40,4 +44,4 @@ export const color = {
|
|
|
40
44
|
textMuted: chalk.hex(hex.textMuted),
|
|
41
45
|
};
|
|
42
46
|
|
|
43
|
-
export const appGradient = gradient(['#
|
|
47
|
+
export const appGradient = gradient(['#FF9F43', '#A855F7', '#3B82F6']);
|
package/src/components/Header.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { appGradient } from '../config/theme.js';
|
|
4
|
-
const { createElement: h } = React;
|
|
5
|
-
|
|
6
|
-
export function Header({ cols }) {
|
|
7
|
-
const label = appGradient('CLARITY');
|
|
8
|
-
return h(Box, { height: 3, width: '100%', alignItems: 'center', justifyContent: 'center' },
|
|
9
|
-
h(Text, { bold: true }, label)
|
|
10
|
-
);
|
|
11
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import TextInput from 'ink-text-input';
|
|
4
|
-
import { hex } from '../config/theme.js';
|
|
5
|
-
const { createElement: h } = React;
|
|
6
|
-
|
|
7
|
-
const DIVIDER = '\u2500';
|
|
8
|
-
|
|
9
|
-
export function PromptCard({ width, compact, provider, model, agentMode, thinking, onSubmit }) {
|
|
10
|
-
const [input, setInput] = useState('');
|
|
11
|
-
const mShort = model.replace(/^[^/]+\//, '').slice(0, compact ? 10 : 20);
|
|
12
|
-
const pShort = provider.slice(0, compact ? 6 : 10);
|
|
13
|
-
const statusLine = '[ ' + pShort + ' \u00B7 ' + mShort + (agentMode ? ' \u00B7 AGENT' : '') + ' ]';
|
|
14
|
-
const innerW = width - 2;
|
|
15
|
-
|
|
16
|
-
function handleSubmit(value) {
|
|
17
|
-
onSubmit(value);
|
|
18
|
-
setInput('');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return h(Box, { flexDirection: 'column', width, backgroundColor: hex.surface },
|
|
22
|
-
h(Box, { height: 1 },
|
|
23
|
-
h(Text, { color: hex.textMuted },
|
|
24
|
-
'\u250C' + '\u2500'.repeat(Math.max(0, innerW)) + '\u2510')
|
|
25
|
-
),
|
|
26
|
-
h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
|
|
27
|
-
h(TextInput, {
|
|
28
|
-
value: input,
|
|
29
|
-
onChange: setInput,
|
|
30
|
-
onSubmit: handleSubmit,
|
|
31
|
-
placeholder: 'Ask anything...',
|
|
32
|
-
focus: !thinking,
|
|
33
|
-
})
|
|
34
|
-
),
|
|
35
|
-
h(Box, { height: 1 },
|
|
36
|
-
h(Text, { color: hex.textMuted },
|
|
37
|
-
'\u2502' + '\u2500'.repeat(Math.max(0, innerW)) + '\u2502')
|
|
38
|
-
),
|
|
39
|
-
h(Box, { height: 1, paddingLeft: 1, paddingRight: 1, alignItems: 'center' },
|
|
40
|
-
h(Text, { color: hex.textDim },
|
|
41
|
-
statusLine + ' '.repeat(Math.max(0, innerW - statusLine.length - 1))
|
|
42
|
-
),
|
|
43
|
-
h(Text, { color: hex.textMuted }, '\u2502')
|
|
44
|
-
),
|
|
45
|
-
h(Box, { height: 1 },
|
|
46
|
-
h(Text, { color: hex.textMuted },
|
|
47
|
-
'\u2514' + '\u2500'.repeat(Math.max(0, innerW)) + '\u2518')
|
|
48
|
-
),
|
|
49
|
-
);
|
|
50
|
-
}
|