Skip to content

Commit

Permalink
feat: add gemini plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
rxliuli committed Oct 12, 2024
1 parent e824c85 commit 7f20c2a
Show file tree
Hide file tree
Showing 19 changed files with 577 additions and 5 deletions.
11 changes: 7 additions & 4 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
{
"name": "@novachat/cli",
"version": "0.1.0",
"private": true,
"description": "Build Novachat plugin with esbuild",
"type": "module",
"bin": {
"novachat": "./bin.js"
},
"files": [
"dist",
"bin.js"
"bin.js",
"templates"
],
"scripts": {
"setup": "pnpm build",
"build": "tsup"
"build": "tsup",
"start": "vite-node src/bin.ts"
},
"license": "GPL-3.0-only",
"devDependencies": {
"@liuli-util/test": "^3.8.0",
"@types/chalk": "^2.2.0",
"@types/node": "^22.7.3",
"tslib": "^2.6.3",
"tsup": "^8.3.0",
"typescript": "^5.6.2"
"typescript": "^5.6.2",
"vitest": "^2.1.2"
},
"dependencies": {
"chalk": "^5.3.0",
Expand Down
44 changes: 44 additions & 0 deletions packages/cli/src/__tests__/lib.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, it, expect } from 'vitest'
import { updateText } from '../lib'
import path from 'path'
import { initTempPath } from '@liuli-util/test'
import { readFile, writeFile } from 'fs/promises'

const tempPath = initTempPath(__filename)

it('updateText', async () => {
await writeFile(
path.resolve(tempPath, 'package.json'),
JSON.stringify({
name: 'novachat.template',
}),
)
await updateText({
rootPath: tempPath,
rules: [
{
path: 'package.json',
replaces: [['novachat.template', 'novachat-plugin-test']],
},
],
})
expect(
JSON.parse(await readFile(path.resolve(tempPath, 'package.json'), 'utf-8')),
).toEqual({
name: 'novachat-plugin-test',
})
})

it("don't exist file", async () => {
await expect(
updateText({
rootPath: tempPath,
rules: [
{
path: 'not-exist-file',
replaces: [['novachat.template', 'novachat-plugin-test']],
},
],
}),
).rejects.toThrow()
})
38 changes: 37 additions & 1 deletion packages/cli/src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,46 @@
import { build, BuildOptions, context } from 'esbuild'
import { Command } from 'commander'
import { Argument, Command } from 'commander'
import path from 'path'
import { novachatPlugin } from './plugin'
import chalk from 'chalk'
import { cp } from 'fs/promises'
import { updateText } from './lib'

new Command()
.addCommand(
new Command('init')
.addArgument(new Argument('name', 'Project name').argRequired())
.description('Bootstrap Project')
.action(async (name) => {
const templateDir = path.resolve(
__dirname,
'../templates/plugin-template',
)
const toPath = path.resolve(name)
await cp(templateDir, toPath, {
recursive: true,
filter: (src) => src !== path.resolve(templateDir, 'node_modules'),
})
console.log(chalk.green('Project initialized successfully'))
await updateText({
rootPath: path.resolve(name),
rules: [
{
path: 'package.json',
replaces: [['@novachat/plugin-template', name]],
},
{
path: 'README.md',
replaces: [['@novachat/plugin-template', name]],
},
{
path: 'src/plugin.json',
replaces: [['novachat.template', name]],
},
],
})
}),
)
.description('Build Novachat plugin with esbuild')
.option('-w, --watch', 'Watch mode')
.action(async ({ watch }) => {
Expand Down
21 changes: 21 additions & 0 deletions packages/cli/src/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import path from 'path'
import { readFile, writeFile } from 'fs/promises'

export async function updateText(options: {
rootPath: string
rules: {
path: string
replaces: [string, string][]
}[]
}) {
for (const rule of options.rules) {
let content = await readFile(
path.resolve(options.rootPath, rule.path),
'utf-8',
)
for (const [from, to] of rule.replaces) {
content = content.replace(from, to)
}
await writeFile(path.resolve(options.rootPath, rule.path), content)
}
}
28 changes: 28 additions & 0 deletions packages/cli/templates/plugin-template/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local
.temp
docs/.vitepress/dist
docs/.vitepress/cache
publish

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
3 changes: 3 additions & 0 deletions packages/cli/templates/plugin-template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @novachat/plugin-template

NovaChat Plugin Template.
29 changes: 29 additions & 0 deletions packages/cli/templates/plugin-template/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@novachat/plugin-template",
"keywords": [
"novachat",
"novachat-plugin"
],
"version": "0.1.0",
"license": "MIT",
"type": "module",
"files": [
"publish"
],
"scripts": {
"setup": "pnpm build",
"build": "novachat",
"dev": "pnpm build --watch"
},
"sideEffects": false,
"devDependencies": {
"@novachat/cli": "workspace:*",
"typescript": "^5.3.3",
"@novachat/plugin": "workspace:*",
"vitest": "^1.2.2"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
5 changes: 5 additions & 0 deletions packages/cli/templates/plugin-template/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as novachat from '@novachat/plugin'

export async function activate() {
console.log('Default model:', await novachat.model.getDefault())
}
6 changes: 6 additions & 0 deletions packages/cli/templates/plugin-template/src/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "novachat.template",
"name": "Template",
"description": "Template Plugin",
"version": "0.1.0"
}
16 changes: 16 additions & 0 deletions packages/cli/templates/plugin-template/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": ["ESNext"],
"outDir": "./dist",
"skipLibCheck": true,
"esModuleInterop": true,
"strict": true,
"module": "ESNext",
"moduleResolution": "bundler",
"sourceMap": true,
"declaration": true,
"declarationMap": true
},
"include": ["src"]
}
1 change: 1 addition & 0 deletions packages/cli/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { defineConfig } from 'tsup'
export default defineConfig({
entryPoints: ['./src/bin.ts'],
format: ['esm'],
shims: true,
})
28 changes: 28 additions & 0 deletions packages/plugin-gemini/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local
.temp
docs/.vitepress/dist
docs/.vitepress/cache
publish

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
3 changes: 3 additions & 0 deletions packages/plugin-gemini/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# plugin-gemini

NovaChat Plugin Template.
30 changes: 30 additions & 0 deletions packages/plugin-gemini/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@novachat/plugin-gemini",
"keywords": [
"novachat",
"novachat-plugin"
],
"version": "0.1.0",
"license": "MIT",
"type": "module",
"files": [
"publish"
],
"scripts": {
"setup": "pnpm build",
"build": "novachat",
"dev": "pnpm build --watch"
},
"sideEffects": false,
"devDependencies": {
"@google/generative-ai": "^0.21.0",
"@novachat/cli": "workspace:*",
"@novachat/plugin": "workspace:*",
"typescript": "^5.3.3",
"vitest": "^1.2.2"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
67 changes: 67 additions & 0 deletions packages/plugin-gemini/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as novachat from '@novachat/plugin'
import GoogleAI, { GoogleGenerativeAI } from '@google/generative-ai'

function parseRequest(
req: novachat.QueryRequest,
): GoogleAI.GenerateContentRequest {
const systemInstruction = () => {
const system = req.messages.find((m) => m.role === 'system')?.content
if (!system) {
return undefined
}
return system
}
return {
systemInstruction: systemInstruction(),
contents: req.messages.map(
(m) =>
({
role: m.role,
parts: [{ text: m.content }],
} as GoogleAI.Content),
),
}
}

export async function activate() {
console.log('Default model:', await novachat.model.getDefault())

async function createClient(req: novachat.QueryRequest) {
const apiKey = await novachat.setting.get('gemini.apiKey')
if (!apiKey) {
throw new Error('API key is not set')
}
const genAI = new GoogleGenerativeAI(apiKey)
const model = genAI.getGenerativeModel({ model: req.model })
return model
}
novachat.model.registerProvider({
name: 'Gemini',
models: [
{ id: 'gemini-1.0-pro', name: 'Gemini 1.0 Pro' },
{ id: 'gemini-1.5-pro', name: 'Gemini 1.5 Pro' },
{ id: 'gemini-1.5-pro-exp-0801', name: 'Gemini 1.5 Pro Exp 0801' },
{ id: 'gemini-1.5-flash', name: 'Gemini 1.5 Flash' },
{ id: 'gemma-2-2b-it', name: 'Gemma 2.2b IT' },
{ id: 'gemma-2-9b-it', name: 'Gemma 2.9b IT' },
{ id: 'gemma-2-27b-it', name: 'Gemma 2.27b IT' },
],
invoke: async (req) => {
const { response } = await (
await createClient(req)
).generateContent(parseRequest(req))
return {
content: response.text(),
}
},
async *stream(req) {
const model = await createClient(req)
const stream = await model.generateContentStream(parseRequest(req))
for await (const chunk of stream.stream) {
yield {
content: chunk.text(),
}
}
},
})
}
15 changes: 15 additions & 0 deletions packages/plugin-gemini/src/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "novachat.plugin-gemini",
"name": "Gemini",
"description": "Gemini Provider",
"version": "0.1.0",
"configuration": {
"title": "gemini",
"properties": {
"gemini.apiKey": {
"type": "string",
"description": "Google Generative AI API Key"
}
}
}
}
Loading

0 comments on commit 7f20c2a

Please sign in to comment.