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