Skip to content

Commit

Permalink
feat: tnf chat (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
sorrycc authored Jan 3, 2025
1 parent 5dbd0e5 commit dbf33f8
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 46 deletions.
5 changes: 5 additions & 0 deletions .changeset/quiet-glasses-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@umijs/tnf': patch
---

feat: tnf chat
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ $ pnpm preview
## Commands

- `tnf build`: Build the project.
- `tnf chat --message=<message> --model=<model> --verbose`: Chat with AI assistant.
- `tnf config list/get/set/remove [name] [value]`: Manage the config.
- `tnf dev`: Start the development server.
- `tnf doctor`: Check the project for potential issues.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"@babel/core": "^7.26.0",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@clack/prompts": "^0.9.0",
"@tanstack/react-router": "^1.92.3",
"@tanstack/router-devtools": "^1.92.3",
"@tanstack/router-generator": "^1.87.7",
Expand All @@ -67,6 +66,7 @@
"@types/react-dom": "^19.0.2",
"@types/spdy": "^3.4.9",
"@types/yargs-parser": "^21.0.3",
"@umijs/clack-prompts": "^0.0.4",
"@umijs/mako": "^0.11.0",
"babel-plugin-react-compiler": "19.0.0-beta-b2e8e9c-20241220",
"body-parser": "^1.20.3",
Expand Down Expand Up @@ -98,6 +98,7 @@
"random-color": "^1.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"resolve": "^1.22.10",
"sirv": "^3.0.0",
"spdy": "^4.0.2",
"tiny-invariant": "^1.3.3",
Expand Down
64 changes: 22 additions & 42 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 97 additions & 0 deletions src/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as p from '@umijs/clack-prompts';
import assert from 'assert';
import fs from 'fs';
import path from 'path';
import { tools } from './ai/ai.js';
import * as logger from './fishkit/logger.js';
import { getNpmClient, installWithNpmClient } from './fishkit/npm.js';
import { type Context } from './types/index.js';

const CANCEL_TEXT = 'Operation cancelled.';

export async function chat({ context }: { context: Context }) {
const { cwd } = context;
p.intro('Welcome to TNF Chat!');
try {
// Check if @umijs/ai is installed
while (true) {
if (isUmijsAiExists(cwd)) {
break;
} else {
p.log.error('@umijs/ai is not installed');
const result = await p.confirm({
message: 'Do you want to install @umijs/ai?',
});
if (p.isCancel(result)) {
throw new Error(CANCEL_TEXT);
}
if (result) {
// install @umijs/ai
logger.debug('Installing @umijs/ai');
addUmijsAiToPackageJson(cwd);
const npmClient = await getNpmClient({ cwd });
logger.debug(`Using npm client: ${npmClient}`);
installWithNpmClient({
npmClient,
cwd,
});
} else {
throw new Error('Process cancelled, please install @umijs/ai first');
}
}
}

// use @umijs/ai
const docsDir = path.join(context.paths.tmpPath, 'docs');
assert(
fs.existsSync(docsDir),
'.tnf/docs directory not found, please run tnf build/sync/dev first',
);
const tnfPath = path.join(docsDir, 'tnf.md');
const generalPath = path.join(docsDir, 'general.md');
const systemPrompts = [
fs.readFileSync(tnfPath, 'utf-8'),
fs.readFileSync(generalPath, 'utf-8'),
];
const umijsAiPath = path.join(
cwd,
'node_modules',
'@umijs',
'ai',
'dist',
'index.js',
);
const ai = await import(umijsAiPath);
await ai.chat({
tools,
verbose: context.argv.verbose,
message: context.argv.message,
systemPrompts,
model: context.argv.model,
});
p.outro('Bye!');
} catch (e: any) {
p.cancel(e.message);
}
}

function addUmijsAiToPackageJson(cwd: string) {
const pkg = readPackageJson(cwd);
pkg.dependencies['@umijs/ai'] = '^0.1.0';
fs.writeFileSync(
path.join(cwd, 'package.json'),
JSON.stringify(pkg, null, 2),
);
}

function readPackageJson(cwd: string) {
const pkgPath = path.join(cwd, 'package.json');
logger.debug(`Reading package.json from ${pkgPath}`);
assert(fs.existsSync(pkgPath), 'package.json not found');
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
}

function isUmijsAiExists(cwd: string) {
const pkg = readPackageJson(cwd);
return pkg.dependencies['@umijs/ai'] || pkg.devDependencies['@umijs/ai'];
}
3 changes: 3 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ async function run(cwd: string) {
case 'build':
const { build } = await import('./build.js');
return build({ context });
case 'chat':
const { chat } = await import('./chat.js');
return chat({ context });
case 'config':
const { config } = await import('./config/config.js');
return config({ context });
Expand Down
13 changes: 10 additions & 3 deletions src/fishkit/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ import { spawnSync } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import fs from 'fs-extra';
import path from 'pathe';
import { fileURLToPath } from 'url';

export type NpmClient = 'npm' | 'cnpm' | 'tnpm' | 'yarn' | 'pnpm';

export const getNpmClient = (opts: { cwd: string }): NpmClient => {
async function resolveModule(id: string) {
return fileURLToPath(await import.meta.resolve(id));
}

export const getNpmClient = async (opts: {
cwd: string;
}): Promise<NpmClient> => {
const tnpmRegistries = ['.alibaba-inc.', '.antgroup-inc.'];
const tcnpmLockPath = path.join(
opts.cwd,
Expand All @@ -20,7 +27,7 @@ export const getNpmClient = (opts: { cwd: string }): NpmClient => {
}

// 检查 pnpm
const chokidarPath = require.resolve('chokidar');
const chokidarPath = await resolveModule('chokidar');
if (
chokidarPath.includes('.pnpm') ||
existsSync(path.join(opts.cwd, 'node_modules', '.pnpm'))
Expand Down Expand Up @@ -123,7 +130,7 @@ export class PackageManager {
try {
await this.writePackageJson();

const npmClient = getNpmClient({ cwd: this.cwd });
const npmClient = await getNpmClient({ cwd: this.cwd });
await installWithNpmClient({
npmClient,
});
Expand Down

0 comments on commit dbf33f8

Please sign in to comment.