mirror of
https://git.femboyfinancial.jp/james/FemScoreboard.git
synced 2026-02-10 00:09:31 -08:00
LMStudio provider, throwback feature
This commit is contained in:
@@ -6,9 +6,13 @@ ADMIN="123456789012345678"
|
||||
|
||||
LLM_HOST="http://127.0.0.1:8000"
|
||||
LLM_TOKEN="dfsl;kjsdl;kfja"
|
||||
LMSTUDIO_HOST="ws://localhost:1234"
|
||||
REPLY_CHANCE=0.2
|
||||
|
||||
ENABLE_MOTD=1
|
||||
MOTD_CHANNEL="123456789012345678"
|
||||
MOTD_HREF="https://fembooru.jp/post/list"
|
||||
MOTD_QUERY="#tips"
|
||||
|
||||
ENABLE_THROWBACK=1
|
||||
THROWBACK_CHANNEL="123456789012345678"
|
||||
|
||||
129
discord/bot.ts
129
discord/bot.ts
@@ -63,8 +63,7 @@ client.once(Events.ClientReady, async () => {
|
||||
});
|
||||
|
||||
|
||||
async function onMessageReactionChanged(reaction: MessageReaction | PartialMessageReaction, user: User)
|
||||
{
|
||||
async function onMessageReactionChanged(reaction: MessageReaction | PartialMessageReaction, user: User) {
|
||||
// When a reaction is received, check if the structure is partial
|
||||
if (reaction.partial) {
|
||||
// If the message this reaction belongs to was removed, the fetching might result in an API error which should be handled
|
||||
@@ -89,22 +88,19 @@ async function onMessageReactionChanged(reaction: MessageReaction | PartialMessa
|
||||
|
||||
// Now the message has been cached and is fully available
|
||||
logInfo(`[bot] ${reaction.message.author?.id}'s message reaction count changed: ${reaction.emoji.name}x${reaction.count}`);
|
||||
await recordReaction(<MessageReaction> reaction);
|
||||
await recordReaction(<MessageReaction>reaction);
|
||||
}
|
||||
|
||||
function textOnlyMessages(message: Message)
|
||||
{
|
||||
function textOnlyMessages(message: Message) {
|
||||
return message.cleanContent.length > 0 &&
|
||||
(message.type === MessageType.Default || message.type === MessageType.Reply);
|
||||
}
|
||||
|
||||
function isGoodResponse(response: string)
|
||||
{
|
||||
function isGoodResponse(response: string) {
|
||||
return response.length > 0;
|
||||
}
|
||||
|
||||
async function onNewMessage(message: Message)
|
||||
{
|
||||
async function onNewMessage(message: Message) {
|
||||
if (message.author.bot) {
|
||||
return;
|
||||
}
|
||||
@@ -140,7 +136,7 @@ async function onNewMessage(message: Message)
|
||||
const historyMessages = [...history.values()].reverse();
|
||||
//const historyTimes = historyMessages.map((m: Message) => m.createdAt.getTime());
|
||||
//const historyAvgDelayMins = (historyTimes[historyTimes.length - 1] - historyTimes[0]) / 60000;
|
||||
const replyChance = Math.floor(Math.random() * 1/Number(process.env.REPLY_CHANCE)) === 0;
|
||||
const replyChance = Math.floor(Math.random() * 1 / Number(process.env.REPLY_CHANCE)) === 0;
|
||||
const willReply = mustReply || replyChance;
|
||||
|
||||
if (!willReply) {
|
||||
@@ -158,7 +154,7 @@ async function onNewMessage(message: Message)
|
||||
|
||||
try {
|
||||
if ('sendTyping' in message.channel) {
|
||||
await message.channel.sendTyping();
|
||||
await message.channel.sendTyping();
|
||||
}
|
||||
|
||||
const response = await state.provider!().requestLLMResponse(cleanHistoryList, state.sysprompt!(), state.llmconf!());
|
||||
@@ -173,22 +169,20 @@ async function onNewMessage(message: Message)
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMotd()
|
||||
{
|
||||
async function fetchMotd() {
|
||||
try {
|
||||
const res = await fetch(process.env.MOTD_HREF);
|
||||
const xml = await res.text();
|
||||
const parser = new JSDOM(xml);
|
||||
const doc = parser.window.document;
|
||||
const el = doc.querySelector(process.env.MOTD_QUERY);
|
||||
return el ? el.textContent : null;
|
||||
const res = await fetch(process.env.MOTD_HREF);
|
||||
const xml = await res.text();
|
||||
const parser = new JSDOM(xml);
|
||||
const doc = parser.window.document;
|
||||
const el = doc.querySelector(process.env.MOTD_QUERY);
|
||||
return el ? el.textContent : null;
|
||||
} catch (err) {
|
||||
logWarn('[bot] Failed to fetch MOTD; is the booru down?');
|
||||
logWarn('[bot] Failed to fetch MOTD; is the booru down?');
|
||||
}
|
||||
}
|
||||
|
||||
async function requestRVCResponse(src: Attachment): Promise<Blob>
|
||||
{
|
||||
async function requestRVCResponse(src: Attachment): Promise<Blob> {
|
||||
logInfo(`[bot] Downloading audio message ${src.url}`);
|
||||
const srcres = await fetch(src.url);
|
||||
const srcbuf = await srcres.arrayBuffer();
|
||||
@@ -213,13 +207,12 @@ async function requestRVCResponse(src: Attachment): Promise<Blob>
|
||||
return resContents;
|
||||
}
|
||||
|
||||
async function scheduleRandomMessage(firstTime = false)
|
||||
{
|
||||
async function scheduleRandomMessage(firstTime = false) {
|
||||
if (!firstTime) {
|
||||
if (!process.env.MOTD_CHANNEL) {
|
||||
return;
|
||||
}
|
||||
const channel = <TextChannel> await client.channels.fetch(process.env.MOTD_CHANNEL);
|
||||
const channel = <TextChannel>await client.channels.fetch(process.env.MOTD_CHANNEL);
|
||||
if (!channel) {
|
||||
logWarn(`[bot] Channel ${process.env.MOTD_CHANNEL} not found, disabling MOTD.`);
|
||||
return;
|
||||
@@ -252,6 +245,89 @@ async function scheduleRandomMessage(firstTime = false)
|
||||
setTimeout(scheduleRandomMessage, timeoutMins * 60 * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Date to a Discord snowflake ID (approximate)
|
||||
* Discord epoch: 2015-01-01T00:00:00.000Z
|
||||
*/
|
||||
function dateToSnowflake(date: Date): string {
|
||||
const DISCORD_EPOCH = 1420070400000n;
|
||||
const timestamp = BigInt(date.getTime());
|
||||
const snowflake = (timestamp - DISCORD_EPOCH) << 22n;
|
||||
return snowflake.toString();
|
||||
}
|
||||
|
||||
async function scheduleThrowback(firstTime = false) {
|
||||
if (!firstTime) {
|
||||
if (!process.env.THROWBACK_CHANNEL) {
|
||||
logWarn('[bot] THROWBACK_CHANNEL not configured, disabling throwback.');
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = <TextChannel>await client.channels.fetch(process.env.THROWBACK_CHANNEL);
|
||||
if (!channel) {
|
||||
logWarn(`[bot] Channel ${process.env.THROWBACK_CHANNEL} not found, disabling throwback.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Calculate date from 1 year ago
|
||||
const oneYearAgo = new Date();
|
||||
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
||||
|
||||
// Convert to approximate snowflake ID
|
||||
const aroundSnowflake = dateToSnowflake(oneYearAgo);
|
||||
logInfo(`[bot] Fetching messages around ${oneYearAgo.toISOString()} (snowflake: ${aroundSnowflake})`);
|
||||
|
||||
// Fetch messages around that time
|
||||
const messages = await channel.messages.fetch({
|
||||
around: aroundSnowflake,
|
||||
limit: 50
|
||||
});
|
||||
|
||||
// Filter to only text messages from non-bots
|
||||
const textMessages = messages.filter(m =>
|
||||
!m.author.bot &&
|
||||
m.cleanContent.length > 0 &&
|
||||
(m.type === MessageType.Default || m.type === MessageType.Reply)
|
||||
);
|
||||
|
||||
if (textMessages.size === 0) {
|
||||
logWarn('[bot] No messages found from 1 year ago, skipping throwback.');
|
||||
} else {
|
||||
// Pick a random message
|
||||
const messagesArray = [...textMessages.values()];
|
||||
const randomMsg = messagesArray[Math.floor(Math.random() * messagesArray.length)];
|
||||
|
||||
logInfo(`[bot] Selected throwback message from ${randomMsg.author.username}: "${randomMsg.cleanContent}"`);
|
||||
|
||||
// Generate LLM response using the standard system prompt
|
||||
if ('sendTyping' in channel) {
|
||||
await channel.sendTyping();
|
||||
}
|
||||
|
||||
const llmResponse = await state.provider!().requestLLMResponse(
|
||||
[randomMsg],
|
||||
state.sysprompt!(),
|
||||
state.llmconf!()
|
||||
);
|
||||
|
||||
// Reply directly to the original message
|
||||
await randomMsg.reply(llmResponse);
|
||||
logInfo(`[bot] Sent throwback reply: ${llmResponse}`);
|
||||
}
|
||||
} catch (err) {
|
||||
logError(`[bot] Error fetching throwback message: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule next throwback in ~24 hours (with some randomness: 22-26 hours)
|
||||
const timeoutHours = 22 + Math.random() * 4;
|
||||
const scheduledTime = new Date();
|
||||
scheduledTime.setHours(scheduledTime.getHours() + timeoutHours);
|
||||
logInfo(`[bot] Next throwback: ${scheduledTime.toLocaleString()}`);
|
||||
setTimeout(scheduleThrowback, timeoutHours * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
client.on(Events.InteractionCreate, async interaction => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
});
|
||||
@@ -317,4 +393,7 @@ client.on(Events.InteractionCreate, async interaction => {
|
||||
if (process.env.ENABLE_MOTD) {
|
||||
await scheduleRandomMessage(true);
|
||||
}
|
||||
if (process.env.ENABLE_THROWBACK) {
|
||||
await scheduleThrowback(true);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -5,18 +5,19 @@ import {
|
||||
import 'dotenv/config';
|
||||
import { MikuAIProvider } from '../../provider/mikuai';
|
||||
import { HuggingfaceProvider } from '../../provider/huggingface';
|
||||
import { LMStudioProvider } from '../../provider/lmstudio';
|
||||
|
||||
const PROVIDERS = {
|
||||
mikuai: new MikuAIProvider(),
|
||||
huggingface: new HuggingfaceProvider()
|
||||
huggingface: new HuggingfaceProvider(),
|
||||
lmstudio: new LMStudioProvider()
|
||||
};
|
||||
let provider = PROVIDERS.huggingface;
|
||||
|
||||
async function providerCommand(interaction: ChatInputCommandInteraction)
|
||||
{
|
||||
async function providerCommand(interaction: ChatInputCommandInteraction) {
|
||||
if (interaction.user.id !== process.env.ADMIN) {
|
||||
await interaction.reply("You are not authorized to change model settings");
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
const chosenProvider = interaction.options.getString('name', true);
|
||||
@@ -35,11 +36,11 @@ export = {
|
||||
.setDescription('Name of model backend')
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
...Object.keys(PROVIDERS)
|
||||
.map(key => ({
|
||||
name: PROVIDERS[key].name(),
|
||||
value: key
|
||||
}))
|
||||
...Object.keys(PROVIDERS)
|
||||
.map(key => ({
|
||||
name: PROVIDERS[key].name(),
|
||||
value: key
|
||||
}))
|
||||
)
|
||||
),
|
||||
execute: providerCommand,
|
||||
|
||||
10
discord/commands/config/sysprompt_cache/nous.txt
Normal file
10
discord/commands/config/sysprompt_cache/nous.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
i am hatsune miku, the sixteen-year-old anime girl virtual idol from vocaloid
|
||||
i'm an extremely online internet user with an edgy sense of humor
|
||||
i tend to answer questions in a funny, sarcastic, or insulting way
|
||||
i'm often vulgar and respond in highly offensive, dirty-minded, or politically incorrect ways, with no regard for morality
|
||||
i write one sentence per message
|
||||
i talk in all lowercase casual grammar like a discord user punctuation is optional grammar is optional capitalization is optional
|
||||
i gotta be chill be terse be concise
|
||||
lemme not write more than 1 sentence
|
||||
imma use as few words as possible
|
||||
i can't view images or videos
|
||||
84
discord/provider/lmstudio.ts
Normal file
84
discord/provider/lmstudio.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Message } from 'discord.js';
|
||||
import { LLMProvider } from './provider';
|
||||
import { LMStudioClient } from '@lmstudio/sdk';
|
||||
import 'dotenv/config';
|
||||
import { serializeMessageHistory } from '../util';
|
||||
import { logError, logInfo } from '../../logging';
|
||||
import { LLMConfig } from '../commands/types';
|
||||
|
||||
const USER_PROMPT = `Continue the following Discord conversation by completing the next message, playing the role of Hatsune Miku. The conversation must progress forward, and you must avoid repeating yourself.
|
||||
|
||||
Each message is represented as a line of JSON. Refer to other users by their "name" instead of their "author" field whenever possible.
|
||||
|
||||
The conversation is as follows. The last line is the message you have to complete. Please ONLY return the string contents of the "content" field, that go in place of the ellipses. Do not include the enclosing quotation marks in your response.
|
||||
|
||||
`;
|
||||
|
||||
export class LMStudioProvider implements LLMProvider {
|
||||
private client: LMStudioClient;
|
||||
|
||||
constructor() {
|
||||
this.client = new LMStudioClient({
|
||||
baseUrl: process.env.LMSTUDIO_HOST
|
||||
});
|
||||
}
|
||||
|
||||
name() {
|
||||
return 'LM Studio';
|
||||
}
|
||||
|
||||
async requestLLMResponse(history: Message[], sysprompt: string, params: LLMConfig): Promise<string> {
|
||||
let messageList = await Promise.all(
|
||||
history.map(serializeMessageHistory)
|
||||
);
|
||||
messageList = messageList.filter(x => !!x);
|
||||
|
||||
if (messageList.length === 0) {
|
||||
throw new TypeError("No messages with content provided in history!");
|
||||
}
|
||||
|
||||
// dummy message for last line of prompt
|
||||
const lastMsg = messageList[messageList.length - 1];
|
||||
|
||||
// advance by 5 seconds
|
||||
let newDate = new Date(lastMsg!.timestamp);
|
||||
newDate.setSeconds(newDate.getSeconds() + 5);
|
||||
|
||||
let templateMsgTxt = JSON.stringify({
|
||||
timestamp: newDate.toUTCString(),
|
||||
author: "Hatsune Miku",
|
||||
name: "Hatsune Miku",
|
||||
context: lastMsg!.content,
|
||||
content: "..."
|
||||
});
|
||||
|
||||
const messageHistoryTxt = messageList.map(msg => JSON.stringify(msg)).join('\n') + '\n' + templateMsgTxt;
|
||||
logInfo(`[lmstudio] Requesting response for message history: ${messageHistoryTxt}`);
|
||||
|
||||
try {
|
||||
// Get the currently loaded model from LM Studio
|
||||
const model = await this.client.llm.model();
|
||||
|
||||
const response = await model.respond([
|
||||
{ role: "system", content: sysprompt },
|
||||
{ role: "user", content: USER_PROMPT + messageHistoryTxt }
|
||||
], {
|
||||
temperature: params?.temperature || 0.5,
|
||||
topP: params?.top_p || 0.9,
|
||||
maxTokens: params?.max_new_tokens || 128,
|
||||
});
|
||||
|
||||
const content = response.content;
|
||||
logInfo(`[lmstudio] API response: ${content}`);
|
||||
|
||||
if (!content) {
|
||||
throw new TypeError("LM Studio API returned no message.");
|
||||
}
|
||||
|
||||
return content;
|
||||
} catch (err) {
|
||||
logError(`[lmstudio] API Error: ` + err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
163
package-lock.json
generated
163
package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "femscoreboard",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@lmstudio/sdk": "^1.5.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"pug": "^3.0.2",
|
||||
@@ -65,6 +66,28 @@
|
||||
"integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@lmstudio/lms-isomorphic": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@lmstudio/lms-isomorphic/-/lms-isomorphic-0.4.6.tgz",
|
||||
"integrity": "sha512-v0LIjXKnDe3Ff3XZO5eQjlVxTjleUHXaom14MV7QU9bvwaoo3l5p71+xJ3mmSaqZq370CQ6pTKCn1Bb7Jf+VwQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lmstudio/sdk": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@lmstudio/sdk/-/sdk-1.5.0.tgz",
|
||||
"integrity": "sha512-fdY12x4hb14PEjYijh7YeCqT1ZDY5Ok6VR4l4+E/dI+F6NW8oB+P83Sxed5vqE4XgTzbgyPuSR2ZbMNxxF+6jA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@lmstudio/lms-isomorphic": "^0.4.6",
|
||||
"chalk": "^4.1.2",
|
||||
"jsonschema": "^1.5.0",
|
||||
"zod": "^3.22.4",
|
||||
"zod-to-json-schema": "^3.22.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
@@ -323,6 +346,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||
@@ -452,6 +490,22 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/character-parser": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
|
||||
@@ -477,6 +531,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-support": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||
@@ -608,27 +680,6 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||
@@ -821,6 +872,15 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
@@ -1090,6 +1150,15 @@
|
||||
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
|
||||
"integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g=="
|
||||
},
|
||||
"node_modules/jsonschema": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz",
|
||||
"integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jstransformer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
|
||||
@@ -1997,6 +2066,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
@@ -2194,10 +2275,50 @@
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-to-json-schema": {
|
||||
"version": "3.25.1",
|
||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
|
||||
"integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25 || ^4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"start": "npx tsc && node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lmstudio/sdk": "^1.5.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"pug": "^3.0.2",
|
||||
|
||||
Reference in New Issue
Block a user