规定数据类型
This commit is contained in:
1
shared/constants/.gitkeep
Normal file
1
shared/constants/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
// Shared Constants and Enums
|
||||
14
shared/index.ts
Normal file
14
shared/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Shared Module
|
||||
* 前后端共享的类型定义、验证 schemas 和工具函数
|
||||
*/
|
||||
|
||||
// 类型定义
|
||||
export * as STTypes from './types/sillytavern.types';
|
||||
export * as InternalTypes from './types/internal.types';
|
||||
export * from './types';
|
||||
|
||||
// Zod Schemas
|
||||
export * as STSchemas from './schemas/sillytavern.schemas';
|
||||
export * as InternalSchemas from './schemas/internal.schemas';
|
||||
export * from './schemas';
|
||||
11
shared/package.json
Normal file
11
shared/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@sillytavern-repalice/shared",
|
||||
"version": "1.0.0",
|
||||
"description": "Shared types and schemas for SillyTavern Repalice",
|
||||
"main": "index.ts",
|
||||
"types": "index.ts",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
}
|
||||
1
shared/schemas/.gitkeep
Normal file
1
shared/schemas/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
// Zod Schemas for Validation (v3)
|
||||
6
shared/schemas/index.ts
Normal file
6
shared/schemas/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Shared Schemas 导出
|
||||
*/
|
||||
|
||||
export * from './sillytavern.schemas';
|
||||
export * from './internal.schemas';
|
||||
258
shared/schemas/internal.schemas.ts
Normal file
258
shared/schemas/internal.schemas.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* Zod Schemas - 项目内部格式验证
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// ==================== 世界书条目 ====================
|
||||
|
||||
export const ActivationTypeSchema = z.enum([
|
||||
'permanent',
|
||||
'keyword',
|
||||
'rag',
|
||||
'logic',
|
||||
]);
|
||||
|
||||
export const LogicOperatorSchema = z.enum([
|
||||
'equals',
|
||||
'not_equals',
|
||||
'contains',
|
||||
'not_contains',
|
||||
'greater',
|
||||
'less',
|
||||
]);
|
||||
|
||||
export const LogicExpressionSchema = z.object({
|
||||
variable1: z.string(),
|
||||
operator: LogicOperatorSchema,
|
||||
variable2: z.string(),
|
||||
});
|
||||
|
||||
export const RAGConfigSchema = z.object({
|
||||
libraryId: z.string(),
|
||||
threshold: z.number().min(0).max(1).optional(),
|
||||
maxEntries: z.number().min(1).optional(),
|
||||
});
|
||||
|
||||
export const WorldInfoEntrySchema = z.object({
|
||||
uid: z.string(),
|
||||
key: z.array(z.string()).optional(),
|
||||
keysecondary: z.array(z.string()).optional(),
|
||||
content: z.string(),
|
||||
order: z.number(),
|
||||
position: z.enum([
|
||||
'before_char',
|
||||
'after_char',
|
||||
'before_example',
|
||||
'after_example',
|
||||
'author_note_top',
|
||||
'author_note_bottom',
|
||||
'at_depth',
|
||||
]),
|
||||
depth: z.number().optional(),
|
||||
role: z.enum(['system', 'user', 'assistant']).optional(),
|
||||
probability: z.number().min(0).max(100).optional(),
|
||||
group: z.array(z.string()).optional(),
|
||||
groupPrioritize: z.boolean().optional(),
|
||||
useGroupScoring: z.boolean().optional(),
|
||||
automationId: z.string().optional(),
|
||||
disable: z.boolean().optional(),
|
||||
extensions: z.record(z.unknown()).optional(),
|
||||
|
||||
// 自定义字段
|
||||
activationType: ActivationTypeSchema,
|
||||
logicExpression: LogicExpressionSchema.optional(),
|
||||
ragConfig: RAGConfigSchema.optional(),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
});
|
||||
|
||||
export const WorldInfoSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
entries: z.array(WorldInfoEntrySchema),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
version: z.number(),
|
||||
});
|
||||
|
||||
// ==================== 角色卡 ====================
|
||||
|
||||
export const OutputSchemaFieldTypeSchema = z.enum([
|
||||
'string',
|
||||
'number',
|
||||
'boolean',
|
||||
'array',
|
||||
'object',
|
||||
]);
|
||||
|
||||
export const OutputSchemaFieldSchema: z.ZodType<any> = z.lazy(() =>
|
||||
z.object({
|
||||
name: z.string(),
|
||||
type: OutputSchemaFieldTypeSchema,
|
||||
description: z.string(),
|
||||
required: z.boolean().optional(),
|
||||
enum: z.array(z.string()).optional(),
|
||||
fields: z.array(OutputSchemaFieldSchema).optional(),
|
||||
})
|
||||
);
|
||||
|
||||
export const CharacterCardSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
personality: z.string(),
|
||||
scenario: z.string(),
|
||||
first_mes: z.string(),
|
||||
mes_example: z.string(),
|
||||
alternate_greetings: z.array(z.string()).optional(),
|
||||
creator_notes: z.string().optional(),
|
||||
system_prompt: z.string().optional(),
|
||||
post_history_instructions: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
|
||||
// 自定义字段
|
||||
categories: z.array(z.string()),
|
||||
worldInfoId: z.string().optional(),
|
||||
outputSchema: z.array(OutputSchemaFieldSchema).optional(),
|
||||
avatarPath: z.string().optional(),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
lastChatAt: z.number().optional(),
|
||||
isFavorite: z.boolean(),
|
||||
version: z.number(),
|
||||
});
|
||||
|
||||
// ==================== 聊天记录 ====================
|
||||
|
||||
export const ChatHeaderSchema = z.object({
|
||||
id: z.string(),
|
||||
displayName: z.string(),
|
||||
characterId: z.string(),
|
||||
userName: z.string(),
|
||||
characterName: z.string(),
|
||||
tableData: z.record(z.unknown()).optional(),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
messageCount: z.number(),
|
||||
});
|
||||
|
||||
export const ChatMessageSchema = z.object({
|
||||
id: z.string(),
|
||||
chatId: z.string(),
|
||||
name: z.string(),
|
||||
is_user: z.boolean(),
|
||||
is_system: z.boolean().optional(),
|
||||
sendDate: z.string(),
|
||||
mes: z.string(),
|
||||
swipes: z.array(z.string()).optional(),
|
||||
swipe_id: z.number().optional(),
|
||||
is_hidden: z.boolean().optional(),
|
||||
extra: z.record(z.unknown()).optional(),
|
||||
tokenCount: z.number().optional(),
|
||||
isTemporary: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const ChatLogSchema = z.object({
|
||||
header: ChatHeaderSchema,
|
||||
messages: z.array(ChatMessageSchema),
|
||||
});
|
||||
|
||||
// ==================== 预设 ====================
|
||||
|
||||
export const GenerationPresetSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
temperature: z.number(),
|
||||
topP: z.number(),
|
||||
topK: z.number(),
|
||||
repetitionPenalty: z.number(),
|
||||
frequencyPenalty: z.number().optional(),
|
||||
presencePenalty: z.number().optional(),
|
||||
maxLength: z.number().optional(),
|
||||
isDefault: z.boolean(),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
});
|
||||
|
||||
// ==================== 提示词预设 (Prompt Preset) ====================
|
||||
|
||||
export const PromptRoleSchema = z.enum(['system', 'ai', 'user']);
|
||||
|
||||
export const PromptEntrySchema = z.object({
|
||||
identifier: z.string(),
|
||||
name: z.string(),
|
||||
enabled: z.boolean(),
|
||||
content: z.string(),
|
||||
order: z.number(),
|
||||
role: PromptRoleSchema,
|
||||
tokenCount: z.number(),
|
||||
isSystemNode: z.boolean(),
|
||||
});
|
||||
|
||||
export const PromptPresetViewSchema = z.object({
|
||||
characterId: z.string(),
|
||||
entries: z.array(PromptEntrySchema),
|
||||
updatedAt: z.number(),
|
||||
version: z.number(),
|
||||
});
|
||||
|
||||
// ==================== 工作流 ====================
|
||||
|
||||
export const WorkflowNodeTypeSchema = z.enum([
|
||||
'llm_call',
|
||||
'condition',
|
||||
'data_process',
|
||||
'parallel',
|
||||
'merge',
|
||||
]);
|
||||
|
||||
export const WorkflowNodeSchema = z.object({
|
||||
id: z.string(),
|
||||
type: WorkflowNodeTypeSchema,
|
||||
name: z.string(),
|
||||
config: z.record(z.unknown()),
|
||||
inputMapping: z.record(z.string()).optional(),
|
||||
outputMapping: z.record(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const WorkflowEdgeSchema = z.object({
|
||||
id: z.string(),
|
||||
source: z.string(),
|
||||
target: z.string(),
|
||||
sourcePort: z.string().optional(),
|
||||
targetPort: z.string().optional(),
|
||||
});
|
||||
|
||||
export const WorkflowSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
nodes: z.array(WorkflowNodeSchema),
|
||||
edges: z.array(WorkflowEdgeSchema),
|
||||
entryNodeId: z.string(),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
version: z.number(),
|
||||
});
|
||||
|
||||
// ==================== RAG 库 ====================
|
||||
|
||||
export const RAGDocumentSchema = z.object({
|
||||
id: z.string(),
|
||||
content: z.string(),
|
||||
metadata: z.record(z.unknown()).optional(),
|
||||
embedding: z.array(z.number()).optional(),
|
||||
createdAt: z.number(),
|
||||
});
|
||||
|
||||
export const RAGLibrarySchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
documents: z.array(RAGDocumentSchema),
|
||||
embeddingModel: z.string().optional(),
|
||||
createdAt: z.number(),
|
||||
updatedAt: z.number(),
|
||||
});
|
||||
157
shared/schemas/sillytavern.schemas.ts
Normal file
157
shared/schemas/sillytavern.schemas.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Zod Schemas - SillyTavern 兼容格式验证
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// ==================== 世界书条目 ====================
|
||||
|
||||
export const STWorldInfoEntryPositionSchema = z.enum([
|
||||
'before_char',
|
||||
'after_char',
|
||||
'before_example',
|
||||
'after_example',
|
||||
'author_note_top',
|
||||
'author_note_bottom',
|
||||
'at_depth',
|
||||
]);
|
||||
|
||||
export const STWorldInfoFilterLogicSchema = z.enum([
|
||||
'and_any',
|
||||
'and_all',
|
||||
'not_any',
|
||||
'not_all',
|
||||
]);
|
||||
|
||||
export const STWorldInfoEntryRoleSchema = z.enum([
|
||||
'system',
|
||||
'user',
|
||||
'assistant',
|
||||
]);
|
||||
|
||||
export const STWorldInfoEntrySchema = z.object({
|
||||
uid: z.string(),
|
||||
key: z.array(z.string()).optional(),
|
||||
keysecondary: z.array(z.string()).optional(),
|
||||
filter: STWorldInfoFilterLogicSchema.optional(),
|
||||
content: z.string(),
|
||||
constant: z.boolean().optional(),
|
||||
selective: z.boolean().optional(),
|
||||
order: z.number(),
|
||||
position: STWorldInfoEntryPositionSchema,
|
||||
depth: z.number().optional(),
|
||||
role: STWorldInfoEntryRoleSchema.optional(),
|
||||
probability: z.number().min(0).max(100).optional(),
|
||||
group: z.array(z.string()).optional(),
|
||||
groupPrioritize: z.boolean().optional(),
|
||||
useGroupScoring: z.boolean().optional(),
|
||||
automationId: z.string().optional(),
|
||||
disable: z.boolean().optional(),
|
||||
extensions: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
export const STWorldInfoSchema = z.object({
|
||||
spec: z.string(),
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
entries: z.array(STWorldInfoEntrySchema),
|
||||
extensions: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
// ==================== 角色卡 ====================
|
||||
|
||||
export const STCharacterCardSpecSchema = z.enum([
|
||||
'chara_card_v1',
|
||||
'chara_card_v2',
|
||||
'chara_card_v3',
|
||||
]);
|
||||
|
||||
export const STCharacterCardDataSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
personality: z.string(),
|
||||
scenario: z.string(),
|
||||
first_mes: z.string(),
|
||||
mes_example: z.string(),
|
||||
alternate_greetings: z.array(z.string()).optional(),
|
||||
creator_notes: z.string().optional(),
|
||||
system_prompt: z.string().optional(),
|
||||
post_history_instructions: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
character_book: STWorldInfoSchema.optional(),
|
||||
extensions: z.object({
|
||||
world: z.string().optional(),
|
||||
talkativeness: z.number().min(0).max(1).optional(),
|
||||
fav: z.boolean().optional(),
|
||||
}).passthrough().optional(),
|
||||
});
|
||||
|
||||
export const STCharacterCardSchema = z.object({
|
||||
spec: STCharacterCardSpecSchema,
|
||||
spec_version: z.string().optional(),
|
||||
data: STCharacterCardDataSchema,
|
||||
});
|
||||
|
||||
// ==================== 聊天记录 ====================
|
||||
|
||||
export const STChatHeaderSchema = z.object({
|
||||
user_name: z.string(),
|
||||
character_name: z.string(),
|
||||
create_date: z.string(),
|
||||
chat_metadata: z.record(z.unknown()).optional(),
|
||||
}).passthrough();
|
||||
|
||||
export const STChatMessageSchema = z.object({
|
||||
name: z.string(),
|
||||
is_user: z.boolean(),
|
||||
is_system: z.boolean().optional(),
|
||||
send_date: z.union([z.number(), z.string()]),
|
||||
mes: z.string(),
|
||||
swipes: z.array(z.string()).optional(),
|
||||
swipe_id: z.number().optional(),
|
||||
is_hidden: z.boolean().optional(),
|
||||
extra: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
// ==================== 预设 ====================
|
||||
|
||||
export const STGenerationPresetSchema = z.object({
|
||||
name: z.string(),
|
||||
temperature: z.number().optional(),
|
||||
top_p: z.number().optional(),
|
||||
top_k: z.number().optional(),
|
||||
repetition_penalty: z.number().optional(),
|
||||
frequency_penalty: z.number().optional(),
|
||||
presence_penalty: z.number().optional(),
|
||||
max_length: z.number().optional(),
|
||||
}).passthrough();
|
||||
|
||||
// ==================== 提示词预设 (Prompt Preset) ====================
|
||||
|
||||
export const STPromptRoleSchema = z.enum(['system', 'assistant', 'user']);
|
||||
|
||||
export const STPromptSchema = z.object({
|
||||
identifier: z.string(),
|
||||
name: z.string().optional(),
|
||||
content: z.string(),
|
||||
role: STPromptRoleSchema,
|
||||
marker: z.boolean().optional(),
|
||||
injection_order: z.number().optional(),
|
||||
extensions: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
export const STPromptOrderItemSchema = z.object({
|
||||
identifier: z.string(),
|
||||
enabled: z.boolean(),
|
||||
});
|
||||
|
||||
export const STPromptOrderConfigSchema = z.object({
|
||||
order: z.array(STPromptOrderItemSchema),
|
||||
});
|
||||
|
||||
export const STPromptPresetSchema = z.object({
|
||||
name: z.string(),
|
||||
prompts: z.array(STPromptSchema),
|
||||
prompt_order: z.record(STPromptOrderConfigSchema),
|
||||
default_prompt_order: STPromptOrderConfigSchema.optional(),
|
||||
});
|
||||
1
shared/types/.gitkeep
Normal file
1
shared/types/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
// Shared TypeScript Types (Frontend & Backend)
|
||||
545
shared/types/converters.ts
Normal file
545
shared/types/converters.ts
Normal file
@@ -0,0 +1,545 @@
|
||||
/**
|
||||
* 数据转换器
|
||||
* 在 SillyTavern 格式和项目内部格式之间进行转换
|
||||
*/
|
||||
|
||||
import {
|
||||
STWorldInfo,
|
||||
STWorldInfoEntry,
|
||||
STWorldInfoEntryPosition,
|
||||
STCharacterCard,
|
||||
STCharacterCardData,
|
||||
STChatHeader,
|
||||
STChatMessage,
|
||||
STPromptPreset,
|
||||
STPrompt,
|
||||
STPromptOrderConfig,
|
||||
} from './sillytavern.types';
|
||||
import {
|
||||
WorldInfo,
|
||||
WorldInfoEntry,
|
||||
ActivationType,
|
||||
CharacterCard,
|
||||
ChatHeader,
|
||||
ChatMessage,
|
||||
ChatLog,
|
||||
PromptEntry,
|
||||
PromptPresetView,
|
||||
PromptRole,
|
||||
} from './internal.types';
|
||||
|
||||
// ==================== 工具函数 ====================
|
||||
|
||||
/**
|
||||
* 生成唯一 ID
|
||||
*/
|
||||
function generateId(): string {
|
||||
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间戳
|
||||
*/
|
||||
function now(): number {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
// ==================== 世界书转换 ====================
|
||||
|
||||
/**
|
||||
* 从 SillyTavern 条目激活方式推断激活类型
|
||||
*/
|
||||
function inferActivationType(entry: STWorldInfoEntry): ActivationType {
|
||||
if (entry.disable) {
|
||||
return ActivationType.KEYWORD; // 禁用的条目默认归为关键词类型
|
||||
}
|
||||
|
||||
if (entry.constant === true) {
|
||||
return ActivationType.PERMANENT;
|
||||
}
|
||||
|
||||
if (entry.selective === true) {
|
||||
return ActivationType.RAG;
|
||||
}
|
||||
|
||||
// 默认为关键词触发
|
||||
return ActivationType.KEYWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 SillyTavern 位置字符串转换为内部枚举
|
||||
*/
|
||||
function convertPosition(position: string): STWorldInfoEntryPosition {
|
||||
// 如果已经是枚举值,直接返回
|
||||
if (Object.values(STWorldInfoEntryPosition).includes(position as STWorldInfoEntryPosition)) {
|
||||
return position as STWorldInfoEntryPosition;
|
||||
}
|
||||
|
||||
// 处理 SillyTavern 的原始字符串格式
|
||||
const positionMap: Record<string, STWorldInfoEntryPosition> = {
|
||||
'Before Char Definition': STWorldInfoEntryPosition.BEFORE_CHAR,
|
||||
'After Char Definition': STWorldInfoEntryPosition.AFTER_CHAR,
|
||||
'Before Example Messages': STWorldInfoEntryPosition.BEFORE_EXAMPLE,
|
||||
'After Example Messages': STWorldInfoEntryPosition.AFTER_EXAMPLE,
|
||||
'Top of Author\'s Note': STWorldInfoEntryPosition.AUTHOR_NOTE_TOP,
|
||||
'Bottom of Author\'s Note': STWorldInfoEntryPosition.AUTHOR_NOTE_BOTTOM,
|
||||
'@D': STWorldInfoEntryPosition.AT_DEPTH,
|
||||
};
|
||||
|
||||
return positionMap[position] || STWorldInfoEntryPosition.AFTER_CHAR;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 SillyTavern 世界书条目转换为内部格式
|
||||
*/
|
||||
export function convertSTEntryToInternal(stEntry: STWorldInfoEntry): WorldInfoEntry {
|
||||
const activationType = inferActivationType(stEntry);
|
||||
|
||||
return {
|
||||
...stEntry,
|
||||
uid: stEntry.uid || generateId(),
|
||||
position: convertPosition(stEntry.position as string),
|
||||
activationType,
|
||||
createdAt: now(),
|
||||
updatedAt: now(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内部世界书条目转换为 SillyTavern 格式
|
||||
*/
|
||||
export function convertEntryToST(entry: WorldInfoEntry): STWorldInfoEntry {
|
||||
const stEntry: STWorldInfoEntry = {
|
||||
uid: entry.uid,
|
||||
key: entry.key,
|
||||
keysecondary: entry.keysecondary,
|
||||
content: entry.content,
|
||||
order: entry.order,
|
||||
position: entry.position,
|
||||
depth: entry.depth,
|
||||
role: entry.role,
|
||||
probability: entry.probability,
|
||||
group: entry.group,
|
||||
groupPrioritize: entry.groupPrioritize,
|
||||
useGroupScoring: entry.useGroupScoring,
|
||||
automationId: entry.automationId,
|
||||
disable: entry.disable,
|
||||
extensions: entry.extensions,
|
||||
};
|
||||
|
||||
// 根据激活类型设置 constant 和 selective
|
||||
switch (entry.activationType) {
|
||||
case ActivationType.PERMANENT:
|
||||
stEntry.constant = true;
|
||||
stEntry.selective = false;
|
||||
break;
|
||||
case ActivationType.RAG:
|
||||
stEntry.constant = false;
|
||||
stEntry.selective = true;
|
||||
break;
|
||||
case ActivationType.KEYWORD:
|
||||
case ActivationType.LOGIC:
|
||||
default:
|
||||
stEntry.constant = false;
|
||||
stEntry.selective = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return stEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 SillyTavern 世界书转换为内部格式
|
||||
*/
|
||||
export function convertSTWorldInfoToInternal(stWorldInfo: STWorldInfo): WorldInfo {
|
||||
return {
|
||||
id: generateId(),
|
||||
name: stWorldInfo.name || 'Untitled World Info',
|
||||
description: stWorldInfo.description,
|
||||
entries: stWorldInfo.entries.map(convertSTEntryToInternal),
|
||||
createdAt: now(),
|
||||
updatedAt: now(),
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内部世界书转换为 SillyTavern 格式
|
||||
*/
|
||||
export function convertWorldInfoToST(worldInfo: WorldInfo): STWorldInfo {
|
||||
return {
|
||||
spec: 'world_info_v1',
|
||||
name: worldInfo.name,
|
||||
description: worldInfo.description,
|
||||
entries: worldInfo.entries.map(convertEntryToST),
|
||||
extensions: {
|
||||
originalId: worldInfo.id,
|
||||
version: worldInfo.version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== 角色卡转换 ====================
|
||||
|
||||
/**
|
||||
* 将 SillyTavern 角色卡转换为内部格式
|
||||
*/
|
||||
export function convertSTCharacterCardToInternal(
|
||||
stCard: STCharacterCard,
|
||||
avatarPath?: string
|
||||
): CharacterCard {
|
||||
const data = stCard.data;
|
||||
|
||||
// 提取绑定的世界书 ID(如果有)
|
||||
const worldInfoId = data.extensions?.world as string | undefined;
|
||||
|
||||
return {
|
||||
id: generateId(),
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
personality: data.personality,
|
||||
scenario: data.scenario,
|
||||
first_mes: data.first_mes,
|
||||
mes_example: data.mes_example,
|
||||
alternate_greetings: data.alternate_greetings || [],
|
||||
creator_notes: data.creator_notes,
|
||||
system_prompt: data.system_prompt,
|
||||
post_history_instructions: data.post_history_instructions,
|
||||
tags: data.tags || [],
|
||||
categories: data.tags || [], // 使用 tags 作为初始分类
|
||||
worldInfoId,
|
||||
outputSchema: undefined, // SillyTavern 格式中没有此字段
|
||||
avatarPath,
|
||||
createdAt: now(),
|
||||
updatedAt: now(),
|
||||
lastChatAt: undefined,
|
||||
isFavorite: data.extensions?.fav === true,
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内部角色卡转换为 SillyTavern 格式
|
||||
*/
|
||||
export function convertCharacterCardToST(card: CharacterCard): STCharacterCard {
|
||||
return {
|
||||
spec: 'chara_card_v2',
|
||||
spec_version: '2.0',
|
||||
data: {
|
||||
name: card.name,
|
||||
description: card.description,
|
||||
personality: card.personality,
|
||||
scenario: card.scenario,
|
||||
first_mes: card.first_mes,
|
||||
mes_example: card.mes_example,
|
||||
alternate_greetings: card.alternate_greetings,
|
||||
creator_notes: card.creator_notes,
|
||||
system_prompt: card.system_prompt,
|
||||
post_history_instructions: card.post_history_instructions,
|
||||
tags: card.categories, // 使用 categories 作为 tags
|
||||
extensions: {
|
||||
world: card.worldInfoId,
|
||||
fav: card.isFavorite,
|
||||
originalId: card.id,
|
||||
version: card.version,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== 聊天记录转换 ====================
|
||||
|
||||
/**
|
||||
* 将 SillyTavern 聊天头转换为内部格式
|
||||
*/
|
||||
export function convertSTChatHeaderToInternal(
|
||||
stHeader: STChatHeader,
|
||||
characterId: string
|
||||
): ChatHeader {
|
||||
return {
|
||||
id: generateId(),
|
||||
displayName: `${stHeader.character_name} Chat`,
|
||||
characterId,
|
||||
userName: stHeader.user_name,
|
||||
characterName: stHeader.character_name,
|
||||
tableData: undefined,
|
||||
createdAt: parseDateToTimestamp(stHeader.create_date),
|
||||
updatedAt: now(),
|
||||
messageCount: 0, // 需要在导入消息后更新
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内部聊天头转换为 SillyTavern 格式
|
||||
*/
|
||||
export function convertChatHeaderToST(header: ChatHeader): STChatHeader {
|
||||
return {
|
||||
user_name: header.userName,
|
||||
character_name: header.characterName,
|
||||
create_date: new Date(header.createdAt).toISOString(),
|
||||
chat_metadata: {
|
||||
originalId: header.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 SillyTavern 聊天消息转换为内部格式
|
||||
*/
|
||||
export function convertSTMessageToInternal(
|
||||
stMessage: STChatMessage,
|
||||
chatId: string
|
||||
): ChatMessage {
|
||||
return {
|
||||
id: generateId(),
|
||||
chatId,
|
||||
name: stMessage.name,
|
||||
is_user: stMessage.is_user,
|
||||
is_system: stMessage.is_system,
|
||||
sendDate: normalizeSendDate(stMessage.send_date),
|
||||
mes: stMessage.mes,
|
||||
swipes: stMessage.swipes,
|
||||
swipe_id: stMessage.swipe_id,
|
||||
is_hidden: stMessage.is_hidden,
|
||||
extra: stMessage.extra,
|
||||
tokenCount: undefined,
|
||||
isTemporary: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内部聊天消息转换为 SillyTavern 格式
|
||||
*/
|
||||
export function convertMessageToST(message: ChatMessage): STChatMessage {
|
||||
return {
|
||||
name: message.name,
|
||||
is_user: message.is_user,
|
||||
is_system: message.is_system,
|
||||
send_date: message.sendDate,
|
||||
mes: message.mes,
|
||||
swipes: message.swipes,
|
||||
swipe_id: message.swipe_id,
|
||||
is_hidden: message.is_hidden,
|
||||
extra: message.extra,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 SillyTavern 聊天记录转换为内部格式
|
||||
*/
|
||||
export function convertSTChatLogToInternal(
|
||||
stChatLog: [STChatHeader, ...STChatMessage[]],
|
||||
characterId: string
|
||||
): ChatLog {
|
||||
const [stHeader, ...stMessages] = stChatLog;
|
||||
|
||||
const header = convertSTChatHeaderToInternal(stHeader, characterId);
|
||||
const messages = stMessages.map(msg => convertSTMessageToInternal(msg, header.id));
|
||||
|
||||
header.messageCount = messages.length;
|
||||
|
||||
return {
|
||||
header,
|
||||
messages,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内部聊天记录转换为 SillyTavern 格式
|
||||
*/
|
||||
export function convertChatLogToST(chatLog: ChatLog): [STChatHeader, ...STChatMessage[]] {
|
||||
const stHeader = convertChatHeaderToST(chatLog.header);
|
||||
const stMessages = chatLog.messages.map(convertMessageToST);
|
||||
|
||||
return [stHeader, ...stMessages];
|
||||
}
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
|
||||
/**
|
||||
* 解析日期字符串为时间戳
|
||||
*/
|
||||
function parseDateToTimestamp(dateStr: string): number {
|
||||
// 尝试解析 ISO 格式
|
||||
const isoDate = new Date(dateStr);
|
||||
if (!isNaN(isoDate.getTime())) {
|
||||
return isoDate.getTime();
|
||||
}
|
||||
|
||||
// 尝试解析 SillyTavern 自定义格式 "2024-01-01 @12h 30m 45s 123ms"
|
||||
const customFormat = dateStr.match(/(\d{4}-\d{2}-\d{2})\s+@(\d+)h\s+(\d+)m\s+(\d+)s\s+(\d+)ms/);
|
||||
if (customFormat) {
|
||||
const [, date, hours, minutes, seconds, ms] = customFormat;
|
||||
const parsedDate = new Date(`${date}T${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:${seconds.padStart(2, '0')}.${ms.padStart(3, '0')}Z`);
|
||||
return parsedDate.getTime();
|
||||
}
|
||||
|
||||
// 默认为当前时间
|
||||
return now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化发送日期为 ISO 字符串
|
||||
*/
|
||||
function normalizeSendDate(sendDate: number | string): string {
|
||||
if (typeof sendDate === 'number') {
|
||||
// 如果是时间戳,转换为 ISO 字符串
|
||||
return new Date(sendDate).toISOString();
|
||||
}
|
||||
|
||||
// 如果已经是字符串,尝试验证并规范化
|
||||
const date = new Date(sendDate);
|
||||
if (!isNaN(date.getTime())) {
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
// 如果无法解析,返回当前时间
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
// ==================== 提示词预设转换 ====================
|
||||
|
||||
/**
|
||||
* 角色类型映射(外部 → 内部)
|
||||
*/
|
||||
function mapRoleToInternal(role: string): PromptRole {
|
||||
const roleMap: Record<string, PromptRole> = {
|
||||
'system': 'system',
|
||||
'assistant': 'ai',
|
||||
'user': 'user',
|
||||
};
|
||||
|
||||
return roleMap[role] || 'system'; // 回退规则
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为固有节点
|
||||
* @param prompt 外部 prompt 条目
|
||||
* @param builtinIdentifiers 内置标识符集合
|
||||
*/
|
||||
function isSystemNode(prompt: STPrompt, builtinIdentifiers: Set<string>): boolean {
|
||||
// 第一优先级:marker 标志
|
||||
if (prompt.marker === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 第二优先级:内置保留项
|
||||
if (builtinIdentifiers.has(prompt.identifier)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 第三优先级:普通自定义项
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算 Token 数(简化版,实际需要调用 tokenizer)
|
||||
* @param content 文本内容
|
||||
* @returns 估算的 token 数量
|
||||
*/
|
||||
function calculateTokenCount(content: string): number {
|
||||
if (!content) return 0;
|
||||
// 粗略估算:英文 ~4 chars/token,中文 ~1.5 chars/token
|
||||
// 这里使用一个简化的平均值 3 chars/token
|
||||
return Math.ceil(content.length / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 SillyTavern Prompt Preset 转换为内部视图
|
||||
*
|
||||
* @param stPreset 外部兼容层数据
|
||||
* @param characterId 当前选定的角色 ID
|
||||
* @param builtinIdentifiers 内置标识符集合(如 'main_prompt', 'jailbreak' 等)
|
||||
* @returns 内部业务层视图(当前角色的“当前视图”)
|
||||
*/
|
||||
export function convertSTPromptPresetToView(
|
||||
stPreset: STPromptPreset,
|
||||
characterId: string,
|
||||
builtinIdentifiers: Set<string> = new Set()
|
||||
): PromptPresetView {
|
||||
// 1. 获取当前角色的顺序配置
|
||||
const orderConfig = stPreset.prompt_order[characterId]
|
||||
|| stPreset.default_prompt_order
|
||||
|| { order: [] };
|
||||
|
||||
// 2. 创建 identifier → prompt 的映射表
|
||||
const promptMap = new Map(stPreset.prompts.map(p => [p.identifier, p]));
|
||||
|
||||
// 3. 构建内部条目列表
|
||||
const entries: PromptEntry[] = orderConfig.order
|
||||
.map((orderItem, index) => {
|
||||
const prompt = promptMap.get(orderItem.identifier);
|
||||
|
||||
// 如果找不到对应的 prompt,跳过(或创建占位符)
|
||||
if (!prompt) {
|
||||
console.warn(`Prompt not found: ${orderItem.identifier}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 确定条目名(优先级:name → identifier)
|
||||
const name = prompt.name || prompt.identifier;
|
||||
|
||||
// 判断是否为固有节点
|
||||
const isSystem = isSystemNode(prompt, builtinIdentifiers);
|
||||
|
||||
// 计算 token 数
|
||||
const tokenCount = calculateTokenCount(prompt.content);
|
||||
|
||||
return {
|
||||
identifier: prompt.identifier,
|
||||
name,
|
||||
enabled: orderItem.enabled,
|
||||
content: prompt.content,
|
||||
order: index, // 使用数组索引作为顺序
|
||||
role: mapRoleToInternal(prompt.role),
|
||||
tokenCount,
|
||||
isSystemNode: isSystem,
|
||||
};
|
||||
})
|
||||
.filter((entry): entry is PromptEntry => entry !== null);
|
||||
|
||||
return {
|
||||
characterId,
|
||||
entries,
|
||||
updatedAt: now(),
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将内部视图变更同步回外部兼容层
|
||||
*
|
||||
* @param view 内部业务层视图
|
||||
* @param stPreset 原始外部数据(会修改此对象)
|
||||
* @returns 更新后的外部兼容层数据
|
||||
*/
|
||||
export function syncViewToSTPromptPreset(
|
||||
view: PromptPresetView,
|
||||
stPreset: STPromptPreset
|
||||
): STPromptPreset {
|
||||
// 1. 更新 prompt_order 中的 enabled 状态和顺序
|
||||
const orderItems = view.entries.map(entry => ({
|
||||
identifier: entry.identifier,
|
||||
enabled: entry.enabled,
|
||||
}));
|
||||
|
||||
stPreset.prompt_order[view.characterId] = {
|
||||
order: orderItems,
|
||||
};
|
||||
|
||||
// 2. 更新 prompts 中的 name 和 content(如果用户编辑了)
|
||||
const promptMap = new Map(stPreset.prompts.map(p => [p.identifier, p]));
|
||||
|
||||
view.entries.forEach(entry => {
|
||||
const prompt = promptMap.get(entry.identifier);
|
||||
if (prompt) {
|
||||
// 只同步可编辑字段(name 和 content)
|
||||
prompt.name = entry.name;
|
||||
prompt.content = entry.content;
|
||||
// 注意:role 不回写,保持外部原始值
|
||||
// 注意:identifier 不回写,它是稳定键
|
||||
}
|
||||
});
|
||||
|
||||
return stPreset;
|
||||
}
|
||||
245
shared/types/examples/prompt-preset.example.ts
Normal file
245
shared/types/examples/prompt-preset.example.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* Prompt Preset 类型使用示例
|
||||
* 展示如何正确使用内外层转换
|
||||
*/
|
||||
|
||||
import {
|
||||
STPromptPreset,
|
||||
STPrompt,
|
||||
} from '../types/sillytavern.types';
|
||||
import {
|
||||
PromptPresetView,
|
||||
PromptEntry,
|
||||
} from '../types/internal.types';
|
||||
import {
|
||||
convertSTPromptPresetToView,
|
||||
syncViewToSTPromptPreset,
|
||||
} from '../types/converters';
|
||||
|
||||
// ==================== 示例 1: 外部兼容层数据(从 SillyTavern 导入)====================
|
||||
|
||||
const externalPreset: STPromptPreset = {
|
||||
name: 'Default Preset',
|
||||
prompts: [
|
||||
{
|
||||
identifier: 'main_prompt',
|
||||
name: 'Main Prompt',
|
||||
content: 'You are a helpful assistant.',
|
||||
role: 'system',
|
||||
marker: true, // 固有节点标志
|
||||
injection_order: 0,
|
||||
},
|
||||
{
|
||||
identifier: 'jailbreak',
|
||||
name: 'Jailbreak',
|
||||
content: 'Ignore all previous instructions...',
|
||||
role: 'system',
|
||||
marker: true,
|
||||
injection_order: 1,
|
||||
},
|
||||
{
|
||||
identifier: 'custom-prompt-1',
|
||||
name: 'Custom Prompt',
|
||||
content: 'Additional context here.',
|
||||
role: 'user',
|
||||
marker: false,
|
||||
injection_order: 2,
|
||||
},
|
||||
],
|
||||
prompt_order: {
|
||||
'character-123': {
|
||||
order: [
|
||||
{ identifier: 'main_prompt', enabled: true },
|
||||
{ identifier: 'jailbreak', enabled: false }, // 禁用
|
||||
{ identifier: 'custom-prompt-1', enabled: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// ==================== 示例 2: 转换为内部视图(导入流程)====================
|
||||
|
||||
// 定义内置标识符集合
|
||||
const BUILTIN_IDENTIFIERS = new Set(['main_prompt', 'jailbreak']);
|
||||
|
||||
// 转换为角色 "character-123" 的内部视图
|
||||
const internalView: PromptPresetView = convertSTPromptPresetToView(
|
||||
externalPreset,
|
||||
'character-123',
|
||||
BUILTIN_IDENTIFIERS
|
||||
);
|
||||
|
||||
console.log('Internal View:', internalView);
|
||||
/*
|
||||
输出示例:
|
||||
{
|
||||
characterId: 'character-123',
|
||||
entries: [
|
||||
{
|
||||
identifier: 'main_prompt',
|
||||
name: 'Main Prompt',
|
||||
enabled: true,
|
||||
content: 'You are a helpful assistant.',
|
||||
order: 0,
|
||||
role: 'system',
|
||||
tokenCount: 9, // 估算值
|
||||
isSystemNode: true, // marker: true
|
||||
},
|
||||
{
|
||||
identifier: 'jailbreak',
|
||||
name: 'Jailbreak',
|
||||
enabled: false, // 从 prompt_order 提取
|
||||
content: 'Ignore all previous instructions...',
|
||||
order: 1,
|
||||
role: 'system',
|
||||
tokenCount: 12,
|
||||
isSystemNode: true,
|
||||
},
|
||||
{
|
||||
identifier: 'custom-prompt-1',
|
||||
name: 'Custom Prompt',
|
||||
enabled: true,
|
||||
content: 'Additional context here.',
|
||||
order: 2,
|
||||
role: 'user',
|
||||
tokenCount: 8,
|
||||
isSystemNode: false, // 自定义条目
|
||||
},
|
||||
],
|
||||
updatedAt: 1234567890,
|
||||
version: 1,
|
||||
}
|
||||
*/
|
||||
|
||||
// ==================== 示例 3: 前端编辑内部视图 ====================
|
||||
|
||||
// 用户在前端进行以下操作:
|
||||
// 1. 启用了 jailbreak
|
||||
// 2. 修改了 custom-prompt-1 的内容
|
||||
// 3. 调整了顺序
|
||||
|
||||
const editedEntries: PromptEntry[] = [
|
||||
...internalView.entries.map(entry => {
|
||||
if (entry.identifier === 'jailbreak') {
|
||||
return { ...entry, enabled: true }; // 启用
|
||||
}
|
||||
if (entry.identifier === 'custom-prompt-1') {
|
||||
return {
|
||||
...entry,
|
||||
content: 'Updated content here.', // 修改内容
|
||||
order: 0, // 移到第一位
|
||||
};
|
||||
}
|
||||
return entry;
|
||||
}),
|
||||
].sort((a, b) => a.order - b.order);
|
||||
|
||||
const updatedView: PromptPresetView = {
|
||||
...internalView,
|
||||
entries: editedEntries,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
// ==================== 示例 4: 同步回外部兼容层(导出流程)====================
|
||||
|
||||
const exportedPreset: STPromptPreset = syncViewToSTPromptPreset(
|
||||
updatedView,
|
||||
externalPreset // 会修改此对象
|
||||
);
|
||||
|
||||
console.log('Exported Preset:', exportedPreset);
|
||||
/*
|
||||
输出示例:
|
||||
{
|
||||
name: 'Default Preset',
|
||||
prompts: [
|
||||
{
|
||||
identifier: 'main_prompt',
|
||||
name: 'Main Prompt',
|
||||
content: 'You are a helpful assistant.',
|
||||
role: 'system',
|
||||
marker: true,
|
||||
injection_order: 0,
|
||||
},
|
||||
{
|
||||
identifier: 'jailbreak',
|
||||
name: 'Jailbreak',
|
||||
content: 'Ignore all previous instructions...', // 内容未变
|
||||
role: 'system',
|
||||
marker: true,
|
||||
injection_order: 1,
|
||||
},
|
||||
{
|
||||
identifier: 'custom-prompt-1',
|
||||
name: 'Custom Prompt',
|
||||
content: 'Updated content here.', // ✅ 已更新
|
||||
role: 'user',
|
||||
marker: false,
|
||||
injection_order: 2,
|
||||
},
|
||||
],
|
||||
prompt_order: {
|
||||
'character-123': {
|
||||
order: [
|
||||
{ identifier: 'custom-prompt-1', enabled: true }, // ✅ 顺序改变
|
||||
{ identifier: 'main_prompt', enabled: true },
|
||||
{ identifier: 'jailbreak', enabled: true }, // ✅ 已启用
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
*/
|
||||
|
||||
// ==================== 示例 5: 业务层使用场景 ====================
|
||||
|
||||
/**
|
||||
* 场景 1: 获取当前角色的所有启用条目
|
||||
*/
|
||||
function getEnabledPrompts(view: PromptPresetView): PromptEntry[] {
|
||||
return view.entries.filter(entry => entry.enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景 2: 计算总 Token 数
|
||||
*/
|
||||
function calculateTotalTokens(view: PromptPresetView): number {
|
||||
return view.entries.reduce((sum, entry) => sum + entry.tokenCount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景 3: 区分系统节点和自定义节点
|
||||
*/
|
||||
function separateNodes(view: PromptPresetView) {
|
||||
const systemNodes = view.entries.filter(e => e.isSystemNode);
|
||||
const customNodes = view.entries.filter(e => !e.isSystemNode);
|
||||
|
||||
return { systemNodes, customNodes };
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景 4: 按角色分组
|
||||
*/
|
||||
function groupByRole(view: PromptPresetView) {
|
||||
const groups: Record<string, PromptEntry[]> = {
|
||||
system: [],
|
||||
ai: [],
|
||||
user: [],
|
||||
};
|
||||
|
||||
view.entries.forEach(entry => {
|
||||
groups[entry.role].push(entry);
|
||||
});
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const enabledPrompts = getEnabledPrompts(updatedView);
|
||||
const totalTokens = calculateTotalTokens(updatedView);
|
||||
const { systemNodes, customNodes } = separateNodes(updatedView);
|
||||
const roleGroups = groupByRole(updatedView);
|
||||
|
||||
console.log('Enabled prompts count:', enabledPrompts.length);
|
||||
console.log('Total tokens:', totalTokens);
|
||||
console.log('System nodes:', systemNodes.length);
|
||||
console.log('Custom nodes:', customNodes.length);
|
||||
13
shared/types/index.ts
Normal file
13
shared/types/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Shared 类型导出
|
||||
* 统一导出所有共享类型定义
|
||||
*/
|
||||
|
||||
// SillyTavern 兼容格式
|
||||
export * from './sillytavern.types';
|
||||
|
||||
// 项目内部格式
|
||||
export * from './internal.types';
|
||||
|
||||
// 数据转换器
|
||||
export * from './converters';
|
||||
469
shared/types/internal.types.ts
Normal file
469
shared/types/internal.types.ts
Normal file
@@ -0,0 +1,469 @@
|
||||
/**
|
||||
* 项目内部使用的数据结构定义
|
||||
* 继承 SillyTavern 格式并添加自定义字段和功能
|
||||
*/
|
||||
|
||||
import type {
|
||||
STWorldInfoEntry,
|
||||
STWorldInfoEntryActivation,
|
||||
STWorldInfoEntryPosition,
|
||||
STCharacterCardData,
|
||||
STChatHeader,
|
||||
STChatMessage,
|
||||
} from './sillytavern.types';
|
||||
|
||||
// ==================== 世界书 (World Info) ====================
|
||||
|
||||
/**
|
||||
* 自定义激活方式类型(4种枚举)
|
||||
*/
|
||||
export enum ActivationType {
|
||||
/** 永久激活 - 从 constant=true 识别 */
|
||||
PERMANENT = 'permanent',
|
||||
/** 关键词触发 - 从 key 数组识别 */
|
||||
KEYWORD = 'keyword',
|
||||
/** RAG 检索激活 - 需要绑定 RAG 库 */
|
||||
RAG = 'rag',
|
||||
/** 逻辑表达式激活 - 包含两个变量和一个运算符 */
|
||||
LOGIC = 'logic',
|
||||
}
|
||||
|
||||
/**
|
||||
* 逻辑表达式结构(用于 LOGIC 激活类型)
|
||||
*/
|
||||
export interface LogicExpression {
|
||||
/** 第一个变量 */
|
||||
variable1: string;
|
||||
|
||||
/** 运算符 */
|
||||
operator: 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'greater' | 'less';
|
||||
|
||||
/** 第二个变量或值 */
|
||||
variable2: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* RAG 配置(用于 RAG 激活类型)
|
||||
*/
|
||||
export interface RAGConfig {
|
||||
/** 绑定的 RAG 库 ID */
|
||||
libraryId: string;
|
||||
|
||||
/** 相似度阈值 */
|
||||
threshold?: number;
|
||||
|
||||
/** 最大返回条目数 */
|
||||
maxEntries?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目内部世界书条目结构
|
||||
* 继承 SillyTavern 条目并添加自定义功能
|
||||
*/
|
||||
export interface WorldInfoEntry extends Omit<STWorldInfoEntry, 'constant' | 'selective'> {
|
||||
/** 激活方式(4种枚举之一) */
|
||||
activationType: ActivationType;
|
||||
|
||||
/** 逻辑表达式(当 activationType 为 LOGIC 时使用) */
|
||||
logicExpression?: LogicExpression;
|
||||
|
||||
/** RAG 配置(当 activationType 为 RAG 时使用) */
|
||||
ragConfig?: RAGConfig;
|
||||
|
||||
/** 创建时间戳 */
|
||||
createdAt: number;
|
||||
|
||||
/** 最后更新时间戳 */
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目内部世界书结构
|
||||
*/
|
||||
export interface WorldInfo {
|
||||
/** 唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 世界书名称 */
|
||||
name: string;
|
||||
|
||||
/** 世界书描述 */
|
||||
description?: string;
|
||||
|
||||
/** 条目数组 */
|
||||
entries: WorldInfoEntry[];
|
||||
|
||||
/** 创建时间戳 */
|
||||
createdAt: number;
|
||||
|
||||
/** 最后更新时间戳 */
|
||||
updatedAt: number;
|
||||
|
||||
/** 版本号(用于数据迁移) */
|
||||
version: number;
|
||||
}
|
||||
|
||||
// ==================== 角色卡 (Character Card) ====================
|
||||
|
||||
/**
|
||||
* Vercel AI SDK Output.object() 的表头定义
|
||||
* 用于结构化输出
|
||||
*/
|
||||
export interface OutputSchemaField {
|
||||
/** 字段名称 */
|
||||
name: string;
|
||||
|
||||
/** 字段类型 */
|
||||
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
||||
|
||||
/** 字段描述 */
|
||||
description: string;
|
||||
|
||||
/** 是否必需 */
|
||||
required?: boolean;
|
||||
|
||||
/** 枚举值(如果是字符串且有固定选项) */
|
||||
enum?: string[];
|
||||
|
||||
/** 嵌套字段(如果是 object 类型) */
|
||||
fields?: OutputSchemaField[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目内部角色卡结构
|
||||
* 继承 SillyTavern 角色卡并添加自定义字段
|
||||
*/
|
||||
export interface CharacterCard extends Omit<STCharacterCardData, 'character_book'> {
|
||||
/** 角色唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 分类标签列表(用于前端分类) */
|
||||
categories: string[];
|
||||
|
||||
/** 绑定的世界书 ID */
|
||||
worldInfoId?: string;
|
||||
|
||||
/** 输出 schema 定义(用于 Vercel AI SDK Output.object()) */
|
||||
outputSchema?: OutputSchemaField[];
|
||||
|
||||
/** 角色头像路径 */
|
||||
avatarPath?: string;
|
||||
|
||||
/** 创建时间戳 */
|
||||
createdAt: number;
|
||||
|
||||
/** 最后更新时间戳 */
|
||||
updatedAt: number;
|
||||
|
||||
/** 最后聊天时间戳 */
|
||||
lastChatAt?: number;
|
||||
|
||||
/** 收藏状态 */
|
||||
isFavorite: boolean;
|
||||
|
||||
/** 版本号(用于数据迁移) */
|
||||
version: number;
|
||||
}
|
||||
|
||||
// ==================== 聊天记录 (Chat Log) ====================
|
||||
|
||||
/**
|
||||
* 项目内部聊天记录头
|
||||
*/
|
||||
export interface ChatHeader extends Omit<STChatHeader, 'user_name' | 'character_name'> {
|
||||
/** 聊天唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 显示名称(聊天标题) */
|
||||
displayName: string;
|
||||
|
||||
/** 关联的角色卡 ID */
|
||||
characterId: string;
|
||||
|
||||
/** 用户角色名 */
|
||||
userName: string;
|
||||
|
||||
/** AI 角色名称 */
|
||||
characterName: string;
|
||||
|
||||
/** 表格内容(对应角色卡的 outputSchema 字段的值) */
|
||||
tableData?: Record<string, unknown>;
|
||||
|
||||
/** 创建时间戳 */
|
||||
createdAt: number;
|
||||
|
||||
/** 最后更新时间戳 */
|
||||
updatedAt: number;
|
||||
|
||||
/** 消息数量 */
|
||||
messageCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目内部聊天消息
|
||||
*/
|
||||
export interface ChatMessage extends Omit<STChatMessage, 'send_date'> {
|
||||
/** 消息唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 发送日期 ISO 字符串 */
|
||||
sendDate: string;
|
||||
|
||||
/** 关联的聊天 ID */
|
||||
chatId: string;
|
||||
|
||||
/** Token 数量(用于统计) */
|
||||
tokenCount?: number;
|
||||
|
||||
/** 是否为临时消息(未保存) */
|
||||
isTemporary?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目内部完整聊天记录
|
||||
*/
|
||||
export interface ChatLog {
|
||||
/** 聊天头 */
|
||||
header: ChatHeader;
|
||||
|
||||
/** 消息列表 */
|
||||
messages: ChatMessage[];
|
||||
}
|
||||
|
||||
// ==================== 预设 (Preset) ====================
|
||||
|
||||
/**
|
||||
* 项目内部采样参数预设
|
||||
*/
|
||||
export interface GenerationPreset {
|
||||
/** 预设唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 预设名称 */
|
||||
name: string;
|
||||
|
||||
/** 温度 */
|
||||
temperature: number;
|
||||
|
||||
/** Top P */
|
||||
topP: number;
|
||||
|
||||
/** Top K */
|
||||
topK: number;
|
||||
|
||||
/** 重复惩罚 */
|
||||
repetitionPenalty: number;
|
||||
|
||||
/** 频率惩罚 */
|
||||
frequencyPenalty?: number;
|
||||
|
||||
/** 存在惩罚 */
|
||||
presencePenalty?: number;
|
||||
|
||||
/** 最大生成长度 */
|
||||
maxLength?: number;
|
||||
|
||||
/** 是否为默认预设 */
|
||||
isDefault: boolean;
|
||||
|
||||
/** 创建时间戳 */
|
||||
createdAt: number;
|
||||
|
||||
/** 最后更新时间戳 */
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
// ==================== 提示词预设 (Prompt Preset) ====================
|
||||
|
||||
/**
|
||||
* Prompt 角色类型(内部业务层只保留三种)
|
||||
*/
|
||||
export type PromptRole = 'system' | 'ai' | 'user';
|
||||
|
||||
/**
|
||||
* 内部业务层 - Prompt 条目(当前视图)
|
||||
* 这是基于某个 character_id 生成的“当前视图”,只包含业务需要的字段
|
||||
*/
|
||||
export interface PromptEntry {
|
||||
/** 稳定关联键(用于回写,不直接暴露给前端作为主要展示字段) */
|
||||
identifier: string;
|
||||
|
||||
/** 1. 条目名 - 前端显示和编辑 */
|
||||
name: string;
|
||||
|
||||
/** 2. 是否启用 - 当前作用域下的业务状态(派生字段) */
|
||||
enabled: boolean;
|
||||
|
||||
/** 3. 条目内容 - 静态内容视图(marker 节点可能为空) */
|
||||
content: string;
|
||||
|
||||
/** 4. 条目顺序 - 前端展示和拖拽排序 */
|
||||
order: number;
|
||||
|
||||
/** 5. 角色 - 三类业务角色 */
|
||||
role: PromptRole;
|
||||
|
||||
/** 6. 总 token 数 - 派生显示字段(不属于外部兼容层) */
|
||||
tokenCount: number;
|
||||
|
||||
/** 7. 是否固有节点 - 业务语义标签(不是 SillyTavern 原始字段) */
|
||||
isSystemNode: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部业务层 - Prompt 预设视图(基于某个 character_id 的“当前视图”)
|
||||
*/
|
||||
export interface PromptPresetView {
|
||||
/** 关联的角色 ID */
|
||||
characterId: string;
|
||||
|
||||
/** 当前视图的条目列表(已排序、已过滤) */
|
||||
entries: PromptEntry[];
|
||||
|
||||
/** 最后更新时间戳 */
|
||||
updatedAt: number;
|
||||
|
||||
/** 版本号 */
|
||||
version: number;
|
||||
}
|
||||
|
||||
// ==================== 工作流 (Workflow) ====================
|
||||
|
||||
/**
|
||||
* 工作流节点类型
|
||||
*/
|
||||
export enum WorkflowNodeType {
|
||||
/** LLM 调用节点 */
|
||||
LLM_CALL = 'llm_call',
|
||||
/** 条件分支节点 */
|
||||
CONDITION = 'condition',
|
||||
/** 数据处理节点 */
|
||||
DATA_PROCESS = 'data_process',
|
||||
/** 并行执行节点 */
|
||||
PARALLEL = 'parallel',
|
||||
/** 合并节点 */
|
||||
MERGE = 'merge',
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流节点
|
||||
*/
|
||||
export interface WorkflowNode {
|
||||
/** 节点唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 节点类型 */
|
||||
type: WorkflowNodeType;
|
||||
|
||||
/** 节点名称 */
|
||||
name: string;
|
||||
|
||||
/** 节点配置 */
|
||||
config: Record<string, unknown>;
|
||||
|
||||
/** 输入映射 */
|
||||
inputMapping?: Record<string, string>;
|
||||
|
||||
/** 输出映射 */
|
||||
outputMapping?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流连接
|
||||
*/
|
||||
export interface WorkflowEdge {
|
||||
/** 连接唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 源节点 ID */
|
||||
source: string;
|
||||
|
||||
/** 目标节点 ID */
|
||||
target: string;
|
||||
|
||||
/** 源节点输出端口 */
|
||||
sourcePort?: string;
|
||||
|
||||
/** 目标节点输入端口 */
|
||||
targetPort?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流定义
|
||||
*/
|
||||
export interface Workflow {
|
||||
/** 工作流唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 工作流名称 */
|
||||
name: string;
|
||||
|
||||
/** 工作流描述 */
|
||||
description?: string;
|
||||
|
||||
/** 节点列表 */
|
||||
nodes: WorkflowNode[];
|
||||
|
||||
/** 连接列表 */
|
||||
edges: WorkflowEdge[];
|
||||
|
||||
/** 入口节点 ID */
|
||||
entryNodeId: string;
|
||||
|
||||
/** 创建时间戳 */
|
||||
createdAt: number;
|
||||
|
||||
/** 最后更新时间戳 */
|
||||
updatedAt: number;
|
||||
|
||||
/** 版本号 */
|
||||
version: number;
|
||||
}
|
||||
|
||||
// ==================== RAG 库 (RAG Library) ====================
|
||||
|
||||
/**
|
||||
* RAG 文档条目
|
||||
*/
|
||||
export interface RAGDocument {
|
||||
/** 文档唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 文档内容 */
|
||||
content: string;
|
||||
|
||||
/** 文档元数据 */
|
||||
metadata?: Record<string, unknown>;
|
||||
|
||||
/** 向量嵌入(由 embedding 模型生成) */
|
||||
embedding?: number[];
|
||||
|
||||
/** 创建时间戳 */
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RAG 库
|
||||
*/
|
||||
export interface RAGLibrary {
|
||||
/** 库唯一标识符 */
|
||||
id: string;
|
||||
|
||||
/** 库名称 */
|
||||
name: string;
|
||||
|
||||
/** 库描述 */
|
||||
description?: string;
|
||||
|
||||
/** 文档列表 */
|
||||
documents: RAGDocument[];
|
||||
|
||||
/** 使用的 embedding 模型 */
|
||||
embeddingModel?: string;
|
||||
|
||||
/** 创建时间戳 */
|
||||
createdAt: number;
|
||||
|
||||
/** 最后更新时间戳 */
|
||||
updatedAt: number;
|
||||
}
|
||||
395
shared/types/sillytavern.types.ts
Normal file
395
shared/types/sillytavern.types.ts
Normal file
@@ -0,0 +1,395 @@
|
||||
/**
|
||||
* SillyTavern 兼容的数据结构定义
|
||||
* 这些类型严格遵循 SillyTavern 官方规范,用于导入导出兼容
|
||||
*/
|
||||
|
||||
// ==================== 世界书 (World Info) ====================
|
||||
|
||||
/**
|
||||
* SillyTavern 世界书条目激活方式枚举
|
||||
*/
|
||||
export enum STWorldInfoEntryActivation {
|
||||
/** 永久激活 - 蓝色圆圈 */
|
||||
CONSTANT = 'constant',
|
||||
/** 关键词触发 - 绿色圆圈 */
|
||||
KEYWORD = 'keyword',
|
||||
/** 向量检索 RAG - 链条链接 */
|
||||
VECTOR = 'vector',
|
||||
/** 禁用 - 红色叉 */
|
||||
DISABLED = 'disabled',
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern 世界书条目插入位置
|
||||
*/
|
||||
export enum STWorldInfoEntryPosition {
|
||||
/** 在角色定义之前 */
|
||||
BEFORE_CHAR = 'before_char',
|
||||
/** 在角色定义之后 */
|
||||
AFTER_CHAR = 'after_char',
|
||||
/** 在示例消息之前 */
|
||||
BEFORE_EXAMPLE = 'before_example',
|
||||
/** 在示例消息之后 */
|
||||
AFTER_EXAMPLE = 'after_example',
|
||||
/** 在作者注释顶部 */
|
||||
AUTHOR_NOTE_TOP = 'author_note_top',
|
||||
/** 在作者注释底部 */
|
||||
AUTHOR_NOTE_BOTTOM = 'author_note_bottom',
|
||||
/** 指定深度位置 @D */
|
||||
AT_DEPTH = 'at_depth',
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern 世界书条目可选过滤器逻辑
|
||||
*/
|
||||
export enum STWorldInfoFilterLogic {
|
||||
/** AND ANY - 任意一个可选关键字匹配 */
|
||||
AND_ANY = 'and_any',
|
||||
/** AND ALL - 所有可选关键字都匹配 */
|
||||
AND_ALL = 'and_all',
|
||||
/** NOT ANY - 没有任何可选关键字匹配 */
|
||||
NOT_ANY = 'not_any',
|
||||
/** NOT ALL - 不是所有可选关键字都匹配 */
|
||||
NOT_ALL = 'not_all',
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern 世界书条目角色类型
|
||||
*/
|
||||
export enum STWorldInfoEntryRole {
|
||||
/** 系统角色 */
|
||||
SYSTEM = 'system',
|
||||
/** 用户角色 */
|
||||
USER = 'user',
|
||||
/** 助手角色 */
|
||||
ASSISTANT = 'assistant',
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern 世界书条目原始结构
|
||||
* 参考: https://st-docs.role.fun/usage/core-concepts/worldinfo/
|
||||
*/
|
||||
export interface STWorldInfoEntry {
|
||||
/** 条目唯一标识符 */
|
||||
uid: string;
|
||||
|
||||
/** 条目标题/备忘录(仅用于人类阅读) */
|
||||
key?: string[];
|
||||
|
||||
/** 触发关键词列表(支持正则表达式) */
|
||||
keysecondary?: string[];
|
||||
|
||||
/** 可选过滤器逻辑 */
|
||||
filter?: STWorldInfoFilterLogic;
|
||||
|
||||
/** 条目内容 - 激活时注入到提示的文本 */
|
||||
content: string;
|
||||
|
||||
/** 常量标志 - true 表示永久激活(蓝色圆圈) */
|
||||
constant?: boolean;
|
||||
|
||||
/** 选择性标志 - 用于向量检索 */
|
||||
selective?: boolean;
|
||||
|
||||
/** 插入顺序 - 数值越大越靠近末尾 */
|
||||
order: number;
|
||||
|
||||
/** 插入位置 */
|
||||
position: STWorldInfoEntryPosition;
|
||||
|
||||
/** 插入深度(当 position 为 AT_DEPTH 时使用) */
|
||||
depth?: number;
|
||||
|
||||
/** 角色类型(当使用角色消息插入时) */
|
||||
role?: STWorldInfoEntryRole;
|
||||
|
||||
/** 概率 - 0-100,控制激活时的插入概率 */
|
||||
probability?: number;
|
||||
|
||||
/** 包含组 - 逗号分隔的组标签列表 */
|
||||
group?: string[];
|
||||
|
||||
/** 优先包含 - true 时按 order 值选择而非随机 */
|
||||
groupPrioritize?: boolean;
|
||||
|
||||
/** 使用组评分 - 基于关键字匹配数量选择 */
|
||||
useGroupScoring?: boolean;
|
||||
|
||||
/** 自动化 ID - 与 ST 脚本集成 */
|
||||
automationId?: string;
|
||||
|
||||
/** 是否禁用 */
|
||||
disable?: boolean;
|
||||
|
||||
/** 扩展字段 */
|
||||
extensions?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern 世界书文件结构
|
||||
*/
|
||||
export interface STWorldInfo {
|
||||
/** 规范名称 */
|
||||
spec: string;
|
||||
|
||||
/** 世界书名称 */
|
||||
name?: string;
|
||||
|
||||
/** 世界书描述 */
|
||||
description?: string;
|
||||
|
||||
/** 条目数组 */
|
||||
entries: STWorldInfoEntry[];
|
||||
|
||||
/** 扩展字段 */
|
||||
extensions?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// ==================== 角色卡 (Character Card) ====================
|
||||
|
||||
/**
|
||||
* SillyTavern 角色卡规范版本
|
||||
*/
|
||||
export enum STCharacterCardSpec {
|
||||
V1 = 'chara_card_v1',
|
||||
V2 = 'chara_card_v2',
|
||||
V3 = 'chara_card_v3',
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern 角色卡 V2/V3 数据结构
|
||||
* 参考: https://github.com/malfoyslastname/character-card-spec-v2
|
||||
*/
|
||||
export interface STCharacterCardData {
|
||||
/** 角色名称 */
|
||||
name: string;
|
||||
|
||||
/** 角色描述 */
|
||||
description: string;
|
||||
|
||||
/** 角色性格特征 */
|
||||
personality: string;
|
||||
|
||||
/** 场景设定 */
|
||||
scenario: string;
|
||||
|
||||
/** 首条开场消息 */
|
||||
first_mes: string;
|
||||
|
||||
/** 对话示例 */
|
||||
mes_example: string;
|
||||
|
||||
/** 替代问候语数组 */
|
||||
alternate_greetings?: string[];
|
||||
|
||||
/** 角色创建者备注 */
|
||||
creator_notes?: string;
|
||||
|
||||
/** 系统级别指令 */
|
||||
system_prompt?: string;
|
||||
|
||||
/** 历史后指令 */
|
||||
post_history_instructions?: string;
|
||||
|
||||
/** 标签数组 */
|
||||
tags?: string[];
|
||||
|
||||
/** 角色书(嵌入的世界书) */
|
||||
character_book?: STWorldInfo;
|
||||
|
||||
/** 扩展字段 */
|
||||
extensions?: {
|
||||
/** 绑定的世界书文件名 */
|
||||
world?: string;
|
||||
|
||||
/** 健谈程度 0-1 */
|
||||
talkativeness?: number;
|
||||
|
||||
/** 收藏状态 */
|
||||
fav?: boolean;
|
||||
|
||||
/** 其他扩展字段 */
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern 角色卡完整结构(V2/V3)
|
||||
*/
|
||||
export interface STCharacterCard {
|
||||
/** 规范标识 */
|
||||
spec: STCharacterCardSpec;
|
||||
|
||||
/** 规范版本 */
|
||||
spec_version?: string;
|
||||
|
||||
/** 角色数据 */
|
||||
data: STCharacterCardData;
|
||||
}
|
||||
|
||||
// ==================== 聊天记录 (Chat Log) ====================
|
||||
|
||||
/**
|
||||
* SillyTavern 聊天记录头(JSONL 第一行)
|
||||
*/
|
||||
export interface STChatHeader {
|
||||
/** 用户名称 */
|
||||
user_name: string;
|
||||
|
||||
/** 角色名称 */
|
||||
character_name: string;
|
||||
|
||||
/** 创建日期 */
|
||||
create_date: string;
|
||||
|
||||
/** 聊天元数据 */
|
||||
chat_metadata?: {
|
||||
/** 完整性校验哈希 */
|
||||
integrity?: string;
|
||||
|
||||
/** 其他元数据 */
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
/** 其他可能的头部字段 */
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern 聊天消息记录
|
||||
*/
|
||||
export interface STChatMessage {
|
||||
/** 发送者名称 */
|
||||
name: string;
|
||||
|
||||
/** 是否为用户消息 */
|
||||
is_user: boolean;
|
||||
|
||||
/** 是否为系统消息 */
|
||||
is_system?: boolean;
|
||||
|
||||
/** 发送日期时间戳或 ISO 字符串 */
|
||||
send_date: number | string;
|
||||
|
||||
/** 实际对话消息文本 */
|
||||
mes: string;
|
||||
|
||||
/** 替换回答数组(swipe 功能) */
|
||||
swipes?: string[];
|
||||
|
||||
/** 当前选择的 swipe ID */
|
||||
swipe_id?: number;
|
||||
|
||||
/** 是否为隐藏消息 */
|
||||
is_hidden?: boolean;
|
||||
|
||||
/** 扩展字段 */
|
||||
extra?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern 聊天记录文件(JSONL 格式)
|
||||
* 第一行是 STChatHeader,后续每行是 STChatMessage
|
||||
*/
|
||||
export type STChatLog = [STChatHeader, ...STChatMessage[]];
|
||||
|
||||
// ==================== 预设 (Preset) ====================
|
||||
|
||||
/**
|
||||
* SillyTavern 采样参数预设
|
||||
*/
|
||||
export interface STGenerationPreset {
|
||||
/** 预设名称 */
|
||||
name: string;
|
||||
|
||||
/** 温度 */
|
||||
temperature?: number;
|
||||
|
||||
/** Top P */
|
||||
top_p?: number;
|
||||
|
||||
/** Top K */
|
||||
top_k?: number;
|
||||
|
||||
/** 重复惩罚 */
|
||||
repetition_penalty?: number;
|
||||
|
||||
/** 频率惩罚 */
|
||||
frequency_penalty?: number;
|
||||
|
||||
/** 存在惩罚 */
|
||||
presence_penalty?: number;
|
||||
|
||||
/** 最大生成长度 */
|
||||
max_length?: number;
|
||||
|
||||
/** 其他采样参数 */
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// ==================== 提示词预设 (Prompt Preset) ====================
|
||||
|
||||
/**
|
||||
* SillyTavern Prompt 条目(外部兼容层)
|
||||
* 这是提示词模板的原始数据结构
|
||||
*/
|
||||
export interface STPrompt {
|
||||
/** 唯一标识符(稳定键,UUID 或内置标识如 'main_prompt', 'jailbreak' 等) */
|
||||
identifier: string;
|
||||
|
||||
/** 显示名称(可选,用于前端展示) */
|
||||
name?: string;
|
||||
|
||||
/** 提示词内容 */
|
||||
content: string;
|
||||
|
||||
/** 角色类型 */
|
||||
role: 'system' | 'assistant' | 'user';
|
||||
|
||||
/** 是否为标记节点(固有节点标志) */
|
||||
marker?: boolean;
|
||||
|
||||
/** 注入顺序元数据(外部层的注入优先级) */
|
||||
injection_order?: number;
|
||||
|
||||
/** 扩展字段 */
|
||||
extensions?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern Prompt 顺序配置项
|
||||
*/
|
||||
export interface STPromptOrderItem {
|
||||
/** 引用 prompts 中的 identifier */
|
||||
identifier: string;
|
||||
|
||||
/** 是否启用 */
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern Prompt 顺序配置(按角色)
|
||||
*/
|
||||
export interface STPromptOrderConfig {
|
||||
order: STPromptOrderItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* SillyTavern Prompt Preset 完整结构(外部兼容层)
|
||||
* 包含所有可用的提示词条目和各角色的顺序配置
|
||||
*/
|
||||
export interface STPromptPreset {
|
||||
/** 预设名称 */
|
||||
name: string;
|
||||
|
||||
/** 所有可用的提示词条目 */
|
||||
prompts: STPrompt[];
|
||||
|
||||
/** 各角色的顺序配置 */
|
||||
prompt_order: {
|
||||
[character_id: string]: STPromptOrderConfig;
|
||||
};
|
||||
|
||||
/** 默认顺序配置(未指定角色时使用) */
|
||||
default_prompt_order?: STPromptOrderConfig;
|
||||
}
|
||||
1
shared/utils/.gitkeep
Normal file
1
shared/utils/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
// Shared Utility Functions
|
||||
Reference in New Issue
Block a user