model_chat/bin/ds

205 lines
5.5 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
// DeepSeek CLI (Node.js version)
// Commands:
// ds k <key> 设置/替换 API Key
// ds s <prompt> 设置 system prompt
// ds v 查看当前 prompt
// ds c <text> 使用 deepseek-chat流式
// ds r <text> 使用 deepseek-reasoner流式含思考
import fs from 'fs';
import os from 'os';
import path from 'path';
import { TextDecoder } from 'util';
const CONFIG_DIR = path.join(os.homedir(), '.config', 'deepseek');
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
const DEFAULT_PROMPT = '你是Deepseek人工智能助手';
function ensureConfig() {
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
if (!fs.existsSync(CONFIG_FILE)) {
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ api_key: '', system_prompt: DEFAULT_PROMPT }, null, 2), 'utf8');
fs.chmodSync(CONFIG_FILE, 0o600);
}
}
function readConfig() {
ensureConfig();
try {
const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
const data = JSON.parse(raw);
return {
apiKey: data.api_key || '',
prompt: data.system_prompt || DEFAULT_PROMPT,
};
} catch {
return { apiKey: '', prompt: DEFAULT_PROMPT };
}
}
function saveConfig(apiKey, prompt) {
ensureConfig();
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ api_key: apiKey, system_prompt: prompt }, null, 2), 'utf8');
fs.chmodSync(CONFIG_FILE, 0o600);
}
function usage() {
console.log(`用法:
ds k <key> 设置/替换 API 密钥
ds s <prompt> 设置系统提示词
ds v 查看当前提示词
ds c <text> 使用 deepseek-chat流式
ds r <text> 使用 deepseek-reasoner流式含思考
环境优先级: 命令行 > 环境变量 DEEPSEEK_API_KEY > 配置文件
默认模型: deepseek-reasoner`);
process.exit(1);
}
const args = process.argv.slice(2);
if (args.length === 0 || ['-h', '--help'].includes(args[0])) usage();
const cmd = args.shift();
// ANSI blue for **bold**
const BLUE = '\x1b[34m';
const RESET = '\x1b[0m';
let inBold = false;
function renderChunk(text) {
let out = '';
for (let i = 0; i < text.length; i++) {
if (text[i] === '*' && text[i + 1] === '*') {
inBold = !inBold;
out += inBold ? BLUE : RESET;
i++;
continue;
}
out += text[i];
}
return out;
}
async function streamChat({ model, apiKey, prompt, userInput }) {
const body = {
model,
messages: [
{ role: 'system', content: prompt },
{ role: 'user', content: userInput },
],
stream: true,
};
const res = await fetch('https://api.deepseek.com/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
});
if (!res.ok) {
const t = await res.text();
console.error(`请求失败 ${res.status}: ${t}`);
process.exit(1);
}
const decoder = new TextDecoder();
let buffer = '';
let sawReason = false;
let sawContent = false;
const enableReason = model === 'deepseek-reasoner';
process.stdout.write(`发送中 (模型: ${model})...\n`);
for await (const chunk of res.body) {
buffer += decoder.decode(chunk, { stream: true });
let lines = buffer.split(/\r?\n/);
buffer = lines.pop(); // keep incomplete line
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const data = line.slice(6);
if (data === '[DONE]') {
process.stdout.write('\n');
return;
}
let delta;
try {
delta = JSON.parse(data)?.choices?.[0]?.delta || {};
} catch {
continue;
}
const rc = delta.reasoning_content || '';
const ct = delta.content || '';
if (enableReason && rc) {
if (!sawReason) {
process.stdout.write('思考:\n');
sawReason = true;
}
process.stdout.write(renderChunk(rc));
}
if (ct) {
if (!sawContent) {
if (sawReason) process.stdout.write('\n\n');
process.stdout.write('回复:\n');
sawContent = true;
}
process.stdout.write(renderChunk(ct));
}
}
}
process.stdout.write('\n');
}
(async () => {
switch (cmd) {
case 'k': {
if (args.length < 1) {
console.error('请输入密钥');
process.exit(1);
}
const cfg = readConfig();
saveConfig(args[0], cfg.prompt);
console.log(`API 密钥已保存到 ${CONFIG_FILE}`);
break;
}
case 's': {
if (args.length < 1) {
console.error('请输入新的 system prompt');
process.exit(1);
}
const cfg = readConfig();
const prompt = args.join(' ');
saveConfig(cfg.apiKey, prompt);
console.log('system prompt 已更新。');
break;
}
case 'v': {
const cfg = readConfig();
console.log('当前 system prompt:');
console.log(cfg.prompt);
break;
}
case 'c':
case 'r': {
if (args.length < 1) {
console.error('请输入要发送的内容');
process.exit(1);
}
const userInput = args.join(' ');
const cfg = readConfig();
const apiKey = process.env.DEEPSEEK_API_KEY || cfg.apiKey;
if (!apiKey) {
console.error('未设置 API 密钥,请先运行: ds k <key> 或设置环境变量 DEEPSEEK_API_KEY');
process.exit(1);
}
const model = cmd === 'c' ? 'deepseek-chat' : 'deepseek-reasoner';
await streamChat({ model, apiKey, prompt: cfg.prompt, userInput });
break;
}
default:
usage();
}
})();