feat: Add simple chat session for AI agent interaction and enhance message rendering
This commit is contained in:
@@ -262,6 +262,10 @@ include(cmake/imgui.cmake)
|
|||||||
file(GLOB THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme")
|
file(GLOB THEME_FILES "${CMAKE_SOURCE_DIR}/assets/themes/*.theme")
|
||||||
file(COPY ${THEME_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/themes/")
|
file(COPY ${THEME_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/themes/")
|
||||||
|
|
||||||
|
# Copy agent resource files to build directory (for AI features)
|
||||||
|
file(GLOB AGENT_FILES "${CMAKE_SOURCE_DIR}/assets/agent/*")
|
||||||
|
file(COPY ${AGENT_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/agent/")
|
||||||
|
|
||||||
# IMPORTANT: Also ensure themes are included in macOS bundles
|
# IMPORTANT: Also ensure themes are included in macOS bundles
|
||||||
# This is handled in src/CMakeLists.txt via YAZE_RESOURCE_FILES
|
# This is handled in src/CMakeLists.txt via YAZE_RESOURCE_FILES
|
||||||
|
|
||||||
|
|||||||
@@ -166,84 +166,46 @@ void AgentChatWidget::RenderMessageBubble(const cli::agent::ChatMessage& msg, in
|
|||||||
// Message content
|
// Message content
|
||||||
ImGui::Indent(20.0f);
|
ImGui::Indent(20.0f);
|
||||||
|
|
||||||
// Check if message is JSON (tool result)
|
// Check if we have table data to render
|
||||||
if (!is_user && msg.text.find('[') != std::string::npos &&
|
if (!is_user && msg.table_data.has_value()) {
|
||||||
msg.text.find('{') != std::string::npos) {
|
RenderTableData(msg.table_data.value());
|
||||||
// Try to render as table
|
} else if (!is_user && msg.json_pretty.has_value()) {
|
||||||
RenderTableFromJson(msg.text);
|
ImGui::TextWrapped("%s", msg.json_pretty.value().c_str());
|
||||||
} else {
|
} else {
|
||||||
// Regular text message
|
// Regular text message
|
||||||
ImGui::TextWrapped("%s", msg.text.c_str());
|
ImGui::TextWrapped("%s", msg.message.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Unindent(20.0f);
|
ImGui::Unindent(20.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AgentChatWidget::RenderTableFromJson(const std::string& json_str) {
|
void AgentChatWidget::RenderTableData(const cli::agent::ChatMessage::TableData& table) {
|
||||||
#ifdef YAZE_WITH_JSON
|
if (table.headers.empty()) {
|
||||||
try {
|
return;
|
||||||
auto json_data = nlohmann::json::parse(json_str);
|
}
|
||||||
|
|
||||||
if (!json_data.is_array() || json_data.empty()) {
|
// Render table
|
||||||
ImGui::TextWrapped("%s", json_str.c_str());
|
if (ImGui::BeginTable("ToolResultTable", table.headers.size(),
|
||||||
return;
|
ImGuiTableFlags_Borders |
|
||||||
}
|
ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_ScrollY)) {
|
||||||
// Extract column headers from first object
|
// Headers
|
||||||
std::vector<std::string> headers;
|
for (const auto& header : table.headers) {
|
||||||
if (json_data[0].is_object()) {
|
ImGui::TableSetupColumn(header.c_str());
|
||||||
for (auto& [key, value] : json_data[0].items()) {
|
}
|
||||||
headers.push_back(key);
|
ImGui::TableHeadersRow();
|
||||||
}
|
|
||||||
}
|
// Rows
|
||||||
|
for (const auto& row : table.rows) {
|
||||||
if (headers.empty()) {
|
ImGui::TableNextRow();
|
||||||
ImGui::TextWrapped("%s", json_str.c_str());
|
for (size_t col = 0; col < std::min(row.size(), table.headers.size()); ++col) {
|
||||||
return;
|
ImGui::TableSetColumnIndex(col);
|
||||||
}
|
ImGui::TextWrapped("%s", row[col].c_str());
|
||||||
|
}
|
||||||
// Render table
|
}
|
||||||
if (ImGui::BeginTable("ToolResultTable", headers.size(),
|
|
||||||
ImGuiTableFlags_Borders |
|
ImGui::EndTable();
|
||||||
ImGuiTableFlags_RowBg |
|
|
||||||
ImGuiTableFlags_ScrollY)) {
|
|
||||||
// Headers
|
|
||||||
for (const auto& header : headers) {
|
|
||||||
ImGui::TableSetupColumn(header.c_str());
|
|
||||||
}
|
|
||||||
ImGui::TableHeadersRow();
|
|
||||||
|
|
||||||
// Rows
|
|
||||||
for (const auto& row : json_data) {
|
|
||||||
if (!row.is_object()) continue;
|
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
|
||||||
for (size_t col = 0; col < headers.size(); ++col) {
|
|
||||||
ImGui::TableSetColumnIndex(col);
|
|
||||||
|
|
||||||
const auto& header = headers[col];
|
|
||||||
if (row.contains(header)) {
|
|
||||||
std::string cell_value;
|
|
||||||
if (row[header].is_string()) {
|
|
||||||
cell_value = row[header].get<std::string>();
|
|
||||||
} else {
|
|
||||||
cell_value = row[header].dump();
|
|
||||||
}
|
|
||||||
ImGui::TextWrapped("%s", cell_value.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (const nlohmann::json::exception& e) {
|
|
||||||
// Fallback to plain text if JSON parsing fails
|
|
||||||
ImGui::TextWrapped("%s", json_str.c_str());
|
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
ImGui::TextWrapped("%s", json_str.c_str());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AgentChatWidget::RenderInputArea() {
|
void AgentChatWidget::RenderInputArea() {
|
||||||
@@ -278,16 +240,10 @@ void AgentChatWidget::SendMessage(const std::string& message) {
|
|||||||
#ifdef Z3ED_AI_AVAILABLE
|
#ifdef Z3ED_AI_AVAILABLE
|
||||||
if (!agent_service_) return;
|
if (!agent_service_) return;
|
||||||
|
|
||||||
// Process message through agent service
|
// Send message through agent service
|
||||||
auto result = agent_service_->ProcessMessage(message);
|
auto result = agent_service_->SendMessage(message);
|
||||||
|
|
||||||
if (!result.ok()) {
|
if (!result.ok()) {
|
||||||
// Add error message to history
|
|
||||||
cli::agent::ChatMessage error_msg;
|
|
||||||
error_msg.sender = cli::agent::ChatMessage::Sender::kAgent;
|
|
||||||
error_msg.text = absl::StrFormat("Error: %s", result.status().message());
|
|
||||||
error_msg.timestamp = absl::Now();
|
|
||||||
// Note: We'd need to expose AddMessage in the service to do this properly
|
|
||||||
std::cerr << "Error processing message: " << result.status() << std::endl;
|
std::cerr << "Error processing message: " << result.status() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +312,7 @@ absl::Status AgentChatWidget::SaveHistory(const std::string& filepath) {
|
|||||||
nlohmann::json msg_json;
|
nlohmann::json msg_json;
|
||||||
msg_json["sender"] = (msg.sender == cli::agent::ChatMessage::Sender::kUser)
|
msg_json["sender"] = (msg.sender == cli::agent::ChatMessage::Sender::kUser)
|
||||||
? "user" : "agent";
|
? "user" : "agent";
|
||||||
msg_json["text"] = msg.text;
|
msg_json["message"] = msg.message;
|
||||||
msg_json["timestamp"] = absl::FormatTime(msg.timestamp);
|
msg_json["timestamp"] = absl::FormatTime(msg.timestamp);
|
||||||
j["messages"].push_back(msg_json);
|
j["messages"].push_back(msg_json);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class AgentChatWidget {
|
|||||||
void RenderInputArea();
|
void RenderInputArea();
|
||||||
void RenderToolbar();
|
void RenderToolbar();
|
||||||
void RenderMessageBubble(const cli::agent::ChatMessage& msg, int index);
|
void RenderMessageBubble(const cli::agent::ChatMessage& msg, int index);
|
||||||
void RenderTableFromJson(const std::string& json_str);
|
void RenderTableData(const cli::agent::ChatMessage::TableData& table);
|
||||||
|
|
||||||
void SendMessage(const std::string& message);
|
void SendMessage(const std::string& message);
|
||||||
void ScrollToBottom();
|
void ScrollToBottom();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace agent {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr absl::string_view kUsage =
|
constexpr absl::string_view kUsage =
|
||||||
"Usage: agent <run|plan|diff|accept|test|test-conversation|gui|learn|list|commit|revert|describe|resource-list|dungeon-list-sprites|overworld-find-tile|overworld-describe-map|overworld-list-warps|chat> "
|
"Usage: agent <run|plan|diff|accept|test|test-conversation|gui|learn|list|commit|revert|describe|resource-list|dungeon-list-sprites|overworld-find-tile|overworld-describe-map|overworld-list-warps|chat|simple-chat> "
|
||||||
"[options]";
|
"[options]";
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -80,6 +80,9 @@ absl::Status Agent::Run(const std::vector<std::string>& arg_vec) {
|
|||||||
if (subcommand == "chat") {
|
if (subcommand == "chat") {
|
||||||
return agent::HandleChatCommand(rom_);
|
return agent::HandleChatCommand(rom_);
|
||||||
}
|
}
|
||||||
|
if (subcommand == "simple-chat") {
|
||||||
|
return agent::HandleSimpleChatCommand(subcommand_args, rom_);
|
||||||
|
}
|
||||||
|
|
||||||
return absl::InvalidArgumentError(std::string(agent::kUsage));
|
return absl::InvalidArgumentError(std::string(agent::kUsage));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ absl::Status HandleOverworldListWarpsCommand(
|
|||||||
const std::vector<std::string>& arg_vec,
|
const std::vector<std::string>& arg_vec,
|
||||||
Rom* rom_context = nullptr);
|
Rom* rom_context = nullptr);
|
||||||
absl::Status HandleChatCommand(Rom& rom);
|
absl::Status HandleChatCommand(Rom& rom);
|
||||||
|
absl::Status HandleSimpleChatCommand(const std::vector<std::string>& arg_vec, Rom& rom);
|
||||||
absl::Status HandleTestConversationCommand(
|
absl::Status HandleTestConversationCommand(
|
||||||
const std::vector<std::string>& arg_vec);
|
const std::vector<std::string>& arg_vec);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "cli/service/ai/gemini_ai_service.h"
|
#include "cli/service/ai/gemini_ai_service.h"
|
||||||
#include "cli/service/ai/ollama_ai_service.h"
|
#include "cli/service/ai/ollama_ai_service.h"
|
||||||
#include "cli/service/ai/service_factory.h"
|
#include "cli/service/ai/service_factory.h"
|
||||||
|
#include "cli/service/agent/simple_chat_session.h"
|
||||||
#include "cli/service/planning/proposal_registry.h"
|
#include "cli/service/planning/proposal_registry.h"
|
||||||
#include "cli/service/planning/tile16_proposal_generator.h"
|
#include "cli/service/planning/tile16_proposal_generator.h"
|
||||||
#include "cli/service/resources/resource_catalog.h"
|
#include "cli/service/resources/resource_catalog.h"
|
||||||
@@ -571,6 +572,32 @@ absl::Status HandleChatCommand(Rom& rom) {
|
|||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
absl::Status HandleSimpleChatCommand(const std::vector<std::string>& arg_vec,
|
||||||
|
Rom& rom) {
|
||||||
|
RETURN_IF_ERROR(EnsureRomLoaded(rom, "agent simple-chat"));
|
||||||
|
|
||||||
|
// Parse flags
|
||||||
|
std::optional<std::string> batch_file;
|
||||||
|
for (size_t i = 0; i < arg_vec.size(); ++i) {
|
||||||
|
const std::string& arg = arg_vec[i];
|
||||||
|
if (absl::StartsWith(arg, "--file=")) {
|
||||||
|
batch_file = arg.substr(7);
|
||||||
|
} else if (arg == "--file" && i + 1 < arg_vec.size()) {
|
||||||
|
batch_file = arg_vec[i + 1];
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleChatSession session;
|
||||||
|
session.SetRomContext(&rom);
|
||||||
|
|
||||||
|
if (batch_file.has_value()) {
|
||||||
|
return session.RunBatch(*batch_file);
|
||||||
|
} else {
|
||||||
|
return session.RunInteractive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
absl::Status HandleAcceptCommand(const std::vector<std::string>& arg_vec,
|
absl::Status HandleAcceptCommand(const std::vector<std::string>& arg_vec,
|
||||||
Rom& rom) {
|
Rom& rom) {
|
||||||
std::optional<std::string> proposal_id;
|
std::optional<std::string> proposal_id;
|
||||||
|
|||||||
160
src/cli/service/agent/simple_chat_session.cc
Normal file
160
src/cli/service/agent/simple_chat_session.cc
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#include "cli/service/agent/simple_chat_session.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/time/time.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace cli {
|
||||||
|
namespace agent {
|
||||||
|
|
||||||
|
SimpleChatSession::SimpleChatSession() = default;
|
||||||
|
|
||||||
|
void SimpleChatSession::SetRomContext(Rom* rom) {
|
||||||
|
agent_service_.SetRomContext(rom);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleChatSession::PrintTable(const ChatMessage::TableData& table) {
|
||||||
|
if (table.headers.empty()) return;
|
||||||
|
|
||||||
|
// Calculate column widths
|
||||||
|
std::vector<size_t> col_widths(table.headers.size(), 0);
|
||||||
|
for (size_t i = 0; i < table.headers.size(); ++i) {
|
||||||
|
col_widths[i] = table.headers[i].length();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& row : table.rows) {
|
||||||
|
for (size_t i = 0; i < std::min(row.size(), col_widths.size()); ++i) {
|
||||||
|
col_widths[i] = std::max(col_widths[i], row[i].length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print header
|
||||||
|
std::cout << " ";
|
||||||
|
for (size_t i = 0; i < table.headers.size(); ++i) {
|
||||||
|
std::cout << std::left << std::setw(col_widths[i] + 2) << table.headers[i];
|
||||||
|
}
|
||||||
|
std::cout << "\n ";
|
||||||
|
for (size_t i = 0; i < table.headers.size(); ++i) {
|
||||||
|
std::cout << std::string(col_widths[i] + 2, '-');
|
||||||
|
}
|
||||||
|
std::cout << "\n";
|
||||||
|
|
||||||
|
// Print rows
|
||||||
|
for (const auto& row : table.rows) {
|
||||||
|
std::cout << " ";
|
||||||
|
for (size_t i = 0; i < std::min(row.size(), table.headers.size()); ++i) {
|
||||||
|
std::cout << std::left << std::setw(col_widths[i] + 2) << row[i];
|
||||||
|
}
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleChatSession::PrintMessage(const ChatMessage& msg, bool show_timestamp) {
|
||||||
|
const char* sender = (msg.sender == ChatMessage::Sender::kUser) ? "You" : "Agent";
|
||||||
|
|
||||||
|
if (show_timestamp) {
|
||||||
|
std::string timestamp = absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone());
|
||||||
|
std::cout << "[" << timestamp << "] ";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << sender << ": ";
|
||||||
|
|
||||||
|
if (msg.table_data.has_value()) {
|
||||||
|
std::cout << "\n";
|
||||||
|
PrintTable(msg.table_data.value());
|
||||||
|
} else if (msg.json_pretty.has_value()) {
|
||||||
|
std::cout << "\n" << msg.json_pretty.value() << "\n";
|
||||||
|
} else {
|
||||||
|
std::cout << msg.message << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status SimpleChatSession::SendAndWaitForResponse(
|
||||||
|
const std::string& message, std::string* response_out) {
|
||||||
|
auto result = agent_service_.SendMessage(message);
|
||||||
|
if (!result.ok()) {
|
||||||
|
return result.status();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& response_msg = result.value();
|
||||||
|
|
||||||
|
if (response_out != nullptr) {
|
||||||
|
*response_out = response_msg.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status SimpleChatSession::RunInteractive() {
|
||||||
|
std::cout << "Z3ED Agent Chat (Simple Mode)\n";
|
||||||
|
std::cout << "Type 'quit' or 'exit' to end the session.\n";
|
||||||
|
std::cout << "Type 'reset' to clear conversation history.\n";
|
||||||
|
std::cout << "----------------------------------------\n\n";
|
||||||
|
|
||||||
|
std::string input;
|
||||||
|
while (true) {
|
||||||
|
std::cout << "You: ";
|
||||||
|
std::getline(std::cin, input);
|
||||||
|
|
||||||
|
if (input.empty()) continue;
|
||||||
|
if (input == "quit" || input == "exit") break;
|
||||||
|
|
||||||
|
if (input == "reset") {
|
||||||
|
Reset();
|
||||||
|
std::cout << "Conversation history cleared.\n\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = agent_service_.SendMessage(input);
|
||||||
|
if (!result.ok()) {
|
||||||
|
std::cerr << "Error: " << result.status().message() << "\n\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintMessage(result.value(), false);
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status SimpleChatSession::RunBatch(const std::string& input_file) {
|
||||||
|
std::ifstream file(input_file);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return absl::NotFoundError(
|
||||||
|
absl::StrFormat("Could not open file: %s", input_file));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Running batch session from: " << input_file << "\n";
|
||||||
|
std::cout << "----------------------------------------\n\n";
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
int line_num = 0;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
++line_num;
|
||||||
|
|
||||||
|
// Skip empty lines and comments
|
||||||
|
if (line.empty() || line[0] == '#') continue;
|
||||||
|
|
||||||
|
std::cout << "Input [" << line_num << "]: " << line << "\n";
|
||||||
|
|
||||||
|
auto result = agent_service_.SendMessage(line);
|
||||||
|
if (!result.ok()) {
|
||||||
|
std::cerr << "Error: " << result.status().message() << "\n\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintMessage(result.value(), false);
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace agent
|
||||||
|
} // namespace cli
|
||||||
|
} // namespace yaze
|
||||||
62
src/cli/service/agent/simple_chat_session.h
Normal file
62
src/cli/service/agent/simple_chat_session.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#ifndef YAZE_SRC_CLI_SERVICE_AGENT_SIMPLE_CHAT_SESSION_H_
|
||||||
|
#define YAZE_SRC_CLI_SERVICE_AGENT_SIMPLE_CHAT_SESSION_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "cli/service/agent/conversational_agent_service.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
|
||||||
|
class Rom;
|
||||||
|
|
||||||
|
namespace cli {
|
||||||
|
namespace agent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class SimpleChatSession
|
||||||
|
* @brief Simple text-based chat session for AI agent interaction
|
||||||
|
*
|
||||||
|
* Provides a basic REPL-style interface without FTXUI dependencies,
|
||||||
|
* suitable for automated testing and AI agent interactions.
|
||||||
|
*/
|
||||||
|
class SimpleChatSession {
|
||||||
|
public:
|
||||||
|
SimpleChatSession();
|
||||||
|
|
||||||
|
// Set ROM context for tool execution
|
||||||
|
void SetRomContext(Rom* rom);
|
||||||
|
|
||||||
|
// Send a single message and get response (blocking)
|
||||||
|
absl::Status SendAndWaitForResponse(const std::string& message,
|
||||||
|
std::string* response_out = nullptr);
|
||||||
|
|
||||||
|
// Run interactive REPL mode (reads from stdin)
|
||||||
|
absl::Status RunInteractive();
|
||||||
|
|
||||||
|
// Run batch mode from file (one message per line)
|
||||||
|
absl::Status RunBatch(const std::string& input_file);
|
||||||
|
|
||||||
|
// Get full conversation history
|
||||||
|
const std::vector<ChatMessage>& GetHistory() const {
|
||||||
|
return agent_service_.GetHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear conversation history
|
||||||
|
void Reset() {
|
||||||
|
agent_service_.ResetConversation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void PrintMessage(const ChatMessage& msg, bool show_timestamp = false);
|
||||||
|
void PrintTable(const ChatMessage::TableData& table);
|
||||||
|
|
||||||
|
ConversationalAgentService agent_service_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace agent
|
||||||
|
} // namespace cli
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_SRC_CLI_SERVICE_AGENT_SIMPLE_CHAT_SESSION_H_
|
||||||
Reference in New Issue
Block a user