flowmind 1.0.1 → 1.2.2
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/README_CN.md +248 -0
- package/bin/flowmind.js +638 -2
- package/config/ai-config.example.json +64 -0
- package/config/claude-mcp-config.example.json +12 -0
- package/core/ai/base-model.js +70 -0
- package/core/ai/index.js +29 -0
- package/core/ai/model-manager.js +320 -0
- package/core/ai/prompts/extraction.js +38 -0
- package/core/ai/prompts/index.js +11 -0
- package/core/ai/prompts/intent.js +43 -0
- package/core/ai/prompts/learning.js +46 -0
- package/core/ai/prompts/selection.js +38 -0
- package/core/ai/prompts/summary.js +35 -0
- package/core/ai/providers/anthropic.js +93 -0
- package/core/ai/providers/deepseek.js +80 -0
- package/core/ai/providers/ernie.js +111 -0
- package/core/ai/providers/glm.js +80 -0
- package/core/ai/providers/mimo.js +80 -0
- package/core/ai/providers/ollama.js +147 -0
- package/core/ai/providers/openai.js +82 -0
- package/core/ai/providers/qwen.js +80 -0
- package/core/event-bus.js +17 -0
- package/core/honor-engine.js +255 -0
- package/core/index.js +115 -13
- package/core/learning-engine.js +29 -1
- package/core/skill-loader.js +31 -9
- package/dashboard/app.jsx +29 -0
- package/dashboard/components/ActivityFeed.jsx +86 -0
- package/dashboard/components/DragonPanel.jsx +67 -0
- package/dashboard/components/McpStatusBar.jsx +43 -0
- package/dashboard/components/StatsRow.jsx +65 -0
- package/mcp/server.js +328 -0
- package/package.json +19 -7
- package/tui/app.jsx +69 -0
- package/tui/components/ChatPanel.jsx +72 -0
- package/tui/components/DragonTotem.jsx +108 -0
- package/tui/components/ResultPanel.jsx +33 -0
- package/tui/components/Sidebar.jsx +70 -0
- package/tui/components/StatusBar.jsx +49 -0
package/tui/app.jsx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text, useApp, useInput } = require('ink');
|
|
3
|
+
const Sidebar = require('./components/Sidebar.jsx');
|
|
4
|
+
const ChatPanel = require('./components/ChatPanel.jsx');
|
|
5
|
+
const ResultPanel = require('./components/ResultPanel.jsx');
|
|
6
|
+
const StatusBar = require('./components/StatusBar.jsx');
|
|
7
|
+
|
|
8
|
+
function App({ flowmind }) {
|
|
9
|
+
const [results, setResults] = React.useState([]);
|
|
10
|
+
const [isProcessing, setIsProcessing] = React.useState(false);
|
|
11
|
+
const mountedRef = React.useRef(true);
|
|
12
|
+
const { exit } = useApp();
|
|
13
|
+
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
return () => { mountedRef.current = false; };
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
useInput((input, key) => {
|
|
19
|
+
if (key.ctrl && input === 'c') exit();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const handleCommand = React.useCallback(async (input, addResponse) => {
|
|
23
|
+
setIsProcessing(true);
|
|
24
|
+
try {
|
|
25
|
+
const result = await flowmind.process(input);
|
|
26
|
+
if (!mountedRef.current) return;
|
|
27
|
+
if (result.type === 'result') {
|
|
28
|
+
const text = typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2);
|
|
29
|
+
addResponse(text.substring(0, 200) + (text.length > 200 ? '...' : ''));
|
|
30
|
+
} else if (result.type === 'learning') {
|
|
31
|
+
addResponse(result.message || 'Learning recorded');
|
|
32
|
+
} else if (result.type === 'error') {
|
|
33
|
+
addResponse('Error: ' + result.message);
|
|
34
|
+
}
|
|
35
|
+
setResults(prev => [...prev, result]);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (mountedRef.current) addResponse('Error: ' + e.message);
|
|
38
|
+
} finally {
|
|
39
|
+
if (mountedRef.current) setIsProcessing(false);
|
|
40
|
+
}
|
|
41
|
+
}, [flowmind]);
|
|
42
|
+
|
|
43
|
+
const handleSkillSelect = React.useCallback((skill) => {
|
|
44
|
+
try {
|
|
45
|
+
setResults(prev => [...prev, {
|
|
46
|
+
type: 'result',
|
|
47
|
+
data: { name: skill.name, description: skill.definition?.description || 'No description', category: skill.category || 'general', path: skill.path },
|
|
48
|
+
metadata: { skill: skill.name }
|
|
49
|
+
}]);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// ignore skill select errors
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
React.createElement(Box, { flexDirection: 'column', width: '100%', height: '100%' },
|
|
57
|
+
React.createElement(Box, { flexDirection: 'row', flexGrow: 1 },
|
|
58
|
+
React.createElement(Sidebar, { flowmind: flowmind, width: 30, onSkillSelect: handleSkillSelect }),
|
|
59
|
+
React.createElement(Box, { flexDirection: 'column', width: '70%', flexGrow: 1 },
|
|
60
|
+
React.createElement(ChatPanel, { onSubmit: handleCommand, isProcessing: isProcessing, onExit: exit }),
|
|
61
|
+
React.createElement(ResultPanel, { results: results })
|
|
62
|
+
)
|
|
63
|
+
),
|
|
64
|
+
React.createElement(StatusBar, { flowmind: flowmind })
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = App;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text } = require('ink');
|
|
3
|
+
const TextInput = require('ink-text-input').default || require('ink-text-input');
|
|
4
|
+
const Spinner = require('ink-spinner').default || require('ink-spinner');
|
|
5
|
+
|
|
6
|
+
function ChatPanel({ onSubmit, isProcessing, onExit }) {
|
|
7
|
+
const [input, setInput] = React.useState('');
|
|
8
|
+
const [history, setHistory] = React.useState([]);
|
|
9
|
+
const mountedRef = React.useRef(true);
|
|
10
|
+
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
return () => { mountedRef.current = false; };
|
|
13
|
+
}, []);
|
|
14
|
+
|
|
15
|
+
const handleSubmit = (value) => {
|
|
16
|
+
if (!value.trim()) return;
|
|
17
|
+
setHistory(prev => [...prev, { role: 'user', text: value }]);
|
|
18
|
+
setInput('');
|
|
19
|
+
if (value.toLowerCase() === 'exit' || value.toLowerCase() === 'quit') {
|
|
20
|
+
if (onExit) onExit();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
onSubmit(value, (response) => {
|
|
25
|
+
if (mountedRef.current) {
|
|
26
|
+
setHistory(prev => [...prev, { role: 'flowmind', text: response }]);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
} catch (e) {
|
|
30
|
+
if (mountedRef.current) {
|
|
31
|
+
setHistory(prev => [...prev, { role: 'flowmind', text: 'Error: ' + e.message }]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const displayHistory = history.slice(-20);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'green', paddingX: 1 },
|
|
40
|
+
React.createElement(Text, { bold: true, color: 'green' }, 'Command Input'),
|
|
41
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1, minHeight: 6 },
|
|
42
|
+
displayHistory.length === 0 && React.createElement(Text, { color: 'gray' }, 'Type a command to get started. Type "exit" to quit.'),
|
|
43
|
+
displayHistory.map((msg, i) =>
|
|
44
|
+
React.createElement(Box, { key: i, flexDirection: 'column' },
|
|
45
|
+
msg.role === 'user'
|
|
46
|
+
? React.createElement(Text, null,
|
|
47
|
+
React.createElement(Text, { color: 'green', bold: true }, '> '),
|
|
48
|
+
React.createElement(Text, { color: 'white' }, msg.text)
|
|
49
|
+
)
|
|
50
|
+
: React.createElement(Text, null,
|
|
51
|
+
React.createElement(Text, { color: 'cyan', bold: true }, '< '),
|
|
52
|
+
React.createElement(Text, { color: 'cyan' }, msg.text)
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
),
|
|
57
|
+
React.createElement(Box, { marginTop: 1 },
|
|
58
|
+
isProcessing
|
|
59
|
+
? React.createElement(Text, { color: 'yellow' },
|
|
60
|
+
React.createElement(Spinner, { type: 'dots' }),
|
|
61
|
+
' Processing...'
|
|
62
|
+
)
|
|
63
|
+
: React.createElement(Box, null,
|
|
64
|
+
React.createElement(Text, { color: 'green', bold: true }, '> '),
|
|
65
|
+
React.createElement(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit })
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = ChatPanel;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text } = require('ink');
|
|
3
|
+
|
|
4
|
+
const DRAGON_ARTS = {
|
|
5
|
+
0: [
|
|
6
|
+
' ╭─────╮ ',
|
|
7
|
+
' ╱ ╭─╮ ╲ ',
|
|
8
|
+
' │ │ │ │ ',
|
|
9
|
+
' │ │ ◎ │ │ ',
|
|
10
|
+
' │ ╰─╯ │ ',
|
|
11
|
+
' ╲ ╱ ',
|
|
12
|
+
' ╰─────╯ ',
|
|
13
|
+
],
|
|
14
|
+
1: [
|
|
15
|
+
' ╭──╮ ',
|
|
16
|
+
' ╭────╯ ╰───╮ ',
|
|
17
|
+
' ╱ ◎ ╰─╯ ╲ ',
|
|
18
|
+
' ╱ ▽ ╲ ',
|
|
19
|
+
' ╲ ╱╲ ╱╲ ╱ ',
|
|
20
|
+
' ╲╱╱ ╲╱╱ ╲╱╲╱ ',
|
|
21
|
+
],
|
|
22
|
+
2: [
|
|
23
|
+
' ╭─╮ ╭─╮ ',
|
|
24
|
+
' ╭────╯ ╰──╯ ╰───╮ ',
|
|
25
|
+
' ╱ ◎ ╰──╯ ╲ ',
|
|
26
|
+
' ╱ ╭────────╮ ╲ ',
|
|
27
|
+
' ╲ ╱ ╱╱╱╱╱╱╱╱ ╲ ╱ ',
|
|
28
|
+
' ╲───╯ ╱╱╱╱╱╱╱╱╱╱ ╰──╱ ',
|
|
29
|
+
' ╰─╯ ╰─╯ ',
|
|
30
|
+
],
|
|
31
|
+
3: [
|
|
32
|
+
' ╭───╮ ╭───╮ ',
|
|
33
|
+
' ╭───╯ ╰──╯ ╰───╮ ',
|
|
34
|
+
' ╱ ◎ ╰───╯ ╲ ',
|
|
35
|
+
'│ ╭──────────╮ │ ',
|
|
36
|
+
'│ ╱ ╱╱╱╱╱╱╱╱╱╱ ╲ │ ',
|
|
37
|
+
' ╲──╯ ╱╱╱╱╱╱╱╱╱╱╱╱ ╰───╯ ',
|
|
38
|
+
' ╲ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╱ ',
|
|
39
|
+
' ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ',
|
|
40
|
+
' ╰───╯ ╰───╯ ',
|
|
41
|
+
],
|
|
42
|
+
4: [
|
|
43
|
+
' ╭───╮ ╭───╮ ',
|
|
44
|
+
'╭───╯ ╰──────╯ ╰───╮ ',
|
|
45
|
+
'│ ◎ ╰───╯ │ ',
|
|
46
|
+
'│ ╭────────────╮ │ ',
|
|
47
|
+
'│ ╱ ╱╱╱╱╱╱╱╱╱╱╱╱ ╲ │ ',
|
|
48
|
+
' ╲───╯ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╰──╯ ',
|
|
49
|
+
' ╲ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╲ ',
|
|
50
|
+
' ╲─╯╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╰─╲ ',
|
|
51
|
+
' ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ',
|
|
52
|
+
' ╰───╯ ╰───╯ ',
|
|
53
|
+
],
|
|
54
|
+
5: [
|
|
55
|
+
' ★ ╭───╮ ╭───╮ ★ ',
|
|
56
|
+
'╭─╯ ╰──╯ ╰──╯ ╰─╮ ',
|
|
57
|
+
'│ ◎ ╰───╯ │ ',
|
|
58
|
+
'│ ╭──────────────╮ │ ',
|
|
59
|
+
'│ ╱ ★╱╱╱╱╱╱╱╱╱╱★╱╱ ╲ │ ',
|
|
60
|
+
' ╲────╯ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╰───╯ ',
|
|
61
|
+
' ╲ ╱╱╱╱★╱╱╱╱╱╱╱╱★╱╱╱╱╱ ╲ ',
|
|
62
|
+
' ╲──╯╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╰──╲ ',
|
|
63
|
+
' ╲─╯╱╱╱★╱╱╱╱╱╱╱╱★╱╱╱╱╱╰──╲ ',
|
|
64
|
+
' ★ ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ★ ',
|
|
65
|
+
' ╰───╯ ╰───╯ ',
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const LEVEL_NAMES = ['Egg', 'Hatchling', 'Juvenile', 'Adult', 'Elder', 'Ascended'];
|
|
70
|
+
const LEVEL_STATES = ['dormant', 'awakening', 'growing', 'soaring', 'wise', 'transcendent'];
|
|
71
|
+
const LEVEL_COLORS = ['gray', 'cyan', 'cyan', 'cyanBright', 'cyanBright', 'cyanBright'];
|
|
72
|
+
|
|
73
|
+
function DragonTotem({ honorData, compact }) {
|
|
74
|
+
const points = honorData?.points || 0;
|
|
75
|
+
const level = honorData?.level || 0;
|
|
76
|
+
const art = DRAGON_ARTS[level] || DRAGON_ARTS[0];
|
|
77
|
+
const color = LEVEL_COLORS[level] || 'gray';
|
|
78
|
+
const levelName = LEVEL_NAMES[level] || 'Unknown';
|
|
79
|
+
const state = LEVEL_STATES[level] || 'unknown';
|
|
80
|
+
const nextLevelPoints = [1, 10, 30, 60, 100];
|
|
81
|
+
const nextPoints = nextLevelPoints[level] || null;
|
|
82
|
+
const pointsToNext = nextPoints !== null ? nextPoints - points : 0;
|
|
83
|
+
const lines = compact ? art.slice(0, 4) : art;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
React.createElement(Box, { flexDirection: 'column', paddingX: 1 },
|
|
87
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'Dragon Totem'),
|
|
88
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
89
|
+
lines.map((line, i) => React.createElement(Text, { key: i, color: color }, line))
|
|
90
|
+
),
|
|
91
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
92
|
+
React.createElement(Text, null,
|
|
93
|
+
React.createElement(Text, { color: 'yellow' }, 'Lv' + level),
|
|
94
|
+
React.createElement(Text, { color: 'white' }, ' ' + levelName),
|
|
95
|
+
React.createElement(Text, { color: 'gray' }, ' (' + state + ')')
|
|
96
|
+
),
|
|
97
|
+
React.createElement(Text, null,
|
|
98
|
+
React.createElement(Text, { color: 'yellow' }, '' + points),
|
|
99
|
+
React.createElement(Text, { color: 'gray' }, ' pts')
|
|
100
|
+
),
|
|
101
|
+
pointsToNext > 0 && React.createElement(Text, { color: 'gray' }, pointsToNext + ' pts to ' + LEVEL_NAMES[level + 1]),
|
|
102
|
+
pointsToNext <= 0 && level >= 5 && React.createElement(Text, { color: 'yellow' }, 'Max level!')
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = DragonTotem;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text } = require('ink');
|
|
3
|
+
|
|
4
|
+
function ResultPanel({ results }) {
|
|
5
|
+
const displayResults = results.slice(-30);
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'yellow', paddingX: 1, flexGrow: 1 },
|
|
9
|
+
React.createElement(Text, { bold: true, color: 'yellow' }, 'Results'),
|
|
10
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1, overflow: 'hidden' },
|
|
11
|
+
displayResults.length === 0 && React.createElement(Text, { color: 'gray' }, 'No results yet. Execute a command to see output here.'),
|
|
12
|
+
displayResults.map((result, i) =>
|
|
13
|
+
React.createElement(Box, { key: i, flexDirection: 'column', marginBottom: 1 },
|
|
14
|
+
result.type === 'result' && React.createElement(React.Fragment, null,
|
|
15
|
+
result.metadata?.skill && React.createElement(Text, null,
|
|
16
|
+
React.createElement(Text, { color: 'gray' }, 'Skill: '),
|
|
17
|
+
React.createElement(Text, { color: 'green' }, result.metadata.skill),
|
|
18
|
+
result.metadata?.duration && React.createElement(Text, { color: 'gray' }, ' (' + result.metadata.duration + 'ms)')
|
|
19
|
+
),
|
|
20
|
+
React.createElement(Text, { color: 'white' },
|
|
21
|
+
typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2)
|
|
22
|
+
)
|
|
23
|
+
),
|
|
24
|
+
result.type === 'learning' && React.createElement(Text, { color: 'cyan' }, result.message),
|
|
25
|
+
result.type === 'error' && React.createElement(Text, { color: 'red' }, 'Error: ' + result.message)
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = ResultPanel;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text, useInput } = require('ink');
|
|
3
|
+
const DragonTotem = require('./DragonTotem.jsx');
|
|
4
|
+
|
|
5
|
+
function Sidebar({ flowmind, width, onSkillSelect }) {
|
|
6
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
7
|
+
const [skills, setSkills] = React.useState([]);
|
|
8
|
+
const [honorData, setHonorData] = React.useState({ points: 0, level: 0, stats: {} });
|
|
9
|
+
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
if (flowmind) {
|
|
12
|
+
try {
|
|
13
|
+
const list = flowmind.skills.list() || [];
|
|
14
|
+
setSkills(list);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
setSkills([]);
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
setHonorData(flowmind.getHonorData());
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// keep default honorData
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}, [flowmind]);
|
|
25
|
+
|
|
26
|
+
useInput((input, key) => {
|
|
27
|
+
if (key.upArrow) setSelectedIndex(prev => Math.max(0, prev - 1));
|
|
28
|
+
else if (key.downArrow) setSelectedIndex(prev => Math.min(skills.length - 1, prev + 1));
|
|
29
|
+
else if (key.return && skills[selectedIndex] && onSkillSelect) onSkillSelect(skills[selectedIndex]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const barWidth = Math.max(10, width - 4);
|
|
33
|
+
const progress = honorData.points > 0 ? Math.min(1, honorData.points / 100) : 0;
|
|
34
|
+
const filled = Math.round(progress * barWidth);
|
|
35
|
+
const progressBar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
React.createElement(Box, { flexDirection: 'column', width: width, borderStyle: 'single', borderColor: 'cyan', paddingX: 1 },
|
|
39
|
+
React.createElement(DragonTotem, { honorData: honorData, compact: true }),
|
|
40
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
41
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'Progress'),
|
|
42
|
+
React.createElement(Text, { color: 'green' }, progressBar),
|
|
43
|
+
React.createElement(Text, { color: 'gray' }, honorData.points + '/100 pts')
|
|
44
|
+
),
|
|
45
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
46
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'Skills (' + skills.length + ')'),
|
|
47
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
48
|
+
skills.length === 0 && React.createElement(Text, { color: 'gray' }, 'No skills loaded'),
|
|
49
|
+
skills.map((skill, i) => {
|
|
50
|
+
const isSelected = i === selectedIndex;
|
|
51
|
+
const category = skill.category || 'general';
|
|
52
|
+
const prefix = isSelected ? '\u25B6 ' : ' ';
|
|
53
|
+
return React.createElement(Text, { key: skill.name },
|
|
54
|
+
React.createElement(Text, { color: isSelected ? 'green' : 'white' }, prefix + skill.name),
|
|
55
|
+
React.createElement(Text, { color: 'gray' }, ' [' + category + ']')
|
|
56
|
+
);
|
|
57
|
+
})
|
|
58
|
+
)
|
|
59
|
+
),
|
|
60
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
61
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'Stats'),
|
|
62
|
+
React.createElement(Text, { color: 'gray' }, 'Skills used: ' + (honorData.stats?.skillUseCount || 0)),
|
|
63
|
+
React.createElement(Text, { color: 'gray' }, 'Learnings: ' + (honorData.stats?.learningCount || 0)),
|
|
64
|
+
React.createElement(Text, { color: 'gray' }, 'New skills: ' + (honorData.stats?.newSkillCount || 0))
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = Sidebar;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const React = require('react');
|
|
2
|
+
const { Box, Text } = require('ink');
|
|
3
|
+
|
|
4
|
+
const LEVEL_NAMES = ['Egg', 'Hatchling', 'Juvenile', 'Adult', 'Elder', 'Ascended'];
|
|
5
|
+
|
|
6
|
+
function StatusBar({ flowmind }) {
|
|
7
|
+
const [aiStatus, setAiStatus] = React.useState(null);
|
|
8
|
+
const [componentStatus, setComponentStatus] = React.useState(null);
|
|
9
|
+
const [honorData, setHonorData] = React.useState(null);
|
|
10
|
+
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
if (flowmind) {
|
|
13
|
+
try {
|
|
14
|
+
setAiStatus(flowmind.getAIStatus());
|
|
15
|
+
setComponentStatus(flowmind.getComponentStatus());
|
|
16
|
+
setHonorData(flowmind.getHonorData());
|
|
17
|
+
} catch (e) { /* Non-blocking */ }
|
|
18
|
+
}
|
|
19
|
+
}, [flowmind]);
|
|
20
|
+
|
|
21
|
+
const aiName = aiStatus?.defaultProvider || 'none';
|
|
22
|
+
const aiOk = aiStatus?.initialized || false;
|
|
23
|
+
const componentCount = componentStatus ? Object.keys(componentStatus).length : 0;
|
|
24
|
+
const activeCount = componentStatus ? Object.values(componentStatus).filter(c => c.active).length : 0;
|
|
25
|
+
const level = honorData?.level || 0;
|
|
26
|
+
const points = honorData?.points || 0;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
React.createElement(Box, { borderStyle: 'single', borderColor: 'gray', paddingX: 1, justifyContent: 'space-between' },
|
|
30
|
+
React.createElement(Text, null,
|
|
31
|
+
React.createElement(Text, { color: 'gray' }, 'AI: '),
|
|
32
|
+
React.createElement(Text, { color: aiOk ? 'green' : 'red' }, aiName),
|
|
33
|
+
React.createElement(Text, { color: aiOk ? 'green' : 'red' }, aiOk ? ' \u25CF' : ' \u25CB')
|
|
34
|
+
),
|
|
35
|
+
React.createElement(Text, null,
|
|
36
|
+
React.createElement(Text, { color: 'gray' }, 'Components: '),
|
|
37
|
+
React.createElement(Text, { color: 'white' }, activeCount + '/' + componentCount),
|
|
38
|
+
React.createElement(Text, { color: 'green' }, ' active')
|
|
39
|
+
),
|
|
40
|
+
React.createElement(Text, null,
|
|
41
|
+
React.createElement(Text, { color: 'gray' }, 'Honor: '),
|
|
42
|
+
React.createElement(Text, { color: 'yellow' }, LEVEL_NAMES[level]),
|
|
43
|
+
React.createElement(Text, { color: 'gray' }, ' (' + points + ' pts)')
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = StatusBar;
|