clarity-ai 6.3.2 → 6.3.3
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/clarity_flash_14b.ipynb +245 -0
- package/clarity_heavy_20b_moe.ipynb +270 -0
- package/package.json +1 -1
- package/src/components/CommandPicker.js +17 -19
- package/src/components/Composer.js +23 -39
- package/src/components/LoadingIndicator.js +4 -7
- package/src/components/MessageList.js +34 -57
- package/src/components/ModelPicker.js +18 -28
- package/src/components/StatusBar.js +6 -7
- package/src/components/ToolCard.js +15 -27
- package/src/config/theme.js +21 -75
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# CLARITY Flash 14B — TPU Fine-Tuning\n",
|
|
8
|
+
"Trains a 14B parameter model on agent CoT + tool-calling data.\n",
|
|
9
|
+
"Target: Google Colab TPU v2-8 (free tier)\n",
|
|
10
|
+
"HF token: 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"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"cell_type": "code",
|
|
15
|
+
"metadata": {},
|
|
16
|
+
"source": [
|
|
17
|
+
"# === Install ===\n",
|
|
18
|
+
"!pip install -q torch torch-xla torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu\n",
|
|
19
|
+
"!pip install -q transformers datasets accelerate peft bitsandbytes sentencepiece huggingface_hub"
|
|
20
|
+
],
|
|
21
|
+
"execution_count": null,
|
|
22
|
+
"outputs": []
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"cell_type": "code",
|
|
26
|
+
"metadata": {},
|
|
27
|
+
"source": [
|
|
28
|
+
"# === HF Auth ===\n",
|
|
29
|
+
"from huggingface_hub import login, HfApi, create_repo\n",
|
|
30
|
+
"HF_TOKEN = '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'\n",
|
|
31
|
+
"login(token=HF_TOKEN, add_to_git_credential=True)\n",
|
|
32
|
+
"api = HfApi(token=HF_TOKEN)"
|
|
33
|
+
],
|
|
34
|
+
"execution_count": null,
|
|
35
|
+
"outputs": []
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"cell_type": "code",
|
|
39
|
+
"metadata": {},
|
|
40
|
+
"source": [
|
|
41
|
+
"# === TPU Setup ===\n",
|
|
42
|
+
"import torch\n",
|
|
43
|
+
"import torch_xla\n",
|
|
44
|
+
"import torch_xla.core.xla_model as xm\n",
|
|
45
|
+
"device = xm.xla_device()\n",
|
|
46
|
+
"print('Device:', device)\n",
|
|
47
|
+
"print('TPU cores:', torch_xla._XLAC._xla_get_num_devices())"
|
|
48
|
+
],
|
|
49
|
+
"execution_count": null,
|
|
50
|
+
"outputs": []
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"cell_type": "code",
|
|
54
|
+
"metadata": {},
|
|
55
|
+
"source": [
|
|
56
|
+
"# === Data Loading ===\n",
|
|
57
|
+
"import requests\n",
|
|
58
|
+
"import json\n",
|
|
59
|
+
"from datasets import Dataset\n",
|
|
60
|
+
"\n",
|
|
61
|
+
"DATA_URLS = [\n",
|
|
62
|
+
" 'https://huggingface.co/spaces/Universal-618/Clarity-main/main-data',\n",
|
|
63
|
+
" 'https://huggingface.co/spaces/Universal-618/Clarity-2/main-data',\n",
|
|
64
|
+
" 'https://huggingface.co/spaces/Universal-618/Clarity-3/main-data',\n",
|
|
65
|
+
"]\n",
|
|
66
|
+
"\n",
|
|
67
|
+
"all_samples = []\n",
|
|
68
|
+
"for url in DATA_URLS:\n",
|
|
69
|
+
" try:\n",
|
|
70
|
+
" r = requests.get(url, headers={'Authorization': f'Bearer {HF_TOKEN}'}, timeout=60)\n",
|
|
71
|
+
" if r.status_code == 200:\n",
|
|
72
|
+
" data = r.json()\n",
|
|
73
|
+
" samples = data if isinstance(data, list) else data.get('data', [])\n",
|
|
74
|
+
" all_samples.extend(samples)\n",
|
|
75
|
+
" print(f'Loaded {len(samples)} from {url}')\n",
|
|
76
|
+
" except Exception as e:\n",
|
|
77
|
+
" print(f'Skipped {url}: {e}')\n",
|
|
78
|
+
"\n",
|
|
79
|
+
"# Fallback: synthetic CoT samples if no data\n",
|
|
80
|
+
"if len(all_samples) < 10:\n",
|
|
81
|
+
" print('No remote data found — using synthetic samples')\n",
|
|
82
|
+
" all_samples = [\n",
|
|
83
|
+
" {'instruction': 'List files in current directory', 'response': 'I will run the ls command.\\n<tool>bash</tool><cmd>ls -la</cmd>', 'tools': 'bash'},\n",
|
|
84
|
+
" {'instruction': 'Read the file config.json', 'response': 'Let me read that file.\\n<tool>read_file</tool><path>config.json</path>', 'tools': 'read_file'},\n",
|
|
85
|
+
" {'instruction': 'Write hello world script', 'response': 'I will create the file.\\n<tool>write_file</tool><path>hello.py</path><content>print(\"hello\")</content>', 'tools': 'write_file'},\n",
|
|
86
|
+
" ]\n",
|
|
87
|
+
"\n",
|
|
88
|
+
"print(f'Total training samples: {len(all_samples)}')"
|
|
89
|
+
],
|
|
90
|
+
"execution_count": null,
|
|
91
|
+
"outputs": []
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"cell_type": "code",
|
|
95
|
+
"metadata": {},
|
|
96
|
+
"source": [
|
|
97
|
+
"# === Format for Training ===\n",
|
|
98
|
+
"def format_chat(sample):\n",
|
|
99
|
+
" inst = sample.get('instruction', sample.get('prompt', sample.get('input', '')))\n",
|
|
100
|
+
" resp = sample.get('response', sample.get('completion', sample.get('output', '')))\n",
|
|
101
|
+
" return {\n",
|
|
102
|
+
" 'text': f'<|im_start|>user\\n{inst}<|im_end|>\\n<|im_start|>assistant\\n{resp}<|im_end|>'\n",
|
|
103
|
+
" }\n",
|
|
104
|
+
"\n",
|
|
105
|
+
"dataset = Dataset.from_list([format_chat(s) for s in all_samples])\n",
|
|
106
|
+
"dataset = dataset.train_test_split(test_size=0.05, seed=42)\n",
|
|
107
|
+
"print(f'Train: {len(dataset[\"train\"])}, Test: {len(dataset[\"test\"])}')"
|
|
108
|
+
],
|
|
109
|
+
"execution_count": null,
|
|
110
|
+
"outputs": []
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"cell_type": "code",
|
|
114
|
+
"metadata": {},
|
|
115
|
+
"source": [
|
|
116
|
+
"# === Load Model ===\n",
|
|
117
|
+
"from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig\n",
|
|
118
|
+
"import torch\n",
|
|
119
|
+
"\n",
|
|
120
|
+
"MODEL_ID = 'Qwen/Qwen2.5-14B-Instruct'\n",
|
|
121
|
+
"\n",
|
|
122
|
+
"bnb = BitsAndBytesConfig(\n",
|
|
123
|
+
" load_in_4bit=True,\n",
|
|
124
|
+
" bnb_4bit_use_double_quant=True,\n",
|
|
125
|
+
" bnb_4bit_quant_type='nf4',\n",
|
|
126
|
+
" bnb_4bit_compute_dtype=torch.bfloat16,\n",
|
|
127
|
+
")\n",
|
|
128
|
+
"\n",
|
|
129
|
+
"tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, token=HF_TOKEN, trust_remote_code=True)\n",
|
|
130
|
+
"tokenizer.pad_token = tokenizer.eos_token\n",
|
|
131
|
+
"\n",
|
|
132
|
+
"model = AutoModelForCausalLM.from_pretrained(\n",
|
|
133
|
+
" MODEL_ID,\n",
|
|
134
|
+
" quantization_config=bnb,\n",
|
|
135
|
+
" device_map='auto',\n",
|
|
136
|
+
" torch_dtype=torch.bfloat16,\n",
|
|
137
|
+
" token=HF_TOKEN,\n",
|
|
138
|
+
" trust_remote_code=True,\n",
|
|
139
|
+
")\n",
|
|
140
|
+
"print(f'Model loaded: {MODEL_ID}')"
|
|
141
|
+
],
|
|
142
|
+
"execution_count": null,
|
|
143
|
+
"outputs": []
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"cell_type": "code",
|
|
147
|
+
"metadata": {},
|
|
148
|
+
"source": [
|
|
149
|
+
"# === LoRA Config ===\n",
|
|
150
|
+
"from peft import LoraConfig, get_peft_model, TaskType\n",
|
|
151
|
+
"\n",
|
|
152
|
+
"lora_config = LoraConfig(\n",
|
|
153
|
+
" task_type=TaskType.CAUSAL_LM,\n",
|
|
154
|
+
" r=16,\n",
|
|
155
|
+
" lora_alpha=32,\n",
|
|
156
|
+
" lora_dropout=0.05,\n",
|
|
157
|
+
" target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj'],\n",
|
|
158
|
+
" bias='none',\n",
|
|
159
|
+
")\n",
|
|
160
|
+
"model = get_peft_model(model, lora_config)\n",
|
|
161
|
+
"model.print_trainable_parameters()"
|
|
162
|
+
],
|
|
163
|
+
"execution_count": null,
|
|
164
|
+
"outputs": []
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"cell_type": "code",
|
|
168
|
+
"metadata": {},
|
|
169
|
+
"source": [
|
|
170
|
+
"# === Training ===\n",
|
|
171
|
+
"from transformers import TrainingArguments, Trainer, DataCollatorForSeq2Seq\n",
|
|
172
|
+
"import numpy as np\n",
|
|
173
|
+
"\n",
|
|
174
|
+
"def tokenize_fn(examples):\n",
|
|
175
|
+
" tok = tokenizer(examples['text'], truncation=True, max_length=2048, padding=False)\n",
|
|
176
|
+
" tok['labels'] = tok['input_ids'].copy()\n",
|
|
177
|
+
" return tok\n",
|
|
178
|
+
"\n",
|
|
179
|
+
"tokenized = dataset.map(tokenize_fn, remove_columns=['text'], batched=True)\n",
|
|
180
|
+
"\n",
|
|
181
|
+
"args = TrainingArguments(\n",
|
|
182
|
+
" output_dir='./clarity-flash-14b',\n",
|
|
183
|
+
" per_device_train_batch_size=1,\n",
|
|
184
|
+
" gradient_accumulation_steps=16,\n",
|
|
185
|
+
" num_train_epochs=3,\n",
|
|
186
|
+
" learning_rate=2e-4,\n",
|
|
187
|
+
" bf16=True,\n",
|
|
188
|
+
" logging_steps=10,\n",
|
|
189
|
+
" save_steps=200,\n",
|
|
190
|
+
" save_total_limit=2,\n",
|
|
191
|
+
" optim='adamw_8bit',\n",
|
|
192
|
+
" report_to='none',\n",
|
|
193
|
+
" dataloader_drop_last=False,\n",
|
|
194
|
+
")\n",
|
|
195
|
+
"\n",
|
|
196
|
+
"trainer = Trainer(\n",
|
|
197
|
+
" model=model,\n",
|
|
198
|
+
" args=args,\n",
|
|
199
|
+
" train_dataset=tokenized['train'],\n",
|
|
200
|
+
" eval_dataset=tokenized['test'],\n",
|
|
201
|
+
" data_collator=DataCollatorForSeq2Seq(tokenizer, padding=True),\n",
|
|
202
|
+
")\n",
|
|
203
|
+
"\n",
|
|
204
|
+
"trainer.train()"
|
|
205
|
+
],
|
|
206
|
+
"execution_count": null,
|
|
207
|
+
"outputs": []
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"cell_type": "code",
|
|
211
|
+
"metadata": {},
|
|
212
|
+
"source": [
|
|
213
|
+
"# === Push Weights to HF ===\n",
|
|
214
|
+
"WEIGHTS_REPO = 'Universal-618/Clarity-flash-weights'\n",
|
|
215
|
+
"try:\n",
|
|
216
|
+
" create_repo(WEIGHTS_REPO, repo_type='dataset', exist_ok=True, token=HF_TOKEN)\n",
|
|
217
|
+
" print(f'Repo {WEIGHTS_REPO} ready')\n",
|
|
218
|
+
"except Exception as e:\n",
|
|
219
|
+
" print(f'Repo exists or error: {e}')\n",
|
|
220
|
+
"\n",
|
|
221
|
+
"model.push_to_hub(WEIGHTS_REPO, token=HF_TOKEN, use_temp_dir=True)\n",
|
|
222
|
+
"tokenizer.push_to_hub(WEIGHTS_REPO, token=HF_TOKEN)\n",
|
|
223
|
+
"print(f'Weights pushed to {WEIGHTS_REPO}')"
|
|
224
|
+
],
|
|
225
|
+
"execution_count": null,
|
|
226
|
+
"outputs": []
|
|
227
|
+
}
|
|
228
|
+
],
|
|
229
|
+
"metadata": {
|
|
230
|
+
"accelerator": "TPU",
|
|
231
|
+
"colab": {
|
|
232
|
+
"provenance": []
|
|
233
|
+
},
|
|
234
|
+
"kernelspec": {
|
|
235
|
+
"display_name": "Python 3",
|
|
236
|
+
"name": "python3"
|
|
237
|
+
},
|
|
238
|
+
"language_info": {
|
|
239
|
+
"name": "python",
|
|
240
|
+
"version": "3.10.0"
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"nbformat": 4,
|
|
244
|
+
"nbformat_minor": 4
|
|
245
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# CLARITY Heavy 20B MoE — Multi-GPU Fine-Tuning\n",
|
|
8
|
+
"Trains a 20B Mixture-of-Experts model on deep CoT + recursive tool execution data.\n",
|
|
9
|
+
"Target: Kaggle dual T4 (2x 16GB) with 4-bit quantization + FSDP.\n",
|
|
10
|
+
"HF token: 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"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"cell_type": "code",
|
|
15
|
+
"metadata": {},
|
|
16
|
+
"source": [
|
|
17
|
+
"# === Install ===\n",
|
|
18
|
+
"!pip install -q torch transformers datasets accelerate peft bitsandbytes\n",
|
|
19
|
+
"!pip install -q deepspeed sentencepiece huggingface_hub"
|
|
20
|
+
],
|
|
21
|
+
"execution_count": null,
|
|
22
|
+
"outputs": []
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"cell_type": "code",
|
|
26
|
+
"metadata": {},
|
|
27
|
+
"source": [
|
|
28
|
+
"# === Check GPUs ===\n",
|
|
29
|
+
"import torch\n",
|
|
30
|
+
"n_gpus = torch.cuda.device_count()\n",
|
|
31
|
+
"for i in range(n_gpus):\n",
|
|
32
|
+
" print(f'GPU {i}: {torch.cuda.get_device_name(i)} — {torch.cuda.get_device_properties(i).total_memory / 1e9:.1f} GB')\n",
|
|
33
|
+
"assert n_gpus >= 2, 'Need at least 2 GPUs'"
|
|
34
|
+
],
|
|
35
|
+
"execution_count": null,
|
|
36
|
+
"outputs": []
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"cell_type": "code",
|
|
40
|
+
"metadata": {},
|
|
41
|
+
"source": [
|
|
42
|
+
"# === HF Auth ===\n",
|
|
43
|
+
"from huggingface_hub import login, HfApi, create_repo\n",
|
|
44
|
+
"HF_TOKEN = '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'\n",
|
|
45
|
+
"login(token=HF_TOKEN, add_to_git_credential=True)\n",
|
|
46
|
+
"api = HfApi(token=HF_TOKEN)"
|
|
47
|
+
],
|
|
48
|
+
"execution_count": null,
|
|
49
|
+
"outputs": []
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"cell_type": "code",
|
|
53
|
+
"metadata": {},
|
|
54
|
+
"source": [
|
|
55
|
+
"# === Data Loading ===\n",
|
|
56
|
+
"import requests\n",
|
|
57
|
+
"import json\n",
|
|
58
|
+
"from datasets import Dataset, concatenate_datasets\n",
|
|
59
|
+
"\n",
|
|
60
|
+
"DATA_URLS = [\n",
|
|
61
|
+
" 'https://huggingface.co/spaces/Universal-618/Clarity-4/main-data',\n",
|
|
62
|
+
" 'https://huggingface.co/spaces/Universal-618/Clarity-5/main-data',\n",
|
|
63
|
+
" 'https://huggingface.co/spaces/Universal-618/Clarity-6/main-data',\n",
|
|
64
|
+
" 'https://huggingface.co/spaces/Universal-618/Clarity-main/main-data',\n",
|
|
65
|
+
"]\n",
|
|
66
|
+
"\n",
|
|
67
|
+
"all_samples = []\n",
|
|
68
|
+
"for url in DATA_URLS:\n",
|
|
69
|
+
" try:\n",
|
|
70
|
+
" r = requests.get(url, headers={'Authorization': f'Bearer {HF_TOKEN}'}, timeout=120)\n",
|
|
71
|
+
" if r.status_code == 200:\n",
|
|
72
|
+
" data = r.json()\n",
|
|
73
|
+
" samples = data if isinstance(data, list) else data.get('data', [])\n",
|
|
74
|
+
" all_samples.extend(samples)\n",
|
|
75
|
+
" print(f'Loaded {len(samples)} from {url}')\n",
|
|
76
|
+
" except Exception as e:\n",
|
|
77
|
+
" print(f'Skipped {url}: {e}')\n",
|
|
78
|
+
"\n",
|
|
79
|
+
"if len(all_samples) < 10:\n",
|
|
80
|
+
" print('No remote data — generating synthetic deep CoT samples')\n",
|
|
81
|
+
" import random\n",
|
|
82
|
+
" code_snippets = [\n",
|
|
83
|
+
" 'def fib(n): return n if n < 2 else fib(n-1) + fib(n-2)',\n",
|
|
84
|
+
" 'for i in range(10): print(i**2)',\n",
|
|
85
|
+
" 'with open(\"data.txt\") as f: content = f.read()',\n",
|
|
86
|
+
" ]\n",
|
|
87
|
+
" for _ in range(50):\n",
|
|
88
|
+
" cs = random.choice(code_snippets)\n",
|
|
89
|
+
" all_samples.append({\n",
|
|
90
|
+
" 'instruction': f'Write and test a function',\n",
|
|
91
|
+
" 'thinking': f'I need to think step by step. First, I will analyze what the user wants. Then I will write the code. Let me reason through this carefully.',\n",
|
|
92
|
+
" 'response': f'I will write the code now.\\n<tool>bash</tool><cmd>cat > /tmp/test.py << \\'EOF\\'\\n{cs}\\nEOF\\npython3 /tmp/test.py</cmd>',\n",
|
|
93
|
+
" 'tools': 'bash,write_file',\n",
|
|
94
|
+
" })\n",
|
|
95
|
+
"\n",
|
|
96
|
+
"print(f'Total training samples: {len(all_samples)}')"
|
|
97
|
+
],
|
|
98
|
+
"execution_count": null,
|
|
99
|
+
"outputs": []
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"cell_type": "code",
|
|
103
|
+
"metadata": {},
|
|
104
|
+
"source": [
|
|
105
|
+
"# === Format ===\n",
|
|
106
|
+
"def format_deep_cot(sample):\n",
|
|
107
|
+
" inst = sample.get('instruction', sample.get('prompt', ''))\n",
|
|
108
|
+
" thinking = sample.get('thinking', '')\n",
|
|
109
|
+
" resp = sample.get('response', sample.get('completion', ''))\n",
|
|
110
|
+
" thinking_block = f'<|thinking_start|>{thinking}<|thinking_end|>' if thinking else ''\n",
|
|
111
|
+
" return {\n",
|
|
112
|
+
" 'text': f'<|im_start|>user\\n{inst}<|im_end|>\\n<|im_start|>assistant\\n{thinking_block}{resp}<|im_end|>'\n",
|
|
113
|
+
" }\n",
|
|
114
|
+
"\n",
|
|
115
|
+
"dataset = Dataset.from_list([format_deep_cot(s) for s in all_samples])\n",
|
|
116
|
+
"split = dataset.train_test_split(test_size=0.05, seed=42)\n",
|
|
117
|
+
"print(f'Train: {len(split[\"train\"])}, Test: {len(split[\"test\"])}')"
|
|
118
|
+
],
|
|
119
|
+
"execution_count": null,
|
|
120
|
+
"outputs": []
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"cell_type": "code",
|
|
124
|
+
"metadata": {},
|
|
125
|
+
"source": [
|
|
126
|
+
"# === Load MoE Model (4-bit) ===\n",
|
|
127
|
+
"from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig\n",
|
|
128
|
+
"\n",
|
|
129
|
+
"MODEL_ID = 'deepseek-ai/DeepSeek-MoE-16B-Chat'\n",
|
|
130
|
+
"\n",
|
|
131
|
+
"bnb = BitsAndBytesConfig(\n",
|
|
132
|
+
" load_in_4bit=True,\n",
|
|
133
|
+
" bnb_4bit_use_double_quant=True,\n",
|
|
134
|
+
" bnb_4bit_quant_type='nf4',\n",
|
|
135
|
+
" bnb_4bit_compute_dtype=torch.bfloat16,\n",
|
|
136
|
+
")\n",
|
|
137
|
+
"\n",
|
|
138
|
+
"tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, token=HF_TOKEN, trust_remote_code=True)\n",
|
|
139
|
+
"tokenizer.pad_token = tokenizer.eos_token\n",
|
|
140
|
+
"\n",
|
|
141
|
+
"model = AutoModelForCausalLM.from_pretrained(\n",
|
|
142
|
+
" MODEL_ID,\n",
|
|
143
|
+
" quantization_config=bnb,\n",
|
|
144
|
+
" device_map='auto',\n",
|
|
145
|
+
" torch_dtype=torch.bfloat16,\n",
|
|
146
|
+
" token=HF_TOKEN,\n",
|
|
147
|
+
" trust_remote_code=True,\n",
|
|
148
|
+
")\n",
|
|
149
|
+
"print(f'MoE model loaded: {MODEL_ID}')\n",
|
|
150
|
+
"print(f'Params: {model.num_parameters():,.0f}')"
|
|
151
|
+
],
|
|
152
|
+
"execution_count": null,
|
|
153
|
+
"outputs": []
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"cell_type": "code",
|
|
157
|
+
"metadata": {},
|
|
158
|
+
"source": [
|
|
159
|
+
"# === LoRA for MoE ===\n",
|
|
160
|
+
"from peft import LoraConfig, get_peft_model, TaskType\n",
|
|
161
|
+
"\n",
|
|
162
|
+
"lora_config = LoraConfig(\n",
|
|
163
|
+
" task_type=TaskType.CAUSAL_LM,\n",
|
|
164
|
+
" r=8,\n",
|
|
165
|
+
" lora_alpha=16,\n",
|
|
166
|
+
" lora_dropout=0.1,\n",
|
|
167
|
+
" target_modules=['q_proj', 'k_proj', 'v_proj', 'o_proj', 'gate_proj', 'up_proj', 'down_proj', 'gate'],\n",
|
|
168
|
+
" bias='none',\n",
|
|
169
|
+
")\n",
|
|
170
|
+
"model = get_peft_model(model, lora_config)\n",
|
|
171
|
+
"model.print_trainable_parameters()"
|
|
172
|
+
],
|
|
173
|
+
"execution_count": null,
|
|
174
|
+
"outputs": []
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"cell_type": "code",
|
|
178
|
+
"metadata": {},
|
|
179
|
+
"source": [
|
|
180
|
+
"# === Gradient Checkpointing (prevents OOM) ===\n",
|
|
181
|
+
"model.gradient_checkpointing_enable(gradient_checkpointing_kwargs={'use_reentrant': False})\n",
|
|
182
|
+
"model.config.use_cache = False\n",
|
|
183
|
+
"print('Gradient checkpointing enabled')"
|
|
184
|
+
],
|
|
185
|
+
"execution_count": null,
|
|
186
|
+
"outputs": []
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"cell_type": "code",
|
|
190
|
+
"metadata": {},
|
|
191
|
+
"source": [
|
|
192
|
+
"# === Training ===\n",
|
|
193
|
+
"from transformers import TrainingArguments, Trainer, DataCollatorForSeq2Seq\n",
|
|
194
|
+
"\n",
|
|
195
|
+
"def tokenize_fn(examples):\n",
|
|
196
|
+
" tok = tokenizer(examples['text'], truncation=True, max_length=2048, padding=False)\n",
|
|
197
|
+
" tok['labels'] = tok['input_ids'].copy()\n",
|
|
198
|
+
" return tok\n",
|
|
199
|
+
"\n",
|
|
200
|
+
"tokenized = split.map(tokenize_fn, remove_columns=['text'], batched=True)\n",
|
|
201
|
+
"\n",
|
|
202
|
+
"args = TrainingArguments(\n",
|
|
203
|
+
" output_dir='./clarity-heavy-20b-moe',\n",
|
|
204
|
+
" per_device_train_batch_size=1,\n",
|
|
205
|
+
" per_device_eval_batch_size=1,\n",
|
|
206
|
+
" gradient_accumulation_steps=8,\n",
|
|
207
|
+
" num_train_epochs=3,\n",
|
|
208
|
+
" learning_rate=1e-4,\n",
|
|
209
|
+
" bf16=True,\n",
|
|
210
|
+
" logging_steps=10,\n",
|
|
211
|
+
" save_steps=200,\n",
|
|
212
|
+
" save_total_limit=2,\n",
|
|
213
|
+
" optim='adamw_8bit',\n",
|
|
214
|
+
" gradient_checkpointing=True,\n",
|
|
215
|
+
" report_to='none',\n",
|
|
216
|
+
" ddp_find_unused_parameters=False,\n",
|
|
217
|
+
")\n",
|
|
218
|
+
"\n",
|
|
219
|
+
"trainer = Trainer(\n",
|
|
220
|
+
" model=model,\n",
|
|
221
|
+
" args=args,\n",
|
|
222
|
+
" train_dataset=tokenized['train'],\n",
|
|
223
|
+
" eval_dataset=tokenized['test'],\n",
|
|
224
|
+
" data_collator=DataCollatorForSeq2Seq(tokenizer, padding=True, pad_to_multiple_of=8),\n",
|
|
225
|
+
")\n",
|
|
226
|
+
"\n",
|
|
227
|
+
"trainer.train()"
|
|
228
|
+
],
|
|
229
|
+
"execution_count": null,
|
|
230
|
+
"outputs": []
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"cell_type": "code",
|
|
234
|
+
"metadata": {},
|
|
235
|
+
"source": [
|
|
236
|
+
"# === Push to HF ===\n",
|
|
237
|
+
"WEIGHTS_REPO = 'Universal-618/Clarity-heavy-weights'\n",
|
|
238
|
+
"try:\n",
|
|
239
|
+
" create_repo(WEIGHTS_REPO, repo_type='dataset', exist_ok=True, token=HF_TOKEN)\n",
|
|
240
|
+
" print(f'Repo {WEIGHTS_REPO} ready')\n",
|
|
241
|
+
"except Exception as e:\n",
|
|
242
|
+
" print(f'Repo notice: {e}')\n",
|
|
243
|
+
"\n",
|
|
244
|
+
"model.push_to_hub(WEIGHTS_REPO, token=HF_TOKEN, use_temp_dir=True)\n",
|
|
245
|
+
"tokenizer.push_to_hub(WEIGHTS_REPO, token=HF_TOKEN)\n",
|
|
246
|
+
"print(f'Weights pushed to https://huggingface.co/datasets/{WEIGHTS_REPO}')"
|
|
247
|
+
],
|
|
248
|
+
"execution_count": null,
|
|
249
|
+
"outputs": []
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
"metadata": {
|
|
253
|
+
"accelerator": "GPU",
|
|
254
|
+
"kaggle": {
|
|
255
|
+
"accelerator": "GPU",
|
|
256
|
+
"gpuModel": "T4",
|
|
257
|
+
"gpuCount": 2
|
|
258
|
+
},
|
|
259
|
+
"kernelspec": {
|
|
260
|
+
"display_name": "Python 3",
|
|
261
|
+
"name": "python3"
|
|
262
|
+
},
|
|
263
|
+
"language_info": {
|
|
264
|
+
"name": "python",
|
|
265
|
+
"version": "3.10.0"
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"nbformat": 4,
|
|
269
|
+
"nbformat_minor": 4
|
|
270
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
|
-
import { hex,
|
|
3
|
+
import { hex, sym } from '../config/theme.js';
|
|
4
|
+
import { getLayout } from '../config/layout.js';
|
|
4
5
|
const { createElement: h } = React;
|
|
5
6
|
|
|
6
7
|
const COMMANDS = [
|
|
@@ -8,7 +9,7 @@ const COMMANDS = [
|
|
|
8
9
|
{ name: '/model', desc: 'Switch model' },
|
|
9
10
|
{ name: '/provider', desc: 'Switch provider' },
|
|
10
11
|
{ name: '/agent', desc: 'Toggle agent mode' },
|
|
11
|
-
{ name: '/stop', desc: 'Cancel
|
|
12
|
+
{ name: '/stop', desc: 'Cancel running stream' },
|
|
12
13
|
{ name: '/clear', desc: 'Clear conversation' },
|
|
13
14
|
{ name: '/export', desc: 'Export conversation' },
|
|
14
15
|
{ name: '/help', desc: 'Show all commands' },
|
|
@@ -18,6 +19,7 @@ const COMMANDS = [
|
|
|
18
19
|
export function CommandPicker({ query, onSelect, onClose }) {
|
|
19
20
|
const [search, setSearch] = useState('');
|
|
20
21
|
const [idx, setIdx] = useState(0);
|
|
22
|
+
const { cols } = getLayout();
|
|
21
23
|
|
|
22
24
|
const filtered = COMMANDS.filter(c =>
|
|
23
25
|
c.name.includes(search) || c.desc.toLowerCase().includes(search.toLowerCase())
|
|
@@ -26,40 +28,36 @@ export function CommandPicker({ query, onSelect, onClose }) {
|
|
|
26
28
|
useInput((input, key) => {
|
|
27
29
|
if (key.upArrow) setIdx(i => Math.max(0, i - 1));
|
|
28
30
|
if (key.downArrow) setIdx(i => Math.min(filtered.length - 1, i + 1));
|
|
29
|
-
if (key.return) onSelect(filtered[idx]
|
|
31
|
+
if (key.return && filtered[idx]) onSelect(filtered[idx].name);
|
|
30
32
|
if (key.escape) onClose();
|
|
31
33
|
if (key.backspace) setSearch(s => s.slice(0, -1));
|
|
32
34
|
else if (input && !key.ctrl && !key.meta) setSearch(s => s + input);
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
const
|
|
36
|
-
const boxWidth = Math.min(tw - 4, 50);
|
|
37
|
+
const w = Math.min(cols - 4, 48);
|
|
37
38
|
|
|
38
|
-
return h(Box, { flexDirection: 'column', width:
|
|
39
|
-
h(Box, {
|
|
40
|
-
h(Text, { color: hex.textMuted },
|
|
41
|
-
h(Text, { color: search ? hex.text : hex.textMuted }, search || 'type to filter...'),
|
|
39
|
+
return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt, width: w },
|
|
40
|
+
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
41
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.star + ' ' + (search || 'filter commands...'))
|
|
42
42
|
),
|
|
43
43
|
filtered.map((cmd, i) =>
|
|
44
44
|
h(Box, {
|
|
45
|
-
key: cmd.name,
|
|
46
|
-
|
|
47
|
-
backgroundColor: i === idx ? hex.selectionBg : undefined,
|
|
48
|
-
width: boxWidth,
|
|
45
|
+
key: cmd.name, height: 1,
|
|
46
|
+
backgroundColor: i === idx ? hex.selectionBg : 'transparent',
|
|
49
47
|
},
|
|
50
48
|
h(Text, {
|
|
51
49
|
color: i === idx ? hex.selectionText : hex.text,
|
|
52
50
|
bold: i === idx,
|
|
53
|
-
backgroundColor: i === idx ? hex.selectionBg :
|
|
54
|
-
|
|
55
|
-
}, ' ' + cmd.name.padEnd(16)),
|
|
51
|
+
backgroundColor: i === idx ? hex.selectionBg : 'transparent',
|
|
52
|
+
}, ' ' + cmd.name + ' '),
|
|
56
53
|
h(Text, {
|
|
57
54
|
color: i === idx ? hex.selectionText : hex.textDim,
|
|
58
|
-
backgroundColor: i === idx ? hex.selectionBg :
|
|
59
|
-
wrap: 'truncate-end',
|
|
55
|
+
backgroundColor: i === idx ? hex.selectionBg : 'transparent',
|
|
60
56
|
}, cmd.desc)
|
|
61
57
|
)
|
|
62
58
|
),
|
|
63
|
-
h(
|
|
59
|
+
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
60
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.arrowU + sym.arrowD + ' nav ' + sym.arrowR + ' select Esc close')
|
|
61
|
+
)
|
|
64
62
|
);
|
|
65
63
|
}
|
|
@@ -1,46 +1,38 @@
|
|
|
1
1
|
import React, { useState, useRef } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
|
-
import { hex,
|
|
3
|
+
import { hex, sym } from '../config/theme.js';
|
|
4
4
|
import { getLayout } from '../config/layout.js';
|
|
5
5
|
const { createElement: h } = React;
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const MAX_ROWS = 3;
|
|
8
8
|
|
|
9
9
|
export function Composer({ provider, model, agentMode, thinking, onSlash, onSubmit }) {
|
|
10
10
|
const [input, setInput] = useState('');
|
|
11
11
|
const [cursor, setCursor] = useState(0);
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const r = useRef('');
|
|
13
|
+
r.current = input;
|
|
14
14
|
|
|
15
15
|
const { cols } = getLayout();
|
|
16
16
|
const w = Math.max(10, cols - 6);
|
|
17
|
-
const lineCount = Math.max(1, Math.ceil((input.
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
17
|
+
const lineCount = Math.max(1, Math.ceil((input.length || 1) / w));
|
|
18
|
+
const visible = Math.min(lineCount, MAX_ROWS);
|
|
19
|
+
const mShort = model.replace(/^[^/]+\//, '').slice(0, 18);
|
|
20
|
+
const isPlaceholder = !input && !thinking;
|
|
21
21
|
|
|
22
22
|
useInput((ch, key) => {
|
|
23
23
|
if (key.ctrl && key.p) { onSlash(); return; }
|
|
24
24
|
if (key.escape) { onSubmit('/exit'); return; }
|
|
25
25
|
if (key.return && !key.shift) {
|
|
26
|
-
if (input.trim()) {
|
|
27
|
-
const text = input;
|
|
28
|
-
setInput('');
|
|
29
|
-
setCursor(0);
|
|
30
|
-
onSubmit(text);
|
|
31
|
-
}
|
|
26
|
+
if (input.trim()) { const t = input; setInput(''); setCursor(0); onSubmit(t); }
|
|
32
27
|
return;
|
|
33
28
|
}
|
|
34
29
|
if (key.return && key.shift) {
|
|
35
|
-
setInput(
|
|
30
|
+
setInput(p => p.slice(0, cursor) + '\n' + p.slice(cursor));
|
|
36
31
|
setCursor(c => c + 1);
|
|
37
32
|
return;
|
|
38
33
|
}
|
|
39
34
|
if (key.backspace || key.delete) {
|
|
40
|
-
if (cursor > 0) {
|
|
41
|
-
setInput(prev => prev.slice(0, cursor - 1) + prev.slice(cursor));
|
|
42
|
-
setCursor(c => c - 1);
|
|
43
|
-
}
|
|
35
|
+
if (cursor > 0) { setInput(p => p.slice(0, cursor - 1) + p.slice(cursor)); setCursor(c => c - 1); }
|
|
44
36
|
return;
|
|
45
37
|
}
|
|
46
38
|
if (key.leftArrow && cursor > 0) { setCursor(c => c - 1); return; }
|
|
@@ -48,43 +40,35 @@ export function Composer({ provider, model, agentMode, thinking, onSlash, onSubm
|
|
|
48
40
|
if (key.home) { setCursor(0); return; }
|
|
49
41
|
if (key.end) { setCursor(input.length); return; }
|
|
50
42
|
if (ch && ch.length === 1 && ch.charCodeAt(0) >= 32) {
|
|
51
|
-
setInput(
|
|
43
|
+
setInput(p => p.slice(0, cursor) + ch + p.slice(cursor));
|
|
52
44
|
setCursor(c => c + 1);
|
|
53
45
|
}
|
|
54
46
|
});
|
|
55
47
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
h(Box, { key: 'dock_header', height: 1, backgroundColor: hex.surfaceAlt },
|
|
62
|
-
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + u.h.repeat(Math.max(0, cols - 2)))
|
|
63
|
-
)
|
|
64
|
-
);
|
|
48
|
+
const rows = [
|
|
49
|
+
h(Box, { key: 'sep', height: 1, backgroundColor: hex.surfaceAlt },
|
|
50
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.lightH.repeat(Math.max(0, cols - 4)))
|
|
51
|
+
),
|
|
52
|
+
];
|
|
65
53
|
|
|
66
|
-
for (let i = 0; i <
|
|
54
|
+
for (let i = 0; i < MAX_ROWS; i++) {
|
|
67
55
|
const start = i * w;
|
|
68
|
-
const
|
|
69
|
-
const seg = displayText.slice(start, end);
|
|
56
|
+
const seg = input.slice(start, start + w);
|
|
70
57
|
rows.push(
|
|
71
|
-
h(Box, { key: '
|
|
58
|
+
h(Box, { key: 'r' + i, height: 1, backgroundColor: hex.bg },
|
|
72
59
|
h(Text, {
|
|
73
60
|
color: isPlaceholder ? hex.textMuted : hex.text,
|
|
74
61
|
backgroundColor: hex.bg,
|
|
75
62
|
wrap: 'truncate-end',
|
|
76
|
-
}, ' ' +
|
|
63
|
+
}, ' ' + sym.triR + ' ' + (seg || (i === 0 && isPlaceholder ? 'type a message...' : ' ')))
|
|
77
64
|
)
|
|
78
65
|
);
|
|
79
66
|
}
|
|
80
67
|
|
|
81
68
|
rows.push(
|
|
82
|
-
h(Box, { key: '
|
|
69
|
+
h(Box, { key: 'st', height: 1, backgroundColor: hex.surfaceAlt },
|
|
83
70
|
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
84
|
-
' ' + provider + ' ' +
|
|
85
|
-
(agentMode ? ' ' + usym.midDot + ' Agent' : '') +
|
|
86
|
-
' ' + usym.midDot + ' Ctrl+P commands'
|
|
87
|
-
)
|
|
71
|
+
' ' + provider + ' ' + sym.midDot + ' ' + mShort + (agentMode ? ' ' + sym.midDot + ' AGENT' : '') + ' ' + sym.midDot + ' Ctrl+P')
|
|
88
72
|
)
|
|
89
73
|
);
|
|
90
74
|
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import
|
|
4
|
-
import { hex, usym } from '../config/theme.js';
|
|
3
|
+
import { hex, sym } from '../config/theme.js';
|
|
5
4
|
const { createElement: h } = React;
|
|
6
5
|
|
|
7
6
|
export function LoadingIndicator({ label }) {
|
|
8
|
-
return h(Box, { flexDirection: 'row',
|
|
9
|
-
h(Text, { color: hex.blue, backgroundColor: hex.surface },
|
|
10
|
-
h(Text, { color: hex.
|
|
11
|
-
h(Spinner, { type: 'dots' }),
|
|
12
|
-
h(Text, { color: hex.textDim, backgroundColor: hex.surface }, ' ' + (label || 'Thinking'))
|
|
7
|
+
return h(Box, { flexDirection: 'row', backgroundColor: hex.surface },
|
|
8
|
+
h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' ' + sym.dot),
|
|
9
|
+
h(Text, { color: hex.textDim, backgroundColor: hex.surface }, ' ' + (label || 'processing'))
|
|
13
10
|
);
|
|
14
11
|
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import { hex,
|
|
3
|
+
import { hex, sym } from '../config/theme.js';
|
|
4
4
|
import { getLayout, sliceToViewport, buildLineArray } from '../config/layout.js';
|
|
5
5
|
const { createElement: h } = React;
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
const { cols } = getLayout();
|
|
7
|
+
function Line({ type, text, data }) {
|
|
9
8
|
switch (type) {
|
|
10
9
|
case 'user_head':
|
|
11
10
|
return h(Box, { height: 1, backgroundColor: hex.userBg },
|
|
12
|
-
h(Text, { color: hex.accent, bold: true, backgroundColor: hex.userBg }, ' ' +
|
|
13
|
-
h(Text, { color: hex.textMuted, backgroundColor: hex.userBg }, ' ' + u.h.repeat(Math.max(0, cols - 10)))
|
|
11
|
+
h(Text, { color: hex.accent, bold: true, backgroundColor: hex.userBg }, ' ' + sym.bullet + ' YOU')
|
|
14
12
|
);
|
|
15
13
|
case 'user_line':
|
|
16
14
|
return h(Box, { height: 1, backgroundColor: hex.userBg },
|
|
@@ -18,12 +16,7 @@ function LineRenderer({ type, text, data }) {
|
|
|
18
16
|
);
|
|
19
17
|
case 'asst_head':
|
|
20
18
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
21
|
-
h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, ' ' +
|
|
22
|
-
h(Text, { color: hex.textMuted, backgroundColor: hex.surface }, ' ' + u.h.repeat(Math.max(0, cols - 14)))
|
|
23
|
-
);
|
|
24
|
-
case 'asst_bar':
|
|
25
|
-
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
26
|
-
h(Text, { color: hex.textMuted, backgroundColor: hex.surface }, ' ' + usym.lightV)
|
|
19
|
+
h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, ' ' + sym.diamond + ' CLARITY')
|
|
27
20
|
);
|
|
28
21
|
case 'asst_line':
|
|
29
22
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
@@ -31,36 +24,27 @@ function LineRenderer({ type, text, data }) {
|
|
|
31
24
|
);
|
|
32
25
|
case 'asst_foot':
|
|
33
26
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
34
|
-
h(Text, { color: hex.textDim, backgroundColor: hex.surface }, ' ' +
|
|
35
|
-
);
|
|
36
|
-
case 'tool_head':
|
|
37
|
-
return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
38
|
-
h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt }, ' ' + usym.circle + ' ' + text)
|
|
27
|
+
h(Text, { color: hex.textDim, backgroundColor: hex.surface }, ' ' + sym.triR + ' ' + (parseInt(text) < 1000 ? text + 'ms' : (parseInt(text) / 1000).toFixed(1) + 's'))
|
|
39
28
|
);
|
|
40
29
|
case 'tool_line':
|
|
41
30
|
return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
42
|
-
h(Text, { color: hex.
|
|
31
|
+
h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt }, ' ' + sym.bullet + ' ' + (text || ''))
|
|
43
32
|
);
|
|
44
33
|
case 'sys_line':
|
|
45
34
|
return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
46
|
-
h(Text, { color: hex.green, backgroundColor: hex.surfaceAlt }, ' ' +
|
|
35
|
+
h(Text, { color: hex.green, backgroundColor: hex.surfaceAlt }, ' ' + sym.bullet + ' ' + (text || ''))
|
|
47
36
|
);
|
|
48
37
|
case 'err_line':
|
|
49
38
|
return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
50
|
-
h(Text, { color: hex.red, backgroundColor: hex.surfaceAlt }, ' ' +
|
|
39
|
+
h(Text, { color: hex.red, backgroundColor: hex.surfaceAlt }, ' ' + sym.cross + ' ' + (text || ''))
|
|
51
40
|
);
|
|
52
41
|
case 'stream_head':
|
|
53
42
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
54
|
-
h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, ' ' +
|
|
55
|
-
h(Text, { color: hex.textMuted, backgroundColor: hex.surface }, ' ' + u.h.repeat(Math.max(0, cols - 14)))
|
|
56
|
-
);
|
|
57
|
-
case 'stream_bar':
|
|
58
|
-
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
59
|
-
h(Text, { color: hex.textMuted, backgroundColor: hex.surface }, ' ' + usym.lightV)
|
|
43
|
+
h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, ' ' + sym.diamond + ' CLARITY')
|
|
60
44
|
);
|
|
61
45
|
case 'stream_status':
|
|
62
46
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
63
|
-
h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' ' +
|
|
47
|
+
h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' ' + sym.dot + ' ' + (text || ''))
|
|
64
48
|
);
|
|
65
49
|
case 'stream_line':
|
|
66
50
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
@@ -74,32 +58,25 @@ function LineRenderer({ type, text, data }) {
|
|
|
74
58
|
export function MessageList({ messages, thinking, streamContent, agentStatus, toolExecutions }) {
|
|
75
59
|
const { viewport, contentWidth } = getLayout();
|
|
76
60
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
status: agentStatus || (thinking ? 'thinking...' : ''),
|
|
93
|
-
completed: false,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const rawEntries = messages.map(entryForMsg);
|
|
97
|
-
const showStream = thinking || streamContent;
|
|
98
|
-
if (showStream) rawEntries.push(streamEntry);
|
|
61
|
+
const entries = useMemo(() => {
|
|
62
|
+
const e = messages.map(m => ({
|
|
63
|
+
id: m.id, role: m.role, content: m.content,
|
|
64
|
+
duration: m.duration, toolName: m.toolName, error: m.error, completed: true,
|
|
65
|
+
}));
|
|
66
|
+
if (thinking || streamContent) {
|
|
67
|
+
e.push({
|
|
68
|
+
id: 'stream', role: 'streaming',
|
|
69
|
+
content: streamContent || '',
|
|
70
|
+
status: agentStatus || (thinking ? 'processing...' : ''),
|
|
71
|
+
completed: false,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return e;
|
|
75
|
+
}, [messages, thinking, streamContent, agentStatus]);
|
|
99
76
|
|
|
100
77
|
const { slice, clipIndex, clipLines } = useMemo(
|
|
101
|
-
() => sliceToViewport(
|
|
102
|
-
[
|
|
78
|
+
() => sliceToViewport(entries, viewport, contentWidth),
|
|
79
|
+
[entries, viewport, contentWidth]
|
|
103
80
|
);
|
|
104
81
|
|
|
105
82
|
const lines = useMemo(
|
|
@@ -107,17 +84,17 @@ export function MessageList({ messages, thinking, streamContent, agentStatus, to
|
|
|
107
84
|
[slice, clipIndex, clipLines, contentWidth]
|
|
108
85
|
);
|
|
109
86
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
87
|
+
const padded = [...lines];
|
|
88
|
+
for (let i = padded.length; i < viewport; i++) {
|
|
89
|
+
padded.unshift({ type: 'empty' });
|
|
90
|
+
}
|
|
113
91
|
|
|
114
92
|
return h(Box, { height: viewport, flexDirection: 'column', overflow: 'hidden' },
|
|
115
|
-
|
|
93
|
+
padded.map((ln, i) =>
|
|
116
94
|
ln.type === 'empty'
|
|
117
|
-
? h(Box, { key: 'e' + i, height: 1, backgroundColor: hex.bg }
|
|
118
|
-
h(Text, { color: hex.textMuted, backgroundColor: hex.bg }, ' '))
|
|
95
|
+
? h(Box, { key: 'e' + i, height: 1, backgroundColor: hex.bg })
|
|
119
96
|
: h(Box, { key: (ln.data?.id || 'l') + '-' + i, height: 1 },
|
|
120
|
-
h(
|
|
97
|
+
h(Line, { type: ln.type, text: ln.text, data: ln.data })
|
|
121
98
|
)
|
|
122
99
|
)
|
|
123
100
|
);
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import React, { useState, useMemo } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
3
|
import { ALL_MODELS } from '../config/models.js';
|
|
4
|
-
import { hex,
|
|
4
|
+
import { hex, sym } from '../config/theme.js';
|
|
5
|
+
import { getLayout } from '../config/layout.js';
|
|
5
6
|
const { createElement: h } = React;
|
|
6
7
|
|
|
7
8
|
export function ModelPicker({ onSelect, onClose }) {
|
|
8
9
|
const [search, setSearch] = useState('');
|
|
9
10
|
const [idx, setIdx] = useState(0);
|
|
11
|
+
const { cols } = getLayout();
|
|
10
12
|
|
|
11
13
|
const flat = useMemo(() => {
|
|
12
14
|
const q = search.toLowerCase();
|
|
@@ -35,44 +37,32 @@ export function ModelPicker({ onSelect, onClose }) {
|
|
|
35
37
|
else if (input && !key.ctrl && !key.meta) setSearch(s => s + input);
|
|
36
38
|
});
|
|
37
39
|
|
|
38
|
-
const
|
|
39
|
-
const boxWidth = Math.min(tw - 4, 54);
|
|
40
|
+
const w = Math.min(cols - 4, 52);
|
|
40
41
|
|
|
41
|
-
return h(Box, { flexDirection: 'column', width:
|
|
42
|
-
h(Box, {
|
|
43
|
-
h(Text, { color: hex.textMuted },
|
|
44
|
-
h(Text, { color: search ? hex.text : hex.textMuted }, search || 'search models...'),
|
|
42
|
+
return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt, width: w },
|
|
43
|
+
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
44
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.star + ' ' + (search || 'search models...'))
|
|
45
45
|
),
|
|
46
46
|
flat.map((m, i) => {
|
|
47
47
|
if (m._header) {
|
|
48
|
-
return h(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
bold: true,
|
|
52
|
-
backgroundColor: hex.surfaceAlt,
|
|
53
|
-
wrap: 'truncate-end',
|
|
54
|
-
}, ' ' + usym.triD + ' ' + m._provider.toUpperCase());
|
|
48
|
+
return h(Box, { key: 'h-' + m._provider, height: 1, backgroundColor: hex.surfaceAlt },
|
|
49
|
+
h(Text, { color: hex.blue, bold: true, backgroundColor: hex.surfaceAlt }, ' ' + sym.triD + ' ' + m._provider.toUpperCase())
|
|
50
|
+
);
|
|
55
51
|
}
|
|
56
52
|
const isSel = i === idx;
|
|
57
53
|
return h(Box, {
|
|
58
|
-
key: m.id,
|
|
59
|
-
|
|
60
|
-
backgroundColor: isSel ? hex.selectionBg : undefined,
|
|
61
|
-
width: boxWidth,
|
|
54
|
+
key: m.id, height: 1,
|
|
55
|
+
backgroundColor: isSel ? hex.selectionBg : 'transparent',
|
|
62
56
|
},
|
|
63
57
|
h(Text, {
|
|
64
58
|
color: isSel ? hex.selectionText : hex.text,
|
|
65
59
|
bold: isSel,
|
|
66
|
-
backgroundColor: isSel ? hex.selectionBg :
|
|
67
|
-
|
|
68
|
-
}, ' ' + m.label),
|
|
69
|
-
m.badge
|
|
70
|
-
? h(Text, {
|
|
71
|
-
color: isSel ? hex.selectionText : hex.textMuted,
|
|
72
|
-
backgroundColor: isSel ? hex.selectionBg : undefined,
|
|
73
|
-
}, ' [' + m.badge + ']')
|
|
74
|
-
: null
|
|
60
|
+
backgroundColor: isSel ? hex.selectionBg : 'transparent',
|
|
61
|
+
}, ' ' + m.label + (m.badge ? ' [' + m.badge + ']' : ''))
|
|
75
62
|
);
|
|
76
|
-
})
|
|
63
|
+
}),
|
|
64
|
+
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
65
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.arrowU + sym.arrowD + ' nav ' + sym.arrowR + ' select Esc close')
|
|
66
|
+
)
|
|
77
67
|
);
|
|
78
68
|
}
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { hex, sym } from '../config/theme.js';
|
|
3
4
|
import { getLayout } from '../config/layout.js';
|
|
4
|
-
import { hex, usym } from '../config/theme.js';
|
|
5
5
|
const { createElement: h } = React;
|
|
6
6
|
|
|
7
7
|
export function StatusBar({ model, provider, agentMode, thinking }) {
|
|
8
8
|
const { cols } = getLayout();
|
|
9
|
-
const
|
|
10
|
-
const left = '
|
|
11
|
-
const right = (agentMode ? '
|
|
12
|
-
const gap = Math.max(1, cols - left.length - right.length -
|
|
13
|
-
const line = left + ' '.repeat(gap) + right;
|
|
9
|
+
const m = model.replace(/^[^/]+\//, '').slice(0, 22);
|
|
10
|
+
const left = sym.circle + ' CLARITY ' + sym.midDot + ' ' + m + ' ' + sym.midDot + ' ' + provider;
|
|
11
|
+
const right = (agentMode ? 'AGENT' : 'USER') + (thinking ? ' ' + sym.dot : '');
|
|
12
|
+
const gap = Math.max(1, cols - left.length - right.length - 4);
|
|
14
13
|
return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
15
|
-
h(Text, { color: hex.textDim, backgroundColor: hex.surfaceAlt }, ' ' +
|
|
14
|
+
h(Text, { color: hex.textDim, backgroundColor: hex.surfaceAlt }, ' ' + left + ' '.repeat(gap) + right + ' ')
|
|
16
15
|
);
|
|
17
16
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import { hex,
|
|
3
|
+
import { hex, sym } from '../config/theme.js';
|
|
4
4
|
import { getLayout } from '../config/layout.js';
|
|
5
5
|
const { createElement: h } = React;
|
|
6
6
|
|
|
@@ -8,37 +8,25 @@ export function ToolCard({ exec, isActive }) {
|
|
|
8
8
|
const { cols } = getLayout();
|
|
9
9
|
const name = exec.name || 'tool';
|
|
10
10
|
const status = exec.status || 'running';
|
|
11
|
-
const
|
|
12
|
-
const args = exec.args || {};
|
|
13
|
-
const
|
|
11
|
+
const dur = exec.duration ? (exec.duration < 1000 ? exec.duration + 'ms' : (exec.duration / 1000).toFixed(1) + 's') : '';
|
|
12
|
+
const args = typeof exec.args === 'string' ? exec.args : JSON.stringify(exec.args || {});
|
|
13
|
+
const isDone = !isActive && (status === 'completed' || status === 'failed');
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const col = status === 'failed' ? hex.red : hex.green;
|
|
21
|
-
const icon = status === 'failed' ? usym.cross : usym.circle;
|
|
22
|
-
const label = icon + ' Tool: ' + name + ' ' + (status === 'failed' ? 'Failed' : 'Successful') + (duration ? ' (' + duration + ')' : '');
|
|
23
|
-
return h(Box, { height: minRender },
|
|
24
|
-
h(Text, { color: col, backgroundColor: hex.surfaceAlt }, ' ' + label)
|
|
15
|
+
if (isDone) {
|
|
16
|
+
const c = status === 'failed' ? hex.red : hex.green;
|
|
17
|
+
const icon = status === 'failed' ? sym.cross : sym.bullet;
|
|
18
|
+
return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
19
|
+
h(Text, { color: c, backgroundColor: hex.surfaceAlt }, ' ' + icon + ' ' + name + (dur ? ' ' + dur : ''))
|
|
25
20
|
);
|
|
26
21
|
}
|
|
27
22
|
|
|
28
|
-
const w = Math.min(cols - 6,
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const hasExtra = errLine || resultLine;
|
|
34
|
-
const lines = [usym.circle + ' ' + name + (duration ? ' ' + duration : '')];
|
|
35
|
-
if (argsStr.length < 80) lines.push(' args: ' + argsStr.slice(0, w));
|
|
36
|
-
if (errLine) lines.push(errLine);
|
|
37
|
-
if (resultLine) lines.push(resultLine);
|
|
38
|
-
if (status === 'running') lines.push(' running...');
|
|
23
|
+
const w = Math.min(cols - 6, 56);
|
|
24
|
+
const lines = [sym.bullet + ' ' + name + (dur ? ' ' + dur : '')];
|
|
25
|
+
if (args.length < 80) lines.push(' ' + sym.triR + ' ' + args.slice(0, w));
|
|
26
|
+
if (status === 'running') lines.push(' ' + sym.dot + ' running');
|
|
27
|
+
if (status === 'failed' && exec.error) lines.push(' ' + sym.cross + ' ' + String(exec.error).slice(0, w - 4));
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
return h(Box, { height: totalLines, flexDirection: 'column', backgroundColor: hex.surfaceAlt },
|
|
29
|
+
return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
|
|
42
30
|
lines.slice(0, 4).map((line, i) =>
|
|
43
31
|
h(Box, { key: i, height: 1 },
|
|
44
32
|
h(Text, { color: i === 0 ? hex.purple : hex.textDim, backgroundColor: hex.surfaceAlt }, ' ' + line)
|
package/src/config/theme.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
|
|
3
3
|
export const hex = {
|
|
4
|
-
bg: '#
|
|
5
|
-
surface: '#
|
|
6
|
-
surfaceAlt: '#
|
|
7
|
-
userBg: '#
|
|
8
|
-
codeBg: '#
|
|
9
|
-
selectionBg: '#
|
|
4
|
+
bg: '#0A0A14',
|
|
5
|
+
surface: '#111125',
|
|
6
|
+
surfaceAlt: '#161630',
|
|
7
|
+
userBg: '#140A28',
|
|
8
|
+
codeBg: '#0D0D18',
|
|
9
|
+
selectionBg: '#FF6B35',
|
|
10
10
|
selectionText: '#FFFFFF',
|
|
11
|
-
borderLight: '#
|
|
12
|
-
accent: '#
|
|
11
|
+
borderLight: '#202050',
|
|
12
|
+
accent: '#FF6B35',
|
|
13
13
|
purple: '#A855F7',
|
|
14
14
|
green: '#22C55E',
|
|
15
15
|
red: '#EF4444',
|
|
16
16
|
gold: '#F59E0B',
|
|
17
17
|
blue: '#3B82F6',
|
|
18
18
|
cyan: '#22D3EE',
|
|
19
|
-
text: '#
|
|
20
|
-
textDim: '#
|
|
21
|
-
textMuted: '#
|
|
19
|
+
text: '#EAEAEE',
|
|
20
|
+
textDim: '#8888AA',
|
|
21
|
+
textMuted: '#555577',
|
|
22
22
|
white: '#FFFFFF',
|
|
23
23
|
black: '#000000',
|
|
24
|
+
modalOverlay: 'rgba(0,0,0,0.85)',
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
export const color = {
|
|
@@ -30,7 +31,6 @@ export const color = {
|
|
|
30
31
|
userBg: chalk.hex(hex.userBg),
|
|
31
32
|
selectionBg: chalk.hex(hex.selectionBg),
|
|
32
33
|
selectionText: chalk.hex(hex.selectionText),
|
|
33
|
-
border: chalk.hex(hex.borderLight),
|
|
34
34
|
accent: chalk.hex(hex.accent),
|
|
35
35
|
purple: chalk.hex(hex.purple),
|
|
36
36
|
green: chalk.hex(hex.green),
|
|
@@ -45,60 +45,16 @@ export const color = {
|
|
|
45
45
|
black: chalk.hex(hex.black),
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
export const
|
|
49
|
-
surface: chalk.hex('#0A0A1A'),
|
|
50
|
-
surfaceAlt: chalk.hex('#12122E'),
|
|
51
|
-
userBg: chalk.hex('#12081E'),
|
|
52
|
-
border: chalk.hex('#1E1E4E'),
|
|
53
|
-
purple: chalk.hex('#7C3AED'),
|
|
54
|
-
accent: chalk.hex('#E05555'),
|
|
55
|
-
green: chalk.hex('#16A34A'),
|
|
56
|
-
gold: chalk.hex('#D97706'),
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export const u = {
|
|
60
|
-
tl: '\u250C',
|
|
61
|
-
tr: '\u2510',
|
|
62
|
-
bl: '\u2514',
|
|
63
|
-
br: '\u2518',
|
|
64
|
-
h: '\u2500',
|
|
65
|
-
v: '\u2502',
|
|
66
|
-
lc: '\u251C',
|
|
67
|
-
rc: '\u2524',
|
|
68
|
-
tc: '\u252C',
|
|
69
|
-
bc: '\u2534',
|
|
70
|
-
cr: '\u253C',
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export const u2 = {
|
|
74
|
-
tl: '\u2554',
|
|
75
|
-
tr: '\u2557',
|
|
76
|
-
bl: '\u255A',
|
|
77
|
-
br: '\u255D',
|
|
78
|
-
h: '\u2550',
|
|
79
|
-
v: '\u2551',
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export const usym = {
|
|
83
|
-
topR: '\u256D',
|
|
84
|
-
topL: '\u256E',
|
|
85
|
-
botR: '\u2570',
|
|
86
|
-
botL: '\u256F',
|
|
87
|
-
triR: '\u25B6',
|
|
88
|
-
triD: '\u25BC',
|
|
89
|
-
triR2: '\u25B8',
|
|
90
|
-
triD2: '\u25BE',
|
|
91
|
-
dot: '\u25CF',
|
|
92
|
-
circle: '\u25C9',
|
|
93
|
-
smallCircle: '\u25CB',
|
|
48
|
+
export const sym = {
|
|
94
49
|
diamond: '\u25C6',
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
50
|
+
circle: '\u25C9',
|
|
51
|
+
dot: '\u25CF',
|
|
52
|
+
smallDot: '\u25CB',
|
|
53
|
+
triR: '\u25B8',
|
|
54
|
+
triD: '\u25BE',
|
|
55
|
+
bullet: '\u25C9',
|
|
98
56
|
cross: '\u2716',
|
|
99
57
|
ellipsis: '\u2026',
|
|
100
|
-
quoteD: '\u201C',
|
|
101
|
-
quoteS: '\u2018',
|
|
102
58
|
mdash: '\u2014',
|
|
103
59
|
ndash: '\u2013',
|
|
104
60
|
midDot: '\u00B7',
|
|
@@ -108,21 +64,11 @@ export const usym = {
|
|
|
108
64
|
arrowD: '\u2193',
|
|
109
65
|
lightV: '\u2502',
|
|
110
66
|
lightH: '\u2500',
|
|
111
|
-
teeL: '\u251C',
|
|
112
|
-
teeR: '\u2524',
|
|
113
67
|
treeJ: '\u2514',
|
|
114
68
|
treeT: '\u251C',
|
|
115
69
|
treeCon: '\u2502',
|
|
116
70
|
treeTip: '\u2570',
|
|
117
71
|
treeFork: '\u256D',
|
|
72
|
+
star: '\u2726',
|
|
73
|
+
asterisk: '\u2731',
|
|
118
74
|
};
|
|
119
|
-
|
|
120
|
-
export function boxTop(w) {
|
|
121
|
-
return u.lc + u.h.repeat(w - 2) + u.rc;
|
|
122
|
-
}
|
|
123
|
-
export function boxBottom(w) {
|
|
124
|
-
return u.lc + u.h.repeat(w - 2) + u.rc;
|
|
125
|
-
}
|
|
126
|
-
export function boxTitle(title, w) {
|
|
127
|
-
return u.tl + ' ' + title + ' ' + u.h.repeat(Math.max(0, w - title.length - 4)) + u.tr;
|
|
128
|
-
}
|