diff --git a/src/plugins/built_in/maizone_refactored/services/content_service.py b/src/plugins/built_in/maizone_refactored/services/content_service.py index 2dc95d949..38442fd09 100644 --- a/src/plugins/built_in/maizone_refactored/services/content_service.py +++ b/src/plugins/built_in/maizone_refactored/services/content_service.py @@ -375,3 +375,63 @@ class ContentService: except Exception as e: logger.error(f"生成基于活动的说说内容异常: {e}") return "" + + + async def generate_random_topic(self) -> str: + """ + 使用一个小型、高效的模型来动态生成一个随机的说说主题。 + """ + try: + # 硬编码使用 'utils_small' 模型 + model_name = "utils_small" + models = llm_api.get_available_models() + model_config = models.get(model_name) + + if not model_config: + logger.error(f"无法找到用于生成主题的模型: {model_name}") + return "" + + prompt = """ + 请你扮演一个想法的“生成器”。 + 你的任务是,随机给出一个适合在QQ空间上发表说说的“主题”或“灵感”。 + 这个主题应该非常简短,通常是一个词、一个短语或一个开放性的问题,用于激发创作。 + + 规则: + 1. **绝对简洁**:输出长度严格控制在15个字以内。 + 2. **多样性**:主题可以涉及日常生活、情感、自然、科技、哲学思考等任何方面。 + 3. **激发性**:主题应该是开放的,能够引发出一条内容丰富的说说。 + 4. **随机性**:每次给出的主题都应该不同。 + 5. **仅输出主题**:你的回答应该只有主题本身,不包含任何解释、引号或多余的文字。 + + 好的例子: + - 一部最近看过的老电影 + - 夏天傍晚的晚霞 + - 关于拖延症的思考 + - 一个奇怪的梦 + - 雨天听什么音乐? + + 错误的例子: + - “我建议的主题是:一部最近看过的老电影” (错误:包含了多余的文字) + - “夏天傍晚的晚霞,那种橙色与紫色交织的感觉,总是能让人心生宁静。” (错误:太长了,变成了说说本身而不是主题) + + 现在,请给出一个随机主题。 + """ + + success, topic, _, _ = await llm_api.generate_with_model( + prompt=prompt, + model_config=model_config, + request_type="story.generate.topic", + temperature=0.8, # 提高创造性以获得更多样的主题 + max_tokens=50, + ) + + if success and topic: + logger.info(f"成功生成随机主题: '{topic}'") + return topic.strip() + else: + logger.error("生成随机主题失败") + return "" + + except Exception as e: + logger.error(f"生成随机主题时发生异常: {e}") + return "" diff --git a/src/plugins/built_in/maizone_refactored/services/scheduler_service.py b/src/plugins/built_in/maizone_refactored/services/scheduler_service.py index 2aee69b57..d5437c0fa 100644 --- a/src/plugins/built_in/maizone_refactored/services/scheduler_service.py +++ b/src/plugins/built_in/maizone_refactored/services/scheduler_service.py @@ -14,6 +14,8 @@ from sqlalchemy import select from src.common.database.compatibility import get_db_session from src.common.database.core.models import MaiZoneScheduleStatus from src.common.logger import get_logger +from src.config.config import model_config as global_model_config +from src.plugin_system.apis import llm_api from src.schedule.schedule_manager import schedule_manager from .qzone_service import QZoneService @@ -61,10 +63,40 @@ class SchedulerService: pass # 任务取消是正常操作 logger.info("基于日程表的说说定时发送任务已停止。") + async def _generate_random_topic(self) -> str | None: + """ + 使用小模型生成一个随机的说说主题。 + """ + try: + logger.info("尝试生成随机说说主题...") + prompt = "请生成一个有趣、简短、积极向上的日常一句话,适合作为社交媒体的动态内容,例如关于天气、心情、动漫、游戏或者某个小发现。请直接返回这句话,不要包含任何多余的解释或标签。" + + task_config = global_model_config.model_task_config.get_task("utils_small") + if not task_config: + logger.error("未找到名为 'utils_small' 的模型任务配置。") + return None + + success, content, _, _ = await llm_api.generate_with_model( + model_config=task_config, + prompt=prompt, + max_tokens=150, + temperature=0.9, + ) + + if success and content and content.strip(): + logger.info(f"成功生成随机主题: {content.strip()}") + return content.strip() + logger.warning("LLM未能生成有效的主题。") + return None + except Exception as e: + logger.error(f"生成随机主题时发生错误: {e}") + return None + async def _schedule_loop(self): """ 定时任务的核心循环。 每隔一段时间检查当前是否有日程活动,并判断是否需要触发发送流程。 + 也支持在没有日程时,根据配置进行不定时发送。 """ while self.is_running: try: @@ -73,52 +105,62 @@ class SchedulerService: await asyncio.sleep(60) # 如果被禁用,则每分钟检查一次状态 continue - # 2. 获取当前时间的日程活动 - current_activity = schedule_manager.get_current_activity() - logger.info(f"当前检测到的日程活动: {current_activity}") + now = datetime.datetime.now() + hour_str = now.strftime("%Y-%m-%d %H") - if current_activity: - # 3. 检查当前时间是否在禁止发送的时间段内 - now = datetime.datetime.now() - forbidden_start = self.get_config("schedule.forbidden_hours_start", 2) - forbidden_end = self.get_config("schedule.forbidden_hours_end", 6) + # 2. 检查是否在禁止发送的时间段内 + forbidden_start = self.get_config("schedule.forbidden_hours_start", 2) + forbidden_end = self.get_config("schedule.forbidden_hours_end", 6) + is_forbidden_time = ( + (forbidden_start < forbidden_end and forbidden_start <= now.hour < forbidden_end) + or (forbidden_start > forbidden_end and (now.hour >= forbidden_start or now.hour < forbidden_end)) + ) - is_forbidden_time = False - if forbidden_start < forbidden_end: - # 例如,2点到6点 - is_forbidden_time = forbidden_start <= now.hour < forbidden_end + if is_forbidden_time: + logger.info(f"当前时间 {now.hour}点 处于禁止发送时段 ({forbidden_start}-{forbidden_end}),本次跳过。") + else: + # 3. 获取当前时间的日程活动 + current_activity_dict = schedule_manager.get_current_activity() + logger.info(f"当前检测到的日程活动: {current_activity_dict}") + + if current_activity_dict: + # --- 有日程活动时的逻辑 --- + current_activity_name = current_activity_dict.get("activity", str(current_activity_dict)) + if current_activity_dict != self.last_processed_activity: + logger.info(f"检测到新的日程活动: '{current_activity_name}',准备发送说说。") + result = await self.qzone_service.send_feed_from_activity(current_activity_name) + await self._mark_as_processed( + hour_str, current_activity_name, result.get("success", False), result.get("message", "") + ) + self.last_processed_activity = current_activity_dict + else: + logger.info(f"活动 '{current_activity_name}' 与上次相同,本次跳过。") else: - # 例如,23点到第二天7点 - is_forbidden_time = now.hour >= forbidden_start or now.hour < forbidden_end + # --- 没有日程活动时的逻辑 --- + activity_placeholder = "No Schedule - Random" + if not await self._is_processed(hour_str, activity_placeholder): + logger.info("没有日程活动,但开启了无日程发送功能,准备生成随机主题。") + topic = await self._generate_random_topic() + if topic: + result = await self.qzone_service.send_feed(topic=topic, stream_id=None) + await self._mark_as_processed( + hour_str, + activity_placeholder, + result.get("success", False), + result.get("message", ""), + ) + else: + logger.error("未能生成随机主题,本次不发送。") + # 即使生成失败,也标记为已处理,防止本小时内反复尝试 + await self._mark_as_processed( + hour_str, activity_placeholder, False, "Failed to generate topic" + ) + else: + logger.info(f"当前小时 {hour_str} 已执行过无日程发送任务,本次跳过。") - if is_forbidden_time: - logger.info( - f"当前时间 {now.hour}点 处于禁止发送时段 ({forbidden_start}-{forbidden_end}),本次跳过。" - ) - self.last_processed_activity = current_activity - - # 4. 检查活动是否是新的活动 - elif current_activity != self.last_processed_activity: - logger.info(f"检测到新的日程活动: '{current_activity}',准备发送说说。") - - # 5. 调用QZoneService执行完整的发送流程 - result = await self.qzone_service.send_feed_from_activity(current_activity) - - # 6. 将处理结果记录到数据库 - now = datetime.datetime.now() - hour_str = now.strftime("%Y-%m-%d %H") - await self._mark_as_processed( - hour_str, current_activity, result.get("success", False), result.get("message", "") - ) - - # 7. 更新上一个处理的活动 - self.last_processed_activity = current_activity - else: - logger.info(f"活动 '{current_activity}' 与上次相同,本次跳过。") - - # 8. 计算并等待一个随机的时间间隔 - min_minutes = self.get_config("schedule.random_interval_min_minutes", 5) - max_minutes = self.get_config("schedule.random_interval_max_minutes", 15) + # 4. 计算并等待一个随机的时间间隔 + min_minutes = self.get_config("schedule.random_interval_min_minutes", 15) + max_minutes = self.get_config("schedule.random_interval_max_minutes", 45) wait_seconds = random.randint(min_minutes * 60, max_minutes * 60) logger.info(f"下一次检查将在 {wait_seconds / 60:.2f} 分钟后进行。") await asyncio.sleep(wait_seconds) @@ -133,10 +175,6 @@ class SchedulerService: async def _is_processed(self, hour_str: str, activity: str) -> bool: """ 检查指定的任务(某个小时的某个活动)是否已经被成功处理过。 - - :param hour_str: 时间字符串,格式为 "YYYY-MM-DD HH"。 - :param activity: 活动名称。 - :return: 如果已处理过,返回 True,否则返回 False。 """ try: async with get_db_session() as session: @@ -154,11 +192,6 @@ class SchedulerService: async def _mark_as_processed(self, hour_str: str, activity: str, success: bool, content: str): """ 将任务的处理状态和结果写入数据库。 - - :param hour_str: 时间字符串。 - :param activity: 活动名称。 - :param success: 发送是否成功。 - :param content: 最终发送的说说内容或错误信息。 """ try: async with get_db_session() as session: