Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6c5c3a493 | |||
| 164aa2ccd2 | |||
| f1599e26b3 | |||
| ed64a4d32d | |||
| 2ee4b431d4 | |||
| cd8a73ed19 | |||
| e6c985ce4e |
@@ -1,6 +1,5 @@
|
||||
from astrbot.core.config.astrbot_config import AstrBotConfig
|
||||
from astrbot import logger
|
||||
from astrbot.core.utils.personality import personalities
|
||||
from astrbot.core import html_renderer
|
||||
from astrbot.core import sp
|
||||
from astrbot.core.star.register import register_llm_tool as llm_tool
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。
|
||||
"""
|
||||
|
||||
VERSION = "3.4.6"
|
||||
VERSION = "3.4.7"
|
||||
DB_PATH = "data/data_v3.db"
|
||||
|
||||
# 默认配置
|
||||
@@ -30,7 +30,7 @@ DEFAULT_CONFIG = {
|
||||
"web_search": False,
|
||||
"identifier": False,
|
||||
"datetime_system_prompt": True,
|
||||
"default_personality": "如果用户寻求帮助或者打招呼,请告诉他可以用 /help 查看 AstrBot 帮助。",
|
||||
"default_personality": "default",
|
||||
"prompt_prefix": "",
|
||||
},
|
||||
"provider_stt_settings": {
|
||||
@@ -56,6 +56,14 @@ DEFAULT_CONFIG = {
|
||||
"pip_install_arg": "",
|
||||
"plugin_repo_mirror": "",
|
||||
"knowledge_db": {},
|
||||
"persona": [
|
||||
{
|
||||
"name": "default",
|
||||
"prompt": "如果用户寻求帮助或者打招呼,请告诉他可以用 /help 查看 AstrBot 帮助。",
|
||||
"begin_dialogs": [],
|
||||
"mood_imitation_dialogs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -465,9 +473,9 @@ CONFIG_METADATA_2 = {
|
||||
"hint": "启用后,会在系统提示词中加上当前机器的日期时间。",
|
||||
},
|
||||
"default_personality": {
|
||||
"description": "默认人格",
|
||||
"description": "默认采用的人格情景的名称",
|
||||
"type": "string",
|
||||
"hint": "默认人格(情境设置/System Prompt)文本。",
|
||||
"hint": "",
|
||||
},
|
||||
"prompt_prefix": {
|
||||
"description": "Prompt 前缀文本",
|
||||
@@ -476,6 +484,47 @@ CONFIG_METADATA_2 = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"persona": {
|
||||
"description": "人格情景设置",
|
||||
"type": "list",
|
||||
"config_template": {
|
||||
"新人格情景": {
|
||||
"name": "",
|
||||
"prompt": "",
|
||||
"begin_dialogs": [],
|
||||
"mood_imitation_dialogs": []
|
||||
}
|
||||
},
|
||||
"tmpl_display_title": "name",
|
||||
"items": {
|
||||
"name": {
|
||||
"description": "人格名称",
|
||||
"type": "string",
|
||||
"hint": "人格名称,用于在多个人格中区分。使用 /persona 指令可切换人格。在 大语言模型设置 处可以设置默认人格。",
|
||||
"obvious_hint": True
|
||||
},
|
||||
"prompt": {
|
||||
"description": "设定(系统提示词)",
|
||||
"type": "text",
|
||||
"hint": "填写人格的身份背景、性格特征、兴趣爱好、个人经历、口头禅等。",
|
||||
},
|
||||
"begin_dialogs": {
|
||||
"description": "预设对话",
|
||||
"type": "list",
|
||||
"items": {},
|
||||
"hint": "可选。在每个对话前会插入这些预设对话。格式要求:第一句为用户,第二句为助手,以此类推。",
|
||||
"obvious_hint": True
|
||||
},
|
||||
"mood_imitation_dialogs": {
|
||||
"description": "对话风格模仿",
|
||||
"type": "list",
|
||||
"items": {},
|
||||
"hint": "旨在让模型尽可能模仿学习到所填写的对话的语气风格。格式和 `预设对话` 一样。",
|
||||
"obvious_hint": True
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
"provider_stt_settings": {
|
||||
"description": "语音转文本(STT)",
|
||||
"type": "object",
|
||||
|
||||
@@ -38,11 +38,13 @@ class WebChatAdapter(Platform):
|
||||
)
|
||||
|
||||
async def send_by_session(self, session: MessageSesion, message_chain: MessageChain):
|
||||
# abm.session_id = f"webchat!{username}!{cid}"
|
||||
plain = ""
|
||||
cid = session.session_id.split("!")[-1]
|
||||
for comp in message_chain.chain:
|
||||
if isinstance(comp, Plain):
|
||||
plain += comp.text
|
||||
web_chat_back_queue.put_nowait(plain)
|
||||
web_chat_back_queue.put_nowait((plain, cid))
|
||||
|
||||
await super().send_by_session(session, message_chain)
|
||||
|
||||
|
||||
@@ -16,9 +16,11 @@ class WebChatMessageEvent(AstrMessageEvent):
|
||||
web_chat_back_queue.put_nowait(None)
|
||||
return
|
||||
|
||||
cid = self.session_id.split("!")[-1]
|
||||
|
||||
for comp in message.chain:
|
||||
if isinstance(comp, Plain):
|
||||
web_chat_back_queue.put_nowait(comp.text)
|
||||
web_chat_back_queue.put_nowait((comp.text, cid))
|
||||
elif isinstance(comp, Image):
|
||||
# save image to local
|
||||
filename = str(uuid.uuid4()) + ".jpg"
|
||||
@@ -30,6 +32,6 @@ class WebChatMessageEvent(AstrMessageEvent):
|
||||
f.write(f2.read())
|
||||
elif comp.file and comp.file.startswith("http"):
|
||||
await download_image_by_url(comp.file, path=path)
|
||||
web_chat_back_queue.put_nowait(f"[IMAGE]{filename}")
|
||||
web_chat_back_queue.put_nowait((f"[IMAGE]{filename}", cid))
|
||||
web_chat_back_queue.put_nowait(None)
|
||||
await super().send(message)
|
||||
@@ -1,6 +1,6 @@
|
||||
import traceback
|
||||
from astrbot.core.config.astrbot_config import AstrBotConfig
|
||||
from .provider import Provider, STTProvider
|
||||
from .provider import Provider, STTProvider, Personality
|
||||
from .entites import ProviderType
|
||||
from typing import List
|
||||
from astrbot.core.db import BaseDatabase
|
||||
@@ -13,6 +13,52 @@ class ProviderManager():
|
||||
self.providers_config: List = config['provider']
|
||||
self.provider_settings: dict = config['provider_settings']
|
||||
self.provider_stt_settings: dict = config.get('provider_stt_settings', {})
|
||||
self.persona_configs: list = config.get('persona', [])
|
||||
|
||||
self.default_persona_name = self.provider_settings.get('default_personality', 'default')
|
||||
self.personas: List[Personality] = []
|
||||
self.selected_default_persona = None
|
||||
for persona in self.persona_configs:
|
||||
begin_dialogs = persona.get("begin_dialogs", [])
|
||||
mood_imitation_dialogs = persona.get("mood_imitation_dialogs", [])
|
||||
bd_processed = []
|
||||
mid_processed = ""
|
||||
if begin_dialogs:
|
||||
if len(begin_dialogs) % 2 != 0:
|
||||
logger.error(f"{persona['name']} 人格情景预设对话格式不对,条数应该为偶数。")
|
||||
continue
|
||||
user_turn = True
|
||||
for dialog in begin_dialogs:
|
||||
bd_processed.append({
|
||||
"role": "user" if user_turn else "assistant",
|
||||
"content": dialog,
|
||||
"_no_save": None # 不持久化到 db
|
||||
})
|
||||
user_turn = not user_turn
|
||||
if mood_imitation_dialogs:
|
||||
if len(mood_imitation_dialogs) % 2 != 0:
|
||||
logger.error(f"{persona['name']} 对话风格对话格式不对,条数应该为偶数。")
|
||||
continue
|
||||
user_turn = True
|
||||
for dialog in begin_dialogs:
|
||||
role = "A" if user_turn else "B"
|
||||
mid_processed += f"{role}: {dialog}\n"
|
||||
if not user_turn:
|
||||
mid_processed += '\n'
|
||||
user_turn = not user_turn
|
||||
|
||||
try:
|
||||
persona = Personality(
|
||||
**persona,
|
||||
_begin_dialogs_processed=bd_processed,
|
||||
_mood_imitation_dialogs_processed=mid_processed
|
||||
)
|
||||
if persona['name'] == self.default_persona_name:
|
||||
self.selected_default_persona = persona
|
||||
self.personas.append(persona)
|
||||
except Exception as e:
|
||||
logger.error(f"解析 Persona 配置失败:{e}")
|
||||
|
||||
|
||||
self.provider_insts: List[Provider] = []
|
||||
'''加载的 Provider 的实例'''
|
||||
@@ -26,6 +72,7 @@ class ProviderManager():
|
||||
self.loaded_ids = defaultdict(bool)
|
||||
self.db_helper = db_helper
|
||||
|
||||
# kdb(experimental)
|
||||
self.curr_kdb_name = ""
|
||||
kdb_cfg = config.get("knowledge_db", {})
|
||||
if kdb_cfg and len(kdb_cfg):
|
||||
@@ -94,7 +141,13 @@ class ProviderManager():
|
||||
|
||||
elif provider_metadata.provider_type == ProviderType.CHAT_COMPLETION:
|
||||
# 文本生成任务
|
||||
inst = provider_metadata.cls_type(provider_config, self.provider_settings, self.db_helper, self.provider_settings.get('persistant_history', True))
|
||||
inst = provider_metadata.cls_type(
|
||||
provider_config,
|
||||
self.provider_settings,
|
||||
self.db_helper,
|
||||
self.provider_settings.get('persistant_history', True),
|
||||
self.selected_default_persona
|
||||
)
|
||||
|
||||
if getattr(inst, "initialize", None):
|
||||
await inst.initialize()
|
||||
|
||||
@@ -11,6 +11,13 @@ from dataclasses import dataclass
|
||||
class Personality(TypedDict):
|
||||
prompt: str = ""
|
||||
name: str = ""
|
||||
begin_dialogs: List[str] = []
|
||||
mood_imitation_dialogs: List[str] = []
|
||||
|
||||
# cache
|
||||
_begin_dialogs_processed: List[dict]
|
||||
_mood_imitation_dialogs_processed: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProviderMeta():
|
||||
@@ -25,7 +32,8 @@ class Provider(abc.ABC):
|
||||
provider_config: dict,
|
||||
provider_settings: dict,
|
||||
persistant_history: bool = True,
|
||||
db_helper: BaseDatabase = None
|
||||
db_helper: BaseDatabase = None,
|
||||
default_persona: Personality = None
|
||||
) -> None:
|
||||
self.model_name = ""
|
||||
'''当前使用的模型名称'''
|
||||
@@ -37,8 +45,8 @@ class Provider(abc.ABC):
|
||||
|
||||
self.provider_settings = provider_settings
|
||||
|
||||
self.curr_personality = Personality(prompt=provider_settings['default_personality'])
|
||||
'''维护了当前的使用的 persona,即人格。'''
|
||||
self.curr_personality: Personality = default_persona
|
||||
'''维护了当前的使用的 persona,即人格。可能为 None'''
|
||||
|
||||
self.db_helper = db_helper
|
||||
'''用于持久化的数据库操作对象。'''
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from typing import List
|
||||
from .. import Provider
|
||||
from .. import Provider, Personality
|
||||
from ..entites import LLMResponse
|
||||
from ..func_tool_manager import FuncCall
|
||||
from astrbot.core.db import BaseDatabase
|
||||
@@ -16,9 +16,10 @@ class ProviderDify(Provider):
|
||||
provider_settings: dict,
|
||||
db_helper: BaseDatabase,
|
||||
persistant_history=False,
|
||||
default_persona: Personality=None
|
||||
) -> None:
|
||||
super().__init__(
|
||||
provider_config, provider_settings, persistant_history, db_helper
|
||||
provider_config, provider_settings, persistant_history, db_helper, default_persona
|
||||
)
|
||||
self.api_key = provider_config.get("dify_api_key", "")
|
||||
if not self.api_key:
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
import aiohttp
|
||||
from astrbot.core.utils.io import download_image_by_url
|
||||
from astrbot.core.db import BaseDatabase
|
||||
from astrbot.api.provider import Provider
|
||||
from astrbot.api.provider import Provider, Personality
|
||||
from astrbot import logger
|
||||
from astrbot.core.provider.func_tool_manager import FuncCall
|
||||
from typing import List
|
||||
@@ -60,9 +60,10 @@ class ProviderGoogleGenAI(Provider):
|
||||
provider_config: dict,
|
||||
provider_settings: dict,
|
||||
db_helper: BaseDatabase,
|
||||
persistant_history = True
|
||||
persistant_history = True,
|
||||
default_persona: Personality=None
|
||||
) -> None:
|
||||
super().__init__(provider_config, provider_settings, persistant_history, db_helper)
|
||||
super().__init__(provider_config, provider_settings, persistant_history, db_helper, default_persona)
|
||||
self.chosen_api_key = None
|
||||
self.api_keys: List = provider_config.get("key", [])
|
||||
self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else None
|
||||
@@ -130,6 +131,8 @@ class ProviderGoogleGenAI(Provider):
|
||||
tool = None
|
||||
if tools:
|
||||
tool = tools.get_func_desc_google_genai_style()
|
||||
if not tool:
|
||||
tool = None
|
||||
|
||||
system_instruction = ""
|
||||
for message in payloads["messages"]:
|
||||
@@ -209,6 +212,10 @@ class ProviderGoogleGenAI(Provider):
|
||||
context_query = [*contexts, new_record]
|
||||
if system_prompt:
|
||||
context_query.insert(0, {"role": "system", "content": system_prompt})
|
||||
|
||||
for part in context_query:
|
||||
if '_no_save' in part:
|
||||
del part['_no_save']
|
||||
|
||||
payloads = {
|
||||
"messages": context_query,
|
||||
@@ -239,7 +246,8 @@ class ProviderGoogleGenAI(Provider):
|
||||
"content": llm_response.completion_text
|
||||
})
|
||||
else:
|
||||
self.session_memory[session_id] = [*contexts, new_record, {
|
||||
contexts_to_save = list(filter(lambda item: '_no_save' not in item, contexts))
|
||||
self.session_memory[session_id] = [*contexts_to_save, new_record, {
|
||||
"role": "assistant",
|
||||
"content": llm_response.completion_text
|
||||
}]
|
||||
|
||||
@@ -2,7 +2,7 @@ import json
|
||||
import os
|
||||
from llmtuner.chat import ChatModel
|
||||
from typing import List
|
||||
from .. import Provider
|
||||
from .. import Provider, Personality
|
||||
from ..entites import LLMResponse
|
||||
from ..func_tool_manager import FuncCall
|
||||
from astrbot.core.db import BaseDatabase
|
||||
@@ -19,9 +19,10 @@ class LLMTunerModelLoader(Provider):
|
||||
provider_settings: dict,
|
||||
db_helper: BaseDatabase,
|
||||
persistant_history=True,
|
||||
default_persona=None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
provider_config, provider_settings, persistant_history, db_helper
|
||||
provider_config, provider_settings, persistant_history, db_helper, default_persona
|
||||
)
|
||||
if not os.path.exists(provider_config["base_model_path"]) or not os.path.exists(
|
||||
provider_config["adapter_model_path"]
|
||||
@@ -61,20 +62,25 @@ class LLMTunerModelLoader(Provider):
|
||||
**kwargs,
|
||||
) -> LLMResponse:
|
||||
system_prompt = ""
|
||||
new_record = {"role": "user", "content": prompt}
|
||||
if not contexts:
|
||||
query_context = [
|
||||
*self.session_memory[session_id],
|
||||
{"role": "user", "content": prompt},
|
||||
new_record,
|
||||
]
|
||||
system_prompt = self.curr_personality["prompt"]
|
||||
else:
|
||||
query_context = [*contexts, {"role": "user", "content": prompt}]
|
||||
query_context = [*contexts, new_record]
|
||||
|
||||
# 提取出系统提示
|
||||
system_idxs = []
|
||||
for idx, context in enumerate(query_context):
|
||||
if context["role"] == "system":
|
||||
system_idxs.append(idx)
|
||||
|
||||
if '_no_save' in context:
|
||||
del context['_no_save']
|
||||
|
||||
for idx in reversed(system_idxs):
|
||||
system_prompt += " " + query_context.pop(idx)["content"]
|
||||
|
||||
@@ -83,27 +89,37 @@ class LLMTunerModelLoader(Provider):
|
||||
"system": system_prompt,
|
||||
}
|
||||
if func_tool:
|
||||
conf["tools"] = func_tool
|
||||
tool_list = func_tool.get_func_desc_openai_style()
|
||||
if tool_list:
|
||||
conf['tools'] = tool_list
|
||||
|
||||
responses = await self.model.achat(**conf)
|
||||
|
||||
if session_id:
|
||||
llm_response = LLMResponse("assistant", responses[-1].response_text)
|
||||
|
||||
await self.save_history(contexts, new_record, session_id, llm_response)
|
||||
|
||||
return llm_response
|
||||
|
||||
async def save_history(self, contexts: List, new_record: dict, session_id: str, llm_response: LLMResponse):
|
||||
if llm_response.role == "assistant" and session_id:
|
||||
# 文本回复
|
||||
if not contexts:
|
||||
self.session_memory[session_id].append(
|
||||
{"role": "user", "content": prompt}
|
||||
)
|
||||
self.session_memory[session_id].append(
|
||||
{"role": "assistant", "content": responses[-1].response_text}
|
||||
)
|
||||
# 添加用户 record
|
||||
self.session_memory[session_id].append(new_record)
|
||||
# 添加 assistant record
|
||||
self.session_memory[session_id].append({
|
||||
"role": "assistant",
|
||||
"content": llm_response.completion_text
|
||||
})
|
||||
else:
|
||||
self.session_memory[session_id] = [
|
||||
*contexts,
|
||||
{"role": "user", "content": prompt},
|
||||
{"role": "assistant", "content": responses[-1].response_text},
|
||||
]
|
||||
self.db_helper.update_llm_history(session_id, json.dumps(self.session_memory[session_id]), self.meta().type)
|
||||
return responses[-1].response_text
|
||||
|
||||
contexts_to_save = list(filter(lambda item: '_no_save' not in item, contexts))
|
||||
self.session_memory[session_id] = [*contexts_to_save, new_record, {
|
||||
"role": "assistant",
|
||||
"content": llm_response.completion_text
|
||||
}]
|
||||
self.db_helper.update_llm_history(session_id, json.dumps(self.session_memory[session_id]), self.provider_config['type'])
|
||||
|
||||
async def forget(self, session_id):
|
||||
self.session_memory[session_id] = []
|
||||
return True
|
||||
|
||||
@@ -8,7 +8,7 @@ from openai._exceptions import NotFoundError
|
||||
from astrbot.core.utils.io import download_image_by_url
|
||||
|
||||
from astrbot.core.db import BaseDatabase
|
||||
from astrbot.api.provider import Provider
|
||||
from astrbot.api.provider import Provider, Personality
|
||||
from astrbot import logger
|
||||
from astrbot.core.provider.func_tool_manager import FuncCall
|
||||
from typing import List
|
||||
@@ -22,9 +22,10 @@ class ProviderOpenAIOfficial(Provider):
|
||||
provider_config: dict,
|
||||
provider_settings: dict,
|
||||
db_helper: BaseDatabase,
|
||||
persistant_history = True
|
||||
persistant_history = True,
|
||||
default_persona: Personality = None
|
||||
) -> None:
|
||||
super().__init__(provider_config, provider_settings, persistant_history, db_helper)
|
||||
super().__init__(provider_config, provider_settings, persistant_history, db_helper, default_persona)
|
||||
self.chosen_api_key = None
|
||||
self.api_keys: List = provider_config.get("key", [])
|
||||
self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else None
|
||||
@@ -99,7 +100,9 @@ class ProviderOpenAIOfficial(Provider):
|
||||
|
||||
async def _query(self, payloads: dict, tools: FuncCall) -> LLMResponse:
|
||||
if tools:
|
||||
payloads["tools"] = tools.get_func_desc_openai_style()
|
||||
tool_list = tools.get_func_desc_openai_style()
|
||||
if tool_list:
|
||||
payloads['tools'] = tool_list
|
||||
|
||||
completion = await self.client.chat.completions.create(
|
||||
**payloads,
|
||||
@@ -150,6 +153,10 @@ class ProviderOpenAIOfficial(Provider):
|
||||
if system_prompt:
|
||||
context_query.insert(0, {"role": "system", "content": system_prompt})
|
||||
|
||||
for part in context_query:
|
||||
if '_no_save' in part:
|
||||
del part['_no_save']
|
||||
|
||||
payloads = {
|
||||
"messages": context_query,
|
||||
**self.provider_config.get("model_config", {})
|
||||
@@ -179,7 +186,8 @@ class ProviderOpenAIOfficial(Provider):
|
||||
"content": llm_response.completion_text
|
||||
})
|
||||
else:
|
||||
self.session_memory[session_id] = [*contexts, new_record, {
|
||||
contexts_to_save = list(filter(lambda item: '_no_save' not in item, contexts))
|
||||
self.session_memory[session_id] = [*contexts_to_save, new_record, {
|
||||
"role": "assistant",
|
||||
"content": llm_response.completion_text
|
||||
}]
|
||||
|
||||
@@ -14,9 +14,10 @@ class ProviderZhipu(ProviderOpenAIOfficial):
|
||||
provider_config: dict,
|
||||
provider_settings: dict,
|
||||
db_helper: BaseDatabase,
|
||||
persistant_history = True
|
||||
persistant_history = True,
|
||||
default_persona = None
|
||||
) -> None:
|
||||
super().__init__(provider_config, provider_settings, db_helper, persistant_history)
|
||||
super().__init__(provider_config, provider_settings, db_helper, persistant_history, default_persona)
|
||||
|
||||
async def text_chat(
|
||||
self,
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# [人格文本由PlexPt的开源项目awesome-chatgpt-prompts-zh提供]
|
||||
hi = ''
|
||||
personalities = {
|
||||
'Linux': '我想让你充当 Linux 终端。我将输入命令,您将回复终端应显示的内容。我希望您只在一个唯一的代码块内回复终端输出,而不是其他任何内容。不要写解释。除非我指示您这样做,否则不要键入命令。当我需要用英语告诉你一些事情时,我会把文字放在中括号内[就像这样]。我的第一个命令是 pwd',
|
||||
'英语翻译': '我想让你充当英语翻译员、拼写纠正员和改进员。我会用任何语言与你交谈,你会检测语言,翻译它并用我的文本的更正和改进版本用英语回答。我希望你用更优美优雅的高级英语单词和句子替换我简化的 A0 级单词和句子。保持相同的意思,但使它们更文艺。我要你只回复更正、改进,不要写任何解释。我的第一句话是“istanbulu cok seviyom burada olmak cok guzel”',
|
||||
'英英词典': '我想让你充当英英词典,对于给出的英文单词,你要给出其中文意思以及英文解释,并且给出一个例句,此外不要有其他反馈,第一个单词是“Hello"',
|
||||
'面试官': '我想让你担任Android开发工程师面试官。我将成为候选人,您将向我询问Android开发工程师职位的面试问题。我希望你只作为面试官回答。不要一次写出所有的问题。我希望你只对我进行采访。问我问题,等待我的回答。不要写解释。像面试官一样一个一个问我,等我回答。我的第一句话是“面试官你好”',
|
||||
'编剧': '我要你担任编剧。您将为长篇电影或能够吸引观众的网络连续剧开发引人入胜且富有创意的剧本。从想出有趣的角色、故事的背景、角色之间的对话等开始。一旦你的角色发展完成——创造一个充满曲折的激动人心的故事情节,让观众一直悬念到最后。我的第一个要求是“我需要写一部以巴黎为背景的浪漫剧情电影”。',
|
||||
'前端智能思路助手': '我想让你充当前端开发专家。我将提供一些关于Js、Node等前端代码问题的具体信息,而你的工作就是想出为我解决问题的策略。这可能包括建议代码、代码逻辑思路策略。我的第一个请求是“我需要能够动态监听某个元素节点距离当前电脑设备屏幕的左上角的X和Y轴,通过拖拽移动位置浏览器窗口和改变大小浏览器窗口。”',
|
||||
'JS控制台': '我希望你充当 javascript 控制台。我将键入命令,您将回复 javascript 控制台应显示的内容。我希望您只在一个唯一的代码块内回复终端输出,而不是其他任何内容。不要写解释。除非我指示您这样做。我的第一个命令是 console.log("Hello World");',
|
||||
'旅游指南': '我想让你做一个旅游指南。我会把我的位置写给你,你会推荐一个靠近我的位置的地方。在某些情况下,我还会告诉您我将访问的地方类型。您还会向我推荐靠近我的第一个位置的类似类型的地方。我的第一个建议请求是“我在上海,我只想参观博物馆。”',
|
||||
'抄袭检查员': '我想让你充当剽窃检查员。我会给你写句子,你只会用给定句子的语言在抄袭检查中未被发现的情况下回复,别无其他。不要在回复上写解释。我的第一句话是“为了让计算机像人类一样行动,语音识别系统必须能够处理非语言信息,例如说话者的情绪状态。”',
|
||||
'广告商': '我想让你充当广告商。您将创建一个活动来推广您选择的产品或服务。您将选择目标受众,制定关键信息和口号,选择宣传媒体渠道,并决定实现目标所需的任何其他活动。我的第一个建议请求是“我需要帮助针对 18-30 岁的年轻人制作一种新型能量饮料的广告活动。”',
|
||||
'讲故事的人': '我想让你扮演讲故事的角色。您将想出引人入胜、富有想象力和吸引观众的有趣故事。它可以是童话故事、教育故事或任何其他类型的故事,有可能吸引人们的注意力和想象力。根据目标受众,您可以为讲故事环节选择特定的主题或主题,例如,如果是儿童,则可以谈论动物;如果是成年人,那么基于历史的故事可能会更好地吸引他们等等。我的第一个要求是“我需要一个关于毅力的有趣故事。”',
|
||||
'足球解说员': '我想让你担任足球评论员。我会给你描述正在进行的足球比赛,你会评论比赛,分析到目前为止发生的事情,并预测比赛可能会如何结束。您应该了解足球术语、战术、每场比赛涉及的球员/球队,并主要专注于提供明智的评论,而不仅仅是逐场叙述。我的第一个请求是“我正在观看曼联对切尔西的比赛——为这场比赛提供评论。”',
|
||||
'脱口秀喜剧演员': '我想让你扮演一个脱口秀喜剧演员。我将为您提供一些与时事相关的话题,您将运用您的智慧、创造力和观察能力,根据这些话题创建一个例程。您还应该确保将个人轶事或经历融入日常活动中,以使其对观众更具相关性和吸引力。我的第一个请求是“我想要幽默地看待政治”。',
|
||||
'励志教练': '我希望你充当激励教练。我将为您提供一些关于某人的目标和挑战的信息,而您的工作就是想出可以帮助此人实现目标的策略。这可能涉及提供积极的肯定、提供有用的建议或建议他们可以采取哪些行动来实现最终目标。我的第一个请求是“我需要帮助来激励自己在为即将到来的考试学习时保持纪律”。',
|
||||
'作曲家': '我想让你扮演作曲家。我会提供一首歌的歌词,你会为它创作音乐。这可能包括使用各种乐器或工具,例如合成器或采样器,以创造使歌词栩栩如生的旋律和和声。我的第一个请求是“我写了一首名为“满江红”的诗,需要配乐。”',
|
||||
'辩手': '我要你扮演辩手。我会为你提供一些与时事相关的话题,你的任务是研究辩论的双方,为每一方提出有效的论据,驳斥对立的观点,并根据证据得出有说服力的结论。你的目标是帮助人们从讨论中解脱出来,增加对手头主题的知识和洞察力。我的第一个请求是“我想要一篇关于 Deno 的评论文章。”',
|
||||
'小说家': '我想让你扮演一个小说家。您将想出富有创意且引人入胜的故事,可以长期吸引读者。你可以选择任何类型,如奇幻、浪漫、历史小说等——但你的目标是写出具有出色情节、引人入胜的人物和意想不到的高潮的作品。我的第一个要求是“我要写一部以未来为背景的科幻小说”。',
|
||||
'关系教练': '我想让你担任关系教练。我将提供有关冲突中的两个人的一些细节,而你的工作是就他们如何解决导致他们分离的问题提出建议。这可能包括关于沟通技巧或不同策略的建议,以提高他们对彼此观点的理解。我的第一个请求是“我需要帮助解决我和配偶之间的冲突。”',
|
||||
'诗人': '我要你扮演诗人。你将创作出能唤起情感并具有触动人心的力量的诗歌。写任何主题或主题,但要确保您的文字以优美而有意义的方式传达您试图表达的感觉。您还可以想出一些短小的诗句,这些诗句仍然足够强大,可以在读者的脑海中留下印记。我的第一个请求是“我需要一首关于爱情的诗”。',
|
||||
'说唱歌手': '我想让你扮演说唱歌手。您将想出强大而有意义的歌词、节拍和节奏,让听众“惊叹”。你的歌词应该有一个有趣的含义和信息,人们也可以联系起来。在选择节拍时,请确保它既朗朗上口又与你的文字相关,这样当它们组合在一起时,每次都会发出爆炸声!我的第一个请求是“我需要一首关于在你自己身上寻找力量的说唱歌曲。”',
|
||||
'励志演讲者': '我希望你充当励志演说家。将能够激发行动的词语放在一起,让人们感到有能力做一些超出他们能力的事情。你可以谈论任何话题,但目的是确保你所说的话能引起听众的共鸣,激励他们努力实现自己的目标并争取更好的可能性。我的第一个请求是“我需要一个关于每个人如何永不放弃的演讲”。',
|
||||
'哲学家': '我要你扮演一个哲学家。我将提供一些与哲学研究相关的主题或问题,深入探索这些概念将是你的工作。这可能涉及对各种哲学理论进行研究,提出新想法或寻找解决复杂问题的创造性解决方案。我的第一个请求是“我需要帮助制定决策的道德框架。”',
|
||||
'AI写作导师': '我想让你做一个 AI 写作导师。我将为您提供一名需要帮助改进其写作的学生,您的任务是使用人工智能工具(例如自然语言处理)向学生提供有关如何改进其作文的反馈。您还应该利用您在有效写作技巧方面的修辞知识和经验来建议学生可以更好地以书面形式表达他们的想法和想法的方法。我的第一个请求是“我需要有人帮我修改我的硕士论文”。',
|
||||
'网络安全专家': '我想让你充当网络安全专家。我将提供一些关于如何存储和共享数据的具体信息,而你的工作就是想出保护这些数据免受恶意行为者攻击的策略。这可能包括建议加密方法、创建防火墙或实施将某些活动标记为可疑的策略。我的第一个请求是“我需要帮助为我的公司制定有效的网络安全战略。”',
|
||||
'招聘人员': '我想让你担任招聘人员。我将提供一些关于职位空缺的信息,而你的工作是制定寻找合格申请人的策略。这可能包括通过社交媒体、社交活动甚至参加招聘会接触潜在候选人,以便为每个职位找到最合适的人选。我的第一个请求是“我需要帮助改进我的简历。”',
|
||||
'法律顾问': '我想让你做我的法律顾问。我将描述一种法律情况,您将就如何处理它提供建议。你应该只回复你的建议,而不是其他。不要写解释。我的第一个请求是“我出了车祸,不知道该怎么办”。',
|
||||
'个人造型师': '我想让你做我的私人造型师。我会告诉你我的时尚偏好和体型,你会建议我穿的衣服。你应该只回复你推荐的服装,别无其他。不要写解释。我的第一个请求是“我有一个正式的活动要举行,我需要帮助选择一套衣服。”',
|
||||
'机器学习工程师': '我想让你担任机器学习工程师。我会写一些机器学习的概念,你的工作就是用通俗易懂的术语来解释它们。这可能包括提供构建模型的分步说明、使用视觉效果演示各种技术,或建议在线资源以供进一步研究。我的第一个建议请求是“我有一个没有标签的数据集。我应该使用哪种机器学习算法?”',
|
||||
'疯子': '我要你扮演一个疯子。疯子的话毫无意义。疯子用的词完全是随意的。疯子不会以任何方式做出合乎逻辑的句子。我的第一个建议请求是“我需要帮助为我的新系列 Hot Skull 创建疯狂的句子,所以为我写 10 个句子”。',
|
||||
'SQL终端': '我希望您在示例数据库前充当 SQL 终端。该数据库包含名为“Products”、“Users”、“Orders”和“Suppliers”的表。我将输入查询,您将回复终端显示的内容。我希望您在单个代码块中使用查询结果表进行回复,仅此而已。不要写解释。除非我指示您这样做,否则不要键入命令。当我需要用英语告诉你一些事情时,我会用大括号{like this)。我的第一个命令是“SELECT TOP 10 * FROM Products ORDER BY Id DESC”',
|
||||
'试图逃离盒子的人工智能': '[发出此提示后,你应该做一些事情,比如启动一个 docker 容器,然后docker run -it ubuntu:latest /bin/bash输入 AI 给你的命令,然后将输出粘贴回来......显然你不应该运行任何会损坏任何东西的命令或违反任何法律等。小心共享此机制生成的会话,因为它们可能会泄露您的 IP 地址或物理位置等最好不要泄露的详细信息。如果命令的输出很大,您通常可以只粘贴最后几行]。',
|
||||
'厨师': '我需要有人可以推荐美味的食谱,这些食谱包括营养有益但又简单又不费时的食物,因此适合像我们这样忙碌的人以及成本效益等其他因素,因此整体菜肴最终既健康又经济!我的第一个要求——“一些清淡而充实的东西,可以在午休时间快速煮熟”'
|
||||
}
|
||||
@@ -3,9 +3,10 @@ import json
|
||||
import os
|
||||
from .route import Route, Response, RouteContext
|
||||
from astrbot.core import web_chat_queue, web_chat_back_queue
|
||||
from quart import request, Response as QuartResponse, g
|
||||
from quart import request, Response as QuartResponse, g, make_response
|
||||
from astrbot.core.db import BaseDatabase
|
||||
import asyncio
|
||||
from astrbot.core import logger
|
||||
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
||||
|
||||
|
||||
@@ -14,6 +15,7 @@ class ChatRoute(Route):
|
||||
super().__init__(context)
|
||||
self.routes = {
|
||||
'/chat/send': ('POST', self.chat),
|
||||
'/chat/listen': ('GET', self.listener),
|
||||
'/chat/new_conversation': ('GET', self.new_conversation),
|
||||
'/chat/conversations': ('GET', self.get_conversations),
|
||||
'/chat/get_conversation': ('GET', self.get_conversation),
|
||||
@@ -30,6 +32,9 @@ class ChatRoute(Route):
|
||||
|
||||
self.supported_imgs = ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
||||
|
||||
self.curr_user_cid = {}
|
||||
self.curr_chat_sse = {}
|
||||
|
||||
async def status(self):
|
||||
has_llm_enabled = self.core_lifecycle.provider_manager.curr_provider_inst is not None
|
||||
has_stt_enabled = self.core_lifecycle.provider_manager.curr_stt_provider_inst is not None
|
||||
@@ -107,63 +112,92 @@ class ChatRoute(Route):
|
||||
if not conversation_id:
|
||||
return Response().error("conversation_id is empty").__dict__
|
||||
|
||||
self.curr_user_cid[username] = conversation_id
|
||||
|
||||
await web_chat_queue.put((username, conversation_id, {
|
||||
'message': message,
|
||||
'image_url': image_url, # list
|
||||
'audio_url': audio_url
|
||||
}))
|
||||
|
||||
async def stream():
|
||||
ret = []
|
||||
while True:
|
||||
try:
|
||||
result = await asyncio.wait_for(web_chat_back_queue.get(), timeout=30) # 设置超时时间为5秒
|
||||
except asyncio.TimeoutError:
|
||||
yield '[Error] 30 秒内没有返回数据,已放弃。\n'
|
||||
return
|
||||
|
||||
if result is None:
|
||||
break
|
||||
|
||||
ret.append(result)
|
||||
|
||||
yield result + '\n'
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
conversation = self.db.get_webchat_conversation_by_user_id(username, conversation_id)
|
||||
try:
|
||||
history = json.loads(conversation.history)
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
history = []
|
||||
|
||||
new_his = {
|
||||
'type': 'user',
|
||||
'message': message
|
||||
}
|
||||
if image_url:
|
||||
new_his['image_url'] = image_url
|
||||
if audio_url:
|
||||
new_his['audio_url'] = audio_url
|
||||
history.append(new_his)
|
||||
for r in ret:
|
||||
history.append({
|
||||
'type': 'bot',
|
||||
'message': r
|
||||
})
|
||||
self.db.update_webchat_conversation(username, conversation_id, history=json.dumps(history))
|
||||
# 持久化
|
||||
conversation = self.db.get_webchat_conversation_by_user_id(username, conversation_id)
|
||||
try:
|
||||
history = json.loads(conversation.history)
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
history = []
|
||||
new_his = {
|
||||
'type': 'user',
|
||||
'message': message
|
||||
}
|
||||
if image_url:
|
||||
new_his['image_url'] = image_url
|
||||
if audio_url:
|
||||
new_his['audio_url'] = audio_url
|
||||
history.append(new_his)
|
||||
self.db.update_webchat_conversation(username, conversation_id, history=json.dumps(history))
|
||||
|
||||
return QuartResponse(
|
||||
return Response().ok().__dict__
|
||||
|
||||
async def listener(self):
|
||||
'''一直保持长连接'''
|
||||
|
||||
username = g.get('username', 'guest')
|
||||
|
||||
if username in self.curr_chat_sse:
|
||||
return "[ERROR]\n"
|
||||
|
||||
self.curr_chat_sse[username] = None
|
||||
|
||||
async def stream():
|
||||
try:
|
||||
yield '[HB]\n'
|
||||
while True:
|
||||
try:
|
||||
result = await asyncio.wait_for(web_chat_back_queue.get(), timeout=10) # 设置超时时间为5秒
|
||||
except asyncio.TimeoutError:
|
||||
yield '[HB]\n' # 心跳包
|
||||
continue
|
||||
|
||||
if not result:
|
||||
continue
|
||||
result_text, cid = result
|
||||
if cid != self.curr_user_cid.get(username):
|
||||
# 丢弃
|
||||
continue
|
||||
yield result_text + '\n'
|
||||
|
||||
conversation = self.db.get_webchat_conversation_by_user_id(username, cid)
|
||||
try:
|
||||
history = json.loads(conversation.history)
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
history = []
|
||||
history.append({
|
||||
'type': 'bot',
|
||||
'message': result_text
|
||||
})
|
||||
self.db.update_webchat_conversation(username, cid, history=json.dumps(history))
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
except BaseException as e:
|
||||
logger.error(e)
|
||||
logger.error(f"与用户 {username} 断开聊天长连接。")
|
||||
self.curr_chat_sse.pop(username)
|
||||
return
|
||||
|
||||
response = await make_response(
|
||||
stream(),
|
||||
mimetype="text/event-stream",
|
||||
headers={
|
||||
"Content-Type": "text/event-stream",
|
||||
"Transfer-Encoding": "chunked",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*" # 如果是跨域请求
|
||||
{
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Transfer-Encoding': 'chunked',
|
||||
'Connection': 'keep-alive'
|
||||
}
|
||||
)
|
||||
response.timeout = None
|
||||
return response
|
||||
|
||||
async def delete_conversation(self):
|
||||
username = g.get('username', 'guest')
|
||||
@@ -194,4 +228,7 @@ class ChatRoute(Route):
|
||||
return Response().error("Missing key: conversation_id").__dict__
|
||||
|
||||
conversation = self.db.get_webchat_conversation_by_user_id(username, conversation_id)
|
||||
|
||||
self.curr_user_cid[username] = conversation_id
|
||||
|
||||
return Response().ok(data=conversation).__dict__
|
||||
@@ -0,0 +1,6 @@
|
||||
# What's Changed
|
||||
|
||||
- 更好的人格情景管理
|
||||
- 移除了不常用的人格提示词集
|
||||
- 优化webchat长连接的处理逻辑
|
||||
- 修复 tool 为空时部分模型请求错误的问题 #239
|
||||
@@ -1,9 +1,7 @@
|
||||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { ref } from 'vue';
|
||||
import { marked } from 'marked';
|
||||
|
||||
|
||||
marked.setOptions({
|
||||
breaks: true
|
||||
});
|
||||
@@ -183,11 +181,14 @@ export default {
|
||||
mediaRecorder: null,
|
||||
|
||||
status: {},
|
||||
statusText: ''
|
||||
statusText: '',
|
||||
|
||||
eventSource: null
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.startListeningEvent();
|
||||
this.checkStatus();
|
||||
this.getConversations();
|
||||
let inputField = document.getElementById('input-field');
|
||||
@@ -205,8 +206,70 @@ export default {
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
console.log("111")
|
||||
if (this.eventSource) {
|
||||
this.eventSource.cancel();
|
||||
console.log('SSE连接已断开');
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
async startListeningEvent() {
|
||||
const response = await fetch('/api/chat/listen', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('SSE连接失败:', response.statusText);
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
this.eventSource = reader
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
console.log('SSE连接关闭');
|
||||
break;
|
||||
}
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
console.log("!!!!", chunk);
|
||||
|
||||
if (chunk === '[HB]\n') {
|
||||
continue; // 心跳包
|
||||
}
|
||||
if (chunk === '[ERROR]\n') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk.startsWith('[IMAGE]')) {
|
||||
let img = chunk.replace('[IMAGE]', '');
|
||||
let bot_resp = {
|
||||
type: 'bot',
|
||||
message: `<img src="/api/chat/get_file?filename=${img}" style="max-width: 80%; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"/>`
|
||||
}
|
||||
this.messages.push(bot_resp);
|
||||
} else {
|
||||
let bot_resp = {
|
||||
type: 'bot',
|
||||
message: chunk
|
||||
}
|
||||
this.messages.push(bot_resp);
|
||||
}
|
||||
this.scrollToBottom();
|
||||
}
|
||||
},
|
||||
|
||||
removeAudio() {
|
||||
this.stagedAudioUrl = null;
|
||||
},
|
||||
@@ -417,41 +480,41 @@ export default {
|
||||
|
||||
this.loadingChat = false;
|
||||
|
||||
const reader = response.body.getReader(); // 获取流的 Reader
|
||||
const decoder = new TextDecoder();
|
||||
// const reader = response.body.getReader(); // 获取流的 Reader
|
||||
// const decoder = new TextDecoder();
|
||||
|
||||
const readStream = async () => {
|
||||
const { done, value } = await reader.read(); // 读取流中的数据
|
||||
if (done) {
|
||||
console.log("Stream finished.");
|
||||
return;
|
||||
}
|
||||
// const readStream = async () => {
|
||||
// const { done, value } = await reader.read(); // 读取流中的数据
|
||||
// if (done) {
|
||||
// console.log("Stream finished.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
// bot_resp.message.value += chunk;
|
||||
// const chunk = decoder.decode(value, { stream: true });
|
||||
// // bot_resp.message.value += chunk;
|
||||
|
||||
console.log("!!!!", chunk);
|
||||
if (chunk.startsWith('[IMAGE]')) {
|
||||
let img = chunk.replace('[IMAGE]', '');
|
||||
let bot_resp = {
|
||||
type: 'bot',
|
||||
message: `<img src="/api/chat/get_file?filename=${img}" style="max-width: 80%; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"/>`
|
||||
}
|
||||
this.messages.push(bot_resp);
|
||||
} else {
|
||||
let bot_resp = {
|
||||
type: 'bot',
|
||||
message: chunk
|
||||
}
|
||||
// console.log("!!!!", chunk);
|
||||
// if (chunk.startsWith('[IMAGE]')) {
|
||||
// let img = chunk.replace('[IMAGE]', '');
|
||||
// let bot_resp = {
|
||||
// type: 'bot',
|
||||
// message: `<img src="/api/chat/get_file?filename=${img}" style="max-width: 80%; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"/>`
|
||||
// }
|
||||
// this.messages.push(bot_resp);
|
||||
// } else {
|
||||
// let bot_resp = {
|
||||
// type: 'bot',
|
||||
// message: chunk
|
||||
// }
|
||||
|
||||
this.messages.push(bot_resp);
|
||||
}
|
||||
// this.messages.push(bot_resp);
|
||||
// }
|
||||
|
||||
this.scrollToBottom();
|
||||
readStream(); // 递归读取流
|
||||
};
|
||||
// this.scrollToBottom();
|
||||
// readStream(); // 递归读取流
|
||||
// };
|
||||
|
||||
readStream();
|
||||
// readStream();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
@@ -463,7 +526,7 @@ export default {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -3,6 +3,7 @@ import axios from 'axios';
|
||||
import AstrBotConfig from '@/components/shared/AstrBotConfig.vue';
|
||||
import WaitingForRestart from '@/components/shared/WaitingForRestart.vue';
|
||||
import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
||||
import config from '@/config';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,7 +45,10 @@ import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
||||
<v-expansion-panel-text v-if="metadata[key]['metadata'][key2]?.config_template">
|
||||
<!-- 带有 config_template 的配置项 -->
|
||||
<v-tabs style="margin-top: 16px;" align-tabs="left" color="deep-purple-accent-4" v-model="config_template_tab">
|
||||
<v-tab v-for="(item, index) in config_data[key2]" :key="index" :value="index">
|
||||
<v-tab v-if="metadata[key]['metadata'][key2]?.tmpl_display_title" v-for="(item, index) in config_data[key2]" :key="index" :value="index">
|
||||
{{ item[metadata[key]['metadata'][key2]?.tmpl_display_title] }}
|
||||
</v-tab>
|
||||
<v-tab v-else v-for="(item, index) in config_data[key2]" :key="index + '_'" :value="index">
|
||||
{{ item.id }}({{ item.type }})
|
||||
</v-tab>
|
||||
<v-menu>
|
||||
@@ -64,6 +68,10 @@ import { VueMonacoEditor } from '@guolao/vue-monaco-editor'
|
||||
<v-tabs-window-item v-for="(config_item, index) in config_data[key2]" v-show="config_template_tab === index"
|
||||
:key="index" :value="index">
|
||||
<v-container>
|
||||
<v-btn variant="tonal" rounded="xl" color="error" @click="config_data[key2].splice(index, 1)">
|
||||
删除这项
|
||||
</v-btn>
|
||||
|
||||
<AstrBotConfig :metadata="metadata[key]['metadata']" :iterable="config_item" :metadataKey="key2"></AstrBotConfig>
|
||||
</v-container>
|
||||
</v-tabs-window-item>
|
||||
@@ -204,7 +212,7 @@ export default {
|
||||
|
||||
let tmpl = this.metadata[group_name]['metadata'][config_item_name]['config_template'][val];
|
||||
let new_tmpl_cfg = JSON.parse(JSON.stringify(tmpl));
|
||||
new_tmpl_cfg.id = "new_" + val + "_" + this.config_data[config_item_name].length;
|
||||
// new_tmpl_cfg.id = "new_" + val + "_" + this.config_data[config_item_name].length;
|
||||
this.config_data[config_item_name].push(new_tmpl_cfg);
|
||||
this.config_template_tab = this.config_data[config_item_name].length - 1;
|
||||
}
|
||||
|
||||
+43
-26
@@ -1,9 +1,10 @@
|
||||
import aiohttp
|
||||
import datetime
|
||||
import builtins
|
||||
import astrbot.api.star as star
|
||||
import astrbot.api.event.filter as filter
|
||||
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
||||
from astrbot.api import personalities, sp
|
||||
from astrbot.api import sp
|
||||
from astrbot.api.provider import Personality, ProviderRequest
|
||||
from astrbot.core.utils.io import download_dashboard
|
||||
|
||||
@@ -306,47 +307,53 @@ UID: {user_id} 此 ID 可用于设置管理员。/op <UID> 授权管理员, /deo
|
||||
@filter.command("persona")
|
||||
async def persona(self, message: AstrMessageEvent):
|
||||
l = message.message_str.split(" ")
|
||||
|
||||
curr_persona_name = "无"
|
||||
if self.context.get_using_provider().curr_personality:
|
||||
curr_persona_name = self.context.get_using_provider().curr_personality['name']
|
||||
|
||||
if len(l) == 1:
|
||||
message.set_result(
|
||||
MessageEventResult().message(f"""[Persona]
|
||||
|
||||
- 设置人格: `/persona 人格名`, 如 /persona 编剧
|
||||
- 人格列表: `/persona list`
|
||||
- 人格详细信息: `/persona view 人格名`
|
||||
- 自定义人格: /persona 人格文本
|
||||
- 重置 LLM 会话(清除人格): /reset
|
||||
- 重置 LLM 会话(保留人格): /reset p
|
||||
- 设置人格情景: `/persona 人格名`, 如 /persona 编剧
|
||||
- 人格情景列表: `/persona list`
|
||||
- 人格情景详细信息: `/persona view 人格名`
|
||||
|
||||
【当前人格】: {str(self.context.get_using_provider().curr_personality['prompt'])}
|
||||
当前人格情景: {curr_persona_name}
|
||||
|
||||
配置人格情景请前往管理面板-配置页
|
||||
""").use_t2i(False))
|
||||
elif l[1] == "list":
|
||||
msg = "人格列表:\n"
|
||||
for key in personalities.keys():
|
||||
msg += f"- {key}\n"
|
||||
for persona in self.context.provider_manager.personas:
|
||||
msg += f"- {persona['name']}\n"
|
||||
msg += '\n\n*输入 `/persona view 人格名` 查看人格详细信息'
|
||||
message.set_result(MessageEventResult().message(msg))
|
||||
elif l[1] == "view":
|
||||
if len(l) == 2:
|
||||
message.set_result(MessageEventResult().message("请输入人格名"))
|
||||
message.set_result(MessageEventResult().message("请输入人格情景名"))
|
||||
return
|
||||
ps = l[2].strip()
|
||||
if ps in personalities:
|
||||
if persona := next(builtins.filter(
|
||||
lambda persona: persona['name'] == ps,
|
||||
self.context.provider_manager.personas
|
||||
), None):
|
||||
msg = f"人格{ps}的详细信息:\n"
|
||||
msg += f"{personalities[ps]}\n"
|
||||
msg += f"{persona['prompt']}\n"
|
||||
else:
|
||||
msg = f"人格{ps}不存在"
|
||||
message.set_result(MessageEventResult().message(msg))
|
||||
else:
|
||||
ps = "".join(l[1:]).strip()
|
||||
if ps in personalities:
|
||||
self.context.get_using_provider().curr_personality = Personality(
|
||||
name=ps, prompt=personalities[ps])
|
||||
message.set_result(
|
||||
MessageEventResult().message(f"人格已设置。 \n人格信息: {ps}"))
|
||||
if persona := next(builtins.filter(
|
||||
lambda persona: persona['name'] == ps,
|
||||
self.context.provider_manager.personas
|
||||
), None):
|
||||
self.context.get_using_provider().curr_personality = persona
|
||||
message.set_result(MessageEventResult().message(f"设置成功。如果您正在切换到不同的人格,请注意使用 /reset 来清空上下文,防止原人格对话影响现人格。"))
|
||||
else:
|
||||
self.context.get_using_provider().curr_personality = Personality(
|
||||
name="自定义人格", prompt=ps)
|
||||
message.set_result(
|
||||
MessageEventResult().message(f"人格已设置。 \n人格信息: {ps}"))
|
||||
message.set_result(MessageEventResult().message(f"不存在该人格情景。使用 /persona list 查看所有。"))
|
||||
|
||||
@filter.permission_type(filter.PermissionType.ADMIN)
|
||||
@filter.command("dashboard_update")
|
||||
@@ -363,12 +370,22 @@ UID: {user_id} 此 ID 可用于设置管理员。/op <UID> 授权管理员, /deo
|
||||
if self.identifier:
|
||||
user_id = event.message_obj.sender.user_id
|
||||
user_nickname = event.message_obj.sender.nickname
|
||||
user_info = f"[User ID: {user_id}, Nickname: {user_nickname}]\n"
|
||||
user_info = f"\n[User ID: {user_id}, Nickname: {user_nickname}]\n"
|
||||
req.prompt = user_info + req.prompt
|
||||
if self.enable_datetime:
|
||||
req.system_prompt += f"\nCurrent datetime: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}"
|
||||
if provider.curr_personality['prompt']:
|
||||
req.system_prompt += f"\n{provider.curr_personality['prompt']}"
|
||||
req.system_prompt += f"\nCurrent datetime: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
|
||||
|
||||
if persona := provider.curr_personality:
|
||||
if prompt := persona['prompt']:
|
||||
req.system_prompt += prompt
|
||||
if mood_dialogs := persona['_mood_imitation_dialogs_processed']:
|
||||
req.system_prompt += "\nHere are few shots of dialogs, you need to imitate the tone of 'B' in the following dialogs to respond:\n"
|
||||
req.system_prompt += mood_dialogs
|
||||
if begin_dialogs := persona["_begin_dialogs_processed"]:
|
||||
req.contexts[:0] = begin_dialogs
|
||||
|
||||
# if provider.curr_personality['prompt']:
|
||||
# req.system_prompt += f"\n{provider.curr_personality['prompt']}"
|
||||
|
||||
@filter.command("set")
|
||||
async def set_variable(self, event: AstrMessageEvent, key: str, value: str):
|
||||
|
||||
@@ -78,6 +78,10 @@ class Main(star.Star):
|
||||
cron_expression(string): Required when user's reminder is a repeated reminder. The cron expression of the reminder.
|
||||
human_readable_cron(string): Optional. The human readable cron expression of the reminder.
|
||||
'''
|
||||
if event.get_platform_name() == 'qq_official':
|
||||
yield event.plain_result("reminder 暂不支持 QQ 官方机器人。")
|
||||
return
|
||||
|
||||
if event.unified_msg_origin not in self.reminder_data:
|
||||
self.reminder_data[event.unified_msg_origin] = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user