diff --git a/.gitignore b/.gitignore
index 32e56ff..4e7cc2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,7 @@ dist-ssr
*.sw?
uploads
#docs
-.claude
+#.claude
plugs
-sillytavern
\ No newline at end of file
+sillytavern
+st
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index f21ff5d..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1,67 +0,0 @@
-# 云酒馆项目文档
-
-## 目录
-
-- [项目概述](./overview.md)
-- [架构设计](./architecture.md)
-- [API 文档](./api.md)
-- [前端开发指南](./frontend-guide.md)
-- [后端开发指南](./backend-guide.md)
-- [部署指南](./deployment.md)
-- [设计系统](./design-system/)
-
-## 项目架构
-
-```
-云酒馆平台
-├── 前端 (web-app)
-│ ├── 公共页面(首页、角色广场、角色详情)
-│ ├── 用户系统(登录、注册、用户中心)
-│ ├── 管理功能(角色卡管理、预设管理)
-│ └── 对话功能(聊天界面、历史记录)
-│
-└── 后端 (server)
- ├── 用户认证 (JWT)
- ├── 角色卡 API
- ├── 对话管理 API
- ├── 预设管理 API
- └── AI 集成
-```
-
-## 核心功能
-
-### 角色卡系统
-- 支持 PNG 格式角色卡(嵌入 JSON 数据)
-- 支持纯 JSON 格式角色卡
-- 角色信息编辑
-- 导入导出功能
-
-### 预设系统
-- 支持多种预设格式(SillyTavern、TavernAI 等)
-- 参数配置(Temperature、Top P、Top K 等)
-- 预设复制和导出
-
-### 对话系统
-- 实时消息发送
-- 对话历史管理
-- 多角色对话支持
-- 对话导出功能
-
-## 开发规范
-
-### 代码风格
-- 前端:ESLint + Prettier
-- 后端:ESLint
-- 提交信息:Conventional Commits
-
-### Git 工作流
-- main: 生产环境
-- develop: 开发环境
-- feature/*: 功能分支
-- bugfix/*: 修复分支
-
-## 快速链接
-
-- [前端 README](../web-app/README.md)
-- [后端 README](../server/README.md)
-- [设计系统](./design-system/)
diff --git a/docs/html/story_renderer_extract.js b/docs/html/story_renderer_extract.js
deleted file mode 100644
index 27b49bf..0000000
--- a/docs/html/story_renderer_extract.js
+++ /dev/null
@@ -1,590 +0,0 @@
- class StoryRenderer {
- constructor(dataSourceId) {
- this.dataSourceId = dataSourceId;
- this.yamlData = null;
- this.rootNode = null; // 根节点名称
- this.initElements();
- }
-
- // 初始化DOM元素引用
- initElements() {
- this.elements = {
- timeDisplay: document.getElementById('time-display'),
- locationDisplay: document.getElementById('location-display'),
- charactersContainer: document.getElementById('characters-container'),
- actionOwner: document.getElementById('action-owner'),
- optionsList: document.getElementById('options-list'),
- };
- }
-
- // 初始化方法
- init() {
- try {
- // 从script标签中加载YAML数据
- this.loadYamlFromScriptTag();
-
- // 如果没有有效数据(显示加载状态的情况),直接返回
- if (!this.yamlData) {
- this.setupEventListeners();
- return;
- }
-
- // 找到根节点
- this.findRootNode();
- this.renderAll();
- this.setupEventListeners();
- } catch (error) {
- this.handleError(error);
- }
- }
-
- // 从script标签加载并解析YAML数据
- loadYamlFromScriptTag() {
- const scriptElement = document.getElementById(this.dataSourceId);
- if (!scriptElement) {
- throw new Error('未找到id为"yaml-data-source"的script标签');
- }
-
- let yamlContent = scriptElement.textContent.trim();
-
- // 检查是否为真正的空内容
- if (!yamlContent) {
- // 当YAML内容为空时,设置默认的加载状态但不抛出错误
- this.showLoadingState();
- return; // 直接返回,不抛出错误
- }
-
- // 如果是"加载中..."文本,也显示加载状态
- if (yamlContent === '加载中...') {
- this.showLoadingState();
- return;
- }
-
- // 有内容,尝试解析YAML
- try {
- this.yamlData = jsyaml.load(yamlContent);
- } catch (e) {
- // YAML格式错误,应该弹出错误对话框
- throw new Error(`YAML格式错误: ${e.message}`);
- }
-
- if (!this.yamlData || Object.keys(this.yamlData).length === 0) {
- // 解析成功但数据为空,这是格式问题
- throw new Error('YAML解析成功但数据为空,请检查YAML格式是否正确');
- }
- }
-
- // 显示加载状态的独立方法
- showLoadingState() {
- this.elements.timeDisplay.textContent = '⏰ 加载中...';
- this.elements.locationDisplay.textContent = '📍 加载中...';
- this.elements.actionOwner.textContent = '加载中...';
- this.elements.charactersContainer.innerHTML = this.createEmptyState('数据加载中...');
- this.elements.optionsList.innerHTML =
- '
';
- }
-
- // 查找根节点
- findRootNode() {
- const rootNodeNames = Object.keys(this.yamlData);
- if (rootNodeNames.length === 0) {
- throw new Error('YAML数据中未找到任何根节点');
- }
-
- this.rootNode = rootNodeNames[0];
- }
-
- // 格式化节点名称,使其更易读
- formatNodeName(name) {
- // 提取emoji后面的文本(如果有emoji)
- const emojiMatch = name.match(/^(\p{Emoji}\s*)(.*)$/u);
- if (emojiMatch && emojiMatch[2]) {
- return emojiMatch[2];
- }
- return name;
- }
-
- // 渲染所有内容
- renderAll() {
- if (!this.rootNode || !this.yamlData[this.rootNode]) {
- throw new Error('未找到有效的根节点数据');
- }
-
- const rootData = this.yamlData[this.rootNode];
- this.renderHeaderInfo(rootData);
- this.renderCharacters(rootData);
- this.renderActionOptions(rootData);
- }
-
- // 渲染头部信息(日期和时间和地点)
- renderHeaderInfo(rootData) {
- // 查找日期时间字段
- const dateTimeField = this.findFieldByKeywords(rootData, ['日期', '时间', 'datetime', 'time']);
- // 查找地点字段
- const locationField = this.findFieldByKeywords(rootData, ['地点', '位置', 'location', 'place']);
-
- // 直接使用包含emoji的值
- this.elements.timeDisplay.textContent = dateTimeField ? rootData[dateTimeField] : '⏰ 时间未知';
- this.elements.locationDisplay.textContent = locationField ? rootData[locationField] : '📍 地点未知';
- }
-
- // 根据关键词查找字段名
- findFieldByKeywords(data, keywords) {
- if (!data || typeof data !== 'object') return null;
-
- const fields = Object.keys(data);
- for (const field of fields) {
- for (const keyword of keywords) {
- if (field.toLowerCase().includes(keyword.toLowerCase())) {
- return field;
- }
- }
- }
- return null;
- }
-
- // 渲染角色列表
- renderCharacters(rootData) {
- // 查找用户列表字段
- const userListField = this.findFieldByKeywords(rootData, ['用户', '角色', '列表', 'user', 'role', 'list']);
- const userList = userListField && Array.isArray(rootData[userListField]) ? rootData[userListField] : [];
-
- this.elements.charactersContainer.innerHTML = '';
-
- if (userList.length === 0) {
- this.elements.charactersContainer.innerHTML = this.createEmptyState('没有角色数据');
- return;
- }
-
- // 处理每个用户项
- userList.forEach(userItem => {
- // 检查是否有外层包装
- let userData = userItem;
-
- if (typeof userItem === 'object' && userItem !== null) {
- const userField = this.findFieldByKeywords(userItem, ['用户', 'user', '角色', 'role']);
- if (userField) {
- userData = userItem[userField];
- }
- }
-
- const characterCard = this.createCharacterCard(userData);
- if (characterCard) {
- this.elements.charactersContainer.appendChild(characterCard);
- }
- });
- }
-
- // 创建单个角色卡片
- createCharacterCard(userData) {
- if (!userData || typeof userData !== 'object') return null;
-
- const card = document.createElement('div');
- card.className =
- 'bg-dark rounded-xl border border-gray-700/30 p-3.5 shadow-sm card-hover character-card theme-transition';
-
- // 查找名字字段
- const nameField = this.findFieldByKeywords(userData, ['名字', '姓名', '名称', 'name']);
- const userName = nameField ? userData[nameField] : '👤 未知角色';
-
- // 创建标题
- const title = document.createElement('h3');
- title.className = 'font-bold text-lg mb-2 pb-1 border-b border-gray-700/30 theme-transition';
- title.textContent = `${this.formatNodeName(userName)}的状态`;
- card.appendChild(title);
-
- // 创建属性列表
- const attributesList = document.createElement('ul');
- attributesList.className = 'space-y-2 text-sm';
- card.appendChild(attributesList);
-
- // 处理所有属性
- Object.keys(userData).forEach(key => {
- // 跳过已经作为标题使用的名字节点
- if (key === nameField) return;
-
- // 创建属性项,直接使用包含emoji的值
- const attributeItem = this.createAttributeItem(key, userData[key]);
- if (attributeItem) {
- attributesList.appendChild(attributeItem);
- }
- });
-
- return card;
- }
-
- // 创建属性项
- createAttributeItem(key, value) {
- const item = document.createElement('li');
-
- // 处理数组类型
- if (Array.isArray(value)) {
- item.innerHTML = `${this.formatNodeName(key)}:`;
-
- const subList = document.createElement('ul');
- subList.className = 'list-disc list-inside ml-4 mt-1 space-y-1 text-gray-400 theme-transition';
-
- value.forEach(itemData => {
- if (typeof itemData === 'object' && itemData !== null) {
- const subKey = Object.keys(itemData)[0];
- const subValue = itemData[subKey];
- const subItem = document.createElement('li');
- subItem.textContent = subValue;
- subList.appendChild(subItem);
- } else {
- const subItem = document.createElement('li');
- subItem.textContent = itemData;
- subList.appendChild(subItem);
- }
- });
-
- item.appendChild(subList);
- }
- // 处理对象类型
- else if (typeof value === 'object' && value !== null) {
- item.innerHTML = `${this.formatNodeName(key)}:`;
-
- const subList = document.createElement('ul');
- subList.className = 'list-disc list-inside ml-4 mt-1 space-y-1 text-gray-400 theme-transition';
-
- Object.keys(value).forEach(subKey => {
- const subItem = document.createElement('li');
- subItem.textContent = value[subKey];
- subList.appendChild(subItem);
- });
-
- item.appendChild(subList);
- }
- // 处理普通文本值
- else if (value !== null && value !== undefined && value.toString().trim() !== '') {
- item.innerHTML = `${this.formatNodeName(key)}: ${value}`;
- }
-
- return item;
- }
-
- // 渲染行动选项
- renderActionOptions(rootData) {
- // 查找行动选项字段
- const actionOptionsField = this.findFieldByKeywords(rootData, ['行动', '选项', 'action', 'option']);
- const actionOptions =
- actionOptionsField && typeof rootData[actionOptionsField] === 'object' ? rootData[actionOptionsField] : {};
-
- // 设置行动所有者
- const ownerField = this.findFieldByKeywords(actionOptions, ['名字', '姓名', '所有者', 'owner', 'name']);
- this.elements.actionOwner.textContent = ownerField
- ? this.formatNodeName(actionOptions[ownerField])
- : '未知角色';
-
- // 渲染选项列表
- const optionsField = this.findFieldByKeywords(actionOptions, ['选项', '选择', 'option', 'choice']);
- const options = optionsField && Array.isArray(actionOptions[optionsField]) ? actionOptions[optionsField] : [];
-
- this.elements.optionsList.innerHTML = '';
-
- if (options.length === 0) {
- this.elements.optionsList.innerHTML = this.createEmptyState('没有可用选项');
- return;
- }
-
- options.forEach(optionText => {
- const optionItem = document.createElement('li');
- optionItem.className =
- 'pl-2 py-1 border-l-2 border-primary/30 ml-1 hover:border-primary/70 transition-colors text-gray-300 theme-transition';
- optionItem.textContent = optionText;
- this.elements.optionsList.appendChild(optionItem);
- });
- }
-
- // 创建空状态提示
- createEmptyState(message) {
- return `
- ${message}
-
`;
- }
-
- // 设置事件监听器
- setupEventListeners() {
- const detailsElement = document.querySelector('details');
- const contentElement = this.elements.charactersContainer;
-
- // 初始化高度为0以实现动画效果
- contentElement.style.maxHeight = '0';
-
- // 监听详情展开/折叠事件
- detailsElement.addEventListener('toggle', () => {
- if (detailsElement.open) {
- // 展开时设置实际高度
- setTimeout(() => {
- contentElement.style.maxHeight = contentElement.scrollHeight + 'px';
- }, 10);
- } else {
- // 折叠时设置高度为0
- contentElement.style.maxHeight = '0';
- }
- });
-
- // 根据自动折叠设置决定默认状态
- const autoCollapseToggle = document.getElementById('auto-collapse-toggle');
- if (autoCollapseToggle) {
- // 从本地存储读取设置,默认为true(折叠)
- const savedAutoCollapse = localStorage.getItem('autoCollapse');
- const shouldCollapse = savedAutoCollapse === null ? true : savedAutoCollapse === 'true';
- detailsElement.open = !shouldCollapse;
-
- // 如果默认展开,需要设置正确的高度
- if (!shouldCollapse) {
- setTimeout(() => {
- contentElement.style.maxHeight = contentElement.scrollHeight + 'px';
- }, 100);
- }
- } else {
- // 如果没有设置切换开关,默认折叠
- detailsElement.open = false;
- }
- }
-
- // 错误处理
- handleError(error) {
- console.error('渲染错误:', error);
-
- // 使用美化的错误弹窗
- showErrorModal(error.message);
-
- // 在角色状态区域显示错误信息
- this.elements.charactersContainer.innerHTML = `
-
- 状态栏渲染失败:
- ${error.message}
-
- `;
-
- // 在行动选项区域也显示错误信息
- this.elements.optionsList.innerHTML = `
-
-
-
-
-
行动选项加载失败
-
请检查YAML格式是否正确
-
-
-
- `;
- }
- }
-
- $(document).ready(function () {
- /**
- * 获取本楼消息
- * @returns {Object|null} 包含本楼消息信息的对象,失败时返回null
- */
- function getCurrentMessage() {
- try {
- if (typeof getCurrentMessageId !== 'function' || typeof getChatMessages !== 'function') {
- return null;
- }
-
- const currentMessageId = getCurrentMessageId();
- if (!currentMessageId && currentMessageId !== 0) return null;
-
- const messageData = getChatMessages(currentMessageId);
- if (!messageData) return null;
-
- return Array.isArray(messageData) && messageData.length > 0 ? messageData[0] : messageData;
- } catch (error) {
- console.error('获取消息失败:', error);
- return null;
- }
- }
-
- function extractMaintext(message) {
- if (!message || typeof message !== 'string') return '';
- const match = message.match(/([\s\S]*?)<\/maintext>/i);
- return match ? match[1].trim() : '';
- }
-
- /**
- * 从消息中提取Status_block内容
- * @param {string} message 消息文本
- * @returns {string} 提取的YAML状态内容
- */
- function extractStatusBlock(message) {
- if (!message || typeof message !== 'string') return '';
-
- const match = message.match(/\s*([\s\S]*?)\s*<\/Status_block>/i);
- return match ? cleanYamlContent(match[1].trim()) : '';
- }
-
- /**
- * 清理YAML内容,修复常见的格式问题
- * @param {string} yamlContent 原始YAML内容
- * @returns {string} 清理后的YAML内容
- */
- function cleanYamlContent(yamlContent) {
- if (!yamlContent) return '';
-
- return yamlContent
- .split('\n')
- .map(line => {
- if (line.trim() === '' || !line.trim().match(/^-\s*"/)) return line;
-
- const match = line.match(/^(\s*-\s*)"(.*)"\s*$/);
- if (!match) return line;
-
- const [, indent, content] = match;
- return content.includes('"') || content.includes("'")
- ? indent + "'" + content.replace(/'/g, "''") + "'"
- : indent + '"' + content + '"';
- })
- .join('\n');
- }
-
- /**
- * 更新YAML数据源
- * @param {string} yamlContent YAML格式的状态内容
- */
- function updateYamlDataSource(yamlContent) {
- const yamlScript = document.getElementById('yaml-data-source');
- if (!yamlScript) return;
-
- // 如果内容为空或无效,设置为加载中状态
- if (!yamlContent || typeof yamlContent !== 'string' || !yamlContent.trim()) {
- yamlScript.textContent = ''; // 设置为空,让后续处理显示加载状态
- return;
- }
-
- // 先设置内容,让StoryRenderer能处理格式错误
- yamlScript.textContent = yamlContent;
-
- // 验证YAML格式,如果有错误会被StoryRenderer捕获并处理
- try {
- jsyaml.load(yamlContent);
- } catch (error) {
- // 尝试修复常见的YAML错误
- const fixedYaml = attemptYamlFix(yamlContent, error);
- if (fixedYaml) {
- try {
- jsyaml.load(fixedYaml);
- yamlScript.textContent = fixedYaml;
- } catch (e) {
- console.error('YAML修复失败:', e.message);
- // 修复失败时保留原内容,让StoryRenderer显示具体错误
- }
- }
- // 如果无法修复,保留原内容,让StoryRenderer显示具体错误
- }
- }
-
- /**
- * 尝试修复常见的YAML错误
- * @param {string} yamlContent 有问题的YAML内容
- * @param {Error} error YAML解析错误
- * @returns {string|null} 修复后的YAML或null
- */
- function attemptYamlFix(yamlContent, error) {
- if (!(error.message.includes('bad indentation') || error.message.includes('quote'))) {
- return null;
- }
-
- return yamlContent
- .split('\n')
- .map(line => {
- const match = line.match(/^(\s*-\s*)"(.*)"\s*$/);
- if (!match) return line;
-
- const [, indent, content] = match;
- return content.includes('"')
- ? indent + "'" + content.replace(/'/g, "''") + "'"
- : indent + '"' + content + '"';
- })
- .join('\n');
- }
-
- /**
- * 更新maintext内容
- * @param {string} maintextContent maintext内容
- */
- function updateMaintext(maintextContent) {
- try {
- const maintextElement = document.getElementById('maintext');
- if (!maintextElement) return;
-
- // 如果内容为空或无效,设置为加载中状态
- if (!maintextContent || typeof maintextContent !== 'string' || !maintextContent.trim()) {
- maintextElement.textContent = '';
- } else {
- maintextElement.textContent = maintextContent;
- }
-
- formatMainText();
- } catch (error) {
- console.error('更新maintext失败:', error);
- // 如果更新失败,直接调用formatMainText,它会处理错误
- formatMainText();
- }
- }
-
- /**
- * 重新渲染状态栏
- */
- function reRenderStatusBar() {
- try {
- const yamlScript = document.getElementById('yaml-data-source');
- if (!yamlScript || !yamlScript.textContent) return;
-
- const storyRenderer = new StoryRenderer('yaml-data-source');
- storyRenderer.init();
- } catch (error) {
- console.error('重新渲染状态栏失败:', error);
- // 状态栏渲染失败时,错误处理由StoryRenderer.handleError处理
- // 这里不需要额外处理,因为StoryRenderer的init方法已经有handleError调用
- }
- }
-
- /**
- * 根据消息数据渲染整个页面
- * @param {Object} messageData 消息数据对象(格式参考test.json)
- */
- function renderPageFromMessage(messageData) {
- let actualMessageData = Array.isArray(messageData) && messageData.length > 0 ? messageData[0] : messageData;
-
- if (!actualMessageData || !actualMessageData.message || typeof actualMessageData.message !== 'string') {
- return;
- }
-
- const messageContent = actualMessageData.message;
-
- // 提取并更新maintext内容
- const maintextContent = extractMaintext(messageContent);
- if (maintextContent) {
- updateMaintext(maintextContent);
- }
-
- // 提取并更新Status_block内容
- const statusContent = extractStatusBlock(messageContent);
- if (statusContent) {
- updateYamlDataSource(statusContent);
- setTimeout(() => reRenderStatusBar(), 100);
- }
- }
-
- // 执行获取操作并处理结果
- try {
- const currentMessage = getCurrentMessage();
- if (currentMessage && typeof currentMessage === 'object') {
- renderPageFromMessage(currentMessage);
- }
- } catch (error) {
- console.error('获取或渲染消息时出错:', error);
- }
-
- window.statusBlockInitialized = true;
- });
-
-
-
-
-
-pm
-