- Core DLL: AI API client (DeepSeek/OpenAI compatible), HTTP(S) via Boost.Beast - BearSSL vendored as TLS backend (MIT license, replacing OpenSSL) - CLI frontend with ANSI colors, /help /model /file /save /load commands - WinHTTP alternative HTTP client for Windows - GPLv3 license with linking exception - Build: CMake + Ninja + Clang, dependencies via Conan2 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
236 lines
7.0 KiB
C++
236 lines
7.0 KiB
C++
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <signal.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "dstalk/dstalk_api.h"
|
|
|
|
// ---- ANSI 简写 ----
|
|
#define CLR_RESET "\033[0m"
|
|
#define CLR_CYAN "\033[36m"
|
|
#define CLR_YELLOW "\033[33m"
|
|
#define CLR_GREEN "\033[32m"
|
|
#define CLR_RED "\033[31m"
|
|
#define CLR_DIM "\033[2m"
|
|
#define CLR_BOLD "\033[1m"
|
|
|
|
// ---- 工具函数 ----
|
|
static void print_banner()
|
|
{
|
|
std::printf("%sdstalk v0.1.0%s | %sDeepSeek V4%s | "
|
|
"%s/help%s 查看帮助 | %s/quit%s 退出\n",
|
|
CLR_CYAN CLR_BOLD, CLR_RESET,
|
|
CLR_GREEN, CLR_RESET,
|
|
CLR_DIM, CLR_RESET,
|
|
CLR_DIM, CLR_RESET);
|
|
}
|
|
|
|
static void print_help()
|
|
{
|
|
std::printf("\n%s命令列表:%s\n", CLR_BOLD, CLR_RESET);
|
|
std::printf(" %s/help%s 显示此帮助\n", CLR_YELLOW, CLR_RESET);
|
|
std::printf(" %s/quit%s 退出程序\n", CLR_YELLOW, CLR_RESET);
|
|
std::printf(" %s/clear%s 清空当前会话上下文\n", CLR_YELLOW, CLR_RESET);
|
|
std::printf(" %s/model <name>%s 切换模型\n", CLR_YELLOW, CLR_RESET);
|
|
std::printf(" %s/file read <path>%s 读取文件内容\n", CLR_YELLOW, CLR_RESET);
|
|
std::printf(" %s/file write <p> <c>%s写入文件\n", CLR_YELLOW, CLR_RESET);
|
|
std::printf(" %s/save <path>%s 保存会话\n", CLR_YELLOW, CLR_RESET);
|
|
std::printf(" %s/load <path>%s 恢复会话\n", CLR_YELLOW, CLR_RESET);
|
|
std::printf("\n直接输入问题即可与 AI 对话。\n\n");
|
|
}
|
|
|
|
static void handle_command(const char* line)
|
|
{
|
|
if (!line || line[0] != '/') return;
|
|
|
|
// /quit
|
|
if (std::strcmp(line, "/quit") == 0 || std::strcmp(line, "/q") == 0) {
|
|
dstalk_destroy();
|
|
std::printf(CLR_DIM "再见!\n" CLR_RESET);
|
|
std::exit(0);
|
|
}
|
|
|
|
// /help
|
|
if (std::strcmp(line, "/help") == 0 || std::strcmp(line, "/h") == 0) {
|
|
print_help();
|
|
return;
|
|
}
|
|
|
|
// /clear
|
|
if (std::strcmp(line, "/clear") == 0) {
|
|
dstalk_session_clear();
|
|
std::printf(CLR_GREEN "[OK] 会话已清空\n" CLR_RESET);
|
|
return;
|
|
}
|
|
|
|
// /model <name>
|
|
if (std::strncmp(line, "/model ", 7) == 0) {
|
|
const char* model = line + 7;
|
|
while (*model == ' ') model++;
|
|
dstalk_set_model(model);
|
|
std::printf(CLR_GREEN "[OK] 模型已切换: %s\n" CLR_RESET, model);
|
|
return;
|
|
}
|
|
|
|
// /file read <path>
|
|
if (std::strncmp(line, "/file read ", 11) == 0) {
|
|
const char* path = line + 11;
|
|
while (*path == ' ') path++;
|
|
char* content = nullptr;
|
|
if (dstalk_file_read(path, &content) == 0 && content) {
|
|
std::printf("%s--- %s ---%s\n", CLR_DIM, path, CLR_RESET);
|
|
std::printf("%s\n", content);
|
|
std::printf(CLR_DIM "--- EOF ---\n" CLR_RESET);
|
|
dstalk_free_string(content);
|
|
} else {
|
|
std::printf(CLR_RED "[ERROR] 无法读取: %s\n" CLR_RESET, path);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// /file write <path> <content...>
|
|
if (std::strncmp(line, "/file write ", 12) == 0) {
|
|
const char* rest = line + 12;
|
|
while (*rest == ' ') rest++;
|
|
// 第一个参数是路径,后面到行尾是内容
|
|
const char* space = std::strchr(rest, ' ');
|
|
if (!space) {
|
|
std::printf(CLR_RED "[ERROR] 用法: /file write <path> <content>\n" CLR_RESET);
|
|
return;
|
|
}
|
|
std::string path(rest, space - rest);
|
|
const char* content = space + 1;
|
|
while (*content == ' ') content++;
|
|
if (dstalk_file_write(path.c_str(), content) == 0) {
|
|
std::printf(CLR_GREEN "[OK] 已写入: %s\n" CLR_RESET, path.c_str());
|
|
} else {
|
|
std::printf(CLR_RED "[ERROR] 写入失败: %s\n" CLR_RESET, path.c_str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
// /save <path>
|
|
if (std::strncmp(line, "/save ", 6) == 0) {
|
|
const char* path = line + 6;
|
|
while (*path == ' ') path++;
|
|
if (dstalk_session_save(path) == 0) {
|
|
std::printf(CLR_GREEN "[OK] 会话已保存: %s\n" CLR_RESET, path);
|
|
} else {
|
|
std::printf(CLR_RED "[ERROR] 保存失败: %s\n" CLR_RESET, path);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// /load <path>
|
|
if (std::strncmp(line, "/load ", 6) == 0) {
|
|
const char* path = line + 6;
|
|
while (*path == ' ') path++;
|
|
if (dstalk_session_load(path) == 0) {
|
|
std::printf(CLR_GREEN "[OK] 会话已恢复: %s\n" CLR_RESET, path);
|
|
} else {
|
|
std::printf(CLR_RED "[ERROR] 恢复失败: %s\n" CLR_RESET, path);
|
|
}
|
|
return;
|
|
}
|
|
|
|
std::printf(CLR_RED "未知命令: %s (输入 /help 查看帮助)\n" CLR_RESET, line);
|
|
}
|
|
|
|
// ---- 主程序 ----
|
|
int main(int argc, char* argv[])
|
|
{
|
|
// Windows: 启用 ANSI 转义码支持
|
|
#ifdef _WIN32
|
|
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
DWORD mode = 0;
|
|
GetConsoleMode(hOut, &mode);
|
|
SetConsoleMode(hOut, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
|
#endif
|
|
|
|
// 查找配置文件
|
|
const char* config_path = nullptr;
|
|
if (argc >= 2) {
|
|
config_path = argv[1];
|
|
} else {
|
|
// 默认路径
|
|
#ifdef _WIN32
|
|
const char* default_configs[] = {"config.toml", nullptr};
|
|
#else
|
|
const char* default_configs[] = {"config.toml", nullptr};
|
|
#endif
|
|
for (int i = 0; default_configs[i]; i++) {
|
|
FILE* f = nullptr;
|
|
#ifdef _WIN32
|
|
fopen_s(&f, default_configs[i], "r");
|
|
#else
|
|
f = fopen(default_configs[i], "r");
|
|
#endif
|
|
if (f) {
|
|
fclose(f);
|
|
config_path = default_configs[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dstalk_init(config_path) != 0) {
|
|
std::fprintf(stderr, CLR_RED "[dstalk] 初始化失败\n" CLR_RESET);
|
|
return 1;
|
|
}
|
|
|
|
std::printf("\n");
|
|
print_banner();
|
|
std::printf("\n");
|
|
|
|
char buffer[8192];
|
|
while (true) {
|
|
std::printf(CLR_YELLOW "> " CLR_RESET);
|
|
std::fflush(stdout);
|
|
|
|
if (!std::fgets(buffer, sizeof(buffer), stdin)) break;
|
|
|
|
// 去除末尾换行
|
|
size_t len = std::strlen(buffer);
|
|
while (len > 0 && (buffer[len-1] == '\n' || buffer[len-1] == '\r')) {
|
|
buffer[--len] = '\0';
|
|
}
|
|
|
|
if (len == 0) continue;
|
|
|
|
// 命令处理
|
|
if (buffer[0] == '/') {
|
|
handle_command(buffer);
|
|
continue;
|
|
}
|
|
|
|
// AI 对话
|
|
std::printf(CLR_DIM "思考中..." CLR_RESET "\n");
|
|
std::fflush(stdout);
|
|
|
|
char* reply = nullptr;
|
|
int ret = dstalk_chat(buffer, &reply);
|
|
if (ret == 0 && reply) {
|
|
std::printf("\n%s\n\n", reply);
|
|
dstalk_free_string(reply);
|
|
} else {
|
|
std::printf(CLR_RED "[ERROR] AI 调用失败" CLR_RESET);
|
|
if (reply) {
|
|
std::printf(": %s", reply);
|
|
dstalk_free_string(reply);
|
|
}
|
|
std::printf("\n");
|
|
}
|
|
}
|
|
|
|
dstalk_destroy();
|
|
return 0;
|
|
}
|