Initial dstalk project: core DLL + CLI + BearSSL TLS
- 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>
This commit is contained in:
235
dstalk-cli/src/main.cpp
Normal file
235
dstalk-cli/src/main.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user