mirror of
https://github.com/run-llama/gemini-live-demo.git
synced 2026-07-01 20:24:02 -04:00
feat: add ts
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
name: PR check
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read # for checkout
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # to be able to publish a GitHub release
|
||||
issues: write # to be able to comment on released issues
|
||||
pull-requests: write # to be able to comment on released pull requests
|
||||
id-token: write # to enable use of OIDC for npm provenance
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ts/
|
||||
run: npm install
|
||||
|
||||
- name: Lint
|
||||
working-directory: ts/
|
||||
run: npm run lint
|
||||
|
||||
- name: Build
|
||||
working-directory: ts/
|
||||
run: npm run build
|
||||
|
||||
- name: Setup npm authentication
|
||||
run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Release
|
||||
working-directory: ts/
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --access public
|
||||
@@ -18,4 +18,4 @@ jobs:
|
||||
run: uv python install
|
||||
|
||||
- name: Run Tests
|
||||
run: uv venv && source .venv/bin/activate && uv pip install pytest llama-index-core && pytest tests/test_*.py
|
||||
run: uv venv && source .venv/bin/activate && uv pip install pytest llama-index-core && pytest python/tests/test_*.py
|
||||
|
||||
@@ -18,5 +18,5 @@ jobs:
|
||||
run: uv python install
|
||||
|
||||
- name: Run Mypy
|
||||
working-directory: src
|
||||
working-directory: python/src
|
||||
run: uv venv && source .venv/bin/activate && uv pip install mypy && mypy gemini_live_demo
|
||||
|
||||
@@ -7,3 +7,7 @@ build/
|
||||
|
||||
# env variables
|
||||
.env
|
||||
|
||||
# npm generated files
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
@@ -21,7 +21,7 @@ repos:
|
||||
- id: ruff
|
||||
args: [--exit-non-zero-on-fix, --fix]
|
||||
- id: ruff-format
|
||||
exclude: ".*poetry.lock|.*_static|.*uv.lock|.*ipynb|.*docs.*"
|
||||
exclude: ".*poetry.lock|.*_static|.*uv.lock|.*ipynb|.*docs.*|.*.ts"
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.0.1
|
||||
@@ -44,7 +44,7 @@ repos:
|
||||
--ignore-missing-imports,
|
||||
--python-version=3.9,
|
||||
]
|
||||
entry: bash -c "export MYPYPATH=src/gemini_live_demo"
|
||||
entry: bash -c "export MYPYPATH=python/src/gemini_live_demo"
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.10.1
|
||||
@@ -52,7 +52,7 @@ repos:
|
||||
- id: black-jupyter
|
||||
name: black-docs-py
|
||||
alias: black
|
||||
files: ^(README.md|CONTRIBUTING.md)
|
||||
files: ^(README.md|CONTRIBUTING.md|.*.ts)
|
||||
# Using PEP 8's line length in docs prevents excess left/right scrolling
|
||||
args: [--line-length=79]
|
||||
|
||||
|
||||
+28
-5
@@ -4,14 +4,14 @@ Do you want to contribute to this project? Make sure to read this guidelines fir
|
||||
|
||||
## Issue
|
||||
|
||||
**When to do it**:
|
||||
### When to do it
|
||||
|
||||
- You found bugs but you don't know how to solve them or don't have time/will to do the solve
|
||||
- You want new features but you don't know how to implement them or don't have time/will to do the implementation
|
||||
|
||||
> ⚠️ _Always check open and closed issues before you submit yours to avoid duplicates_
|
||||
|
||||
**How to do it**:
|
||||
### How to do it
|
||||
|
||||
- Open an issue
|
||||
- Give the issue a meaningful title (short but effective problem/feature request description)
|
||||
@@ -19,13 +19,15 @@ Do you want to contribute to this project? Make sure to read this guidelines fir
|
||||
|
||||
## Traditional contribution
|
||||
|
||||
**When to do it**:
|
||||
### When to do it
|
||||
|
||||
- You found bugs and corrected them
|
||||
- You optimized/improved the code
|
||||
- You added new features that you think could be useful to others
|
||||
|
||||
**How to do it**:
|
||||
### How to do it
|
||||
|
||||
**Python**
|
||||
|
||||
1. Fork this repository
|
||||
2. Install `pre-commit` and make sure to have it within the Git Hooks for your fork:
|
||||
@@ -38,11 +40,32 @@ pre-commit install
|
||||
3. Change the things you want, and make sure tests still pass or add new ones:
|
||||
|
||||
```bash
|
||||
pytest tests/test_*.py
|
||||
pytest python/tests/test_*.py
|
||||
```
|
||||
|
||||
3. Commit your changes
|
||||
4. Make sure your changes pass the pre-commit linting/type checking, if not modify them so that they pass
|
||||
5. Submit pull request (make sure to provide a thorough description of the changes)
|
||||
|
||||
**TypeScript**
|
||||
|
||||
1. Fork this repository
|
||||
2. Install `pre-commit` and make sure to have it within the Git Hooks for your fork:
|
||||
|
||||
```bash
|
||||
pip install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
3. Make changes, and make sure the package _builds_, _is linted_ and _works_:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm run lint
|
||||
npm run start
|
||||
```
|
||||
|
||||
4. Make sure your changes pass the pre-commit linting/type checking, if not modify them so that they pass
|
||||
5. Submit pull request (make sure to provide a thorough description of the changes)
|
||||
|
||||
### Thanks for contributing!
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
This is a demo repository showcasing Gemini Live x LlamaIndex integration.
|
||||
|
||||
Watch the [demo](https://www.loom.com/share/3f9b5c53d6c84fa89e7498a3ce7f1b99?sid=b87329ec-9278-439c-a626-dd1e13adbc97) for a quick overview!
|
||||
Watch the [python](https://www.loom.com/share/3f9b5c53d6c84fa89e7498a3ce7f1b99?sid=b87329ec-9278-439c-a626-dd1e13adbc97) and the [typescript]() demo videos for a quick overview!
|
||||
|
||||
## Install and Launch
|
||||
## Python
|
||||
|
||||
### Install and Launch
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
@@ -14,7 +16,7 @@ Clone this repository locally:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/run-llama/gemini-live-demo
|
||||
cd gemini-live-demo
|
||||
cd gemini-live-demo/python
|
||||
```
|
||||
|
||||
And install the needed dependencies:
|
||||
@@ -36,6 +38,49 @@ Launch the application with
|
||||
uv run src/gemini_live_demo/main.py
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
### Install and Launch
|
||||
|
||||
Make sure to export your `GOOGLE_API_KEY` before running the demo.
|
||||
|
||||
```bash
|
||||
export GOOGLE_API_KEY="my-google-api-key"
|
||||
```
|
||||
|
||||
**User Set-Up**
|
||||
|
||||
```bash
|
||||
npx @cle-does-things/live-chat
|
||||
```
|
||||
|
||||
**Developer Set-Up**
|
||||
|
||||
Clone this repository locally:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/run-llama/gemini-live-demo
|
||||
cd gemini-live-demo/ts
|
||||
```
|
||||
|
||||
And install the needed dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Build the package:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Run the package:
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) to get started.
|
||||
|
||||
@@ -13,7 +13,10 @@ def messages() -> List[ChatMessage]:
|
||||
ChatMessage(role="user", blocks=[AudioBlock(audio=b"Hello")]),
|
||||
ChatMessage(
|
||||
role="assistant",
|
||||
blocks=[AudioBlock(audio=b"Hello back"), TextBlock(text="hello back")],
|
||||
blocks=[
|
||||
AudioBlock(audio=b"Hello back"),
|
||||
TextBlock(text="hello back"),
|
||||
],
|
||||
),
|
||||
ChatMessage(role="user", content="now what?"),
|
||||
]
|
||||
Generated
@@ -0,0 +1,15 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
||||
plugins: { js },
|
||||
extends: ["js/recommended"],
|
||||
languageOptions: { globals: globals.browser },
|
||||
},
|
||||
{ files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } },
|
||||
tseslint.configs.recommended,
|
||||
]);
|
||||
Generated
+3732
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@cle-does-things/live-chat",
|
||||
"version": "0.1.0",
|
||||
"description": "Demo for Gemini Live and LlamaIndexTS",
|
||||
"keywords": [
|
||||
"ai",
|
||||
"voice",
|
||||
"gemini",
|
||||
"live",
|
||||
"demo",
|
||||
"llm"
|
||||
],
|
||||
"homepage": "https://github.com/run-llama/gemini-live-demo#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/run-llama/gemini-live-demo/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/run-llama/gemini-live-demo.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Clelia Astra Bertelli",
|
||||
"type": "commonjs",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "npx tsx src/index.ts",
|
||||
"lint": "eslint src/",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc --watch",
|
||||
"dev": "npx tsx --watch src/index.ts",
|
||||
"prod": "npm run build && node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llamaindex/google": "^0.3.17",
|
||||
"consola": "^3.4.2",
|
||||
"oh-my-logo": "^0.2.1",
|
||||
"picocolors": "1.0.1",
|
||||
"speaker": "^0.5.5",
|
||||
"tsx": "^4.20.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
||||
"@typescript-eslint/parser": "^8.38.0",
|
||||
"eslint": "^9.32.0",
|
||||
"globals": "^16.3.0",
|
||||
"jiti": "^2.5.1",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.38.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { gemini, GEMINI_MODEL } from "@llamaindex/google";
|
||||
import { outputAudio, decodePCMData, consoleInput, renderLogo } from "./utils";
|
||||
import { logger } from "./logger";
|
||||
import pc from "picocolors";
|
||||
|
||||
export async function main(): Promise<number> {
|
||||
// Server-side: Generate ephemeral key
|
||||
const serverLlm = gemini({
|
||||
model: GEMINI_MODEL.GEMINI_2_0_FLASH_LIVE,
|
||||
httpOptions: { apiVersion: "v1alpha" },
|
||||
});
|
||||
const ephemeralKey = await serverLlm.live.getEphemeralKey();
|
||||
|
||||
// Client-side: Use ephemeral key for Live API
|
||||
const llm = gemini({
|
||||
apiKey: ephemeralKey,
|
||||
model: GEMINI_MODEL.GEMINI_2_0_FLASH_LIVE,
|
||||
voiceName: "Zephyr",
|
||||
httpOptions: { apiVersion: "v1alpha" },
|
||||
});
|
||||
|
||||
const session = await llm.live.connect();
|
||||
await renderLogo();
|
||||
logger.log(
|
||||
`Welcome to ${pc.bold(pc.cyan("✨Live Chat✨"))}, our demo for ${pc.bold(
|
||||
pc.magenta("Gemini Live🎙️"),
|
||||
)} and ${pc.bold(
|
||||
pc.magenta("LlamaIndexTS🦙"),
|
||||
)}.\nWrite messages to Gemini Live and wait for its answer in the chat below.\nIf you wish to exit, just type ${pc.bold(
|
||||
pc.gray("quit"),
|
||||
)}.\n`,
|
||||
);
|
||||
while (true) {
|
||||
const audioBuffers: Buffer[] = [];
|
||||
const userInput = await consoleInput();
|
||||
if (userInput == "quit") {
|
||||
break;
|
||||
}
|
||||
session.sendMessage({
|
||||
role: "user",
|
||||
content: userInput,
|
||||
});
|
||||
for await (const event of session.streamEvents()) {
|
||||
if (event.type == "audio") {
|
||||
audioBuffers.push(decodePCMData(event.data));
|
||||
} else if (event.type == "text") {
|
||||
logger.log(pc.bold(pc.magenta("Gemini:")), event.text);
|
||||
} else if (event.type == "turnComplete") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
outputAudio(Buffer.concat(audioBuffers));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createConsola } from "consola";
|
||||
|
||||
export const logger = createConsola({
|
||||
formatOptions: {
|
||||
date: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Readable } from "stream";
|
||||
import Speaker from "speaker";
|
||||
import * as readline from "readline/promises";
|
||||
import { renderFilled } from "oh-my-logo";
|
||||
|
||||
export async function renderLogo(): Promise<void> {
|
||||
const logo = await renderFilled("LIVE CHAT", {
|
||||
palette: ["#F8E9D8", "#FFA6EA", "#45DFF8", "#BB8DEB"],
|
||||
});
|
||||
console.log(logo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays raw PCM audio (16-bit LE, mono, 24000Hz) through the system speaker.
|
||||
* @param pcmBuffer - A Buffer of raw PCM data (Int16LE, mono, 24000 Hz).
|
||||
*/
|
||||
export function outputAudio(pcmBuffer: Buffer): void {
|
||||
const speaker = new Speaker({
|
||||
channels: 1,
|
||||
bitDepth: 16,
|
||||
sampleRate: 24000,
|
||||
});
|
||||
|
||||
const readable = new Readable();
|
||||
readable.push(pcmBuffer);
|
||||
readable.push(null); // Signal end of stream
|
||||
readable.pipe(speaker);
|
||||
}
|
||||
|
||||
export function decodePCMData(dataLine: string): Buffer {
|
||||
// Decode base64 string into a Buffer
|
||||
return Buffer.from(dataLine, "base64");
|
||||
}
|
||||
|
||||
export async function consoleInput(): Promise<string> {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const answer = await rl.question("Your message to Gemini: ");
|
||||
rl.close();
|
||||
return answer;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
|
||||
/* Module Resolution */
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
/* Emit */
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"removeComments": false,
|
||||
"importHelpers": true,
|
||||
|
||||
/* Advanced */
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
|
||||
"ts-node": {
|
||||
"esm": false,
|
||||
"experimentalSpecifierResolution": "node"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user