cli-qa-scaffold 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/bin/codeat.js +159 -0
- package/package.json +20 -0
package/README.md
ADDED
|
File without changes
|
package/bin/codeat.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
+
|
|
5
|
+
const state = {
|
|
6
|
+
speedMs: 30, // 每个字符的输出速度
|
|
7
|
+
thinkMs: 1200, // 思考总时长(可调)
|
|
8
|
+
isStreaming: false,
|
|
9
|
+
abortController: null,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const rl = readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout,
|
|
15
|
+
prompt: '\n💬 请输入你的问题(输入 "/help" 查看命令):\n> ',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
console.log("🚀 欢迎使用 CLI 问答助手(本地 Mock 模式)!");
|
|
19
|
+
rl.prompt();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Ctrl+C 行为:
|
|
23
|
+
* - 若正在流式输出:中断本次输出,不退出
|
|
24
|
+
* - 否则:退出
|
|
25
|
+
*/
|
|
26
|
+
process.on("SIGINT", () => {
|
|
27
|
+
if (state.isStreaming && state.abortController) {
|
|
28
|
+
state.abortController.abort();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log("\n👋 再见!");
|
|
32
|
+
rl.close();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
rl.on("line", async (input) => {
|
|
36
|
+
const text = input.trim();
|
|
37
|
+
if (!text) return rl.prompt();
|
|
38
|
+
|
|
39
|
+
// 仅保留这三个命令:/help /exit /clear
|
|
40
|
+
if (text.startsWith("/")) {
|
|
41
|
+
handleCommand(text);
|
|
42
|
+
return rl.prompt();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await streamMockAnswer(text);
|
|
46
|
+
rl.prompt();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
rl.on("close", () => process.exit(0));
|
|
50
|
+
|
|
51
|
+
function handleCommand(raw) {
|
|
52
|
+
const cmd = raw.slice(1).trim().toLowerCase();
|
|
53
|
+
|
|
54
|
+
switch (cmd) {
|
|
55
|
+
case "help":
|
|
56
|
+
console.log("可用命令:");
|
|
57
|
+
console.log(` /help 显示帮助`);
|
|
58
|
+
console.log(` /exit 退出`);
|
|
59
|
+
console.log(` /clear 清屏`);
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
case "exit":
|
|
63
|
+
console.log("👋 再见!");
|
|
64
|
+
rl.close();
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
case "clear":
|
|
68
|
+
process.stdout.write("\x1bc");
|
|
69
|
+
console.log("🚀 欢迎使用 CLI 问答助手(本地 Mock 模式)!");
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
default:
|
|
73
|
+
console.log(`⚠️ 未知命令:/${cmd}(输入 /help 查看命令)`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function clearLastLines(n) {
|
|
78
|
+
for (let i = 0; i < n; i++) {
|
|
79
|
+
process.stdout.write("\x1b[1A\x1b[2K"); // 上移一行 + 清空
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 启动一个“单行动态 loading”:
|
|
85
|
+
* - 使用 \r 回到行首,不换行地刷新内容
|
|
86
|
+
* - 返回 stop(),调用后会停止刷新并清掉该行
|
|
87
|
+
*/
|
|
88
|
+
function startLoading(text = "🤖 正在思考") {
|
|
89
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
90
|
+
let i = 0;
|
|
91
|
+
|
|
92
|
+
// 先输出一行(确保后续 clear 时有“这一行”可清)
|
|
93
|
+
process.stdout.write("\n");
|
|
94
|
+
// 定时刷新同一行
|
|
95
|
+
const timer = setInterval(() => {
|
|
96
|
+
const frame = frames[i++ % frames.length];
|
|
97
|
+
process.stdout.write(`\r${frame} ${text}...`); // \r 回到行首覆盖
|
|
98
|
+
}, 80);
|
|
99
|
+
|
|
100
|
+
const stop = () => {
|
|
101
|
+
clearInterval(timer);
|
|
102
|
+
// 清掉当前这行(不留下 spinner 文本)
|
|
103
|
+
process.stdout.write("\r\x1b[2K");
|
|
104
|
+
// 把光标移回上一行,让后续 clearLastLines(1) 正常工作也行
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return stop;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 模拟流式回答(本地 mock)
|
|
111
|
+
async function streamMockAnswer(question) {
|
|
112
|
+
const ac = new AbortController();
|
|
113
|
+
state.abortController = ac;
|
|
114
|
+
state.isStreaming = true;
|
|
115
|
+
|
|
116
|
+
const answer = generateMockAnswer(question);
|
|
117
|
+
|
|
118
|
+
// ✅ 动态 loading
|
|
119
|
+
const stopLoading = startLoading("🤖 正在思考");
|
|
120
|
+
try {
|
|
121
|
+
await delay(state.thinkMs, undefined, { signal: ac.signal });
|
|
122
|
+
} catch {
|
|
123
|
+
stopLoading();
|
|
124
|
+
process.stdout.write("⚠️ 已中断本次输出\n\n");
|
|
125
|
+
state.isStreaming = false;
|
|
126
|
+
state.abortController = null;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
stopLoading();
|
|
130
|
+
|
|
131
|
+
// 输出回答
|
|
132
|
+
process.stdout.write("🤖 回答:");
|
|
133
|
+
try {
|
|
134
|
+
for (let i = 0; i < answer.length; i++) {
|
|
135
|
+
process.stdout.write(answer[i]);
|
|
136
|
+
await delay(state.speedMs, undefined, { signal: ac.signal });
|
|
137
|
+
}
|
|
138
|
+
process.stdout.write("\n\n");
|
|
139
|
+
} catch {
|
|
140
|
+
process.stdout.write("\n⚠️ 已中断本次输出\n\n");
|
|
141
|
+
} finally {
|
|
142
|
+
state.isStreaming = false;
|
|
143
|
+
state.abortController = null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function generateMockAnswer(question) {
|
|
148
|
+
const lowerQ = question.toLowerCase();
|
|
149
|
+
|
|
150
|
+
if (lowerQ.includes("你好") || lowerQ.includes("hello")) {
|
|
151
|
+
return "你好呀!很高兴见到你!";
|
|
152
|
+
} else if (lowerQ.includes("时间") || lowerQ.includes("time")) {
|
|
153
|
+
return `当前时间是:${new Date().toLocaleString("zh-CN")}`;
|
|
154
|
+
} else if (lowerQ.includes("你是谁") || lowerQ.includes("who are you")) {
|
|
155
|
+
return "我是一个运行在你终端里的本地问答机器人!";
|
|
156
|
+
} else {
|
|
157
|
+
return '这是一个模拟的回答。你可以问我“你好”、“时间”或“你是谁”试试看!';
|
|
158
|
+
}
|
|
159
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cli-qa-scaffold",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI Q&A scaffold (streaming mock)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codeat": "bin/codeat.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
}
|
|
20
|
+
}
|