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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clarity-ai",
3
- "version": "4.1.0",
4
- "description": "Premium terminal AI chat for Termux — Ink+React TUI with streaming, markdown, agent mode",
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": "^5",
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, useApp } from 'ink';
2
+ import { Box } from 'ink';
3
3
  import { Banner } from './components/Banner.js';
4
4
  import { MessageList } from './components/MessageList.js';
5
- import { InputArea } from './components/InputArea.js';
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', overflowY: 'hidden' },
49
+ h(Box, { flexGrow: 1, flexDirection: 'column' },
55
50
  showBanner ? h(Banner) : null,
56
- h(MessageList, { messages, thinking: state.thinking, scrollOffset, termHeight })
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(InputArea, { onSubmit, onSlash: () => setShowCommands(true), thinking: state.thinking }),
68
- h(StatusBar, { provider, model, agentMode: state.agentMode })
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 YouMessage({ text }) {
13
- return h(Box, { flexDirection: 'column', marginBottom: 1 },
14
- h(Box, null,
15
- h(Text, { color: '#00FFD1', bold: true }, 'YOU '),
16
- h(Text, { color: '#FF2EF7' }, '\u2500'.repeat(Math.max(0, termWidth() - 7)))
17
- ),
18
- h(Box, { paddingLeft: 2 },
19
- h(Text, { wrap: 'wrap' }, text)
20
- )
21
- );
22
- }
23
-
24
- function AssistantMessage({ text, streaming }) {
25
- let content;
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
- return h(Box, { flexDirection: 'column', marginBottom: 1 },
39
- h(Box, null,
40
- h(Text, { color: '#F0F0F0', bold: true }, 'CLARITY '),
41
- h(Text, { color: '#555555' }, '\u2500'.repeat(Math.max(0, termWidth() - 11)))
42
- ),
43
- h(Box, { paddingLeft: 2 }, content)
44
- );
45
- }
46
-
47
- function ToolMessage({ text }) {
48
- const lines = text.split('\n');
49
- const header = lines[0];
50
- const body = lines.slice(1).join('\n');
51
-
52
- return h(Box, { flexDirection: 'column', marginBottom: 1 },
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
- function SystemMessage({ text }) {
67
- return h(Box, { paddingLeft: 2, marginBottom: 1 },
68
- h(Text, { color: '#00FF88' }, '\u2714 '),
69
- h(Text, { color: '#00FF88' }, text)
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
- return h(ErrorMessage, { raw: msg.content });
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
- if (msg.role === 'system') return h(SystemMessage, { text: msg.content });
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
+ }