npcsh 0.3.31__py3-none-any.whl → 1.0.0__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.
- npcsh/_state.py +942 -0
- npcsh/alicanto.py +1074 -0
- npcsh/guac.py +785 -0
- npcsh/mcp_helpers.py +357 -0
- npcsh/mcp_npcsh.py +822 -0
- npcsh/mcp_server.py +184 -0
- npcsh/npc.py +218 -0
- npcsh/npcsh.py +1161 -0
- npcsh/plonk.py +387 -269
- npcsh/pti.py +234 -0
- npcsh/routes.py +958 -0
- npcsh/spool.py +315 -0
- npcsh/wander.py +550 -0
- npcsh/yap.py +573 -0
- npcsh-1.0.0.dist-info/METADATA +596 -0
- npcsh-1.0.0.dist-info/RECORD +21 -0
- {npcsh-0.3.31.dist-info → npcsh-1.0.0.dist-info}/WHEEL +1 -1
- npcsh-1.0.0.dist-info/entry_points.txt +9 -0
- {npcsh-0.3.31.dist-info → npcsh-1.0.0.dist-info}/licenses/LICENSE +1 -1
- npcsh/audio.py +0 -210
- npcsh/cli.py +0 -545
- npcsh/command_history.py +0 -566
- npcsh/conversation.py +0 -291
- npcsh/data_models.py +0 -46
- npcsh/dataframes.py +0 -163
- npcsh/embeddings.py +0 -168
- npcsh/helpers.py +0 -641
- npcsh/image.py +0 -298
- npcsh/image_gen.py +0 -79
- npcsh/knowledge_graph.py +0 -1006
- npcsh/llm_funcs.py +0 -2027
- npcsh/load_data.py +0 -83
- npcsh/main.py +0 -5
- npcsh/model_runner.py +0 -189
- npcsh/npc_compiler.py +0 -2870
- npcsh/npc_sysenv.py +0 -383
- npcsh/npc_team/assembly_lines/test_pipeline.py +0 -181
- npcsh/npc_team/corca.npc +0 -13
- npcsh/npc_team/foreman.npc +0 -7
- npcsh/npc_team/npcsh.ctx +0 -11
- npcsh/npc_team/sibiji.npc +0 -4
- npcsh/npc_team/templates/analytics/celona.npc +0 -0
- npcsh/npc_team/templates/hr_support/raone.npc +0 -0
- npcsh/npc_team/templates/humanities/eriane.npc +0 -4
- npcsh/npc_team/templates/it_support/lineru.npc +0 -0
- npcsh/npc_team/templates/marketing/slean.npc +0 -4
- npcsh/npc_team/templates/philosophy/maurawa.npc +0 -0
- npcsh/npc_team/templates/sales/turnic.npc +0 -4
- npcsh/npc_team/templates/software/welxor.npc +0 -0
- npcsh/npc_team/tools/bash_executer.tool +0 -32
- npcsh/npc_team/tools/calculator.tool +0 -8
- npcsh/npc_team/tools/code_executor.tool +0 -16
- npcsh/npc_team/tools/generic_search.tool +0 -27
- npcsh/npc_team/tools/image_generation.tool +0 -25
- npcsh/npc_team/tools/local_search.tool +0 -149
- npcsh/npc_team/tools/npcsh_executor.tool +0 -9
- npcsh/npc_team/tools/screen_cap.tool +0 -27
- npcsh/npc_team/tools/sql_executor.tool +0 -26
- npcsh/response.py +0 -623
- npcsh/search.py +0 -248
- npcsh/serve.py +0 -1460
- npcsh/shell.py +0 -538
- npcsh/shell_helpers.py +0 -3529
- npcsh/stream.py +0 -700
- npcsh/video.py +0 -49
- npcsh-0.3.31.data/data/npcsh/npc_team/bash_executer.tool +0 -32
- npcsh-0.3.31.data/data/npcsh/npc_team/calculator.tool +0 -8
- npcsh-0.3.31.data/data/npcsh/npc_team/celona.npc +0 -0
- npcsh-0.3.31.data/data/npcsh/npc_team/code_executor.tool +0 -16
- npcsh-0.3.31.data/data/npcsh/npc_team/corca.npc +0 -13
- npcsh-0.3.31.data/data/npcsh/npc_team/eriane.npc +0 -4
- npcsh-0.3.31.data/data/npcsh/npc_team/foreman.npc +0 -7
- npcsh-0.3.31.data/data/npcsh/npc_team/generic_search.tool +0 -27
- npcsh-0.3.31.data/data/npcsh/npc_team/image_generation.tool +0 -25
- npcsh-0.3.31.data/data/npcsh/npc_team/lineru.npc +0 -0
- npcsh-0.3.31.data/data/npcsh/npc_team/local_search.tool +0 -149
- npcsh-0.3.31.data/data/npcsh/npc_team/maurawa.npc +0 -0
- npcsh-0.3.31.data/data/npcsh/npc_team/npcsh.ctx +0 -11
- npcsh-0.3.31.data/data/npcsh/npc_team/npcsh_executor.tool +0 -9
- npcsh-0.3.31.data/data/npcsh/npc_team/raone.npc +0 -0
- npcsh-0.3.31.data/data/npcsh/npc_team/screen_cap.tool +0 -27
- npcsh-0.3.31.data/data/npcsh/npc_team/sibiji.npc +0 -4
- npcsh-0.3.31.data/data/npcsh/npc_team/slean.npc +0 -4
- npcsh-0.3.31.data/data/npcsh/npc_team/sql_executor.tool +0 -26
- npcsh-0.3.31.data/data/npcsh/npc_team/test_pipeline.py +0 -181
- npcsh-0.3.31.data/data/npcsh/npc_team/turnic.npc +0 -4
- npcsh-0.3.31.data/data/npcsh/npc_team/welxor.npc +0 -0
- npcsh-0.3.31.dist-info/METADATA +0 -1853
- npcsh-0.3.31.dist-info/RECORD +0 -76
- npcsh-0.3.31.dist-info/entry_points.txt +0 -3
- {npcsh-0.3.31.dist-info → npcsh-1.0.0.dist-info}/top_level.txt +0 -0
npcsh/command_history.py
DELETED
|
@@ -1,566 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sqlite3
|
|
3
|
-
import json
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
import uuid
|
|
6
|
-
from typing import Optional, List, Dict, Any, Tuple
|
|
7
|
-
import pandas as pd
|
|
8
|
-
import numpy as np
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def deep_to_dict(obj):
|
|
12
|
-
"""
|
|
13
|
-
Recursively convert objects that have a 'to_dict' method to dictionaries,
|
|
14
|
-
otherwise drop them from the output.
|
|
15
|
-
"""
|
|
16
|
-
if isinstance(obj, dict):
|
|
17
|
-
return {key: deep_to_dict(val) for key, val in obj.items()}
|
|
18
|
-
|
|
19
|
-
if isinstance(obj, list):
|
|
20
|
-
return [deep_to_dict(item) for item in obj]
|
|
21
|
-
|
|
22
|
-
if hasattr(obj, "to_dict") and callable(getattr(obj, "to_dict", None)):
|
|
23
|
-
return deep_to_dict(obj.to_dict())
|
|
24
|
-
|
|
25
|
-
if isinstance(obj, (int, float, str, bool, type(None))):
|
|
26
|
-
return obj
|
|
27
|
-
|
|
28
|
-
return None # Drop objects that don't have a known conversion
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class CustomJSONEncoder(json.JSONEncoder):
|
|
32
|
-
def default(self, obj):
|
|
33
|
-
try:
|
|
34
|
-
return deep_to_dict(obj)
|
|
35
|
-
except TypeError:
|
|
36
|
-
return super().default(obj)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def show_history(command_history, args):
|
|
40
|
-
if args:
|
|
41
|
-
search_results = command_history.search(args[0])
|
|
42
|
-
if search_results:
|
|
43
|
-
return "\n".join(
|
|
44
|
-
[f"{item[0]}. [{item[1]}] {item[2]}" for item in search_results]
|
|
45
|
-
)
|
|
46
|
-
else:
|
|
47
|
-
return f"No commands found matching '{args[0]}'"
|
|
48
|
-
else:
|
|
49
|
-
all_history = command_history.get_all()
|
|
50
|
-
return "\n".join([f"{item[0]}. [{item[1]}] {item[2]}" for item in all_history])
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def query_history_for_llm(command_history, query):
|
|
54
|
-
results = command_history.search(query)
|
|
55
|
-
formatted_results = [
|
|
56
|
-
f"Command: {r[2]}\nOutput: {r[4]}\nLocation: {r[5]}" for r in results
|
|
57
|
-
]
|
|
58
|
-
return "\n\n".join(formatted_results)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class CommandHistory:
|
|
62
|
-
def __init__(self, path="~/npcsh_history.db"):
|
|
63
|
-
self.db_path = os.path.expanduser(path)
|
|
64
|
-
self.conn = sqlite3.connect(self.db_path)
|
|
65
|
-
self.conn.execute("PRAGMA foreign_keys = ON") # Enable foreign key support
|
|
66
|
-
self.cursor = self.conn.cursor()
|
|
67
|
-
self.create_command_table()
|
|
68
|
-
self.create_conversation_table()
|
|
69
|
-
self.create_attachment_table()
|
|
70
|
-
|
|
71
|
-
def create_command_table(self):
|
|
72
|
-
self.cursor.execute(
|
|
73
|
-
"""
|
|
74
|
-
CREATE TABLE IF NOT EXISTS command_history (
|
|
75
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
76
|
-
timestamp TEXT,
|
|
77
|
-
command TEXT,
|
|
78
|
-
subcommands TEXT,
|
|
79
|
-
output TEXT,
|
|
80
|
-
location TEXT
|
|
81
|
-
)
|
|
82
|
-
"""
|
|
83
|
-
)
|
|
84
|
-
self.conn.commit()
|
|
85
|
-
|
|
86
|
-
def create_conversation_table(self):
|
|
87
|
-
self.cursor.execute(
|
|
88
|
-
"""
|
|
89
|
-
CREATE TABLE IF NOT EXISTS conversation_history (
|
|
90
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
91
|
-
message_id TEXT UNIQUE,
|
|
92
|
-
timestamp TEXT,
|
|
93
|
-
role TEXT,
|
|
94
|
-
content TEXT,
|
|
95
|
-
conversation_id TEXT,
|
|
96
|
-
directory_path TEXT,
|
|
97
|
-
model TEXT,
|
|
98
|
-
provider TEXT,
|
|
99
|
-
npc TEXT
|
|
100
|
-
)
|
|
101
|
-
"""
|
|
102
|
-
)
|
|
103
|
-
self.conn.commit()
|
|
104
|
-
|
|
105
|
-
def create_attachment_table(self):
|
|
106
|
-
self.cursor.execute(
|
|
107
|
-
"""
|
|
108
|
-
CREATE TABLE IF NOT EXISTS message_attachments (
|
|
109
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
110
|
-
message_id TEXT,
|
|
111
|
-
attachment_name TEXT,
|
|
112
|
-
attachment_type TEXT,
|
|
113
|
-
attachment_data BLOB,
|
|
114
|
-
attachment_size INTEGER,
|
|
115
|
-
upload_timestamp TEXT,
|
|
116
|
-
FOREIGN KEY (message_id) REFERENCES conversation_history(message_id)
|
|
117
|
-
)
|
|
118
|
-
"""
|
|
119
|
-
)
|
|
120
|
-
self.conn.commit()
|
|
121
|
-
|
|
122
|
-
def add_command(self, command, subcommands, output, location):
|
|
123
|
-
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
124
|
-
|
|
125
|
-
# Convert everything to strings to ensure it can be stored
|
|
126
|
-
safe_subcommands = str(subcommands)
|
|
127
|
-
safe_output = str(output)
|
|
128
|
-
|
|
129
|
-
self.cursor.execute(
|
|
130
|
-
"""
|
|
131
|
-
INSERT INTO command_history (timestamp, command, subcommands, output, location)
|
|
132
|
-
VALUES (?, ?, ?, ?, ?)
|
|
133
|
-
""",
|
|
134
|
-
(timestamp, command, safe_subcommands, safe_output, location),
|
|
135
|
-
)
|
|
136
|
-
self.conn.commit()
|
|
137
|
-
|
|
138
|
-
def generate_message_id(self) -> str:
|
|
139
|
-
"""Generate a unique ID for a message."""
|
|
140
|
-
return str(uuid.uuid4())
|
|
141
|
-
|
|
142
|
-
def add_conversation(
|
|
143
|
-
self,
|
|
144
|
-
role,
|
|
145
|
-
content,
|
|
146
|
-
conversation_id,
|
|
147
|
-
directory_path,
|
|
148
|
-
model=None,
|
|
149
|
-
provider=None,
|
|
150
|
-
npc=None,
|
|
151
|
-
attachments=None,
|
|
152
|
-
message_id=None,
|
|
153
|
-
):
|
|
154
|
-
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
155
|
-
|
|
156
|
-
if message_id is None:
|
|
157
|
-
message_id = self.generate_message_id()
|
|
158
|
-
|
|
159
|
-
if isinstance(content, dict):
|
|
160
|
-
content = json.dumps(content, cls=CustomJSONEncoder)
|
|
161
|
-
# Check if message_id already exists
|
|
162
|
-
self.cursor.execute(
|
|
163
|
-
"SELECT content FROM conversation_history WHERE message_id = ?",
|
|
164
|
-
(message_id,),
|
|
165
|
-
)
|
|
166
|
-
existing_row = self.cursor.fetchone()
|
|
167
|
-
|
|
168
|
-
if existing_row:
|
|
169
|
-
# Append to existing content
|
|
170
|
-
new_content = existing_row[0] + content
|
|
171
|
-
self.cursor.execute(
|
|
172
|
-
"UPDATE conversation_history SET content = ?, timestamp = ? WHERE message_id = ?",
|
|
173
|
-
(new_content, timestamp, message_id),
|
|
174
|
-
)
|
|
175
|
-
else:
|
|
176
|
-
# Insert new message
|
|
177
|
-
self.cursor.execute(
|
|
178
|
-
"""
|
|
179
|
-
INSERT INTO conversation_history
|
|
180
|
-
(message_id, timestamp, role, content, conversation_id, directory_path, model, provider, npc)
|
|
181
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
182
|
-
""",
|
|
183
|
-
(
|
|
184
|
-
message_id,
|
|
185
|
-
timestamp,
|
|
186
|
-
role,
|
|
187
|
-
content,
|
|
188
|
-
conversation_id,
|
|
189
|
-
directory_path,
|
|
190
|
-
model,
|
|
191
|
-
provider,
|
|
192
|
-
npc,
|
|
193
|
-
),
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
self.conn.commit()
|
|
197
|
-
if attachments:
|
|
198
|
-
for attachment in attachments:
|
|
199
|
-
self.add_attachment(
|
|
200
|
-
message_id,
|
|
201
|
-
attachment["name"],
|
|
202
|
-
attachment["type"],
|
|
203
|
-
attachment["data"],
|
|
204
|
-
attachment_size=attachment.get("size"),
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
return message_id
|
|
208
|
-
|
|
209
|
-
def add_attachment(
|
|
210
|
-
self,
|
|
211
|
-
message_id,
|
|
212
|
-
attachment_name,
|
|
213
|
-
attachment_type,
|
|
214
|
-
attachment_data,
|
|
215
|
-
attachment_size=None,
|
|
216
|
-
):
|
|
217
|
-
"""
|
|
218
|
-
Add an attachment to a message.
|
|
219
|
-
|
|
220
|
-
Args:
|
|
221
|
-
message_id: The ID of the message to attach to
|
|
222
|
-
attachment_name: The name of the attachment file
|
|
223
|
-
attachment_type: The MIME type or file extension
|
|
224
|
-
attachment_data: The binary data of the attachment
|
|
225
|
-
attachment_size: The size in bytes (calculated if None)
|
|
226
|
-
"""
|
|
227
|
-
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
228
|
-
|
|
229
|
-
# Calculate size if not provided
|
|
230
|
-
if attachment_size is None and attachment_data is not None:
|
|
231
|
-
attachment_size = len(attachment_data)
|
|
232
|
-
|
|
233
|
-
self.cursor.execute(
|
|
234
|
-
"""
|
|
235
|
-
INSERT INTO message_attachments
|
|
236
|
-
(message_id, attachment_name, attachment_type, attachment_data, attachment_size, upload_timestamp)
|
|
237
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
238
|
-
""",
|
|
239
|
-
(
|
|
240
|
-
message_id,
|
|
241
|
-
attachment_name,
|
|
242
|
-
attachment_type,
|
|
243
|
-
attachment_data,
|
|
244
|
-
attachment_size,
|
|
245
|
-
timestamp,
|
|
246
|
-
),
|
|
247
|
-
)
|
|
248
|
-
self.conn.commit()
|
|
249
|
-
|
|
250
|
-
def get_full_message_content(self, message_id):
|
|
251
|
-
self.cursor.execute(
|
|
252
|
-
"SELECT content FROM conversation_history WHERE message_id = ? ORDER BY timestamp ASC",
|
|
253
|
-
(message_id,),
|
|
254
|
-
)
|
|
255
|
-
return "".join(row[0] for row in self.cursor.fetchall()) # Merge chunks
|
|
256
|
-
|
|
257
|
-
def update_message_content(self, message_id, full_content):
|
|
258
|
-
self.cursor.execute(
|
|
259
|
-
"UPDATE conversation_history SET content = ? WHERE message_id = ?",
|
|
260
|
-
(full_content, message_id),
|
|
261
|
-
)
|
|
262
|
-
self.conn.commit()
|
|
263
|
-
|
|
264
|
-
def get_message_attachments(self, message_id) -> List[Dict]:
|
|
265
|
-
"""
|
|
266
|
-
Retrieve all attachments for a specific message.
|
|
267
|
-
|
|
268
|
-
Args:
|
|
269
|
-
message_id: The ID of the message
|
|
270
|
-
|
|
271
|
-
Returns:
|
|
272
|
-
List of dictionaries containing attachment metadata (without binary data)
|
|
273
|
-
"""
|
|
274
|
-
self.cursor.execute(
|
|
275
|
-
"""
|
|
276
|
-
SELECT id, message_id, attachment_name, attachment_type, attachment_size, upload_timestamp
|
|
277
|
-
FROM message_attachments
|
|
278
|
-
WHERE message_id = ?
|
|
279
|
-
""",
|
|
280
|
-
(message_id,),
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
attachments = []
|
|
284
|
-
for row in self.cursor.fetchall():
|
|
285
|
-
attachments.append(
|
|
286
|
-
{
|
|
287
|
-
"id": row[0],
|
|
288
|
-
"message_id": row[1],
|
|
289
|
-
"name": row[2],
|
|
290
|
-
"type": row[3],
|
|
291
|
-
"size": row[4],
|
|
292
|
-
"timestamp": row[5],
|
|
293
|
-
}
|
|
294
|
-
)
|
|
295
|
-
return attachments
|
|
296
|
-
|
|
297
|
-
def get_attachment_data(self, attachment_id) -> Tuple[bytes, str, str]:
|
|
298
|
-
"""
|
|
299
|
-
Retrieve the binary data of a specific attachment.
|
|
300
|
-
|
|
301
|
-
Args:
|
|
302
|
-
attachment_id: The ID of the attachment
|
|
303
|
-
|
|
304
|
-
Returns:
|
|
305
|
-
Tuple of (binary_data, attachment_name, attachment_type)
|
|
306
|
-
"""
|
|
307
|
-
self.cursor.execute(
|
|
308
|
-
"""
|
|
309
|
-
SELECT attachment_data, attachment_name, attachment_type
|
|
310
|
-
FROM message_attachments
|
|
311
|
-
WHERE id = ?
|
|
312
|
-
""",
|
|
313
|
-
(attachment_id,),
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
result = self.cursor.fetchone()
|
|
317
|
-
if result:
|
|
318
|
-
return result[0], result[1], result[2]
|
|
319
|
-
return None, None, None
|
|
320
|
-
|
|
321
|
-
def delete_attachment(self, attachment_id) -> bool:
|
|
322
|
-
"""
|
|
323
|
-
Delete a specific attachment.
|
|
324
|
-
|
|
325
|
-
Args:
|
|
326
|
-
attachment_id: The ID of the attachment to delete
|
|
327
|
-
|
|
328
|
-
Returns:
|
|
329
|
-
Boolean indicating success
|
|
330
|
-
"""
|
|
331
|
-
try:
|
|
332
|
-
self.cursor.execute(
|
|
333
|
-
"""
|
|
334
|
-
DELETE FROM message_attachments
|
|
335
|
-
WHERE id = ?
|
|
336
|
-
""",
|
|
337
|
-
(attachment_id,),
|
|
338
|
-
)
|
|
339
|
-
self.conn.commit()
|
|
340
|
-
return self.cursor.rowcount > 0
|
|
341
|
-
except sqlite3.Error:
|
|
342
|
-
return False
|
|
343
|
-
|
|
344
|
-
def get_last_command(self):
|
|
345
|
-
self.cursor.execute(
|
|
346
|
-
"""
|
|
347
|
-
SELECT * FROM command_history ORDER BY id DESC LIMIT 1
|
|
348
|
-
"""
|
|
349
|
-
)
|
|
350
|
-
return self.cursor.fetchone()
|
|
351
|
-
|
|
352
|
-
def close(self):
|
|
353
|
-
self.conn.close()
|
|
354
|
-
|
|
355
|
-
def get_most_recent_conversation_id(self):
|
|
356
|
-
self.cursor.execute(
|
|
357
|
-
"""
|
|
358
|
-
SELECT conversation_id FROM conversation_history
|
|
359
|
-
ORDER BY id DESC LIMIT 1
|
|
360
|
-
"""
|
|
361
|
-
)
|
|
362
|
-
return self.cursor.fetchone()
|
|
363
|
-
|
|
364
|
-
def get_last_conversation(self, conversation_id):
|
|
365
|
-
self.cursor.execute(
|
|
366
|
-
"""
|
|
367
|
-
SELECT * FROM conversation_history WHERE conversation_id = ? and role = 'user'
|
|
368
|
-
ORDER BY id DESC LIMIT 1
|
|
369
|
-
""",
|
|
370
|
-
(conversation_id,),
|
|
371
|
-
)
|
|
372
|
-
return self.cursor.fetchone()
|
|
373
|
-
|
|
374
|
-
def get_message_by_id(self, message_id):
|
|
375
|
-
"""
|
|
376
|
-
Retrieve a message by its message_id.
|
|
377
|
-
|
|
378
|
-
Args:
|
|
379
|
-
message_id: The unique message ID
|
|
380
|
-
|
|
381
|
-
Returns:
|
|
382
|
-
The message row or None if not found
|
|
383
|
-
"""
|
|
384
|
-
self.cursor.execute(
|
|
385
|
-
"""
|
|
386
|
-
SELECT * FROM conversation_history
|
|
387
|
-
WHERE message_id = ?
|
|
388
|
-
""",
|
|
389
|
-
(message_id,),
|
|
390
|
-
)
|
|
391
|
-
return self.cursor.fetchone()
|
|
392
|
-
|
|
393
|
-
def get_last_conversation_by_path(self, directory_path):
|
|
394
|
-
most_recent_conversation_id = self.get_most_recent_conversation_id_by_path(
|
|
395
|
-
directory_path
|
|
396
|
-
)
|
|
397
|
-
if most_recent_conversation_id and most_recent_conversation_id[0]:
|
|
398
|
-
convo = self.get_conversations_by_id(most_recent_conversation_id[0])
|
|
399
|
-
return convo
|
|
400
|
-
return None
|
|
401
|
-
|
|
402
|
-
def get_most_recent_conversation_id_by_path(self, path) -> Optional[str]:
|
|
403
|
-
"""Retrieve the most recent conversation ID for the current path."""
|
|
404
|
-
self.cursor.execute(
|
|
405
|
-
"""
|
|
406
|
-
SELECT conversation_id FROM conversation_history
|
|
407
|
-
WHERE directory_path = ?
|
|
408
|
-
ORDER BY timestamp DESC LIMIT 1
|
|
409
|
-
""",
|
|
410
|
-
(path,),
|
|
411
|
-
)
|
|
412
|
-
result = self.cursor.fetchone()
|
|
413
|
-
return result
|
|
414
|
-
|
|
415
|
-
def get_conversations_by_id(self, conversation_id: str) -> List[Dict[str, Any]]:
|
|
416
|
-
"""Retrieve all messages for a specific conversation ID."""
|
|
417
|
-
self.cursor.execute(
|
|
418
|
-
"""
|
|
419
|
-
SELECT * FROM conversation_history
|
|
420
|
-
WHERE conversation_id = ?
|
|
421
|
-
ORDER BY timestamp ASC
|
|
422
|
-
""",
|
|
423
|
-
(conversation_id,),
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
results = []
|
|
427
|
-
for row in self.cursor.fetchall():
|
|
428
|
-
message_dict = {
|
|
429
|
-
"id": row[0],
|
|
430
|
-
"message_id": row[1],
|
|
431
|
-
"timestamp": row[2],
|
|
432
|
-
"role": row[3],
|
|
433
|
-
"content": row[4],
|
|
434
|
-
"conversation_id": row[5],
|
|
435
|
-
"directory_path": row[6],
|
|
436
|
-
"model": row[7],
|
|
437
|
-
"provider": row[8],
|
|
438
|
-
"npc": row[9],
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
# Get attachments for this message
|
|
442
|
-
attachments = self.get_message_attachments(row[1])
|
|
443
|
-
if attachments:
|
|
444
|
-
message_dict["attachments"] = attachments
|
|
445
|
-
|
|
446
|
-
results.append(message_dict)
|
|
447
|
-
|
|
448
|
-
return results
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
def start_new_conversation() -> str:
|
|
452
|
-
"""
|
|
453
|
-
Starts a new conversation and returns a unique conversation ID.
|
|
454
|
-
"""
|
|
455
|
-
return f"conversation_{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
def save_conversation_message(
|
|
459
|
-
command_history: CommandHistory,
|
|
460
|
-
conversation_id: str,
|
|
461
|
-
role: str,
|
|
462
|
-
content: str,
|
|
463
|
-
wd: str = None,
|
|
464
|
-
model: str = None,
|
|
465
|
-
provider: str = None,
|
|
466
|
-
npc: str = None,
|
|
467
|
-
attachments: List[Dict] = None,
|
|
468
|
-
message_id: str = None,
|
|
469
|
-
):
|
|
470
|
-
"""
|
|
471
|
-
Saves a conversation message linked to a conversation ID with optional attachments.
|
|
472
|
-
|
|
473
|
-
Args:
|
|
474
|
-
command_history: The CommandHistory instance
|
|
475
|
-
conversation_id: The conversation identifier
|
|
476
|
-
role: The message sender role ('user', 'assistant', etc.)
|
|
477
|
-
content: The message content
|
|
478
|
-
wd: Working directory (defaults to current directory)
|
|
479
|
-
model: The model identifier (optional)
|
|
480
|
-
provider: The provider identifier (optional)
|
|
481
|
-
npc: The NPC identifier (optional)
|
|
482
|
-
attachments: List of attachment dictionaries (optional)
|
|
483
|
-
Each attachment dict should have:
|
|
484
|
-
- name: Filename/title
|
|
485
|
-
- type: MIME type or extension
|
|
486
|
-
- data: Binary blob data
|
|
487
|
-
- size: Size in bytes (optional)
|
|
488
|
-
|
|
489
|
-
Returns:
|
|
490
|
-
The message ID
|
|
491
|
-
"""
|
|
492
|
-
if wd is None:
|
|
493
|
-
wd = os.getcwd()
|
|
494
|
-
|
|
495
|
-
return command_history.add_conversation(
|
|
496
|
-
role=role,
|
|
497
|
-
content=content,
|
|
498
|
-
conversation_id=conversation_id,
|
|
499
|
-
directory_path=wd,
|
|
500
|
-
model=model,
|
|
501
|
-
provider=provider,
|
|
502
|
-
npc=npc,
|
|
503
|
-
attachments=attachments,
|
|
504
|
-
message_id=message_id,
|
|
505
|
-
)
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
def retrieve_last_conversation(
|
|
509
|
-
command_history: CommandHistory, conversation_id: str
|
|
510
|
-
) -> str:
|
|
511
|
-
"""
|
|
512
|
-
Retrieves and formats all messages from the last conversation.
|
|
513
|
-
"""
|
|
514
|
-
last_message = command_history.get_last_conversation(conversation_id)
|
|
515
|
-
if last_message:
|
|
516
|
-
return last_message[3] # content
|
|
517
|
-
return "No previous conversation messages found."
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
def save_attachment_to_message(
|
|
521
|
-
command_history: CommandHistory,
|
|
522
|
-
message_id: str,
|
|
523
|
-
file_path: str,
|
|
524
|
-
attachment_name: str = None,
|
|
525
|
-
attachment_type: str = None,
|
|
526
|
-
):
|
|
527
|
-
"""
|
|
528
|
-
Helper function to save a file from disk as an attachment.
|
|
529
|
-
|
|
530
|
-
Args:
|
|
531
|
-
command_history: The CommandHistory instance
|
|
532
|
-
message_id: The message ID to attach to
|
|
533
|
-
file_path: Path to the file on disk
|
|
534
|
-
attachment_name: Name to save (defaults to basename)
|
|
535
|
-
attachment_type: MIME type (defaults to guessing from extension)
|
|
536
|
-
|
|
537
|
-
Returns:
|
|
538
|
-
Boolean indicating success
|
|
539
|
-
"""
|
|
540
|
-
try:
|
|
541
|
-
# Get file name if not specified
|
|
542
|
-
if not attachment_name:
|
|
543
|
-
attachment_name = os.path.basename(file_path)
|
|
544
|
-
|
|
545
|
-
# Try to guess MIME type if not specified
|
|
546
|
-
if not attachment_type:
|
|
547
|
-
_, ext = os.path.splitext(file_path)
|
|
548
|
-
if ext:
|
|
549
|
-
attachment_type = ext.lower()[1:] # Remove the dot
|
|
550
|
-
|
|
551
|
-
# Read file data
|
|
552
|
-
with open(file_path, "rb") as f:
|
|
553
|
-
data = f.read()
|
|
554
|
-
|
|
555
|
-
# Add attachment
|
|
556
|
-
command_history.add_attachment(
|
|
557
|
-
message_id=message_id,
|
|
558
|
-
attachment_name=attachment_name,
|
|
559
|
-
attachment_type=attachment_type,
|
|
560
|
-
attachment_data=data,
|
|
561
|
-
attachment_size=len(data),
|
|
562
|
-
)
|
|
563
|
-
return True
|
|
564
|
-
except Exception as e:
|
|
565
|
-
print(f"Error saving attachment: {str(e)}")
|
|
566
|
-
return False
|