hanzo 0.2.2__py3-none-any.whl → 0.2.5__py3-none-any.whl
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.
Potentially problematic release.
This version of hanzo might be problematic. Click here for more details.
- hanzo/__init__.py +4 -93
- hanzo/__main__.py +6 -0
- hanzo/cli.py +232 -8
- hanzo/commands/__init__.py +3 -0
- hanzo/commands/agent.py +112 -0
- hanzo/commands/auth.py +324 -0
- hanzo/commands/chat.py +183 -0
- hanzo/commands/cluster.py +428 -0
- hanzo/commands/config.py +240 -0
- hanzo/commands/mcp.py +235 -0
- hanzo/commands/miner.py +323 -0
- hanzo/commands/network.py +333 -0
- hanzo/commands/repl.py +186 -0
- hanzo/commands/tools.py +335 -0
- hanzo/interactive/__init__.py +3 -0
- hanzo/interactive/dashboard.py +125 -0
- hanzo/interactive/repl.py +184 -0
- hanzo/router/__init__.py +13 -7
- hanzo/utils/__init__.py +3 -0
- hanzo/utils/config.py +170 -0
- hanzo/utils/output.py +103 -0
- hanzo-0.2.5.dist-info/METADATA +137 -0
- hanzo-0.2.5.dist-info/RECORD +27 -0
- hanzo-0.2.2.dist-info/METADATA +0 -74
- hanzo-0.2.2.dist-info/RECORD +0 -9
- {hanzo-0.2.2.dist-info → hanzo-0.2.5.dist-info}/WHEEL +0 -0
- {hanzo-0.2.2.dist-info → hanzo-0.2.5.dist-info}/entry_points.txt +0 -0
hanzo/commands/auth.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Authentication commands."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import httpx
|
|
10
|
+
from rich.prompt import Prompt
|
|
11
|
+
|
|
12
|
+
from ..utils.output import console, handle_errors
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group(name="auth")
|
|
16
|
+
def auth_group():
|
|
17
|
+
"""Manage Hanzo authentication."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@auth_group.command()
|
|
22
|
+
@click.option("--email", "-e", help="Email address")
|
|
23
|
+
@click.option("--password", "-p", help="Password (not recommended, use prompt)")
|
|
24
|
+
@click.option("--api-key", "-k", help="API key for direct authentication")
|
|
25
|
+
@click.option("--sso", is_flag=True, help="Use SSO authentication")
|
|
26
|
+
@click.pass_context
|
|
27
|
+
async def login(ctx, email: str, password: str, api_key: str, sso: bool):
|
|
28
|
+
"""Login to Hanzo AI."""
|
|
29
|
+
try:
|
|
30
|
+
from hanzoai.auth import HanzoAuth
|
|
31
|
+
except ImportError:
|
|
32
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
33
|
+
console.print("Install with: pip install hanzo")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
auth = HanzoAuth()
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
if api_key:
|
|
40
|
+
# Direct API key authentication
|
|
41
|
+
console.print("Authenticating with API key...")
|
|
42
|
+
result = await auth.login_with_api_key(api_key)
|
|
43
|
+
elif sso:
|
|
44
|
+
# SSO authentication via browser
|
|
45
|
+
console.print("Opening browser for SSO login...")
|
|
46
|
+
console.print("If browser doesn't open, visit: https://iam.hanzo.ai/login")
|
|
47
|
+
result = await auth.login_with_sso()
|
|
48
|
+
else:
|
|
49
|
+
# Email/password authentication
|
|
50
|
+
if not email:
|
|
51
|
+
email = Prompt.ask("Email")
|
|
52
|
+
if not password:
|
|
53
|
+
password = Prompt.ask("Password", password=True)
|
|
54
|
+
|
|
55
|
+
console.print("Authenticating...")
|
|
56
|
+
result = await auth.login(email, password)
|
|
57
|
+
|
|
58
|
+
# Save credentials
|
|
59
|
+
config_dir = Path.home() / ".hanzo"
|
|
60
|
+
config_dir.mkdir(exist_ok=True)
|
|
61
|
+
|
|
62
|
+
config_file = config_dir / "auth.json"
|
|
63
|
+
await auth.save_credentials(config_file)
|
|
64
|
+
|
|
65
|
+
# Also set environment variable if API key is available
|
|
66
|
+
if api_key or result.get("api_key"):
|
|
67
|
+
key = api_key or result.get("api_key")
|
|
68
|
+
console.print(f"\n[dim]To use in environment:[/dim]")
|
|
69
|
+
console.print(f"export HANZO_API_KEY={key}")
|
|
70
|
+
|
|
71
|
+
console.print(f"[green]✓[/green] Logged in as {result.get('email', 'user')}")
|
|
72
|
+
|
|
73
|
+
# Check organization
|
|
74
|
+
if org := result.get("organization"):
|
|
75
|
+
console.print(f" Organization: {org}")
|
|
76
|
+
|
|
77
|
+
# Check permissions
|
|
78
|
+
if permissions := result.get("permissions"):
|
|
79
|
+
console.print(f" Permissions: {', '.join(permissions)}")
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
console.print(f"[red]Login failed: {e}[/red]")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@auth_group.command()
|
|
86
|
+
@click.pass_context
|
|
87
|
+
async def logout(ctx):
|
|
88
|
+
"""Logout from Hanzo AI."""
|
|
89
|
+
try:
|
|
90
|
+
from hanzoai.auth import HanzoAuth
|
|
91
|
+
except ImportError:
|
|
92
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
auth = HanzoAuth()
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
await auth.logout()
|
|
99
|
+
|
|
100
|
+
# Remove saved credentials
|
|
101
|
+
config_file = Path.home() / ".hanzo" / "auth.json"
|
|
102
|
+
if config_file.exists():
|
|
103
|
+
config_file.unlink()
|
|
104
|
+
|
|
105
|
+
console.print("[green]✓[/green] Logged out successfully")
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
console.print(f"[red]Logout failed: {e}[/red]")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@auth_group.command()
|
|
112
|
+
@click.pass_context
|
|
113
|
+
async def status(ctx):
|
|
114
|
+
"""Show authentication status."""
|
|
115
|
+
# Check environment variable
|
|
116
|
+
if api_key := os.environ.get("HANZO_API_KEY"):
|
|
117
|
+
console.print("[green]✓[/green] HANZO_API_KEY environment variable set")
|
|
118
|
+
console.print(f" Key: {api_key[:8]}...{api_key[-4:]}")
|
|
119
|
+
|
|
120
|
+
# Check saved credentials
|
|
121
|
+
config_file = Path.home() / ".hanzo" / "auth.json"
|
|
122
|
+
if config_file.exists():
|
|
123
|
+
try:
|
|
124
|
+
from hanzoai.auth import HanzoAuth
|
|
125
|
+
auth = HanzoAuth()
|
|
126
|
+
creds = await auth.load_credentials(config_file)
|
|
127
|
+
|
|
128
|
+
console.print("[green]✓[/green] Saved credentials found")
|
|
129
|
+
if email := creds.get("email"):
|
|
130
|
+
console.print(f" Email: {email}")
|
|
131
|
+
if org := creds.get("organization"):
|
|
132
|
+
console.print(f" Organization: {org}")
|
|
133
|
+
|
|
134
|
+
# Verify credentials are still valid
|
|
135
|
+
with console.status("Verifying credentials..."):
|
|
136
|
+
try:
|
|
137
|
+
user_info = await auth.get_user_info()
|
|
138
|
+
console.print("[green]✓[/green] Credentials are valid")
|
|
139
|
+
|
|
140
|
+
# Show usage stats if available
|
|
141
|
+
if usage := user_info.get("usage"):
|
|
142
|
+
console.print("\n[cyan]Usage:[/cyan]")
|
|
143
|
+
console.print(f" API calls: {usage.get('api_calls', 0)}")
|
|
144
|
+
console.print(f" Tokens: {usage.get('tokens', 0)}")
|
|
145
|
+
if quota := usage.get('quota'):
|
|
146
|
+
console.print(f" Quota: {usage.get('tokens', 0)} / {quota}")
|
|
147
|
+
except:
|
|
148
|
+
console.print("[yellow]![/yellow] Credentials may be expired")
|
|
149
|
+
console.print("Run 'hanzo auth login' to refresh")
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
console.print(f"[red]Error reading credentials: {e}[/red]")
|
|
153
|
+
else:
|
|
154
|
+
console.print("[yellow]![/yellow] Not logged in")
|
|
155
|
+
console.print("Run 'hanzo auth login' to authenticate")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@auth_group.command()
|
|
159
|
+
@click.option("--name", "-n", required=True, help="API key name")
|
|
160
|
+
@click.option("--permissions", "-p", multiple=True, help="Permissions (e.g., read, write, admin)")
|
|
161
|
+
@click.option("--expires", "-e", help="Expiration (e.g., 30d, 1y, never)")
|
|
162
|
+
@click.pass_context
|
|
163
|
+
async def create_key(ctx, name: str, permissions: tuple, expires: str):
|
|
164
|
+
"""Create a new API key."""
|
|
165
|
+
try:
|
|
166
|
+
from hanzoai.auth import HanzoAuth
|
|
167
|
+
except ImportError:
|
|
168
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
auth = HanzoAuth()
|
|
172
|
+
|
|
173
|
+
# Ensure authenticated
|
|
174
|
+
if not await auth.is_authenticated():
|
|
175
|
+
console.print("[red]Error:[/red] Not authenticated")
|
|
176
|
+
console.print("Run 'hanzo auth login' first")
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
with console.status(f"Creating API key '{name}'..."):
|
|
180
|
+
try:
|
|
181
|
+
result = await auth.create_api_key(
|
|
182
|
+
name=name,
|
|
183
|
+
permissions=list(permissions) if permissions else None,
|
|
184
|
+
expires=expires
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
key = result.get("key")
|
|
188
|
+
console.print(f"[green]✓[/green] Created API key: {name}")
|
|
189
|
+
console.print(f"\n[yellow]Save this key - it won't be shown again:[/yellow]")
|
|
190
|
+
console.print(f"{key}")
|
|
191
|
+
console.print(f"\nTo use:")
|
|
192
|
+
console.print(f"export HANZO_API_KEY={key}")
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
console.print(f"[red]Failed to create key: {e}[/red]")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@auth_group.command()
|
|
199
|
+
@click.pass_context
|
|
200
|
+
async def list_keys(ctx):
|
|
201
|
+
"""List your API keys."""
|
|
202
|
+
try:
|
|
203
|
+
from hanzoai.auth import HanzoAuth
|
|
204
|
+
except ImportError:
|
|
205
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
auth = HanzoAuth()
|
|
209
|
+
|
|
210
|
+
# Ensure authenticated
|
|
211
|
+
if not await auth.is_authenticated():
|
|
212
|
+
console.print("[red]Error:[/red] Not authenticated")
|
|
213
|
+
console.print("Run 'hanzo auth login' first")
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
with console.status("Loading API keys..."):
|
|
217
|
+
try:
|
|
218
|
+
keys = await auth.list_api_keys()
|
|
219
|
+
|
|
220
|
+
if keys:
|
|
221
|
+
from rich.table import Table
|
|
222
|
+
|
|
223
|
+
table = Table(title="API Keys")
|
|
224
|
+
table.add_column("Name", style="cyan")
|
|
225
|
+
table.add_column("Created", style="green")
|
|
226
|
+
table.add_column("Last Used", style="yellow")
|
|
227
|
+
table.add_column("Permissions", style="blue")
|
|
228
|
+
table.add_column("Status", style="magenta")
|
|
229
|
+
|
|
230
|
+
for key in keys:
|
|
231
|
+
table.add_row(
|
|
232
|
+
key.get("name", "unknown"),
|
|
233
|
+
key.get("created_at", "unknown"),
|
|
234
|
+
key.get("last_used", "never"),
|
|
235
|
+
", ".join(key.get("permissions", [])),
|
|
236
|
+
key.get("status", "active")
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
console.print(table)
|
|
240
|
+
else:
|
|
241
|
+
console.print("[yellow]No API keys found[/yellow]")
|
|
242
|
+
console.print("Create one with: hanzo auth create-key")
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
console.print(f"[red]Failed to list keys: {e}[/red]")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@auth_group.command()
|
|
249
|
+
@click.argument("name")
|
|
250
|
+
@click.pass_context
|
|
251
|
+
async def revoke_key(ctx, name: str):
|
|
252
|
+
"""Revoke an API key."""
|
|
253
|
+
try:
|
|
254
|
+
from hanzoai.auth import HanzoAuth
|
|
255
|
+
except ImportError:
|
|
256
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
auth = HanzoAuth()
|
|
260
|
+
|
|
261
|
+
# Ensure authenticated
|
|
262
|
+
if not await auth.is_authenticated():
|
|
263
|
+
console.print("[red]Error:[/red] Not authenticated")
|
|
264
|
+
console.print("Run 'hanzo auth login' first")
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
if click.confirm(f"Revoke API key '{name}'?"):
|
|
268
|
+
with console.status(f"Revoking key '{name}'..."):
|
|
269
|
+
try:
|
|
270
|
+
await auth.revoke_api_key(name)
|
|
271
|
+
console.print(f"[green]✓[/green] Revoked API key: {name}")
|
|
272
|
+
except Exception as e:
|
|
273
|
+
console.print(f"[red]Failed to revoke key: {e}[/red]")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@auth_group.command()
|
|
277
|
+
@click.pass_context
|
|
278
|
+
async def whoami(ctx):
|
|
279
|
+
"""Show current user information."""
|
|
280
|
+
try:
|
|
281
|
+
from hanzoai.auth import HanzoAuth
|
|
282
|
+
except ImportError:
|
|
283
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
auth = HanzoAuth()
|
|
287
|
+
|
|
288
|
+
# Check if authenticated
|
|
289
|
+
if not await auth.is_authenticated():
|
|
290
|
+
console.print("[yellow]Not authenticated[/yellow]")
|
|
291
|
+
|
|
292
|
+
# Check if API key is set
|
|
293
|
+
if os.environ.get("HANZO_API_KEY"):
|
|
294
|
+
console.print("HANZO_API_KEY is set but may be invalid")
|
|
295
|
+
else:
|
|
296
|
+
console.print("Run 'hanzo auth login' to authenticate")
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
with console.status("Loading user information..."):
|
|
300
|
+
try:
|
|
301
|
+
user = await auth.get_user_info()
|
|
302
|
+
|
|
303
|
+
console.print(f"[cyan]User Information:[/cyan]")
|
|
304
|
+
console.print(f" ID: {user.get('id', 'unknown')}")
|
|
305
|
+
console.print(f" Email: {user.get('email', 'unknown')}")
|
|
306
|
+
console.print(f" Name: {user.get('name', 'unknown')}")
|
|
307
|
+
|
|
308
|
+
if org := user.get('organization'):
|
|
309
|
+
console.print(f"\n[cyan]Organization:[/cyan]")
|
|
310
|
+
console.print(f" Name: {org.get('name', 'unknown')}")
|
|
311
|
+
console.print(f" Role: {org.get('role', 'member')}")
|
|
312
|
+
|
|
313
|
+
if teams := user.get('teams'):
|
|
314
|
+
console.print(f"\n[cyan]Teams:[/cyan]")
|
|
315
|
+
for team in teams:
|
|
316
|
+
console.print(f" • {team}")
|
|
317
|
+
|
|
318
|
+
if perms := user.get('permissions'):
|
|
319
|
+
console.print(f"\n[cyan]Permissions:[/cyan]")
|
|
320
|
+
for perm in sorted(perms):
|
|
321
|
+
console.print(f" • {perm}")
|
|
322
|
+
|
|
323
|
+
except Exception as e:
|
|
324
|
+
console.print(f"[red]Error: {e}[/red]")
|
hanzo/commands/chat.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Chat command for interactive AI conversations."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import httpx
|
|
8
|
+
from rich.markdown import Markdown
|
|
9
|
+
|
|
10
|
+
from ..utils.output import console, handle_errors
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command(name="chat")
|
|
14
|
+
@click.option("--model", "-m", default="llama-3.2-3b", help="Model to use")
|
|
15
|
+
@click.option("--local/--cloud", default=True, help="Use local or cloud model")
|
|
16
|
+
@click.option("--once", is_flag=True, help="Single question mode")
|
|
17
|
+
@click.option("--system", "-s", help="System prompt")
|
|
18
|
+
@click.option("--repl", is_flag=True, help="Start full REPL interface (like Claude Code)")
|
|
19
|
+
@click.option("--ipython", is_flag=True, help="Use IPython REPL interface")
|
|
20
|
+
@click.option("--tui", is_flag=True, help="Use beautiful TUI interface")
|
|
21
|
+
@click.argument("prompt", nargs=-1)
|
|
22
|
+
@click.pass_context
|
|
23
|
+
def chat_command(ctx, model: str, local: bool, once: bool, system: Optional[str], repl: bool, ipython: bool, tui: bool, prompt: tuple):
|
|
24
|
+
"""Interactive AI chat."""
|
|
25
|
+
# Check if REPL mode requested
|
|
26
|
+
if repl or ipython or tui:
|
|
27
|
+
try:
|
|
28
|
+
import sys
|
|
29
|
+
import os
|
|
30
|
+
|
|
31
|
+
# Set up environment
|
|
32
|
+
if model:
|
|
33
|
+
os.environ["HANZO_DEFAULT_MODEL"] = model
|
|
34
|
+
if local:
|
|
35
|
+
os.environ["HANZO_USE_LOCAL"] = "true"
|
|
36
|
+
if system:
|
|
37
|
+
os.environ["HANZO_SYSTEM_PROMPT"] = system
|
|
38
|
+
|
|
39
|
+
if ipython:
|
|
40
|
+
from hanzo_repl.ipython_repl import main
|
|
41
|
+
sys.exit(main())
|
|
42
|
+
elif tui:
|
|
43
|
+
from hanzo_repl.textual_repl import main
|
|
44
|
+
sys.exit(main())
|
|
45
|
+
else:
|
|
46
|
+
from hanzo_repl.cli import main
|
|
47
|
+
sys.exit(main())
|
|
48
|
+
except ImportError:
|
|
49
|
+
console.print("[red]Error:[/red] hanzo-repl not installed")
|
|
50
|
+
console.print("Install with: pip install hanzo[repl]")
|
|
51
|
+
console.print("\nAlternatively:")
|
|
52
|
+
console.print(" pip install hanzo-repl")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
prompt_text = " ".join(prompt) if prompt else None
|
|
56
|
+
|
|
57
|
+
if once or prompt_text:
|
|
58
|
+
# Single question mode
|
|
59
|
+
asyncio.run(ask_once(ctx, prompt_text or "Hello", model, local, system))
|
|
60
|
+
else:
|
|
61
|
+
# Interactive chat
|
|
62
|
+
asyncio.run(interactive_chat(ctx, model, local, system))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def ask_once(ctx, prompt: str, model: str, local: bool, system: Optional[str] = None):
|
|
66
|
+
"""Ask a single question."""
|
|
67
|
+
messages = []
|
|
68
|
+
if system:
|
|
69
|
+
messages.append({"role": "system", "content": system})
|
|
70
|
+
messages.append({"role": "user", "content": prompt})
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
if local:
|
|
74
|
+
# Use local cluster
|
|
75
|
+
base_url = "http://localhost:8000"
|
|
76
|
+
|
|
77
|
+
# Check if cluster is running
|
|
78
|
+
try:
|
|
79
|
+
async with httpx.AsyncClient() as client:
|
|
80
|
+
await client.get(f"{base_url}/health")
|
|
81
|
+
except httpx.ConnectError:
|
|
82
|
+
console.print("[yellow]Local cluster not running. Start with: hanzo serve[/yellow]")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
# Make request to local cluster
|
|
86
|
+
async with httpx.AsyncClient() as client:
|
|
87
|
+
response = await client.post(
|
|
88
|
+
f"{base_url}/v1/chat/completions",
|
|
89
|
+
json={
|
|
90
|
+
"model": model,
|
|
91
|
+
"messages": messages,
|
|
92
|
+
"stream": False
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
response.raise_for_status()
|
|
96
|
+
result = response.json()
|
|
97
|
+
content = result["choices"][0]["message"]["content"]
|
|
98
|
+
else:
|
|
99
|
+
# Use cloud API
|
|
100
|
+
try:
|
|
101
|
+
from hanzoai import completion
|
|
102
|
+
result = completion(
|
|
103
|
+
model=f"anthropic/{model}" if "claude" in model else model,
|
|
104
|
+
messages=messages
|
|
105
|
+
)
|
|
106
|
+
content = result.choices[0].message.content
|
|
107
|
+
except ImportError:
|
|
108
|
+
console.print("[red]Error:[/red] hanzoai not installed")
|
|
109
|
+
console.print("Install with: pip install hanzo[all]")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# Display response
|
|
113
|
+
if ctx.obj.get("json"):
|
|
114
|
+
console.print_json(data={"response": content})
|
|
115
|
+
else:
|
|
116
|
+
console.print(Markdown(content))
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def interactive_chat(ctx, model: str, local: bool, system: Optional[str]):
|
|
123
|
+
"""Run interactive chat session."""
|
|
124
|
+
from prompt_toolkit import PromptSession
|
|
125
|
+
from prompt_toolkit.history import FileHistory
|
|
126
|
+
|
|
127
|
+
console.print(f"[cyan]Chat session started[/cyan] (model: {model}, mode: {'local' if local else 'cloud'})")
|
|
128
|
+
console.print("Type 'exit' or Ctrl+D to quit\n")
|
|
129
|
+
|
|
130
|
+
session = PromptSession(history=FileHistory(".hanzo_chat_history"))
|
|
131
|
+
messages = []
|
|
132
|
+
|
|
133
|
+
if system:
|
|
134
|
+
messages.append({"role": "system", "content": system})
|
|
135
|
+
|
|
136
|
+
while True:
|
|
137
|
+
try:
|
|
138
|
+
# Get user input
|
|
139
|
+
user_input = await session.prompt_async("You: ")
|
|
140
|
+
|
|
141
|
+
if user_input.lower() in ["exit", "quit"]:
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
# Add to messages
|
|
145
|
+
messages.append({"role": "user", "content": user_input})
|
|
146
|
+
|
|
147
|
+
# Get response
|
|
148
|
+
console.print("AI: ", end="")
|
|
149
|
+
with console.status(""):
|
|
150
|
+
if local:
|
|
151
|
+
# Use local cluster
|
|
152
|
+
async with httpx.AsyncClient() as client:
|
|
153
|
+
response = await client.post(
|
|
154
|
+
"http://localhost:8000/v1/chat/completions",
|
|
155
|
+
json={
|
|
156
|
+
"model": model,
|
|
157
|
+
"messages": messages,
|
|
158
|
+
"stream": False
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
response.raise_for_status()
|
|
162
|
+
result = response.json()
|
|
163
|
+
content = result["choices"][0]["message"]["content"]
|
|
164
|
+
else:
|
|
165
|
+
# Use cloud API
|
|
166
|
+
from hanzoai import completion
|
|
167
|
+
result = completion(
|
|
168
|
+
model=f"anthropic/{model}" if "claude" in model else model,
|
|
169
|
+
messages=messages
|
|
170
|
+
)
|
|
171
|
+
content = result.choices[0].message.content
|
|
172
|
+
|
|
173
|
+
# Display and save response
|
|
174
|
+
console.print(Markdown(content))
|
|
175
|
+
messages.append({"role": "assistant", "content": content})
|
|
176
|
+
console.print()
|
|
177
|
+
|
|
178
|
+
except KeyboardInterrupt:
|
|
179
|
+
continue
|
|
180
|
+
except EOFError:
|
|
181
|
+
break
|
|
182
|
+
except Exception as e:
|
|
183
|
+
console.print(f"\n[red]Error: {e}[/red]\n")
|