diff --git a/backend/core/models/PromptComponent.py b/backend/core/models/PromptComponent.py new file mode 100644 index 0000000..42f8d15 --- /dev/null +++ b/backend/core/models/PromptComponent.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel, Field + + +class PromptComponent(BaseModel): + """预设组件类,代表一个独立的提示词模块""" + + identifier: str = Field(..., description="唯一标识符,用于引用和定位组件") + name: str = Field(..., description="组件显示名称") + content: str = Field("", description="组件内容文本") + # 0:System,1:User,2:Assistant + role: int = Field(0, description="角色身份(0:System,1:User,2:Assistant)") + system_prompt: bool = Field(False, description="是否强制作为系统提示词处理") + marker: bool = Field(False, description="是否为动态插入点占位符") + + diff --git a/backend/core/models/PromptList.py b/backend/core/models/PromptList.py new file mode 100644 index 0000000..8080d64 --- /dev/null +++ b/backend/core/models/PromptList.py @@ -0,0 +1,52 @@ +from pydantic import BaseModel, Field +import PromptComponent +from typing import List, Dict, Any + + +class AIDesignSpec(BaseModel): + """AI设计规范类,包含模型生成的核心参数和动态结构配置""" + + # [Base] 基础核心参数 + temperature: float = Field(1.0, description="生成温度,控制随机性(0-2)") + frequency_penalty: float = Field(0.0, description="频率惩罚,降低重复token概率") + presence_penalty: float = Field(0.0, description="存在惩罚,鼓励谈论新话题") + top_p: float = Field(1.0, description="核采样,控制词汇选择范围") + top_k: int = Field(0, description="随机采样范围,从概率最高的K个词中选择") + top_a: float = Field(0.0, description="基于平方概率分布的采样") + min_p: float = Field(0.0, description="最小概率阈值") + repetition_penalty: float = Field(1.0, description="重复惩罚系数(1.0-1.2)") + openai_max_context: int = Field(2048, description="上下文窗口大小(Token上限)") + openai_max_tokens: int = Field(250, description="单次回复的最大长度") + max_context_unlocked: bool = Field(False, description="是否允许超出限制的上下文") + names_behavior: int = Field(0, description="名字处理行为(0=默认,1=始终包含,2=仅角色)") + send_if_empty: str = Field("", description="用户发送空消息时自动填充的内容") + impersonation_prompt: str = Field("", description="模仿模式下使用的提示词") + new_chat_prompt: str = Field("", description="开启新聊天时自动发送的系统提示") + new_group_chat_prompt: str = Field("", description="开启新群组聊天时的提示") + new_example_chat_prompt: str = Field("", description="新示例聊天的提示") + continue_nudge_prompt: str = Field("", description="续写功能触发的提示词") + bias_preset_selected: str = Field("", description="选用的偏见预设") + wi_format: str = Field("{0}", description="世界书条目的格式化字符串") + scenario_format: str = Field("{{scenario}}", description="场景描述的格式化字符串") + personality_format: str = Field("", description="角色性格的格式化字符串") + group_nudge_prompt: str = Field("", description="群组聊天中提示AI仅以特定角色回复的提示词") + stream_openai: bool = Field(True, description="是否使用流式输出") + assistant_prefill: str = Field("", description="强制AI回复的开头内容") + assistant_impersonation: str = Field("", description="模仿模式下强制AI回复的开头内容") + use_sysprompt: bool = Field(True, description="是否强制将提示词注入系统层") + squash_system_messages: bool = Field(False, description="是否压缩系统消息") + media_inlining: bool = Field(False, description="是否内联媒体描述") + continue_prefill: bool = Field(True, description="续写时是否预填充内容") + continue_postfix: str = Field(" ", description="续写时添加的后缀") + seed: int = Field(-1, description="随机种子(-1为随机)") + n: int = Field(1, description="生成回复的数量") + + # [Dynamic] 动态结构 + prompts: List[PromptComponent] = Field( + default_factory=list, + description="组件库,定义所有可用的积木块" + ) + prompt_order: List[str] = Field( + default_factory=list, + description="组装说明书,定义构建最终提示词的顺序" + ) diff --git a/backend/nodes/PresetAssemblyNode.py b/backend/nodes/PresetAssemblyNode.py new file mode 100644 index 0000000..b03932c --- /dev/null +++ b/backend/nodes/PresetAssemblyNode.py @@ -0,0 +1,231 @@ +from typing import List, Dict, Any, Optional +from pydantic import BaseModel, Field +from backend.core.models.PromptList import AIDesignSpec +from backend.core.models.PromptComponent import PromptComponent +from enum import Enum + +from enum import Enum + + +class SpecialIdentifier(str, Enum): + """ + 特殊组件标识符枚举 + 定义所有提示词组件的类型及其在最终 Prompt 中的默认物理流向 + 顺序大致遵循:系统层 -> 角色层 -> 动态层 -> 历史层 -> 尾部指令 + """ + + WORLD_INFO_BEFORE = "worldInfoBefore" + """前置世界书:通常用于全局设定(如物理法则),紧接在 Main Prompt 之后,拥有最高优先级""" + + PERSONA_DESCRIPTION = "personaDescription" + """用户设定:告诉 AI {{user}} 是谁,通常放在场景之后,完成“谁在对谁说话”的闭环""" + + ENHANCE_DEFINITIONS = "enhanceDefinitions" + """增强定义:通常是 "If you have more knowledge...",用于补充 AI 的知识库,这里用rag获取""" + + WORLD_INFO_AFTER = "worldInfoAfter" + """后置世界书:通常用于特定场景规则,位于中间层底部,用于覆盖或补充前面的全局设定""" + + CHAT_HISTORY = "chatHistory" + """聊天历史:包含用户与 AI 的过往对话,占据提示词的下半部分""" + + JAILBREAK = "jailbreak" + """后置指令/注释,也即d0层:通常位于聊天记录之后、AI 生成之前,用于最后时刻的强调(如“不要重复”)""" + +class PresetAssemblyNode(BaseModel): + """预设组装节点类,负责根据组装指令动态组装提示词内容""" + + # 输入数据 + design_spec: AIDesignSpec = Field( + ..., + description="AI设计规范,包含组件库和组装顺序" + ) + target_character_id: int = Field( + ..., + description="目标角色ID,用于选择对应的组装指令" + ) + + # 内部状态(不参与序列化) + _component_map: Dict[str, PromptComponent] = Field( + default_factory=dict, + description="组件标识符到组件对象的映射" + ) + + def __init__(self, **data): + """初始化方法,构建组件映射""" + super().__init__(**data) + # 构建组件映射字典,提高查找效率 + self._component_map = { + comp.identifier: comp + for comp in self.design_spec.prompts + } + + def _process_special_component(self, component: PromptComponent) -> Optional[Dict[str, Any]]: + """ + 处理特殊组件(marker为True的组件) + + 参数: + component: 要处理的组件 + + 返回: + Optional[Dict[str, Any]]: 处理后的消息,如果组件无法处理则返回None + """ + try: + # 尝试将标识符转换为枚举 + special_id = SpecialIdentifier(component.identifier) + + # 根据不同标识符执行不同处理逻辑 + if special_id == SpecialIdentifier.CHAT_HISTORY: + return self._handle_chat_history(component) + elif special_id == SpecialIdentifier.WORLD_INFO_BEFORE: + return self._handle_world_info_before(component) + elif special_id == SpecialIdentifier.WORLD_INFO_AFTER: + return self._handle_world_info_after(component) + elif special_id == SpecialIdentifier.CHAR_DESCRIPTION: + return self._handle_char_description(component) + else: + # 未知特殊组件,使用默认处理 + return self._process_regular_component(component) + except ValueError: + # 不是特殊标识符,使用默认处理 + return self._process_regular_component(component) + + def _process_special_component(self, component: PromptComponent) -> Optional[Dict[str, Any]]: + """ + 处理特殊组件(marker为True的组件) + + 参数: + component: 要处理的组件 + + 返回: + Optional[Dict[str, Any]]: 处理后的消息,如果组件无法处理则返回None + """ + try: + # 尝试将标识符转换为枚举 + special_id = SpecialIdentifier(component.identifier) + + # 根据不同标识符执行不同处理逻辑 + if special_id == SpecialIdentifier.CHAT_HISTORY: + return self._handle_chat_history(component) + elif special_id == SpecialIdentifier.WORLD_INFO_BEFORE: + return self._handle_world_info_before(component) + elif special_id == SpecialIdentifier.WORLD_INFO_AFTER: + return self._handle_world_info_after(component) + elif special_id == SpecialIdentifier.DIALOGUE_EXAMPLES: + return self._handle_dialogue_examples(component) + elif special_id == SpecialIdentifier.CHAR_DESCRIPTION: + return self._handle_char_description(component) + elif special_id == SpecialIdentifier.CHAR_PERSONALITY: + return self._handle_char_personality(component) + elif special_id == SpecialIdentifier.SCENARIO: + return self._handle_scenario(component) + elif special_id == SpecialIdentifier.PERSONA_DESCRIPTION: + return self._handle_persona_description(component) + else: + # 未知特殊组件,使用默认处理 + return self._process_regular_component(component) + except ValueError: + # 不是特殊标识符,使用默认处理 + return self._process_regular_component(component) + + def _process_regular_component(self, component: PromptComponent) -> Dict[str, Any]: + """ + 处理普通组件(marker为False的组件) + + 参数: + component: 要处理的组件 + + 返回: + Dict[str, Any]: 处理后的消息 + """ + # 角色映射表 + role_map = {0: "system", 1: "user", 2: "assistant"} + + # 构建消息 + message = { + "role": role_map.get(component.role, "system"), + "content": component.content + } + + # 添加系统提示词标记 + if component.system_prompt: + message["system_prompt"] = True + + return message + + # 以下为特殊组件处理方法 + + def _handle_chat_history(self, component: PromptComponent) -> Dict[str, Any]: + """ + 处理聊天历史组件 + + 参数: + component: 聊天历史组件 + + 返回: + Dict[str, Any]: 处理后的消息 + """ + # 这里应该从外部获取实际的聊天历史 + # 示例实现,实际需要根据业务逻辑调整 + return { + "role": "system", + "content": "聊天历史内容...", + "marker": True, + "type": "chat_history" + } + + def _handle_world_info_before(self, component: PromptComponent) -> Dict[str, Any]: + """ + 处理前置世界信息组件 + + 参数: + component: 世界信息组件 + + 返回: + Dict[str, Any]: 处理后的消息 + """ + # 这里应该从外部获取实际的世界信息 + return { + "role": "system", + "content": "前置世界信息...", + "marker": True, + "type": "world_info_before" + } + + def _handle_world_info_after(self, component: PromptComponent) -> Dict[str, Any]: + """ + 处理后置世界信息组件 + + 参数: + component: 世界信息组件 + + 返回: + Dict[str, Any]: 处理后的消息 + """ + # 这里应该从外部获取实际的世界信息 + return { + "role": "system", + "content": "后置世界信息...", + "marker": True, + "type": "world_info_after" + } + + + def _handle_char_description(self, component: PromptComponent) -> Dict[str, Any]: + """ + 处理角色描述组件 + + 参数: + component: 角色描述组件 + + 返回: + Dict[str, Any]: 处理后的消息 + """ + # 这里应该从外部获取实际的角色描述 + return { + "role": "system", + "content": "角色描述内容...", + "marker": True, + "type": "char_description" + } + diff --git a/frontend-react/src/Store/Slices/LeftTabsSlice.jsx b/frontend-react/src/Store/Slices/LeftTabsSlice.jsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend-react/src/components/SideBarLeft/SideBarLeft.jsx b/frontend-react/src/components/SideBarLeft/SideBarLeft.jsx index 2f83edd..f2cfaa2 100644 --- a/frontend-react/src/components/SideBarLeft/SideBarLeft.jsx +++ b/frontend-react/src/components/SideBarLeft/SideBarLeft.jsx @@ -3,6 +3,10 @@ import React from 'react'; import './SideBarLeft.css'; import { useSideBarLeftStore } from '../../Store/indexStore'; import useSideBarRightStore from '../../Store/Slices/SideBarLeftSlice'; +import Gallery from './tab/Gallery'; +import ApiConfig from './tab/ApiConfig'; +import Presets from './tab/Presets'; +import WorldBook from './tab/WorldBook'; const SideBarLeft = () => { const { activeTab, tabs, setActiveTab } = useSideBarLeftStore(); @@ -22,10 +26,10 @@ const SideBarLeft = () => {
- {activeTab === 'gallery' &&
画廊内容
} - {activeTab === 'api' &&
API配置内容
} - {activeTab === 'presets' &&
预设配置内容
} - {activeTab === 'worldbook' &&
世界书内容
} + {activeTab === 'gallery' && } + {activeTab === 'api' && } + {activeTab === 'presets' && } + {activeTab === 'worldbook' && }
); diff --git a/frontend-react/src/components/SideBarLeft/tab/ApiConfig.jsx b/frontend-react/src/components/SideBarLeft/tab/ApiConfig.jsx new file mode 100644 index 0000000..90d1bc4 --- /dev/null +++ b/frontend-react/src/components/SideBarLeft/tab/ApiConfig.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import '../tabcss/ApiConfig.css'; + +const ApiConfig = () => { + return ( +
+

API配置

+ {/* 在这里实现API配置的具体内容 */} +
+ ); +}; + +export default ApiConfig; diff --git a/frontend-react/src/components/SideBarLeft/tab/Gallery.jsx b/frontend-react/src/components/SideBarLeft/tab/Gallery.jsx new file mode 100644 index 0000000..5f3b242 --- /dev/null +++ b/frontend-react/src/components/SideBarLeft/tab/Gallery.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import '../tabcss/Gallery.css'; + +const Gallery = () => { + return ( +
+

画廊

+ {/* 在这里实现画廊的具体内容 */} +
+ ); +}; + +export default Gallery; diff --git a/frontend-react/src/components/SideBarLeft/tab/Presets.jsx b/frontend-react/src/components/SideBarLeft/tab/Presets.jsx new file mode 100644 index 0000000..5b033fb --- /dev/null +++ b/frontend-react/src/components/SideBarLeft/tab/Presets.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import '../tabcss/Presets.css'; + +const Presets = () => { + return ( +
+

预设配置

+ {/* 在这里实现预设配置的具体内容 */} +
+ ); +}; + +export default Presets; diff --git a/frontend-react/src/components/SideBarLeft/tab/WorldBook.jsx b/frontend-react/src/components/SideBarLeft/tab/WorldBook.jsx new file mode 100644 index 0000000..709f2e7 --- /dev/null +++ b/frontend-react/src/components/SideBarLeft/tab/WorldBook.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import '../tabcss/WorldBook.css'; + +const WorldBook = () => { + return ( +
+

世界书

+ {/* 在这里实现世界书的具体内容 */} +
+ ); +}; + +export default WorldBook; diff --git a/frontend-react/src/components/SideBarLeft/tabcss/ApiConfig.css b/frontend-react/src/components/SideBarLeft/tabcss/ApiConfig.css new file mode 100644 index 0000000..1e37d21 --- /dev/null +++ b/frontend-react/src/components/SideBarLeft/tabcss/ApiConfig.css @@ -0,0 +1 @@ +WorldBook.css \ No newline at end of file diff --git a/frontend-react/src/components/SideBarLeft/tabcss/Gallery.css b/frontend-react/src/components/SideBarLeft/tabcss/Gallery.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend-react/src/components/SideBarLeft/tabcss/Presets.css b/frontend-react/src/components/SideBarLeft/tabcss/Presets.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend-react/src/components/SideBarLeft/tabcss/WorldBook.css b/frontend-react/src/components/SideBarLeft/tabcss/WorldBook.css new file mode 100644 index 0000000..e69de29