Skip to content

Commit 3d67b77

Browse files
authored
feat: add groq ai (#108)
1 parent 10ee02e commit 3d67b77

File tree

10 files changed

+186
-113
lines changed

10 files changed

+186
-113
lines changed

README.md

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Fanyi
44

5-
A 🇨🇳 and 🇺🇸🇬🇧 translate tool in your command line, powered by iciba.
5+
A 🇨🇳 and 🇺🇸🇬🇧 translate tool in your command line, powered by iciba and groq.
66

77
[![NPM version][npm-image]][npm-url]
88
[![npm download][download-image]][download-url]
@@ -112,25 +112,16 @@ Also, you can use `list` command to see the history of your search.
112112
113113
## Configuration
114114
115-
A configuration file can be put into ~/.config/fanyi/.fanyirc, in the user's home directory
115+
A configuration file can be put into `~/.config/fanyi/.fanyirc`, in the user's home directory.
116116
117-
Use subcommand `fanyi config [options]`
117+
Use subcommand `fanyi config set <key> <value>` to set configuration options.
118118
119119
Example:
120120
121121
```bash
122-
# or
123-
$ fanyi config --no-color // disable color globally
124-
$ fanyi config --color // enable color globally
125-
$ fanyi config --no-iciba // disable iciba globally
126-
$ fanyi config --iciba // enable iciba globally
127-
```
128-
129-
A sample `~/.config/fanyi/.fanyirc` file:
130-
131-
```json
132-
{
133-
"iciba": true,
134-
"color": true,
135-
}
136-
```
122+
$ fanyi config list // list all configuration options
123+
$ fanyi config set iciba false // disable iciba globally
124+
$ fanyi config set groq false // disable groq globally
125+
$ fanyi config set color false // disable color globally
126+
$ fanyi config set GROQ_API_KEY your-api-key // set GROQ_API_KEY
127+
```

bin/fanyi.js

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,54 @@
11
#!/usr/bin/env node
22

3-
const { program } = require('commander');
3+
const { Command } = require('commander');
44
const chalk = require('chalk');
55
const updateNotifier = require('update-notifier');
66
const pkg = require('../package.json');
77
const config = require('../lib/config');
88
const { searchList } = require('../lib/searchHistory');
99

1010
updateNotifier({ pkg }).notify();
11+
const program = new Command();
1112

1213
program
1314
.name(pkg.name)
1415
.description(pkg.description)
1516
.version(pkg.version)
1617
.action(() => {
17-
// If the input is "fanyi", no parameters, ignore.
18+
// 如果输入是 "fanyi",没有参数,则忽略
1819
if (process.argv.length > 2) {
1920
return runFY();
2021
}
2122
});
2223

2324
program
2425
.command('config')
25-
.description('Set the global options')
26-
.option('-c, --color', 'Output with color')
27-
.option('-C, --no-color', 'Output without color')
28-
.option('-i, --iciba', 'Enable the iciba translation engine')
29-
.option('-I, --no-iciba', 'Disable the iciba translation engine')
30-
.action((args) => {
31-
// hack
32-
// If the input is "fanyi config", then translate the word config.
33-
if (process.argv.length === 3) {
34-
return runFY();
35-
}
36-
const { color, iciba } = args;
37-
const options = resolveOptions({ color, iciba });
38-
return config.write(options);
39-
});
26+
.description('设置全局选项')
27+
.addCommand(
28+
new Command('list').description('查看配置项').action(async () => {
29+
const options = await config.load();
30+
console.log(`${chalk.gray(config.getConfigPath())}`);
31+
console.log();
32+
for (const [key, value] of Object.entries(options)) {
33+
console.log(`${chalk.cyan(key)}: ${chalk.yellow(value)}`);
34+
}
35+
}),
36+
)
37+
.addCommand(
38+
new Command('set')
39+
.description('设置配置项')
40+
.argument('<key>', '配置项键名')
41+
.argument('<value>', '配置项值')
42+
.action(async (key, value) => {
43+
const options = {};
44+
if (key === 'GROQ_API_KEY') {
45+
options[key] = value;
46+
} else {
47+
options[key] = value === 'true' ? true : value === 'false' ? false : value;
48+
}
49+
await config.write(options);
50+
}),
51+
);
4052

4153
program
4254
.command('list')
@@ -49,10 +61,15 @@ program
4961

5062
program.on('--help', () => {
5163
console.log('');
52-
console.log(chalk.gray('Examples:'));
64+
console.log(chalk.gray('示例:'));
5365
console.log(`${chalk.cyan(' $ ')}fanyi word`);
5466
console.log(`${chalk.cyan(' $ ')}fanyi world peace`);
5567
console.log(`${chalk.cyan(' $ ')}fanyi chinglish`);
68+
console.log(`${chalk.cyan(' $ ')}fanyi config set color true`);
69+
console.log(`${chalk.cyan(' $ ')}fanyi config set iciba true`);
70+
console.log(`${chalk.cyan(' $ ')}fanyi config set groq true`);
71+
console.log(`${chalk.cyan(' $ ')}fanyi config set GROQ_API_KEY your_api_key_here`);
72+
console.log(`${chalk.cyan(' $ ')}fanyi config list`);
5673
console.log('');
5774
});
5875

bun.lockb

8.13 KB
Binary file not shown.

index.js

Lines changed: 101 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,114 @@
11
const needle = require('needle');
2-
const chalk = require('chalk');
3-
const SOURCE = require('./lib/source');
2+
const { Groq } = require('groq-sdk');
43
const print = require('./lib/print');
54
const parseString = require('xml2js').parseString;
65
const ora = require('ora');
76

8-
module.exports = (word, options, callback) => {
7+
module.exports = async (word, options) => {
98
console.log('');
10-
const { iciba } = options;
11-
const requestCounts = [iciba].filter(isTrueOrUndefined).length;
12-
const spinner = ora().start();
13-
14-
let count = 0;
15-
const callbackAll = () => {
16-
count += 1;
17-
if (count >= requestCounts) {
18-
spinner.stop();
19-
spinner.clear();
20-
callback?.();
21-
}
22-
};
23-
9+
const { iciba, groq, GROQ_API_KEY } = options;
2410
const endcodedWord = encodeURIComponent(word);
2511

2612
// iciba
27-
isTrueOrUndefined(iciba) &&
28-
needle.get(
29-
SOURCE.iciba.replace('${word}', endcodedWord),
30-
{ parse: false },
31-
(error, response) => {
32-
if (error) {
33-
console.log(chalk.yellow('访问 iciba 失败,请检查网络'));
34-
} else if (response.statusCode === 200) {
35-
parseString(response.body, (err, result) => {
36-
if (err) {
37-
return;
38-
}
39-
print.iciba(result.dict, options);
13+
if (isTrueOrUndefined(iciba)) {
14+
const ICIBA_URL =
15+
'http://dict-co.iciba.com/api/dictionary.php?key=D191EBD014295E913574E1EAF8E06666&w=';
16+
const spinner = ora('正在查询 iciba...').start();
17+
try {
18+
const response = await needle('get', `${ICIBA_URL}${endcodedWord}`, { parse: false });
19+
if (response.statusCode === 200) {
20+
const result = await new Promise((resolve, reject) => {
21+
parseString(response.body, (err, res) => {
22+
if (err) reject(err);
23+
else resolve(res);
4024
});
41-
}
42-
callbackAll();
43-
},
44-
);
25+
});
26+
spinner.stop();
27+
print.iciba(result.dict, options);
28+
}
29+
} catch (error) {
30+
spinner.fail('访问 iciba 失败,请检查网络');
31+
}
32+
}
33+
34+
// groq ai
35+
if (isTrueOrUndefined(groq)) {
36+
const groqClient = new Groq({
37+
apiKey: GROQ_API_KEY || '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',
38+
});
39+
const model = 'llama3-groq-70b-8192-tool-use-preview';
40+
41+
const spinner = ora('正在查询 Groq AI...').start();
42+
try {
43+
const chatCompletion = await groqClient.chat.completions.create({
44+
messages: [
45+
{
46+
role: 'system',
47+
content: `
48+
你是一本专业的中英文双语词典。请按照以下要求提供翻译和解释:
49+
50+
1. 格式要求:
51+
[原词] [音标] ~ [翻译] [拼音]
52+
53+
- [词性] [释义1]
54+
- [词性] [释义2]
55+
...
56+
57+
例句:
58+
1. [原文例句]
59+
[翻译]
60+
2. [原文例句]
61+
[翻译]
62+
...
63+
64+
❤️
65+
[座右铭]
66+
-----
67+
68+
2. 翻译规则:
69+
- 英文输入翻译为中文,中文输入翻译为英文
70+
- 提供准确的音标(英文)或拼音(中文)
71+
- 列出所有常见词性及其对应的释义
72+
- 释义应简洁明了,涵盖词语的主要含义
73+
- 提供2-3个地道的例句,体现词语的不同用法和语境
74+
75+
3. 内容质量:
76+
- 确保翻译和释义的准确性和权威性
77+
- 例句应当实用、常见,并能体现词语的典型用法
78+
- 注意词语的语体色彩,如正式、口语、书面语等
79+
- 对于多义词,按照使用频率由高到低排列释义
80+
81+
4. 特殊情况:
82+
- 对于习语、谚语或特殊表达,提供对应的解释和等效表达
83+
- 注明词语的使用范围,如地域、行业特定用语等
84+
- 对于缩写词,提供完整形式和解释
85+
86+
请基于以上要求,为用户提供简洁、专业、全面且易于理解的词语翻译和解释。
87+
88+
---
89+
最后使用这个词写一句简短的积极向上令人深思的英文座右铭。
90+
`,
91+
},
92+
{
93+
role: 'user',
94+
content: `请翻译:${word}`,
95+
},
96+
],
97+
model,
98+
temperature: 0.3,
99+
max_tokens: 1024,
100+
top_p: 0.8,
101+
stream: true,
102+
stop: null,
103+
});
104+
spinner.stop();
105+
for await (const chunk of chatCompletion) {
106+
process.stdout.write(chunk.choices[0]?.delta?.content || '');
107+
}
108+
} catch (error) {
109+
spinner.fail('访问 Groq AI 失败,请检查网络或 API 密钥');
110+
}
111+
}
45112
};
46113

47114
function isTrueOrUndefined(val) {

lib/config.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ const config = {
3131
console.log(
3232
`${chalkInstance.bgGreen(JSON.stringify(options))} config saved at ${chalkInstance.gray(path)}:`,
3333
);
34-
console.log();
35-
console.log(chalkInstance.greenBright(content));
34+
for (const [key, value] of Object.entries(options)) {
35+
console.log(`${chalk.cyan(key)}: ${chalk.yellow(value)}`);
36+
}
37+
},
38+
getConfigPath() {
39+
return configPath;
3640
},
3741
};
3842

lib/print.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ exports.iciba = (data, options = {}) => {
3030

3131
log(firstLine + chalk.gray(' ~ iciba.com'));
3232

33+
if (data.pos?.length) {
34+
log();
35+
}
36+
3337
// pos & acceptation
3438
data.pos?.forEach((item, i) => {
3539
if (typeof data.pos[i] !== 'string' || !data.pos[i]) {
@@ -64,7 +68,7 @@ exports.iciba = (data, options = {}) => {
6468
function log(message, indentNum = 1) {
6569
let indent = '';
6670
for (let i = 1; i < indentNum; i += 1) {
67-
indent += ' ';
71+
indent += ' ';
6872
}
6973
console.log(indent, message || '');
7074
}

lib/source.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@
3434
"commander": "^12.1.0",
3535
"dayjs": "^1.11.5",
3636
"fs-extra": "^10.1.0",
37+
"groq-sdk": "^0.7.0",
3738
"needle": "^3.1.0",
3839
"update-notifier": "^4.1.0",
3940
"xml2js": "^0.4.8"
4041
},
4142
"lint-staged": {
42-
"*": [
43+
"*.{js,ts,json,yml}": [
4344
"biome check --write --files-ignore-unknown=true",
4445
"biome format --write --files-ignore-unknown=true",
4546
"biome lint --write --files-ignore-unknown=true"

0 commit comments

Comments
 (0)