diff --git a/app/core/storage.py b/app/core/storage.py index c66c84ec..5defce4b 100644 --- a/app/core/storage.py +++ b/app/core/storage.py @@ -41,6 +41,20 @@ def json_dumps(obj: Any) -> str: def json_loads(obj: str | bytes) -> Any: return orjson.loads(obj) + +def _toml_escape_string(value: str) -> str: + """Escape a string so it is safe in a single-line TOML basic string.""" + return ( + value + .replace("\\", "\\\\") + .replace('"', '\\"') + .replace("\b", "\\b") + .replace("\f", "\\f") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + ) + class StorageError(Exception): """存储服务基础异常""" pass @@ -169,14 +183,14 @@ async def save_config(self, data: Dict[str, Any]): if isinstance(val, bool): val_str = "true" if val else "false" elif isinstance(val, str): - escaped = val.replace('"', '\\"') + escaped = _toml_escape_string(val) val_str = f'"{escaped}"' elif isinstance(val, (int, float)): val_str = str(val) elif isinstance(val, (list, dict)): val_str = json_dumps(val) else: - val_str = f'"{str(val)}"' + val_str = f'"{_toml_escape_string(str(val))}"' lines.append(f"{key} = {val_str}") lines.append("") diff --git a/app/services/grok/chat.py b/app/services/grok/chat.py index 747d9a6a..69eacc09 100644 --- a/app/services/grok/chat.py +++ b/app/services/grok/chat.py @@ -32,6 +32,15 @@ BROWSER = "chrome136" +def _read_custom_personality() -> str: + """Read configured custom personality text and keep original formatting.""" + value = get_config("grok.custom_personality", "") + if value is None: + return "" + text = str(value) + return text if text.strip() else "" + + @dataclass class ChatRequest: """聊天请求数据""" @@ -256,6 +265,15 @@ def build_payload( } } + @staticmethod + def apply_custom_personality(payload: Dict[str, Any]) -> Dict[str, Any]: + """Inject optional customPersonality into upstream payload.""" + custom_personality = _read_custom_personality() + if custom_personality: + payload["customPersonality"] = custom_personality + else: + payload.pop("customPersonality", None) + return payload # ==================== Grok 服务 ==================== @@ -300,6 +318,7 @@ async def chat( message, model, mode, think, file_attachments, image_attachments ) + payload = ChatRequestBuilder.apply_custom_personality(payload) proxies = {"http": self.proxy, "https": self.proxy} if self.proxy else None timeout = get_config("grok.timeout", TIMEOUT) diff --git a/app/services/grok/imagine_experimental.py b/app/services/grok/imagine_experimental.py index 044bc3e7..07f60925 100644 --- a/app/services/grok/imagine_experimental.py +++ b/app/services/grok/imagine_experimental.py @@ -365,6 +365,7 @@ async def chat_edit( last_error: Optional[Exception] = None for payload in payloads: + payload = ChatRequestBuilder.apply_custom_personality(payload) session = AsyncSession(impersonate=BROWSER) response = None try: diff --git a/app/services/grok/media.py b/app/services/grok/media.py index 987bb803..9b273e37 100644 --- a/app/services/grok/media.py +++ b/app/services/grok/media.py @@ -17,6 +17,7 @@ from app.services.token import get_token_manager from app.services.grok.processor import VideoStreamProcessor, VideoCollectProcessor from app.services.request_stats import request_stats +from app.services.grok.chat import ChatRequestBuilder # API 端点 CREATE_POST_API = "https://grok.com/rest/media/post/create" @@ -243,6 +244,7 @@ async def generate( # Step 2: 建立连接 headers = self._build_headers(token) payload = self._build_payload(prompt, post_id, aspect_ratio, video_length, resolution, preset) + payload = ChatRequestBuilder.apply_custom_personality(payload) session = AsyncSession(impersonate=BROWSER) response = await session.post( @@ -323,6 +325,7 @@ async def generate_from_image( # Step 2: 建立连接 headers = self._build_headers(token) payload = self._build_payload(prompt, post_id, aspect_ratio, video_length, resolution, preset) + payload = ChatRequestBuilder.apply_custom_personality(payload) session = AsyncSession(impersonate=BROWSER) response = await session.post( diff --git a/app/static/config/config.js b/app/static/config/config.js index 99eb43b6..8684de1c 100644 --- a/app/static/config/config.js +++ b/app/static/config/config.js @@ -35,6 +35,7 @@ const LOCALE_MAP = { "temporary": { title: "临时对话", desc: "是否启用临时对话模式。" }, "stream": { title: "流式响应", desc: "是否默认启用流式输出。" }, "thinking": { title: "思维链", desc: "是否启用模型思维链输出。" }, + "custom_personality": { title: "自定义指令", desc: "附加到所有 Grok 聊天请求的自定义指令内容(支持多行)。" }, "dynamic_statsig": { title: "动态指纹", desc: "是否启用动态生成 Statsig 值。" }, "filter_tags": { title: "过滤标签", desc: "自动过滤 Grok 响应中的特殊标签。" }, "video_poster_preview": { title: "视频海报预览", desc: "启用后会将返回内容中的