refactor: 重构项目结构和代码组织

This commit is contained in:
2026-04-23 23:40:28 +08:00
parent 481555c257
commit ab860c61d9
165 changed files with 51 additions and 17248 deletions

View File

@@ -1,6 +0,0 @@
node_modules
dist
.env
.git
.idea
.vscode

View File

@@ -1,57 +0,0 @@
# =====================================================
# Server Configuration
# =====================================================
NODE_ENV=development
PORT=3000
API_PREFIX=/api
# =====================================================
# Database Configuration (File-based, no DB server needed)
# =====================================================
DB_TYPE=better-sqlite3
DB_DATABASE=./data/db/app.db
# =====================================================
# JWT Authentication
# =====================================================
JWT_SECRET=change-this-to-a-random-string-at-least-32-chars
JWT_EXPIRES_IN=7d
JWT_REFRESH_SECRET=change-this-to-another-random-string
JWT_REFRESH_EXPIRES_IN=30d
# =====================================================
# LLM Provider API Keys (Backend Only - Secure)
# =====================================================
OPENAI_API_KEY=sk-your-openai-api-key-here
ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-here
GOOGLE_API_KEY=your-google-api-key-here
LOCAL_LLM_URL=http://localhost:11434
# =====================================================
# File Storage
# =====================================================
UPLOAD_DIR=./data/files
MAX_FILE_SIZE=10485760
# =====================================================
# CORS Configuration
# =====================================================
CORS_ORIGIN=http://localhost:23338
# =====================================================
# Rate Limiting
# =====================================================
RATE_LIMIT_TTL=60
RATE_LIMIT_MAX=100
# =====================================================
# Workflow Configuration
# =====================================================
WORKFLOW_MAX_PARALLEL=5
WORKFLOW_TIMEOUT=300000
# =====================================================
# Logging
# =====================================================
LOG_LEVEL=debug
LOG_DIR=./data/logs

View File

@@ -1,4 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all"
}

View File

@@ -1,24 +0,0 @@
# Development Dockerfile for Backend (NestJS)
FROM node:20-alpine
# Install wget for healthcheck
RUN apk add --no-cache wget
# Set working directory
WORKDIR /usr/src/app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy tsconfig and other config files
COPY tsconfig*.json ./
COPY nest-cli.json ./
# Expose port
EXPOSE 3000
# Default command (will be overridden by docker-compose)
CMD ["npm", "run", "start:dev"]

View File

@@ -1,98 +0,0 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Project setup
```bash
$ npm install
```
## Compile and run the project
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Run tests
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Deployment
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
$ npm install -g @nestjs/mau
$ mau deploy
```
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
## Resources
Check out a few resources that may come in handy when working with NestJS:
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

View File

@@ -1,35 +0,0 @@
// @ts-check
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: ['eslint.config.mjs'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
"prettier/prettier": ["error", { endOfLine: "auto" }],
},
},
);

View File

@@ -1,8 +0,0 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

9906
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,71 +0,0 @@
{
"name": "server",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^11.0.1",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.0",
"@types/supertest": "^7.0.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^17.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -1,13 +0,0 @@
// server/src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // 全局可用,无需在每个模块导入
envFilePath: '.env', // 指定 .env 文件路径
}),
],
})
export class AppModule {}

View File

View File

View File

View File

View File

View File

View File

View File

@@ -1,10 +0,0 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors(); // Allow cross-origin
await app.listen(3000);
console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();

View File

View File

View File

@@ -1,182 +0,0 @@
/**
* Chat Adapter
* Converts between SillyTavern JSONL format and internal format
*/
import type {
STChatMetadata,
STChatMessage,
STChatSession,
} from '@shared/sillytavern/chat.types';
import type {
ExtendedChatMetadata,
ExtendedChatMessage,
ExtendedChatSession,
DynamicHeader,
} from '@shared/extended/chat-ext.types';
import type { InternalChatSession } from '@shared/adapters/import-export.types';
export class ChatAdapter {
/**
* Import from SillyTavern JSONL format
*/
importFromST(jsonlContent: string): InternalChatSession {
const lines = jsonlContent.trim().split('\n');
if (lines.length === 0) {
throw new Error('Empty JSONL content');
}
// Parse metadata (first line)
const metadataLine = JSON.parse(lines[0]) as STChatMetadata;
const internalMetadata = this.convertMetadata(metadataLine);
// Parse messages (subsequent lines)
const messages: ExtendedChatMessage[] = [];
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim()) {
const stMessage = JSON.parse(lines[i]) as STChatMessage;
messages.push(this.convertMessage(stMessage));
}
}
return {
id: this.generateId(),
characterId: this.generateId(), // Will be set by caller
metadata: internalMetadata,
messages,
createdAt: new Date(metadataLine.create_date),
updatedAt: new Date(),
};
}
/**
* Export to SillyTavern JSONL format
*/
exportToST(chat: InternalChatSession): string {
const lines: string[] = [];
// Convert metadata to ST format
const stMetadata = this.convertMetadataToST(chat.metadata);
lines.push(JSON.stringify(stMetadata));
// Convert messages to ST format
for (const message of chat.messages) {
const stMessage = this.convertMessageToST(message);
lines.push(JSON.stringify(stMessage));
}
return lines.join('\n');
}
/**
* Parse JSONL to structured ST data
*/
parseJSONL(jsonlContent: string): STChatSession {
const lines = jsonlContent.trim().split('\n');
if (lines.length === 0) {
throw new Error('Empty JSONL content');
}
const metadata = JSON.parse(lines[0]) as STChatMetadata;
const messages: STChatMessage[] = [];
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim()) {
messages.push(JSON.parse(lines[i]) as STChatMessage);
}
}
return { metadata, messages };
}
/**
* Convert structured ST data to JSONL
*/
toJSONL(session: STChatSession): string {
const lines: string[] = [JSON.stringify(session.metadata)];
for (const message of session.messages) {
lines.push(JSON.stringify(message));
}
return lines.join('\n');
}
/**
* Convert ST metadata to internal metadata
*/
private convertMetadata(stMetadata: STChatMetadata): ExtendedChatMetadata {
return {
characterName: stMetadata.character_name,
dynamicHeaders: [], // Default empty, will be populated by user
characterDescription: '', // Will be set from character card
characterAvatar: undefined,
aiCharacterName: stMetadata.character_name,
creatorNotes: '',
firstMessages: [],
systemPrompt: '',
extensions: stMetadata.chat_metadata || {},
createdAt: new Date(stMetadata.create_date),
updatedAt: new Date(),
tags: [],
category: undefined,
};
}
/**
* Convert internal metadata to ST metadata
*/
private convertMetadataToST(metadata: ExtendedChatMetadata): STChatMetadata {
return {
user_name: 'User', // Default, can be customized
character_name: metadata.characterName,
create_date: metadata.createdAt.toISOString(),
chat_metadata: metadata.extensions,
};
}
/**
* Convert ST message to internal message
*/
private convertMessage(stMessage: STChatMessage): ExtendedChatMessage {
return {
senderName: stMessage.name,
senderRole: stMessage.is_user ? 'user' : 'assistant',
mes: stMessage.mes,
swipes: stMessage.extra?.swipes,
reasoning: stMessage.extra?.reasoning,
image: stMessage.extra?.image,
audio: stMessage.extra?.audio,
timestamp: new Date(stMessage.send_date),
tokens: undefined,
model: stMessage.extra?.model,
dynamicData: {},
};
}
/**
* Convert internal message to ST message
*/
private convertMessageToST(message: ExtendedChatMessage): STChatMessage {
return {
name: message.senderName,
is_user: message.senderRole === 'user',
send_date: message.timestamp.getTime(),
mes: message.mes,
extra: {
swipes: message.swipes,
reasoning: message.reasoning,
image: message.image,
audio: message.audio,
model: message.model,
},
};
}
/**
* Generate unique ID
*/
private generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}

View File

@@ -1,121 +0,0 @@
/**
* Adapter Usage Examples
* Demonstrates how to use the adapters for import/export
*/
import { ChatAdapter } from '../chat/adapters/chat.adapter';
import { WorldInfoAdapter } from '../world-info/adapters/world-info.adapter';
import { PresetAdapter } from '../preset/adapters/preset.adapter';
// ============================================
// Example 1: Chat Import/Export
// ============================================
export function chatExample() {
const adapter = new ChatAdapter();
// Sample SillyTavern JSONL content
const jsonlContent = `{"user_name":"User","character_name":"Assistant","create_date":"2024-01-01T12:00:00.000Z"}
{"name":"User","is_user":true,"send_date":1704115200000,"mes":"你好!"}
{"name":"Assistant","is_user":false,"send_date":1704115210000,"mes":"你好!有什么可以帮助你的吗?"}`;
// Import from ST format
const internalChat = adapter.importFromST(jsonlContent);
console.log('Imported chat:', internalChat.metadata.characterName);
console.log('Message count:', internalChat.messages.length);
// Export back to ST format
const exportedJsonl = adapter.exportToST(internalChat);
console.log('Exported JSONL length:', exportedJsonl.length);
}
// ============================================
// Example 2: World Info with Unified Triggers
// ============================================
export function worldInfoExample() {
const adapter = new WorldInfoAdapter();
// Sample ST World Info
const stWorldInfo = {
name: '奇幻世界',
description: '一个魔法世界',
entries: [
{
key: ['魔法', '法术'],
content: '这个世界充满了魔法能量。',
constant: false,
selective: false,
order: 1,
position: 'after_char' as const,
use_regex: false,
enabled: true,
},
{
key: [],
content: '这是一个永恒的背景设定。',
constant: true, // Permanent trigger
selective: false,
order: 0,
position: 'before_char' as const,
use_regex: false,
enabled: true,
},
],
};
// Import from ST format (converts to unified triggers)
const extendedWorldInfo = adapter.importFromST(stWorldInfo);
console.log('World Info:', extendedWorldInfo.name);
console.log('Entry 1 triggers:', extendedWorldInfo.entries[0].triggers);
// Export back to ST format (converts triggers back to keys + constant)
const exportedST = adapter.exportToST(extendedWorldInfo);
console.log('Exported entries:', exportedST.entries.length);
}
// ============================================
// Example 3: Preset with Position Anchors
// ============================================
export function presetExample() {
const adapter = new PresetAdapter();
// Sample ST Preset
const stPreset = {
name: '创意写作预设',
temp: 0.9,
top_p: 0.95,
top_k: 50,
rep_pen: 1.05,
maxContextTokens: 8192,
streaming: true,
description: '用于创意写作的预设配置',
};
// Import from ST format
const extendedPreset = adapter.importFromST(stPreset);
console.log('Preset:', extendedPreset.name);
console.log('Temperature:', extendedPreset.temperature);
console.log('Entries count:', extendedPreset.entries.length);
console.log('Entry positions:', extendedPreset.entries.map((e) => e.position));
// Export back to ST format
const exportedST = adapter.exportToST(extendedPreset);
console.log('Exported preset:', exportedST.name);
}
// ============================================
// Run examples
// ============================================
if (require.main === module) {
console.log('=== Chat Example ===');
chatExample();
console.log('\n=== World Info Example ===');
worldInfoExample();
console.log('\n=== Preset Example ===');
presetExample();
}

View File

View File

@@ -1,94 +0,0 @@
/**
* Preset Adapter
* Converts between SillyTavern preset format and extended format with position anchors
*/
import type { STPreset } from '@shared/sillytavern/preset.types';
import type { ExtendedPreset, PresetEntry, PresetPosition } from '@shared/extended/preset-ext.types';
export class PresetAdapter {
/**
* Import from SillyTavern format
*/
importFromST(stPreset: STPreset): ExtendedPreset {
return {
id: this.generateId(),
name: stPreset.name,
description: stPreset.description,
temperature: stPreset.temp || 0.7,
maxReplyLength: 500, // Default
topP: stPreset.top_p || 0.9,
topK: stPreset.top_k || 40,
repetitionPenalty: stPreset.rep_pen || 1.1,
entryOrder: [], // Will be populated by user
maxContextTokens: stPreset.maxContextTokens || 4096,
streaming: stPreset.streaming || false,
createdAt: new Date(stPreset.created_at || Date.now()),
updatedAt: new Date(stPreset.updated_at || Date.now()),
entries: this.createDefaultEntries(),
};
}
/**
* Export to SillyTavern format
*/
exportToST(extendedPreset: ExtendedPreset): STPreset {
return {
name: extendedPreset.name,
description: extendedPreset.description,
temp: extendedPreset.temperature,
top_p: extendedPreset.topP,
top_k: extendedPreset.topK,
rep_pen: extendedPreset.repetitionPenalty,
maxContextTokens: extendedPreset.maxContextTokens,
streaming: extendedPreset.streaming,
created_at: extendedPreset.createdAt.getTime(),
updated_at: extendedPreset.updatedAt.getTime(),
};
}
/**
* Create default preset entries with fixed positions
*/
private createDefaultEntries(): PresetEntry[] {
const positions: PresetPosition[] = [
'system',
'world_info_before',
'persona_description',
'char_description',
'world_info_after',
'history',
'user_input',
];
return positions.map((position, index) => ({
name: this.getPositionName(position),
context: '',
position,
order: index,
}));
}
/**
* Get human-readable name for position
*/
private getPositionName(position: PresetPosition): string {
const names: Record<PresetPosition, string> = {
system: '系统提示词',
world_info_before: '世界书(前置)',
persona_description: '角色设定描述',
char_description: '角色详细描述',
world_info_after: '世界书(后置)',
history: '聊天记录',
user_input: '用户输入',
};
return names[position];
}
/**
* Generate unique ID
*/
private generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}

View File

@@ -1,187 +0,0 @@
/**
* World Info Adapter
* Converts between SillyTavern format and extended format with unified triggers
*/
import type { STWorldInfo, STWorldInfoEntry } from '@shared/sillytavern/world.types';
import type {
ExtendedWorldInfo,
ExtendedWorldInfoEntry,
TriggerConfig,
} from '@shared/extended/world-ext.types';
export class WorldInfoAdapter {
/**
* Import from SillyTavern format
*/
importFromST(stWorldInfo: STWorldInfo): ExtendedWorldInfo {
return {
id: this.generateId(),
name: stWorldInfo.name,
description: stWorldInfo.description,
entries: stWorldInfo.entries.map((entry) => this.importEntryFromST(entry)),
metadata: {
entryCount: stWorldInfo.entries.length,
categories: [],
tags: [],
},
createdAt: new Date(),
updatedAt: new Date(),
};
}
/**
* Export to SillyTavern format
*/
exportToST(extendedWorldInfo: ExtendedWorldInfo): STWorldInfo {
return {
name: extendedWorldInfo.name,
description: extendedWorldInfo.description,
entries: extendedWorldInfo.entries.map((entry) => this.exportEntryToST(entry)),
metadata: {
created_at: extendedWorldInfo.createdAt.getTime(),
updated_at: extendedWorldInfo.updatedAt.getTime(),
},
};
}
/**
* Convert unified trigger config to ST format (keys + constant)
*/
triggersToST(triggers: TriggerConfig): { keys: string[]; constant: boolean } {
// If permanent trigger is enabled, set constant = true
if (triggers.permanent.enabled) {
return {
keys: [],
constant: true,
};
}
// Otherwise, use keyword triggers
if (triggers.keyword.enabled) {
return {
keys: triggers.keyword.keywords,
constant: false,
};
}
// Default: no triggers
return {
keys: [],
constant: false,
};
}
/**
* Convert ST format (keys + constant) to unified trigger config
*/
triggersFromST(keys: string[], constant: boolean): TriggerConfig {
if (constant) {
// Permanent trigger
return {
permanent: { enabled: true },
keyword: {
enabled: false,
keywords: [],
useRegex: false,
matchWholeWords: false,
caseSensitive: false,
},
rag: { enabled: false, databaseName: '' },
variable: {
enabled: false,
variableA: '',
operator: '=',
},
};
} else if (keys && keys.length > 0) {
// Keyword trigger
return {
permanent: { enabled: false },
keyword: {
enabled: true,
keywords: keys,
useRegex: false,
matchWholeWords: true,
caseSensitive: false,
},
rag: { enabled: false, databaseName: '' },
variable: {
enabled: false,
variableA: '',
operator: '=',
},
};
} else {
// No triggers
return {
permanent: { enabled: false },
keyword: {
enabled: false,
keywords: [],
useRegex: false,
matchWholeWords: false,
caseSensitive: false,
},
rag: { enabled: false, databaseName: '' },
variable: {
enabled: false,
variableA: '',
operator: '=',
},
};
}
}
/**
* Import single entry from ST format
*/
private importEntryFromST(stEntry: STWorldInfoEntry): ExtendedWorldInfoEntry {
const triggers = this.triggersFromST(stEntry.key, stEntry.constant);
return {
id: stEntry.uid?.toString() || this.generateId(),
content: stEntry.content,
position: stEntry.position,
scanDepth: stEntry.depth || stEntry.scan_depth || 4,
priority: stEntry.order,
triggers,
enabled: stEntry.enabled,
category: undefined,
tags: [],
usageCount: 0,
lastUsed: undefined,
};
}
/**
* Export single entry to ST format
*/
private exportEntryToST(entry: ExtendedWorldInfoEntry): STWorldInfoEntry {
const { keys, constant } = this.triggersToST(entry.triggers);
return {
uid: parseInt(entry.id) || undefined,
key: keys,
content: entry.content,
constant,
selective: entry.triggers.keyword.enabled && entry.triggers.keyword.keywords.length > 1,
order: entry.priority,
position: entry.position,
use_regex: entry.triggers.keyword.useRegex,
match_whole_words: entry.triggers.keyword.matchWholeWords,
case_sensitive: entry.triggers.keyword.caseSensitive,
depth: entry.scanDepth,
enabled: entry.enabled,
comment: undefined,
display_index: undefined,
};
}
/**
* Generate unique ID
*/
private generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}

View File

View File

View File

View File

View File

View File

View File

@@ -1,29 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { App } from 'supertest/types';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication<App>;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
afterEach(async () => {
await app.close();
});
});

View File

View File

View File

@@ -1,9 +0,0 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

View File

View File

@@ -1,4 +0,0 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@@ -1,34 +0,0 @@
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"resolvePackageJsonExports": true,
"esModuleInterop": true,
"isolatedModules": true,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2023",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": false,
"paths": {
"@shared/*": ["../shared/types/*"],
"@core/*": ["src/core/*"],
"@modules/*": ["src/modules/*"],
"@common/*": ["src/common/*"],
"@events/*": ["src/events/*"],
"@queues/*": ["src/queues/*"]
}
}
}