clarity-ai 4.1.0 → 4.2.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/package.json +4 -10
- package/src/app.js +17 -11
- package/src/components/MessageBubble.js +53 -69
- package/src/components/PromptBox.js +41 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarity-ai",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "Premium terminal AI chat for Termux —
|
|
3
|
+
"version": "4.2.0",
|
|
4
|
+
"description": "Premium terminal AI chat for Termux — OpenCode-style UI with prompt box, bg colors, side lines",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"clarity": "bin/clarity.js"
|
|
@@ -15,17 +15,11 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"ink": "^5",
|
|
17
17
|
"react": "^18",
|
|
18
|
-
"ink-text-input": "^
|
|
19
|
-
"ink-select-input": "^5",
|
|
18
|
+
"ink-text-input": "^6.0.0",
|
|
20
19
|
"ink-spinner": "^5",
|
|
21
20
|
"ink-big-text": "^2",
|
|
22
21
|
"ink-gradient": "^3",
|
|
23
22
|
"marked": "^12",
|
|
24
|
-
"cli-highlight": "^2"
|
|
25
|
-
"wrap-ansi": "^9",
|
|
26
|
-
"string-width": "^7",
|
|
27
|
-
"strip-ansi": "^7",
|
|
28
|
-
"ansi-escapes": "^6",
|
|
29
|
-
"groq-sdk": "^2"
|
|
23
|
+
"cli-highlight": "^2"
|
|
30
24
|
}
|
|
31
25
|
}
|
package/src/app.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import React, { useState, useCallback } from 'react';
|
|
2
|
-
import { Box
|
|
2
|
+
import { Box } from 'ink';
|
|
3
3
|
import { Banner } from './components/Banner.js';
|
|
4
4
|
import { MessageList } from './components/MessageList.js';
|
|
5
|
-
import {
|
|
6
|
-
import { StatusBar } from './components/StatusBar.js';
|
|
5
|
+
import { PromptBox } from './components/PromptBox.js';
|
|
7
6
|
import { CommandPicker } from './components/CommandPicker.js';
|
|
8
7
|
import { ModelPicker } from './components/ModelPicker.js';
|
|
9
8
|
import { useScroll } from './hooks/useScroll.js';
|
|
10
|
-
import { useHistory } from './hooks/useHistory.js';
|
|
11
9
|
import { createChatState, handleSend, handleCommand } from './chat.js';
|
|
12
10
|
const { createElement: h } = React;
|
|
13
11
|
|
|
@@ -19,7 +17,6 @@ export function App({ config }) {
|
|
|
19
17
|
const [showModels, setShowModels] = useState(false);
|
|
20
18
|
const [showBanner, setShowBanner] = useState(true);
|
|
21
19
|
const { scrollOffset, termHeight } = useScroll(state.messages.length);
|
|
22
|
-
const { goBack, goForward } = useHistory();
|
|
23
20
|
|
|
24
21
|
const onSubmit = useCallback(async (input) => {
|
|
25
22
|
if (input.startsWith('/')) {
|
|
@@ -48,12 +45,15 @@ export function App({ config }) {
|
|
|
48
45
|
}));
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
const messages = state.messages;
|
|
52
|
-
|
|
53
48
|
return h(Box, { flexDirection: 'column', height: '100%' },
|
|
54
|
-
h(Box, { flexGrow: 1, flexDirection: 'column'
|
|
49
|
+
h(Box, { flexGrow: 1, flexDirection: 'column' },
|
|
55
50
|
showBanner ? h(Banner) : null,
|
|
56
|
-
h(MessageList, {
|
|
51
|
+
h(MessageList, {
|
|
52
|
+
messages: state.messages,
|
|
53
|
+
thinking: state.thinking,
|
|
54
|
+
scrollOffset,
|
|
55
|
+
termHeight,
|
|
56
|
+
})
|
|
57
57
|
),
|
|
58
58
|
showCommands ? h(CommandPicker, {
|
|
59
59
|
query: '',
|
|
@@ -64,7 +64,13 @@ export function App({ config }) {
|
|
|
64
64
|
onSelect: handleModelSelect,
|
|
65
65
|
onClose: () => setShowModels(false),
|
|
66
66
|
}) : null,
|
|
67
|
-
h(
|
|
68
|
-
|
|
67
|
+
h(PromptBox, {
|
|
68
|
+
provider,
|
|
69
|
+
model,
|
|
70
|
+
agentMode: state.agentMode,
|
|
71
|
+
thinking: state.thinking,
|
|
72
|
+
onSlash: () => setShowCommands(true),
|
|
73
|
+
onSubmit,
|
|
74
|
+
})
|
|
69
75
|
);
|
|
70
76
|
}
|
|
@@ -1,82 +1,66 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import { marked } from 'marked';
|
|
4
|
-
import { renderMarkdown } from '../renderer/markdown.js';
|
|
5
|
-
import { renderTable } from '../renderer/table.js';
|
|
6
|
-
import { renderDiff } from '../renderer/diff.js';
|
|
7
|
-
import { ErrorMessage } from './ErrorMessage.js';
|
|
8
3
|
const { createElement: h } = React;
|
|
9
|
-
|
|
10
4
|
const termWidth = () => process.stdout.columns || 80;
|
|
11
5
|
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
h(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (streaming) {
|
|
27
|
-
content = h(Text, { wrap: 'wrap' }, text);
|
|
28
|
-
} else {
|
|
29
|
-
try {
|
|
30
|
-
const tokens = marked.lexer(text);
|
|
31
|
-
const rendered = renderMarkdown(tokens);
|
|
32
|
-
content = h(Box, { flexDirection: 'column' }, ...rendered);
|
|
33
|
-
} catch {
|
|
34
|
-
content = h(Text, { wrap: 'wrap' }, text);
|
|
35
|
-
}
|
|
6
|
+
export function MessageBubble({ msg }) {
|
|
7
|
+
if (msg.role === 'user') {
|
|
8
|
+
const lines = String(msg.content).split('\n');
|
|
9
|
+
return h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
10
|
+
h(Box, null,
|
|
11
|
+
h(Text, { color: '#9B59FF', bold: true }, '\u276f YOU '),
|
|
12
|
+
h(Text, { color: '#9B59FF' }, '\u2500'.repeat(Math.max(0, termWidth() - 9)))
|
|
13
|
+
),
|
|
14
|
+
lines.map((line, i) =>
|
|
15
|
+
h(Text, { key: i, color: '#C39BD3', backgroundColor: '#2D1B4E' },
|
|
16
|
+
' ' + line
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
);
|
|
36
20
|
}
|
|
37
21
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
h(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
h(Box, null,
|
|
54
|
-
h(Text, { color: '#FFD700', bold: true }, 'TOOL '),
|
|
55
|
-
h(Text, { color: '#555555' }, '\u2500'.repeat(Math.max(0, termWidth() - 8)))
|
|
56
|
-
),
|
|
57
|
-
h(Box, { paddingLeft: 2 },
|
|
58
|
-
h(Text, { color: '#FFD700', dimColor: true }, header)
|
|
59
|
-
),
|
|
60
|
-
body ? h(Box, { paddingLeft: 2 },
|
|
61
|
-
h(Text, { color: '#F0F0F0' }, body)
|
|
62
|
-
) : null
|
|
63
|
-
);
|
|
64
|
-
}
|
|
22
|
+
if (msg.role === 'assistant') {
|
|
23
|
+
const lines = String(msg.content).split('\n');
|
|
24
|
+
return h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
25
|
+
h(Box, null,
|
|
26
|
+
h(Text, { color: '#7B2FFF', bold: true }, '\u25c6 CLARITY '),
|
|
27
|
+
h(Text, { color: '#555555' }, '\u2500'.repeat(Math.max(0, termWidth() - 13)))
|
|
28
|
+
),
|
|
29
|
+
lines.map((line, i) =>
|
|
30
|
+
h(Box, { key: i },
|
|
31
|
+
h(Text, { color: '#7B2FFF' }, '\u2502'),
|
|
32
|
+
h(Text, { color: '#F0F0F0' }, ' ' + line)
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
65
37
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
38
|
+
if (msg.role === 'tool') {
|
|
39
|
+
return h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
40
|
+
h(Box, null,
|
|
41
|
+
h(Text, { color: '#FFD700', bold: true }, '\u2699 TOOL '),
|
|
42
|
+
h(Text, { color: '#555555' }, '\u2500'.repeat(Math.max(0, termWidth() - 11)))
|
|
43
|
+
),
|
|
44
|
+
h(Box, { paddingLeft: 2 },
|
|
45
|
+
h(Text, { color: '#AAAAAA' }, String(msg.content))
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
72
49
|
|
|
73
|
-
export function MessageBubble({ msg }) {
|
|
74
|
-
if (msg.role === 'user') return h(YouMessage, { text: msg.content });
|
|
75
|
-
if (msg.role === 'assistant') return h(AssistantMessage, { text: msg.content, streaming: msg.streaming });
|
|
76
|
-
if (msg.role === 'tool') return h(ToolMessage, { text: msg.content });
|
|
77
50
|
if (msg.role === 'error') {
|
|
78
|
-
|
|
51
|
+
let display = String(msg.content).slice(0, 120);
|
|
52
|
+
return h(Box, { paddingLeft: 2, marginBottom: 1 },
|
|
53
|
+
h(Text, { color: '#FF4455' }, '\u2716 '),
|
|
54
|
+
h(Text, { color: '#FF4455' }, display)
|
|
55
|
+
);
|
|
79
56
|
}
|
|
80
|
-
|
|
57
|
+
|
|
58
|
+
if (msg.role === 'system') {
|
|
59
|
+
return h(Box, { paddingLeft: 2, marginBottom: 1 },
|
|
60
|
+
h(Text, { color: '#00FF88' }, '\u2714 '),
|
|
61
|
+
h(Text, { color: '#00FF88' }, String(msg.content))
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
81
65
|
return null;
|
|
82
66
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
const { createElement: h } = React;
|
|
5
|
+
|
|
6
|
+
export function PromptBox({ provider, model, agentMode, thinking, onSlash, onSubmit }) {
|
|
7
|
+
const [value, setValue] = useState('');
|
|
8
|
+
const w = process.stdout.columns || 80;
|
|
9
|
+
|
|
10
|
+
function handleChange(val) {
|
|
11
|
+
setValue(val);
|
|
12
|
+
if (val === '/') { setValue(''); onSlash?.(); }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function handleSubmit(val) {
|
|
16
|
+
setValue('');
|
|
17
|
+
onSubmit(val);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return h(Box, { flexDirection: 'column', flexShrink: 0 },
|
|
21
|
+
h(Text, null,
|
|
22
|
+
h(Text, { color: '#333333' }, '\u2502 '),
|
|
23
|
+
h(Text, { color: '#00FFFF' }, '\u276f '),
|
|
24
|
+
h(Text, { color: '#555555' }, provider + '/' + model + ' '),
|
|
25
|
+
h(Text, { color: agentMode ? '#00FF9F' : '#555555' }, agentMode ? '\u2714 agent' : '\u2716 agent'),
|
|
26
|
+
h(Text, { color: '#333333' }, ' '.repeat(Math.max(1, w - (provider + '/' + model).length - 18)) + '\u2502')
|
|
27
|
+
),
|
|
28
|
+
h(Text, { color: '#333333' },
|
|
29
|
+
'\u2514' + '\u2500'.repeat(Math.max(0, w - 2)) + '\u2518'
|
|
30
|
+
),
|
|
31
|
+
h(Box, null,
|
|
32
|
+
h(Text, { color: '#00FFFF' }, ' \u276f '),
|
|
33
|
+
h(TextInput, {
|
|
34
|
+
value,
|
|
35
|
+
onChange: handleChange,
|
|
36
|
+
onSubmit: handleSubmit,
|
|
37
|
+
placeholder: thinking ? 'Thinking...' : 'Ask anything or type / for commands',
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
}
|