mirror of
https://github.com/tcsenpai/PerplexiBot.git
synced 2025-06-04 10:20:06 +00:00
initial commit
This commit is contained in:
commit
1e45da611f
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
||||
PPLX_API_KEY="pplx-yourkey"
|
178
.gitignore
vendored
Normal file
178
.gitignore
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
bun.lockdb
|
||||
config.js
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# tcsenpplx
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.0.30. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
4
context.json.example
Normal file
4
context.json.example
Normal file
@ -0,0 +1,4 @@
|
||||
[
|
||||
{ "role": "user", "content": "*enters the room*" },
|
||||
{ "role": "assistant", "content": "Hi! " }
|
||||
]
|
74
index.ts
Normal file
74
index.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import Perplexity from "./libs/Perplexity";
|
||||
import "dotenv/config";
|
||||
import required from "./libs/required";
|
||||
import readin from "./libs/readin";
|
||||
import fs from "fs"
|
||||
import parseCommands from "./libs/parseCommands";
|
||||
import config from "./config"
|
||||
import type Message from "./libs/Perplexity"
|
||||
import type { ListFormat } from "typescript";
|
||||
|
||||
required(process.env.PPLX_API_KEY);
|
||||
const PPLX_API_KEY = process.env.PPLX_API_KEY as string;
|
||||
let perplexity: Perplexity;
|
||||
|
||||
// NOTE Helper to load context from file
|
||||
function inject_context() {
|
||||
let loaded_context = fs.readFileSync("./context.json", {encoding: "utf-8"})
|
||||
let list_context = JSON.parse(loaded_context)
|
||||
for (let i=0; i<list_context.length; i++) {
|
||||
perplexity.add_to_context(list_context[i])
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE Helper to save context long term
|
||||
function save_in_context(role: string, message: string) {
|
||||
let loaded_context = fs.readFileSync("./context.json", {encoding: "utf-8"})
|
||||
let list_context = JSON.parse(loaded_context) as Array<{"role": string, "content": string}>
|
||||
let new_insertion = {
|
||||
role: role,
|
||||
content: message
|
||||
}
|
||||
list_context.push(new_insertion)
|
||||
fs.writeFileSync("./context.json", JSON.stringify(list_context))
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("[TCSenPPLX] Working with: " + PPLX_API_KEY);
|
||||
// Connecting to perplexity
|
||||
perplexity = new Perplexity(
|
||||
PPLX_API_KEY,
|
||||
"https://api.perplexity.ai",
|
||||
"sonar-medium-chat",
|
||||
false,
|
||||
{}
|
||||
);
|
||||
// Setting our ai personality
|
||||
perplexity.add_to_context(config);
|
||||
// If any, inject context
|
||||
inject_context()
|
||||
// Chatting
|
||||
await chat();
|
||||
}
|
||||
|
||||
async function chat(loop: boolean = true, save: boolean = true) {
|
||||
var proceed = true;
|
||||
while (proceed) {
|
||||
proceed = loop;
|
||||
let question = await readin("You: ");
|
||||
let parsed = parseCommands(question);
|
||||
proceed = parsed.proceed
|
||||
let logpart = parsed.logpart
|
||||
let response = await perplexity.ask(question);
|
||||
console.log("Assistant: " + response);
|
||||
if (save) {
|
||||
save_in_context("user", question)
|
||||
if (logpart) {
|
||||
response += +"\n[" + logpart + "]"
|
||||
}
|
||||
save_in_context("assistant", response as string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
106
libs/Perplexity.ts
Normal file
106
libs/Perplexity.ts
Normal file
@ -0,0 +1,106 @@
|
||||
// Wrapper class for easy tasking with Perplexity (and OpenAI in general)
|
||||
|
||||
import OpenAI from "openai";
|
||||
import type { RequestOptions } from "openai/core.mjs";
|
||||
|
||||
interface Message {
|
||||
role: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
messages: Message[];
|
||||
}
|
||||
|
||||
export default class Perplexity {
|
||||
API_KEY: string;
|
||||
instance: OpenAI;
|
||||
current_model: string;
|
||||
verbose: boolean;
|
||||
|
||||
// Instance vars
|
||||
roles = {
|
||||
a: "system",
|
||||
b: "user",
|
||||
c: "assistant",
|
||||
};
|
||||
context: Context = { messages: [] };
|
||||
options: RequestOptions = {}
|
||||
|
||||
constructor(
|
||||
api_key: string,
|
||||
base_url: string = "https://api.perplexity.ai",
|
||||
base_model: string = "sonar-small-chat",
|
||||
verbose: boolean = false,
|
||||
options: RequestOptions = {}
|
||||
) {
|
||||
// Setting our API Key
|
||||
this.API_KEY = api_key;
|
||||
this.current_model = base_model;
|
||||
// Setting verbosity
|
||||
this.verbose = verbose;
|
||||
// Setting options
|
||||
this.options = options;
|
||||
// Creating an openai agent
|
||||
this.instance = new OpenAI({
|
||||
baseURL: base_url,
|
||||
apiKey: this.API_KEY, // This is the default and can be omitted
|
||||
});
|
||||
}
|
||||
|
||||
// INFO Adding something to context
|
||||
add_to_context(message: Message) {
|
||||
// Validate roles
|
||||
if (
|
||||
message.role != this.roles.a &&
|
||||
message.role != this.roles.b &&
|
||||
message.role != this.roles.c
|
||||
) {
|
||||
console.log("[ERROR] Not added. Possible roles can be:");
|
||||
console.log(this.roles);
|
||||
return false;
|
||||
}
|
||||
this.context.messages.push(message);
|
||||
if(this.verbose) console.log("[OK] Added to context for: " + message.role);
|
||||
}
|
||||
|
||||
// INFO Setting a default model
|
||||
set_current_model(model: string) {
|
||||
this.current_model = model;
|
||||
}
|
||||
|
||||
// INFO Context viewer
|
||||
show_context() {
|
||||
console.log(this.context)
|
||||
}
|
||||
|
||||
// INFO Wrapper for asking
|
||||
async ask(question: string) {
|
||||
let insertion = {
|
||||
role: "user",
|
||||
content: question,
|
||||
};
|
||||
this.add_to_context(insertion);
|
||||
let response = await this.complete(false, this.options);
|
||||
this.add_to_context(response as Message);
|
||||
return response.content;
|
||||
}
|
||||
|
||||
// INFO Wrapper around completion
|
||||
async complete(override_model?: string | boolean, options: RequestOptions = {}) {
|
||||
if (this.verbose) console.log("[WORKING] Thinking...");
|
||||
let used_model = override_model ? override_model : this.current_model;
|
||||
const chatCompletion = await this.instance.chat.completions.create(
|
||||
{
|
||||
messages: this.context.messages as [],
|
||||
model: used_model as string,
|
||||
stream: false,
|
||||
},
|
||||
options
|
||||
);
|
||||
let return_prompt = chatCompletion.choices[0].message;
|
||||
return return_prompt;
|
||||
}
|
||||
|
||||
// TODO Support streams if needed
|
||||
}
|
25
libs/parseCommands.ts
Normal file
25
libs/parseCommands.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export interface ContextPart {
|
||||
role: string
|
||||
content: string
|
||||
}
|
||||
|
||||
export default function parseCommands(input: string): {proceed: boolean, logpart: string | null} {
|
||||
let proceed = true
|
||||
let logpart = null
|
||||
switch (input) {
|
||||
case "exit":
|
||||
case "quit":
|
||||
case "stop":
|
||||
case "end":
|
||||
case "bye":
|
||||
proceed = false
|
||||
logpart = "Last conversation ended at: " + String(Date.now())
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return {
|
||||
proceed: proceed,
|
||||
logpart: logpart
|
||||
}
|
||||
}
|
11
libs/readin.ts
Normal file
11
libs/readin.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import * as readline from "readline/promises";
|
||||
|
||||
export default async function readin(q: string = ""): Promise<string> {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
let answer = await rl.question(q);
|
||||
rl.close();
|
||||
return answer;
|
||||
}
|
15
libs/required.ts
Normal file
15
libs/required.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export default function required(
|
||||
condition: any,
|
||||
message: string = "Oops! Something went wrong!",
|
||||
fatal: boolean = true,
|
||||
exitcode: number = -1
|
||||
) {
|
||||
if (condition) return
|
||||
|
||||
if (fatal) {
|
||||
console.log("[FATAL] " + message);
|
||||
process.exit(exitcode);
|
||||
} else {
|
||||
console.log("[ERROR] " + message);
|
||||
}
|
||||
}
|
15
package.json
Normal file
15
package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "tcsenpplx",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.4.5",
|
||||
"openai": "^4.29.2"
|
||||
}
|
||||
}
|
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user