yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
learned_knowledge_service.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <fstream>
6#include <iostream>
7#include <sstream>
8
9#include "absl/strings/str_cat.h"
10#include "absl/strings/str_format.h"
11#include "absl/time/clock.h"
12#include "absl/time/time.h"
13#include "nlohmann/json.hpp"
14#include "util/platform_paths.h"
15
16namespace yaze {
17namespace cli {
18namespace agent {
19
20namespace {
21
23 return absl::ToUnixMillis(absl::Now());
24}
25
26std::string GenerateRandomID() {
27 static int counter = 0;
28 auto now = std::chrono::system_clock::now().time_since_epoch().count();
29 return absl::StrFormat("%lld_%d", now, counter++);
30}
31
32bool FileExists(const std::filesystem::path& path) {
33 return util::PlatformPaths::Exists(path);
34}
35
36} // namespace
37
39 // Get user documents directory for agent knowledge (visible to user)
40 auto docs_result = util::PlatformPaths::GetUserDocumentsSubdirectory("agent");
41 if (docs_result.ok()) {
42 data_dir_ = *docs_result;
43 } else {
44 // Fallback to current directory -> agent (e.g. portable mode)
45 data_dir_ = std::filesystem::current_path() / "agent";
46 }
47
48 prefs_file_ = data_dir_ / "preferences.json";
49 patterns_file_ = data_dir_ / "patterns.json";
50 projects_file_ = data_dir_ / "projects.json";
51 memories_file_ = data_dir_ / "memories.json";
52}
53
55 const std::filesystem::path& data_dir)
56 : data_dir_(data_dir),
57 prefs_file_(data_dir / "preferences.json"),
58 patterns_file_(data_dir / "patterns.json"),
59 projects_file_(data_dir / "projects.json"),
60 memories_file_(data_dir / "memories.json") {}
61
63 if (initialized_) {
64 return absl::OkStatus();
65 }
66
67 // Ensure data directory exists
69 if (!status.ok()) {
70 return status;
71 }
72
73 // Load existing data
74 LoadPreferences(); // Ignore errors for empty files
78
79 initialized_ = true;
80 return absl::OkStatus();
81}
82
84 auto status = SavePreferences();
85 if (!status.ok())
86 return status;
87
88 status = SavePatterns();
89 if (!status.ok())
90 return status;
91
92 status = SaveProjects();
93 if (!status.ok())
94 return status;
95
96 status = SaveMemories();
97 if (!status.ok())
98 return status;
99
100 return absl::OkStatus();
101}
102
103// === Preference Management ===
104
105absl::Status LearnedKnowledgeService::SetPreference(const std::string& key,
106 const std::string& value) {
107 if (!initialized_) {
108 return absl::FailedPreconditionError("Service not initialized");
109 }
110
111 preferences_[key] = value;
112 return SavePreferences();
113}
114
115std::optional<std::string> LearnedKnowledgeService::GetPreference(
116 const std::string& key) const {
117 auto it = preferences_.find(key);
118 if (it != preferences_.end()) {
119 return it->second;
120 }
121 return std::nullopt;
122}
123
124std::map<std::string, std::string> LearnedKnowledgeService::GetAllPreferences()
125 const {
126 return preferences_;
127}
128
129absl::Status LearnedKnowledgeService::RemovePreference(const std::string& key) {
130 if (!initialized_) {
131 return absl::FailedPreconditionError("Service not initialized");
132 }
133
134 preferences_.erase(key);
135 return SavePreferences();
136}
137
138// === ROM Pattern Learning ===
139
140absl::Status LearnedKnowledgeService::LearnPattern(const std::string& type,
141 const std::string& rom_hash,
142 const std::string& data,
143 float confidence) {
144 if (!initialized_) {
145 return absl::FailedPreconditionError("Service not initialized");
146 }
147
148 ROMPattern pattern;
149 pattern.pattern_type = type;
150 pattern.rom_hash = rom_hash;
151 pattern.pattern_data = data;
152 pattern.confidence = confidence;
153 pattern.learned_at = CurrentTimestamp();
154 pattern.access_count = 1;
155
156 patterns_.push_back(pattern);
157 return SavePatterns();
158}
159
160std::vector<LearnedKnowledgeService::ROMPattern>
162 const std::string& rom_hash) const {
163 std::vector<ROMPattern> results;
164
165 for (const auto& pattern : patterns_) {
166 bool type_match = type.empty() || pattern.pattern_type == type;
167 bool hash_match = rom_hash.empty() || pattern.rom_hash == rom_hash;
168
169 if (type_match && hash_match) {
170 results.push_back(pattern);
171 }
172 }
173
174 return results;
175}
176
178 const std::string& type, const std::string& rom_hash,
179 float new_confidence) {
180 bool found = false;
181
182 for (auto& pattern : patterns_) {
183 if (pattern.pattern_type == type && pattern.rom_hash == rom_hash) {
184 pattern.confidence = new_confidence;
185 pattern.access_count++;
186 found = true;
187 }
188 }
189
190 if (!found) {
191 return absl::NotFoundError("Pattern not found");
192 }
193
194 return SavePatterns();
195}
196
197// === Project Context ===
198
200 const std::string& project_name, const std::string& rom_hash,
201 const std::string& context) {
202 if (!initialized_) {
203 return absl::FailedPreconditionError("Service not initialized");
204 }
205
206 // Update existing or create new
207 bool found = false;
208 for (auto& project : projects_) {
209 if (project.project_name == project_name) {
210 project.rom_hash = rom_hash;
211 project.context_data = context;
212 project.last_accessed = CurrentTimestamp();
213 found = true;
214 break;
215 }
216 }
217
218 if (!found) {
219 ProjectContext project;
220 project.project_name = project_name;
221 project.rom_hash = rom_hash;
222 project.context_data = context;
223 project.last_accessed = CurrentTimestamp();
224 projects_.push_back(project);
225 }
226
227 return SaveProjects();
228}
229
230std::optional<LearnedKnowledgeService::ProjectContext>
232 const std::string& project_name) const {
233 for (const auto& project : projects_) {
234 if (project.project_name == project_name) {
235 return project;
236 }
237 }
238 return std::nullopt;
239}
240
241std::vector<LearnedKnowledgeService::ProjectContext>
245
246// === Conversation Memory ===
247
249 const std::string& topic, const std::string& summary,
250 const std::vector<std::string>& key_facts) {
251 if (!initialized_) {
252 return absl::FailedPreconditionError("Service not initialized");
253 }
254
255 ConversationMemory memory;
256 memory.id = GenerateRandomID();
257 memory.topic = topic;
258 memory.summary = summary;
259 memory.key_facts = key_facts;
260 memory.created_at = CurrentTimestamp();
261 memory.access_count = 1;
262
263 memories_.push_back(memory);
264
265 // Keep only last 100 memories
266 if (memories_.size() > 100) {
267 memories_.erase(memories_.begin());
268 }
269
270 return SaveMemories();
271}
272
273std::vector<LearnedKnowledgeService::ConversationMemory>
274LearnedKnowledgeService::SearchMemories(const std::string& query) const {
275 std::vector<ConversationMemory> results;
276
277 std::string query_lower = query;
278 std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(),
279 ::tolower);
280
281 for (const auto& memory : memories_) {
282 std::string topic_lower = memory.topic;
283 std::string summary_lower = memory.summary;
284 std::transform(topic_lower.begin(), topic_lower.end(), topic_lower.begin(),
285 ::tolower);
286 std::transform(summary_lower.begin(), summary_lower.end(),
287 summary_lower.begin(), ::tolower);
288
289 if (topic_lower.find(query_lower) != std::string::npos ||
290 summary_lower.find(query_lower) != std::string::npos) {
291 results.push_back(memory);
292 }
293 }
294
295 return results;
296}
297
298std::vector<LearnedKnowledgeService::ConversationMemory>
300 std::vector<ConversationMemory> recent = memories_;
301
302 // Sort by created_at descending
303 std::sort(recent.begin(), recent.end(),
304 [](const ConversationMemory& a, const ConversationMemory& b) {
305 return a.created_at > b.created_at;
306 });
307
308 if (recent.size() > static_cast<size_t>(limit)) {
309 recent.resize(limit);
310 }
311
312 return recent;
313}
314
315// === Import/Export ===
316
317#ifdef YAZE_WITH_JSON
318absl::StatusOr<std::string> LearnedKnowledgeService::ExportToJSON() const {
319 nlohmann::json export_data;
320
321 // Export preferences
322 export_data["preferences"] = preferences_;
323
324 // Export patterns
325 export_data["patterns"] = nlohmann::json::array();
326 for (const auto& pattern : patterns_) {
327 nlohmann::json p;
328 p["type"] = pattern.pattern_type;
329 p["rom_hash"] = pattern.rom_hash;
330 p["data"] = pattern.pattern_data;
331 p["confidence"] = pattern.confidence;
332 p["learned_at"] = pattern.learned_at;
333 p["access_count"] = pattern.access_count;
334 export_data["patterns"].push_back(p);
335 }
336
337 // Export projects
338 export_data["projects"] = nlohmann::json::array();
339 for (const auto& project : projects_) {
340 nlohmann::json p;
341 p["name"] = project.project_name;
342 p["rom_hash"] = project.rom_hash;
343 p["context"] = project.context_data;
344 p["last_accessed"] = project.last_accessed;
345 export_data["projects"].push_back(p);
346 }
347
348 // Export memories
349 export_data["memories"] = nlohmann::json::array();
350 for (const auto& memory : memories_) {
351 nlohmann::json m;
352 m["id"] = memory.id;
353 m["topic"] = memory.topic;
354 m["summary"] = memory.summary;
355 m["key_facts"] = memory.key_facts;
356 m["created_at"] = memory.created_at;
357 m["access_count"] = memory.access_count;
358 export_data["memories"].push_back(m);
359 }
360
361 return export_data.dump(2);
362}
363
365 const std::string& json_data) {
366 try {
367 auto data = nlohmann::json::parse(json_data);
368
369 // Import preferences
370 if (data.contains("preferences")) {
371 for (const auto& [key, value] : data["preferences"].items()) {
372 preferences_[key] = value.get<std::string>();
373 }
374 }
375
376 // Import patterns
377 if (data.contains("patterns")) {
378 for (const auto& p : data["patterns"]) {
379 ROMPattern pattern;
380 pattern.pattern_type = p["type"];
381 pattern.rom_hash = p["rom_hash"];
382 pattern.pattern_data = p["data"];
383 pattern.confidence = p["confidence"];
384 pattern.learned_at = p["learned_at"];
385 pattern.access_count = p["access_count"];
386 patterns_.push_back(pattern);
387 }
388 }
389
390 // Import projects
391 if (data.contains("projects")) {
392 for (const auto& p : data["projects"]) {
393 ProjectContext project;
394 project.project_name = p["name"];
395 project.rom_hash = p["rom_hash"];
396 project.context_data = p["context"];
397 project.last_accessed = p["last_accessed"];
398 projects_.push_back(project);
399 }
400 }
401
402 // Import memories
403 if (data.contains("memories")) {
404 for (const auto& m : data["memories"]) {
405 ConversationMemory memory;
406 memory.id = m["id"];
407 memory.topic = m["topic"];
408 memory.summary = m["summary"];
409 memory.key_facts = m["key_facts"].get<std::vector<std::string>>();
410 memory.created_at = m["created_at"];
411 memory.access_count = m["access_count"];
412 memories_.push_back(memory);
413 }
414 }
415
416 return SaveAll();
417 } catch (const std::exception& e) {
418 return absl::InternalError(absl::StrCat("JSON parse error: ", e.what()));
419 }
420}
421#else
422absl::StatusOr<std::string> LearnedKnowledgeService::ExportToJSON() const {
423 return absl::UnimplementedError(
424 "JSON support not enabled. Build with -DYAZE_WITH_JSON=ON");
425}
426
427absl::Status LearnedKnowledgeService::ImportFromJSON(const std::string&) {
428 return absl::UnimplementedError(
429 "JSON support not enabled. Build with -DYAZE_WITH_JSON=ON");
430}
431#endif
432
434 preferences_.clear();
435 patterns_.clear();
436 projects_.clear();
437 memories_.clear();
438 return SaveAll();
439}
440
441// === Statistics ===
442
444 Stats stats;
445 stats.preference_count = preferences_.size();
446 stats.pattern_count = patterns_.size();
447 stats.project_count = projects_.size();
448 stats.memory_count = memories_.size();
449
450 // Find earliest learned_at
451 int64_t earliest = CurrentTimestamp();
452 for (const auto& pattern : patterns_) {
453 if (pattern.learned_at < earliest) {
454 earliest = pattern.learned_at;
455 }
456 }
457 for (const auto& memory : memories_) {
458 if (memory.created_at < earliest) {
459 earliest = memory.created_at;
460 }
461 }
462 stats.first_learned_at = earliest;
463
464 // Last updated is now
465 stats.last_updated_at = CurrentTimestamp();
466
467 return stats;
468}
469
470// === Internal Helpers ===
471
472#ifdef YAZE_WITH_JSON
474 if (!FileExists(prefs_file_)) {
475 return absl::OkStatus(); // No file yet, empty preferences
476 }
477
478 try {
479 std::ifstream file(prefs_file_);
480 if (!file.is_open()) {
481 return absl::InternalError("Failed to open preferences file");
482 }
483
484 nlohmann::json data;
485 file >> data;
486
487 for (const auto& [key, value] : data.items()) {
488 preferences_[key] = value.get<std::string>();
489 }
490
491 return absl::OkStatus();
492 } catch (const std::exception& e) {
493 return absl::InternalError(
494 absl::StrCat("Failed to load preferences: ", e.what()));
495 }
496}
497
499 try {
500 nlohmann::json data = preferences_;
501
502 std::ofstream file(prefs_file_);
503 if (!file.is_open()) {
504 return absl::InternalError("Failed to open preferences file for writing");
505 }
506
507 file << data.dump(2);
508 return absl::OkStatus();
509 } catch (const std::exception& e) {
510 return absl::InternalError(
511 absl::StrCat("Failed to save preferences: ", e.what()));
512 }
513}
514
517 return absl::OkStatus();
518 }
519
520 try {
521 std::ifstream file(patterns_file_);
522 nlohmann::json data;
523 file >> data;
524
525 for (const auto& p : data) {
526 ROMPattern pattern;
527 pattern.pattern_type = p["type"];
528 pattern.rom_hash = p["rom_hash"];
529 pattern.pattern_data = p["data"];
530 pattern.confidence = p["confidence"];
531 pattern.learned_at = p["learned_at"];
532 pattern.access_count = p.value("access_count", 0);
533 patterns_.push_back(pattern);
534 }
535
536 return absl::OkStatus();
537 } catch (const std::exception& e) {
538 return absl::InternalError(
539 absl::StrCat("Failed to load patterns: ", e.what()));
540 }
541}
542
544 try {
545 nlohmann::json data = nlohmann::json::array();
546
547 for (const auto& pattern : patterns_) {
548 nlohmann::json p;
549 p["type"] = pattern.pattern_type;
550 p["rom_hash"] = pattern.rom_hash;
551 p["data"] = pattern.pattern_data;
552 p["confidence"] = pattern.confidence;
553 p["learned_at"] = pattern.learned_at;
554 p["access_count"] = pattern.access_count;
555 data.push_back(p);
556 }
557
558 std::ofstream file(patterns_file_);
559 file << data.dump(2);
560 return absl::OkStatus();
561 } catch (const std::exception& e) {
562 return absl::InternalError(
563 absl::StrCat("Failed to save patterns: ", e.what()));
564 }
565}
566
569 return absl::OkStatus();
570 }
571
572 try {
573 std::ifstream file(projects_file_);
574 nlohmann::json data;
575 file >> data;
576
577 for (const auto& p : data) {
578 ProjectContext project;
579 project.project_name = p["name"];
580 project.rom_hash = p["rom_hash"];
581 project.context_data = p["context"];
582 project.last_accessed = p["last_accessed"];
583 projects_.push_back(project);
584 }
585
586 return absl::OkStatus();
587 } catch (const std::exception& e) {
588 return absl::InternalError(
589 absl::StrCat("Failed to load projects: ", e.what()));
590 }
591}
592
594 try {
595 nlohmann::json data = nlohmann::json::array();
596
597 for (const auto& project : projects_) {
598 nlohmann::json p;
599 p["name"] = project.project_name;
600 p["rom_hash"] = project.rom_hash;
601 p["context"] = project.context_data;
602 p["last_accessed"] = project.last_accessed;
603 data.push_back(p);
604 }
605
606 std::ofstream file(projects_file_);
607 file << data.dump(2);
608 return absl::OkStatus();
609 } catch (const std::exception& e) {
610 return absl::InternalError(
611 absl::StrCat("Failed to save projects: ", e.what()));
612 }
613}
614
616 try {
617 nlohmann::json data = nlohmann::json::array();
618
619 for (const auto& memory : memories_) {
620 nlohmann::json m;
621 m["id"] = memory.id;
622 m["topic"] = memory.topic;
623 m["summary"] = memory.summary;
624 m["key_facts"] = memory.key_facts;
625 m["created_at"] = memory.created_at;
626 m["access_count"] = memory.access_count;
627 data.push_back(m);
628 }
629
630 std::ofstream file(memories_file_);
631 file << data.dump(2);
632 return absl::OkStatus();
633 } catch (const std::exception& e) {
634 return absl::InternalError(
635 absl::StrCat("Failed to save memories: ", e.what()));
636 }
637}
638
641 return absl::OkStatus();
642 }
643
644 try {
645 std::ifstream file(memories_file_);
646 nlohmann::json data;
647 file >> data;
648
649 for (const auto& m : data) {
650 ConversationMemory memory;
651 memory.id = m["id"];
652 memory.topic = m["topic"];
653 memory.summary = m["summary"];
654 memory.key_facts = m["key_facts"].get<std::vector<std::string>>();
655 memory.created_at = m["created_at"];
656 memory.access_count = m.value("access_count", 0);
657 memories_.push_back(memory);
658 }
659
660 return absl::OkStatus();
661 } catch (const std::exception& e) {
662 return absl::InternalError(
663 absl::StrCat("Failed to load memories: ", e.what()));
664 }
665}
666
667#else
668// Stub implementations when JSON is not available
670 return absl::OkStatus();
671}
673 return absl::UnimplementedError("JSON support required");
674}
676 return absl::OkStatus();
677}
679 return absl::UnimplementedError("JSON support required");
680}
682 return absl::OkStatus();
683}
685 return absl::UnimplementedError("JSON support required");
686}
688 return absl::OkStatus();
689}
691 return absl::UnimplementedError("JSON support required");
692}
693#endif
694
696 return GenerateRandomID();
697}
698
699} // namespace agent
700} // namespace cli
701} // namespace yaze
std::optional< std::string > GetPreference(const std::string &key) const
absl::Status StoreConversationSummary(const std::string &topic, const std::string &summary, const std::vector< std::string > &key_facts)
std::vector< ProjectContext > GetAllProjects() const
std::vector< ConversationMemory > GetRecentMemories(int limit=10) const
absl::Status RemovePreference(const std::string &key)
std::vector< ConversationMemory > SearchMemories(const std::string &query) const
std::vector< ROMPattern > QueryPatterns(const std::string &type, const std::string &rom_hash="") const
absl::Status ImportFromJSON(const std::string &json_data)
absl::Status UpdatePatternConfidence(const std::string &type, const std::string &rom_hash, float new_confidence)
std::map< std::string, std::string > preferences_
std::optional< ProjectContext > GetProjectContext(const std::string &project_name) const
absl::Status SetPreference(const std::string &key, const std::string &value)
absl::StatusOr< std::string > ExportToJSON() const
absl::Status SaveProjectContext(const std::string &project_name, const std::string &rom_hash, const std::string &context)
absl::Status LearnPattern(const std::string &type, const std::string &rom_hash, const std::string &data, float confidence=1.0f)
std::map< std::string, std::string > GetAllPreferences() const
static absl::StatusOr< std::filesystem::path > GetUserDocumentsSubdirectory(const std::string &subdir)
Get a subdirectory within the user documents folder.
static absl::Status EnsureDirectoryExists(const std::filesystem::path &path)
Ensure a directory exists, creating it if necessary.
static bool Exists(const std::filesystem::path &path)
Check if a file or directory exists.