Compare commits

..

2 Commits

29 changed files with 461 additions and 168 deletions
+1 -2
View File
@@ -234,8 +234,7 @@ pre-commit install
- Group 7: 743746109 - Group 7: 743746109
- Group 8: 1030353265 - Group 8: 1030353265
- Developer Group(Chit-chat): 975206796 - Developer Group: 975206796
- Developer Group(Formal): 1039761811
### Discord Server ### Discord Server
-1
View File
@@ -222,7 +222,6 @@ pre-commit install
- Groupe 5 : 822130018 - Groupe 5 : 822130018
- Groupe 6 : 753075035 - Groupe 6 : 753075035
- Groupe développeurs : 975206796 - Groupe développeurs : 975206796
- Groupe développeurs (officiel) : 1039761811
### Serveur Discord ### Serveur Discord
-1
View File
@@ -223,7 +223,6 @@ pre-commit install
- 5群: 822130018 - 5群: 822130018
- 6群: 753075035 - 6群: 753075035
- 開発者群: 975206796 - 開発者群: 975206796
- 開発者群(正式): 1039761811
### Discord サーバー ### Discord サーバー
-1
View File
@@ -222,7 +222,6 @@ pre-commit install
- Группа 5: 822130018 - Группа 5: 822130018
- Группа 6: 753075035 - Группа 6: 753075035
- Группа разработчиков: 975206796 - Группа разработчиков: 975206796
- Группа разработчиков (официальная): 1039761811
### Сервер Discord ### Сервер Discord
+1 -2
View File
@@ -225,8 +225,7 @@ pre-commit install
- 6 群:753075035 - 6 群:753075035
- 7 群:743746109 - 7 群:743746109
- 8 群:1030353265 - 8 群:1030353265
- 開發者群(闲聊吹水)975206796 - 開發者群:975206796
- 開發者群(正式):1039761811
### Discord 群組 ### Discord 群組
+1 -2
View File
@@ -226,8 +226,7 @@ pre-commit install
- 6 群:753075035 - 6 群:753075035
- 7 群:743746109 - 7 群:743746109
- 8 群:1030353265 - 8 群:1030353265
- 开发者群(偏闲聊吹水)975206796 - 开发者群:975206796
- 开发者群(正式):1039761811
### Discord 频道 ### Discord 频道
+1 -14
View File
@@ -204,7 +204,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
"type": "string", "type": "string",
"description": ( "description": (
"Component type. One of: " "Component type. One of: "
"plain, image, record, video, file, mention_user. Record is voice message." "plain, image, record, file, mention_user"
), ),
}, },
"text": { "text": {
@@ -320,19 +320,6 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
components.append(Comp.Record.fromURL(url=url)) components.append(Comp.Record.fromURL(url=url))
else: else:
return f"error: messages[{idx}] must include path or url for record component." return f"error: messages[{idx}] must include path or url for record component."
elif msg_type == "video":
path = msg.get("path")
url = msg.get("url")
if path:
(
local_path,
file_from_sandbox,
) = await self._resolve_path_from_sandbox(context, path)
components.append(Comp.Video.fromFileSystem(path=local_path))
elif url:
components.append(Comp.Video.fromURL(url=url))
else:
return f"error: messages[{idx}] must include path or url for video component."
elif msg_type == "file": elif msg_type == "file":
path = msg.get("path") path = msg.get("path")
url = msg.get("url") url = msg.get("url")
-6
View File
@@ -422,12 +422,6 @@ async def get_booter(
) -> ComputerBooter: ) -> ComputerBooter:
config = context.get_config(umo=session_id) config = context.get_config(umo=session_id)
runtime = config.get("provider_settings", {}).get("computer_use_runtime", "local")
if runtime == "local":
return get_local_booter()
elif runtime == "none":
raise RuntimeError("Sandbox runtime is disabled by configuration.")
sandbox_cfg = config.get("provider_settings", {}).get("sandbox", {}) sandbox_cfg = config.get("provider_settings", {}).get("sandbox", {})
booter_type = sandbox_cfg.get("booter", "shipyard_neo") booter_type = sandbox_cfg.get("booter", "shipyard_neo")
-3
View File
@@ -219,9 +219,6 @@ DEFAULT_CONFIG = {
"telegram": { "telegram": {
"pre_ack_emoji": {"enable": False, "emojis": ["✍️"]}, "pre_ack_emoji": {"enable": False, "emojis": ["✍️"]},
}, },
"discord": {
"pre_ack_emoji": {"enable": False, "emojis": ["🤔"]},
},
}, },
"wake_prefix": ["/"], "wake_prefix": ["/"],
"log_level": "INFO", "log_level": "INFO",
+153 -1
View File
@@ -43,6 +43,7 @@ class SessionManagementRoute(Route):
"/session/group/create": ("POST", self.create_group), "/session/group/create": ("POST", self.create_group),
"/session/group/update": ("POST", self.update_group), "/session/group/update": ("POST", self.update_group),
"/session/group/delete": ("POST", self.delete_group), "/session/group/delete": ("POST", self.delete_group),
"/session/group/update-config": ("POST", self.update_group_config),
} }
self.conv_mgr = core_lifecycle.conversation_manager self.conv_mgr = core_lifecycle.conversation_manager
self.core_lifecycle = core_lifecycle self.core_lifecycle = core_lifecycle
@@ -145,9 +146,20 @@ class SessionManagementRoute(Route):
page=page, page_size=page_size, search=search page=page, page_size=page_size, search=search
) )
# 构建规则列表 # 收集属于有配置分组的 UMO,避免重复显示
grouped_umos = set()
groups = self._get_groups()
for group_data in groups.values():
if group_data.get("config"):
grouped_umos.update(group_data.get("umos", []))
# 构建规则列表(排除已被分组管理的 UMO)
rules_list = [] rules_list = []
filtered_count = 0
for umo, rules in umo_rules.items(): for umo, rules in umo_rules.items():
if umo in grouped_umos:
filtered_count += 1
continue
rule_info = { rule_info = {
"umo": umo, "umo": umo,
"rules": rules, "rules": rules,
@@ -159,6 +171,7 @@ class SessionManagementRoute(Route):
rule_info["message_type"] = parts[1] rule_info["message_type"] = parts[1]
rule_info["session_id"] = parts[2] rule_info["session_id"] = parts[2]
rules_list.append(rule_info) rules_list.append(rule_info)
total -= filtered_count
# 获取可用的 providers 和 personas # 获取可用的 providers 和 personas
provider_manager = self.core_lifecycle.provider_manager provider_manager = self.core_lifecycle.provider_manager
@@ -240,6 +253,7 @@ class SessionManagementRoute(Route):
"available_plugins": available_plugins, "available_plugins": available_plugins,
"available_kbs": available_kbs, "available_kbs": available_kbs,
"available_rule_keys": AVAILABLE_SESSION_RULE_KEYS, "available_rule_keys": AVAILABLE_SESSION_RULE_KEYS,
"group_rules": self._get_group_rules(),
} }
) )
.__dict__ .__dict__
@@ -793,6 +807,51 @@ class SessionManagementRoute(Route):
"""保存分组""" """保存分组"""
sp.put("session_groups", groups) sp.put("session_groups", groups)
def _get_group_rules(self) -> list:
"""获取有配置的分组列表,用于在规则列表中显示"""
groups = self._get_groups()
group_rules = []
for group_id, group_data in groups.items():
config = group_data.get("config", {})
if config: # 只返回有配置的分组
group_rules.append(
{
"group_id": group_id,
"name": group_data.get("name", ""),
"umo_count": len(group_data.get("umos", [])),
"config": config,
}
)
return group_rules
async def _sync_group_config_to_umos(
self, config: dict, umos: list[str]
) -> tuple[int, list[str]]:
"""将分组配置同步到指定的 UMO 列表
Returns:
(success_count, failed_umos)
"""
success_count = 0
failed_umos = []
for umo in umos:
try:
for rule_key, rule_value in config.items():
if rule_key not in AVAILABLE_SESSION_RULE_KEYS:
continue
if rule_value is None:
continue
if rule_key == "session_plugin_config":
# session_plugin_config 需要包裹 umo key
await sp.session_put(umo, rule_key, {umo: rule_value})
else:
await sp.session_put(umo, rule_key, rule_value)
success_count += 1
except Exception as e:
logger.error(f"同步配置到 {umo} 失败: {e!s}")
failed_umos.append(umo)
return success_count, failed_umos
async def list_groups(self): async def list_groups(self):
"""获取所有分组列表""" """获取所有分组列表"""
try: try:
@@ -806,6 +865,7 @@ class SessionManagementRoute(Route):
"name": group_data.get("name", ""), "name": group_data.get("name", ""),
"umos": group_data.get("umos", []), "umos": group_data.get("umos", []),
"umo_count": len(group_data.get("umos", [])), "umo_count": len(group_data.get("umos", [])),
"config": group_data.get("config", {}),
} }
) )
return Response().ok({"groups": groups_list}).__dict__ return Response().ok({"groups": groups_list}).__dict__
@@ -875,6 +935,7 @@ class SessionManagementRoute(Route):
return Response().error(f"分组 '{group_id}' 不存在").__dict__ return Response().error(f"分组 '{group_id}' 不存在").__dict__
group = groups[group_id] group = groups[group_id]
old_umos = set(group.get("umos", []))
# 更新名称 # 更新名称
if name is not None: if name is not None:
@@ -883,6 +944,7 @@ class SessionManagementRoute(Route):
# 直接设置 umos 列表 # 直接设置 umos 列表
if umos is not None: if umos is not None:
group["umos"] = umos group["umos"] = umos
new_umos = set(umos)
else: else:
# 增量更新 # 增量更新
current_umos = set(group.get("umos", [])) current_umos = set(group.get("umos", []))
@@ -891,9 +953,21 @@ class SessionManagementRoute(Route):
if remove_umos: if remove_umos:
current_umos.difference_update(remove_umos) current_umos.difference_update(remove_umos)
group["umos"] = list(current_umos) group["umos"] = list(current_umos)
new_umos = current_umos
self._save_groups(groups) self._save_groups(groups)
# 自动同步分组配置给新加入的成员
group_config = group.get("config", {})
newly_added = new_umos - old_umos
if group_config and newly_added:
sync_count, _ = await self._sync_group_config_to_umos(
group_config, list(newly_added)
)
logger.info(
f"自动同步分组 '{group['name']}' 配置到 {sync_count} 个新成员"
)
return ( return (
Response() Response()
.ok( .ok(
@@ -936,3 +1010,81 @@ class SessionManagementRoute(Route):
except Exception as e: except Exception as e:
logger.error(f"删除分组失败: {e!s}") logger.error(f"删除分组失败: {e!s}")
return Response().error(f"删除分组失败: {e!s}").__dict__ return Response().error(f"删除分组失败: {e!s}").__dict__
async def update_group_config(self):
"""更新分组的配置,并同步到所有成员 UMO
请求体:
{
"group_id": "分组ID",
"config": {
"session_service_config": {...},
"session_plugin_config": {...},
"kb_config": {...},
"provider_perf_chat_completion": ...,
"provider_perf_speech_to_text": ...,
"provider_perf_text_to_speech": ...
}
}
"""
try:
data = await request.get_json()
group_id = data.get("group_id")
config = data.get("config", {})
if not group_id:
return Response().error("缺少必要参数: group_id").__dict__
groups = self._get_groups()
if group_id not in groups:
return Response().error(f"分组 '{group_id}' 不存在").__dict__
group = groups[group_id]
# 保存配置到分组
group["config"] = config
self._save_groups(groups)
# 同步到所有成员 UMO
umos = group.get("umos", [])
if not config:
# 空配置 → 清除成员上的所有分组下发规则
success_count = 0
failed_umos = []
for umo in umos:
try:
for rule_key in AVAILABLE_SESSION_RULE_KEYS:
try:
await sp.session_remove(umo, rule_key)
except Exception:
pass
success_count += 1
except Exception as e:
logger.error(f"清除 {umo} 规则失败: {e!s}")
failed_umos.append(umo)
else:
success_count, failed_umos = await self._sync_group_config_to_umos(
config, umos
)
msg = f"分组 '{group['name']}' 配置已保存并同步到 {success_count}/{len(umos)} 个会话"
if failed_umos:
msg += f"{len(failed_umos)} 个失败"
return (
Response()
.ok(
{
"message": msg,
"success_count": success_count,
"failed_count": len(failed_umos),
"failed_umos": failed_umos,
}
)
.__dict__
)
except Exception as e:
logger.error(f"更新分组配置失败: {e!s}")
return Response().error(f"更新分组配置失败: {e!s}").__dict__
@@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { useModuleI18n } from '@/i18n/composables'; import { useModuleI18n } from '@/i18n/composables';
import { normalizeTextInput } from '@/utils/inputValue';
const { tm } = useModuleI18n('features/command'); const { tm } = useModuleI18n('features/command');
@@ -53,7 +52,6 @@ const statusItems = [
{ title: tm('filters.disabled'), value: 'disabled' }, { title: tm('filters.disabled'), value: 'disabled' },
{ title: tm('filters.conflict'), value: 'conflict' } { title: tm('filters.conflict'), value: 'conflict' }
]; ];
</script> </script>
<template> <template>
@@ -110,11 +108,10 @@ const statusItems = [
<div style="min-width: 200px; max-width: 350px; flex: 1; border: 1px solid #B9B9B9; border-radius: 16px;"> <div style="min-width: 200px; max-width: 350px; flex: 1; border: 1px solid #B9B9B9; border-radius: 16px;">
<v-text-field <v-text-field
:model-value="searchQuery" :model-value="searchQuery"
@update:model-value="emit('update:searchQuery', normalizeTextInput($event))" @update:model-value="emit('update:searchQuery', $event)"
density="compact" density="compact"
:label="tm('search.placeholder')" :label="tm('search.placeholder')"
prepend-inner-icon="mdi-magnify" prepend-inner-icon="mdi-magnify"
clearable
variant="solo-filled" variant="solo-filled"
flat flat
hide-details hide-details
@@ -3,7 +3,6 @@
*/ */
import { ref, computed, type Ref } from 'vue'; import { ref, computed, type Ref } from 'vue';
import type { CommandItem, FilterState } from '../types'; import type { CommandItem, FilterState } from '../types';
import { normalizeTextInput } from '@/utils/inputValue';
export function useCommandFilters(commands: Ref<CommandItem[]>) { export function useCommandFilters(commands: Ref<CommandItem[]>) {
// 过滤状态 // 过滤状态
@@ -96,7 +95,7 @@ export function useCommandFilters(commands: Ref<CommandItem[]>) {
* *
*/ */
const filteredCommands = computed(() => { const filteredCommands = computed(() => {
const query = normalizeTextInput(searchQuery.value).toLowerCase(); const query = searchQuery.value.toLowerCase();
const conflictCmds: CommandItem[] = []; const conflictCmds: CommandItem[] = [];
const normalCmds: CommandItem[] = []; const normalCmds: CommandItem[] = [];
@@ -185,3 +184,4 @@ export function useCommandFilters(commands: Ref<CommandItem[]>) {
isGroupExpanded isGroupExpanded
}; };
} }
@@ -15,7 +15,6 @@
import { computed, onActivated, onMounted, ref, watch} from 'vue'; import { computed, onActivated, onMounted, ref, watch} from 'vue';
import axios from 'axios'; import axios from 'axios';
import { useModuleI18n } from '@/i18n/composables'; import { useModuleI18n } from '@/i18n/composables';
import { normalizeTextInput } from '@/utils/inputValue';
// Composables // Composables
import { useComponentData } from './composables/useComponentData'; import { useComponentData } from './composables/useComponentData';
@@ -84,7 +83,7 @@ const {
} = useCommandActions(toast, () => fetchCommands(tm('messages.loadFailed'))); } = useCommandActions(toast, () => fetchCommands(tm('messages.loadFailed')));
const filteredTools = computed(() => { const filteredTools = computed(() => {
const query = normalizeTextInput(toolSearch.value).trim().toLowerCase(); const query = toolSearch.value.trim().toLowerCase();
if (!query) return tools.value; if (!query) return tools.value;
return tools.value.filter(tool => return tools.value.filter(tool =>
tool.name?.toLowerCase().includes(query) || tool.name?.toLowerCase().includes(query) ||
@@ -254,8 +253,7 @@ watch(viewMode, async (mode) => {
<div class="d-flex flex-wrap align-center ga-3 mb-4"> <div class="d-flex flex-wrap align-center ga-3 mb-4">
<div style="min-width: 240px; max-width: 380px; flex: 1;"> <div style="min-width: 240px; max-width: 380px; flex: 1;">
<v-text-field <v-text-field
:model-value="toolSearch" v-model="toolSearch"
@update:model-value="toolSearch = normalizeTextInput($event)"
prepend-inner-icon="mdi-magnify" prepend-inner-icon="mdi-magnify"
:label="tmTool('functionTools.search')" :label="tmTool('functionTools.search')"
variant="outlined" variant="outlined"
@@ -7,7 +7,6 @@
v-model="modelSearchProxy" v-model="modelSearchProxy"
density="compact" density="compact"
prepend-inner-icon="mdi-magnify" prepend-inner-icon="mdi-magnify"
clearable
hide-details hide-details
variant="solo-filled" variant="solo-filled"
flat flat
@@ -162,7 +161,6 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { normalizeTextInput } from '@/utils/inputValue'
const props = defineProps({ const props = defineProps({
entries: { entries: {
@@ -224,7 +222,7 @@ const emit = defineEmits([
const modelSearchProxy = computed({ const modelSearchProxy = computed({
get: () => props.modelSearch, get: () => props.modelSearch,
set: (val) => emit('update:modelSearch', normalizeTextInput(val)) set: (val) => emit('update:modelSearch', val)
}) })
const isProviderTesting = (providerId) => props.testingProviders.includes(providerId) const isProviderTesting = (providerId) => props.testingProviders.includes(providerId)
@@ -2,7 +2,6 @@ import { ref, computed, onMounted, nextTick, watch } from 'vue'
import axios from 'axios' import axios from 'axios'
import { getProviderIcon } from '@/utils/providerUtils' import { getProviderIcon } from '@/utils/providerUtils'
import { askForConfirmation as askForConfirmationDialog, useConfirmDialog } from '@/utils/confirmDialog' import { askForConfirmation as askForConfirmationDialog, useConfirmDialog } from '@/utils/confirmDialog'
import { normalizeTextInput } from '@/utils/inputValue'
export interface UseProviderSourcesOptions { export interface UseProviderSourcesOptions {
defaultTab?: string defaultTab?: string
@@ -158,7 +157,7 @@ export function useProviderSources(options: UseProviderSourcesOptions) {
}) })
const filteredMergedModelEntries = computed(() => { const filteredMergedModelEntries = computed(() => {
const term = normalizeTextInput(modelSearch.value).trim().toLowerCase() const term = modelSearch.value.trim().toLowerCase()
if (!term) return mergedModelEntries.value if (!term) return mergedModelEntries.value
return mergedModelEntries.value.filter((entry: any) => { return mergedModelEntries.value.filter((entry: any) => {
@@ -873,8 +873,7 @@
] ]
}, },
"regex": { "regex": {
"description": "Segmentation Regular Expression", "description": "Segmentation Regular Expression"
"hint": "Used to identify split points with a regular expression. Prefer patterns that match separators."
}, },
"split_words": { "split_words": {
"description": "Split Word List", "description": "Split Word List",
@@ -876,8 +876,7 @@
] ]
}, },
"regex": { "regex": {
"description": "分段正则表达式", "description": "分段正则表达式"
"hint": "用于按正则规则识别分段点。建议使用能匹配分隔符的表达式。"
}, },
"split_words": { "split_words": {
"description": "分段词列表", "description": "分段词列表",
-2
View File
@@ -1,2 +0,0 @@
export const normalizeTextInput = (value: unknown): string =>
typeof value === 'string' ? value : '';
+1 -7
View File
@@ -13,11 +13,9 @@
</v-select> </v-select>
<v-text-field <v-text-field
class="config-search-input" class="config-search-input"
:model-value="configSearchKeyword" v-model="configSearchKeyword"
@update:model-value="onConfigSearchInput"
prepend-inner-icon="mdi-magnify" prepend-inner-icon="mdi-magnify"
:label="tm('search.placeholder')" :label="tm('search.placeholder')"
clearable
hide-details hide-details
density="compact" density="compact"
rounded="md" rounded="md"
@@ -213,7 +211,6 @@ import {
useConfirmDialog useConfirmDialog
} from '@/utils/confirmDialog'; } from '@/utils/confirmDialog';
import UnsavedChangesConfirmDialog from '@/components/config/UnsavedChangesConfirmDialog.vue'; import UnsavedChangesConfirmDialog from '@/components/config/UnsavedChangesConfirmDialog.vue';
import { normalizeTextInput } from '@/utils/inputValue';
export default { export default {
name: 'ConfigPage', name: 'ConfigPage',
@@ -422,9 +419,6 @@ export default {
}, },
methods: { methods: {
onConfigSearchInput(value) {
this.configSearchKeyword = normalizeTextInput(value);
},
extractConfigTypeFromHash(hash) { extractConfigTypeFromHash(hash) {
const rawHash = String(hash || ''); const rawHash = String(hash || '');
const lastHashIndex = rawHash.lastIndexOf('#'); const lastHashIndex = rawHash.lastIndexOf('#');
+273 -51
View File
@@ -1,4 +1,4 @@
<template> <template>
<div class="session-management-page"> <div class="session-management-page">
<v-container fluid class="pa-0"> <v-container fluid class="pa-0">
<v-card flat> <v-card flat>
@@ -35,7 +35,16 @@
<!-- UMO 信息 --> <!-- UMO 信息 -->
<template v-slot:item.umo_info="{ item }"> <template v-slot:item.umo_info="{ item }">
<div> <div>
<div class="d-flex align-center"> <div class="d-flex align-center" v-if="item.isGroup">
<v-chip size="x-small" color="deep-purple" variant="flat" class="mr-2">
分组
</v-chip>
<span class="font-weight-medium">{{ item.groupName }}</span>
<v-chip size="x-small" variant="outlined" class="ml-2">
{{ item.umo_count }} 个会话
</v-chip>
</div>
<div class="d-flex align-center" v-else>
<v-chip size="x-small" :color="getPlatformColor(item.platform)" class="mr-2"> <v-chip size="x-small" :color="getPlatformColor(item.platform)" class="mr-2">
{{ item.platform || 'unknown' }} {{ item.platform || 'unknown' }}
</v-chip> </v-chip>
@@ -282,14 +291,24 @@
{{ tm('addRule.description') }} {{ tm('addRule.description') }}
</v-alert> </v-alert>
<v-autocomplete v-model="selectedNewUmo" :items="availableUmos" :loading="loadingUmos" <v-radio-group v-model="addRuleTargetType" inline hide-details class="mb-4">
<v-radio label="单个会话" value="session"></v-radio>
<v-radio label="分组" value="group" :disabled="groups.length === 0"></v-radio>
</v-radio-group>
<v-autocomplete v-if="addRuleTargetType === 'session'" v-model="selectedNewUmo" :items="availableUmos" :loading="loadingUmos"
:label="tm('addRule.selectUmo')" variant="outlined" clearable :no-data-text="tm('addRule.noUmos')" /> :label="tm('addRule.selectUmo')" variant="outlined" clearable :no-data-text="tm('addRule.noUmos')" />
<v-select v-if="addRuleTargetType === 'group'" v-model="selectedGroup" :items="groupSelectOptions"
item-title="label" item-value="value" return-object
label="选择分组" variant="outlined" clearable
:no-data-text="'暂无分组,请先创建分组'" />
</v-card-text> </v-card-text>
<v-card-actions class="px-4 pb-4"> <v-card-actions class="px-4 pb-4">
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn variant="text" @click="addRuleDialog = false">{{ tm('buttons.cancel') }}</v-btn> <v-btn variant="text" @click="addRuleDialog = false">{{ tm('buttons.cancel') }}</v-btn>
<v-btn color="primary" variant="tonal" @click="createNewRule" :disabled="!selectedNewUmo"> <v-btn color="primary" variant="tonal" @click="createNewRule" :disabled="addRuleTargetType === 'session' ? !selectedNewUmo : !selectedGroup">
{{ tm('buttons.next') }} {{ tm('buttons.next') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@@ -334,12 +353,7 @@
</v-col> </v-col>
</v-row> </v-row>
<div class="d-flex justify-end mt-4">
<v-btn color="primary" variant="tonal" size="small" @click="saveServiceConfig" :loading="saving"
prepend-icon="mdi-content-save">
{{ tm('buttons.save') }}
</v-btn>
</div>
<!-- Provider Config Section --> <!-- Provider Config Section -->
<div class="d-flex align-center mb-4 mt-4"> <div class="d-flex align-center mb-4 mt-4">
@@ -364,12 +378,7 @@
</v-col> </v-col>
</v-row> </v-row>
<div class="d-flex justify-end mt-4">
<v-btn color="primary" variant="tonal" size="small" @click="saveProviderConfig" :loading="saving"
prepend-icon="mdi-content-save">
{{ tm('buttons.save') }}
</v-btn>
</div>
<!-- Persona Config Section --> <!-- Persona Config Section -->
<div class="d-flex align-center mb-4 mt-4"> <div class="d-flex align-center mb-4 mt-4">
@@ -389,12 +398,7 @@
</v-col> </v-col>
</v-row> </v-row>
<div class="d-flex justify-end mt-4">
<v-btn color="primary" variant="tonal" size="small" @click="saveServiceConfig" :loading="saving"
prepend-icon="mdi-content-save">
{{ tm('buttons.save') }}
</v-btn>
</div>
<!-- Plugin Config Section --> <!-- Plugin Config Section -->
<div class="d-flex align-center mb-4 mt-4"> <div class="d-flex align-center mb-4 mt-4">
@@ -414,12 +418,7 @@
</v-col> </v-col>
</v-row> </v-row>
<div class="d-flex justify-end mt-4">
<v-btn color="primary" variant="tonal" size="small" @click="savePluginConfig" :loading="saving"
prepend-icon="mdi-content-save">
{{ tm('buttons.save') }}
</v-btn>
</div>
<!-- KB Config Section --> <!-- KB Config Section -->
<div class="d-flex align-center mb-4 mt-4"> <div class="d-flex align-center mb-4 mt-4">
@@ -442,14 +441,17 @@
</v-col> </v-col>
</v-row> </v-row>
<div class="d-flex justify-end mt-4">
<v-btn color="primary" variant="tonal" size="small" @click="saveKbConfig" :loading="saving"
prepend-icon="mdi-content-save">
{{ tm('buttons.save') }}
</v-btn>
</div>
</div> </div>
</v-card-text> </v-card-text>
<v-card-actions class="px-6 pb-4">
<v-spacer></v-spacer>
<v-btn variant="text" @click="closeRuleEditor">{{ tm('buttons.cancel') }}</v-btn>
<v-btn color="primary" variant="tonal" @click="saveAllConfigs" :loading="saving"
prepend-icon="mdi-content-save">
{{ tm('buttons.save') }}
</v-btn>
</v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
@@ -567,6 +569,8 @@ export default {
addRuleDialog: false, addRuleDialog: false,
availableUmos: [], availableUmos: [],
selectedNewUmo: null, selectedNewUmo: null,
addRuleTargetType: 'session',
selectedGroup: null,
// //
ruleDialog: false, ruleDialog: false,
@@ -729,6 +733,13 @@ export default {
return options return options
}, },
groupSelectOptions() {
return this.groups.map(g => ({
label: `${g.name} (${g.umo_count} 个会话)`,
value: g,
}))
},
groupOptions() { groupOptions() {
return this.groups.map(g => ({ return this.groups.map(g => ({
label: `${g.name} (${g.umo_count} 个会话)`, label: `${g.name} (${g.umo_count} 个会话)`,
@@ -811,7 +822,7 @@ export default {
}) })
if (response.data.status === 'ok') { if (response.data.status === 'ok') {
const data = response.data.data const data = response.data.data
this.rulesList = data.rules this.rulesList = data.rules || []
this.totalItems = data.total this.totalItems = data.total
this.availablePersonas = data.available_personas this.availablePersonas = data.available_personas
this.availableChatProviders = data.available_chat_providers this.availableChatProviders = data.available_chat_providers
@@ -819,6 +830,20 @@ export default {
this.availableTtsProviders = data.available_tts_providers this.availableTtsProviders = data.available_tts_providers
this.availablePlugins = data.available_plugins || [] this.availablePlugins = data.available_plugins || []
this.availableKbs = data.available_kbs || [] this.availableKbs = data.available_kbs || []
//
const groupRules = data.group_rules || []
for (const gr of groupRules) {
this.rulesList.unshift({
umo: `[\u5206\u7ec4] ${gr.name}`,
isGroup: true,
groupId: gr.group_id,
groupName: gr.name,
umo_count: gr.umo_count,
rules: gr.config || {},
})
}
this.totalItems += groupRules.length
} else { } else {
this.showError(response.data.message || this.tm('messages.loadError')) this.showError(response.data.message || this.tm('messages.loadError'))
} }
@@ -872,10 +897,89 @@ export default {
async openAddRuleDialog() { async openAddRuleDialog() {
this.addRuleDialog = true this.addRuleDialog = true
this.selectedNewUmo = null this.selectedNewUmo = null
this.addRuleTargetType = 'session'
this.selectedGroup = null
await this.loadUmos() await this.loadUmos()
}, },
async saveAllConfigs() {
if (!this.selectedUmo) return
// API
if (this.selectedUmo.isGroup) {
this.saving = true
try {
const config = {
session_service_config: { ...this.serviceConfig },
provider_perf_chat_completion: this.providerConfig.chat_completion || null,
provider_perf_speech_to_text: this.providerConfig.speech_to_text || null,
provider_perf_text_to_speech: this.providerConfig.text_to_speech || null,
session_plugin_config: { ...this.pluginConfig },
kb_config: { ...this.kbConfig },
}
//
if (!config.session_service_config.custom_name) delete config.session_service_config.custom_name
if (config.session_service_config.persona_id === null) delete config.session_service_config.persona_id
const response = await axios.post('/api/session/group/update-config', {
group_id: this.selectedUmo.groupId,
config: config
})
if (response.data.status === 'ok') {
this.showSuccess(response.data.data?.message || '分组配置已保存并同步')
await this.loadData()
} else {
this.showError(response.data.message || this.tm('messages.saveError'))
}
} catch (error) {
this.showError(error.response?.data?.message || this.tm('messages.saveError'))
} finally {
this.saving = false
}
return
}
//
this.saving = true
this._batchSaving = true
try {
await this.saveServiceConfig()
await this.saveProviderConfig()
await this.savePluginConfig()
await this.saveKbConfig()
this.showSuccess(this.tm('messages.saveSuccess'))
} catch (error) {
this.showError(error.response?.data?.message || this.tm('messages.saveError'))
} finally {
this._batchSaving = false
this.saving = false
}
},
createNewRule() { createNewRule() {
if (this.addRuleTargetType === 'group') {
//
if (!this.selectedGroup) return
const group = this.selectedGroup.value || this.selectedGroup
if (!group.umos || group.umos.length === 0) {
this.showError('该分组没有成员会话')
return
}
//
const newItem = {
umo: `[分组] ${group.name}`,
isGroup: true,
groupId: group.id,
groupName: group.name,
groupUmos: group.umos,
rules: {},
}
this.addRuleDialog = false
this.openRuleEditor(newItem)
return
}
//
if (!this.selectedNewUmo) return if (!this.selectedNewUmo) return
// //
@@ -943,13 +1047,37 @@ export default {
async saveServiceConfig() { async saveServiceConfig() {
if (!this.selectedUmo) return if (!this.selectedUmo) return
this.saving = true if (!this._batchSaving) this.saving = true
try { try {
const config = { ...this.serviceConfig } const config = { ...this.serviceConfig }
// //
if (!config.custom_name) delete config.custom_name if (!config.custom_name) delete config.custom_name
if (config.persona_id === null) delete config.persona_id if (config.persona_id === null) delete config.persona_id
//
if (this.selectedUmo.isGroup) {
const umos = this.selectedUmo.groupUmos
let successCount = 0
for (const umo of umos) {
try {
await axios.post('/api/session/update-rule', {
umo: umo,
rule_key: 'session_service_config',
rule_value: config
})
successCount++
} catch (e) {
console.error(`更新 ${umo} 失败:`, e)
}
}
if (!this._batchSaving) {
this.showSuccess(`已更新 ${successCount}/${umos.length} 个会话的服务配置`)
await this.loadData()
this.saving = false
}
return
}
const response = await axios.post('/api/session/update-rule', { const response = await axios.post('/api/session/update-rule', {
umo: this.selectedUmo.umo, umo: this.selectedUmo.umo,
rule_key: 'session_service_config', rule_key: 'session_service_config',
@@ -957,7 +1085,7 @@ export default {
}) })
if (response.data.status === 'ok') { if (response.data.status === 'ok') {
this.showSuccess(this.tm('messages.saveSuccess')) if (!this._batchSaving) this.showSuccess(this.tm('messages.saveSuccess'))
this.editingRules.session_service_config = config this.editingRules.session_service_config = config
// //
@@ -980,17 +1108,45 @@ export default {
} catch (error) { } catch (error) {
this.showError(error.response?.data?.message || this.tm('messages.saveError')) this.showError(error.response?.data?.message || this.tm('messages.saveError'))
} }
this.saving = false if (!this._batchSaving) this.saving = false
}, },
async saveProviderConfig() { async saveProviderConfig() {
if (!this.selectedUmo) return if (!this.selectedUmo) return
this.saving = true if (!this._batchSaving) this.saving = true
try { try {
const providerTypes = ['chat_completion', 'speech_to_text', 'text_to_speech']
//
if (this.selectedUmo.isGroup) {
const umos = this.selectedUmo.groupUmos
let successCount = 0
for (const umo of umos) {
try {
const tasks = []
for (const type of providerTypes) {
const value = this.providerConfig[type]
if (value) {
tasks.push(axios.post('/api/session/update-rule', { umo, rule_key: `provider_perf_${type}`, rule_value: value }))
}
}
if (tasks.length > 0) await Promise.all(tasks)
successCount++
} catch (e) {
console.error(`更新 ${umo} Provider 失败:`, e)
}
}
if (!this._batchSaving) {
this.showSuccess(`已更新 ${successCount}/${umos.length} 个会话的 Provider 配置`)
await this.loadData()
this.saving = false
}
return
}
const updateTasks = [] const updateTasks = []
const deleteTasks = [] const deleteTasks = []
const providerTypes = ['chat_completion', 'speech_to_text', 'text_to_speech']
for (const type of providerTypes) { for (const type of providerTypes) {
const value = this.providerConfig[type] const value = this.providerConfig[type]
@@ -1017,7 +1173,7 @@ export default {
const allTasks = [...updateTasks, ...deleteTasks] const allTasks = [...updateTasks, ...deleteTasks]
if (allTasks.length > 0) { if (allTasks.length > 0) {
await Promise.all(allTasks) await Promise.all(allTasks)
this.showSuccess(this.tm('messages.saveSuccess')) if (!this._batchSaving) this.showSuccess(this.tm('messages.saveSuccess'))
// //
let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo) let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo)
@@ -1042,24 +1198,48 @@ export default {
} }
} }
} else { } else {
this.showSuccess(this.tm('messages.noChanges')) if (!this._batchSaving) this.showSuccess(this.tm('messages.noChanges'))
} }
} catch (error) { } catch (error) {
this.showError(error.response?.data?.message || this.tm('messages.saveError')) this.showError(error.response?.data?.message || this.tm('messages.saveError'))
} }
this.saving = false if (!this._batchSaving) this.saving = false
}, },
async savePluginConfig() { async savePluginConfig() {
if (!this.selectedUmo) return if (!this.selectedUmo) return
this.saving = true if (!this._batchSaving) this.saving = true
try { try {
const config = { const config = {
enabled_plugins: this.pluginConfig.enabled_plugins, enabled_plugins: this.pluginConfig.enabled_plugins,
disabled_plugins: this.pluginConfig.disabled_plugins, disabled_plugins: this.pluginConfig.disabled_plugins,
} }
//
if (this.selectedUmo.isGroup) {
const umos = this.selectedUmo.groupUmos
let successCount = 0
for (const umo of umos) {
try {
if (config.enabled_plugins.length === 0 && config.disabled_plugins.length === 0) {
await axios.post('/api/session/delete-rule', { umo, rule_key: 'session_plugin_config' })
} else {
await axios.post('/api/session/update-rule', { umo, rule_key: 'session_plugin_config', rule_value: config })
}
successCount++
} catch (e) {
console.error(`更新 ${umo} 插件配置失败:`, e)
}
}
if (!this._batchSaving) {
this.showSuccess(`已更新 ${successCount}/${umos.length} 个会话的插件配置`)
await this.loadData()
this.saving = false
}
return
}
// //
if (config.enabled_plugins.length === 0 && config.disabled_plugins.length === 0) { if (config.enabled_plugins.length === 0 && config.disabled_plugins.length === 0) {
if (this.editingRules.session_plugin_config) { if (this.editingRules.session_plugin_config) {
@@ -1071,7 +1251,7 @@ export default {
let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo) let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo)
if (item) delete item.rules.session_plugin_config if (item) delete item.rules.session_plugin_config
} }
this.showSuccess(this.tm('messages.saveSuccess')) if (!this._batchSaving) this.showSuccess(this.tm('messages.saveSuccess'))
} else { } else {
const response = await axios.post('/api/session/update-rule', { const response = await axios.post('/api/session/update-rule', {
umo: this.selectedUmo.umo, umo: this.selectedUmo.umo,
@@ -1080,7 +1260,7 @@ export default {
}) })
if (response.data.status === 'ok') { if (response.data.status === 'ok') {
this.showSuccess(this.tm('messages.saveSuccess')) if (!this._batchSaving) this.showSuccess(this.tm('messages.saveSuccess'))
this.editingRules.session_plugin_config = config this.editingRules.session_plugin_config = config
let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo) let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo)
@@ -1102,13 +1282,13 @@ export default {
} catch (error) { } catch (error) {
this.showError(error.response?.data?.message || this.tm('messages.saveError')) this.showError(error.response?.data?.message || this.tm('messages.saveError'))
} }
this.saving = false if (!this._batchSaving) this.saving = false
}, },
async saveKbConfig() { async saveKbConfig() {
if (!this.selectedUmo) return if (!this.selectedUmo) return
this.saving = true if (!this._batchSaving) this.saving = true
try { try {
const config = { const config = {
kb_ids: this.kbConfig.kb_ids, kb_ids: this.kbConfig.kb_ids,
@@ -1116,6 +1296,30 @@ export default {
enable_rerank: this.kbConfig.enable_rerank, enable_rerank: this.kbConfig.enable_rerank,
} }
//
if (this.selectedUmo.isGroup) {
const umos = this.selectedUmo.groupUmos
let successCount = 0
for (const umo of umos) {
try {
if (config.kb_ids.length === 0) {
await axios.post('/api/session/delete-rule', { umo, rule_key: 'kb_config' })
} else {
await axios.post('/api/session/update-rule', { umo, rule_key: 'kb_config', rule_value: config })
}
successCount++
} catch (e) {
console.error(`更新 ${umo} 知识库配置失败:`, e)
}
}
if (!this._batchSaving) {
this.showSuccess(`已更新 ${successCount}/${umos.length} 个会话的知识库配置`)
await this.loadData()
this.saving = false
}
return
}
// kb_ids // kb_ids
if (config.kb_ids.length === 0) { if (config.kb_ids.length === 0) {
if (this.editingRules.kb_config) { if (this.editingRules.kb_config) {
@@ -1127,7 +1331,7 @@ export default {
let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo) let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo)
if (item) delete item.rules.kb_config if (item) delete item.rules.kb_config
} }
this.showSuccess(this.tm('messages.saveSuccess')) if (!this._batchSaving) this.showSuccess(this.tm('messages.saveSuccess'))
} else { } else {
const response = await axios.post('/api/session/update-rule', { const response = await axios.post('/api/session/update-rule', {
umo: this.selectedUmo.umo, umo: this.selectedUmo.umo,
@@ -1136,7 +1340,7 @@ export default {
}) })
if (response.data.status === 'ok') { if (response.data.status === 'ok') {
this.showSuccess(this.tm('messages.saveSuccess')) if (!this._batchSaving) this.showSuccess(this.tm('messages.saveSuccess'))
this.editingRules.kb_config = config this.editingRules.kb_config = config
let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo) let item = this.rulesList.find(u => u.umo === this.selectedUmo.umo)
@@ -1158,7 +1362,7 @@ export default {
} catch (error) { } catch (error) {
this.showError(error.response?.data?.message || this.tm('messages.saveError')) this.showError(error.response?.data?.message || this.tm('messages.saveError'))
} }
this.saving = false if (!this._batchSaving) this.saving = false
}, },
confirmDeleteRules(item) { confirmDeleteRules(item) {
@@ -1171,6 +1375,24 @@ export default {
this.deleting = true this.deleting = true
try { try {
//
if (this.deleteTarget.isGroup) {
const response = await axios.post('/api/session/group/update-config', {
group_id: this.deleteTarget.groupId,
config: {}
})
if (response.data.status === 'ok') {
this.showSuccess('分组配置已清除')
this.deleteDialog = false
this.deleteTarget = null
await this.loadData()
} else {
this.showError(response.data.message || this.tm('messages.deleteError'))
}
this.deleting = false
return
}
const response = await axios.post('/api/session/delete-rule', { const response = await axios.post('/api/session/delete-rule', {
umo: this.deleteTarget.umo umo: this.deleteTarget.umo
}) })
+4 -10
View File
@@ -353,11 +353,10 @@
<v-window-item value="search"> <v-window-item value="search">
<div class="search-container pa-4"> <div class="search-container pa-4">
<v-form @submit.prevent="searchKnowledgeBase" class="d-flex align-center"> <v-form @submit.prevent="searchKnowledgeBase" class="d-flex align-center">
<v-text-field :model-value="searchQuery" <v-text-field v-model="searchQuery" :label="tm('search.queryLabel')"
@update:model-value="onSearchQueryInput" :label="tm('search.queryLabel')"
append-icon="mdi-magnify" variant="outlined" class="flex-grow-1 me-2" append-icon="mdi-magnify" variant="outlined" class="flex-grow-1 me-2"
@click:append="searchKnowledgeBase" @keyup.enter="searchKnowledgeBase" @click:append="searchKnowledgeBase" @keyup.enter="searchKnowledgeBase"
:placeholder="tm('search.queryPlaceholder')" hide-details clearable></v-text-field> :placeholder="tm('search.queryPlaceholder')" hide-details></v-text-field>
<v-select v-model="topK" :items="[3, 5, 10, 20]" <v-select v-model="topK" :items="[3, 5, 10, 20]"
:label="tm('search.resultCountLabel')" variant="outlined" :label="tm('search.resultCountLabel')" variant="outlined"
@@ -435,7 +434,6 @@
import axios from 'axios'; import axios from 'axios';
import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue'; import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue';
import { useModuleI18n } from '@/i18n/composables'; import { useModuleI18n } from '@/i18n/composables';
import { normalizeTextInput } from '@/utils/inputValue';
export default { export default {
name: 'KnowledgeBase', name: 'KnowledgeBase',
@@ -582,9 +580,6 @@ export default {
this.getProviderList(); this.getProviderList();
}, },
methods: { methods: {
onSearchQueryInput(value) {
this.searchQuery = normalizeTextInput(value);
},
getSelectedGitHubProxy() { getSelectedGitHubProxy() {
if (typeof window === "undefined" || !window.localStorage) return ""; if (typeof window === "undefined" || !window.localStorage) return "";
return localStorage.getItem("githubProxyRadioValue") === "1" return localStorage.getItem("githubProxyRadioValue") === "1"
@@ -908,8 +903,7 @@ export default {
}, },
searchKnowledgeBase() { searchKnowledgeBase() {
const query = normalizeTextInput(this.searchQuery).trim(); if (!this.searchQuery.trim()) {
if (!query) {
this.showSnackbar(this.tm('messages.pleaseEnterSearchContent'), 'warning'); this.showSnackbar(this.tm('messages.pleaseEnterSearchContent'), 'warning');
return; return;
} }
@@ -920,7 +914,7 @@ export default {
axios.get(`/api/plug/alkaid/kb/collection/search`, { axios.get(`/api/plug/alkaid/kb/collection/search`, {
params: { params: {
collection_name: this.currentKB.collection_name, collection_name: this.currentKB.collection_name,
query, query: this.searchQuery,
top_k: this.topK top_k: this.topK
} }
}) })
+8 -19
View File
@@ -37,12 +37,10 @@
<h3>{{ tm('search.title') }}</h3> <h3>{{ tm('search.title') }}</h3>
<v-card variant="outlined" class="mt-2 pa-3"> <v-card variant="outlined" class="mt-2 pa-3">
<div> <div>
<v-text-field :model-value="searchMemoryUserId" <v-text-field v-model="searchMemoryUserId" :label="tm('search.userIdLabel')" variant="outlined" density="compact" hide-details
@update:model-value="onSearchMemoryUserIdInput" :label="tm('search.userIdLabel')" variant="outlined" density="compact" hide-details class="mb-2"></v-text-field>
class="mb-2" clearable></v-text-field> <v-text-field v-model="searchQuery" :label="tm('search.queryLabel')" variant="outlined" density="compact" hide-details
<v-text-field :model-value="searchQuery" @keyup.enter="searchMemory" class="mb-2"></v-text-field>
@update:model-value="onSearchQueryInput" :label="tm('search.queryLabel')" variant="outlined" density="compact" hide-details
@keyup.enter="searchMemory" class="mb-2" clearable></v-text-field>
<v-btn color="info" @click="searchMemory" :loading="isSearching" variant="tonal"> <v-btn color="info" @click="searchMemory" :loading="isSearching" variant="tonal">
<v-icon start>mdi-text-search</v-icon> <v-icon start>mdi-text-search</v-icon>
{{ tm('search.searchButton') }} {{ tm('search.searchButton') }}
@@ -256,7 +254,6 @@
import axios from 'axios'; import axios from 'axios';
// import * as d3 from "d3"; // npm install d3 // import * as d3 from "d3"; // npm install d3
import { useModuleI18n } from '@/i18n/composables'; import { useModuleI18n } from '@/i18n/composables';
import { normalizeTextInput } from '@/utils/inputValue';
export default { export default {
name: 'LongTermMemory', name: 'LongTermMemory',
@@ -339,16 +336,9 @@ export default {
this.searchResults = []; this.searchResults = [];
}, },
methods: { methods: {
onSearchMemoryUserIdInput(value) {
this.searchMemoryUserId = normalizeTextInput(value);
},
onSearchQueryInput(value) {
this.searchQuery = normalizeTextInput(value);
},
// //
searchMemory() { searchMemory() {
const query = normalizeTextInput(this.searchQuery).trim(); if (!this.searchQuery.trim()) {
if (!query) {
this.$toast.warning(this.tm('messages.searchQueryRequired')); this.$toast.warning(this.tm('messages.searchQueryRequired'));
return; return;
} }
@@ -359,13 +349,12 @@ export default {
// //
const params = { const params = {
query query: this.searchQuery
}; };
// ID // ID
const normalizedUserId = normalizeTextInput(this.searchMemoryUserId).trim(); if (this.searchMemoryUserId) {
if (normalizedUserId) { params.user_id = this.searchMemoryUserId;
params.user_id = normalizedUserId;
} }
axios.get('/api/plug/alkaid/ltm/graph/search', { params }) axios.get('/api/plug/alkaid/ltm/graph/search', { params })
@@ -3,7 +3,6 @@ import PluginSortControl from "@/components/extension/PluginSortControl.vue";
import ExtensionCard from "@/components/shared/ExtensionCard.vue"; import ExtensionCard from "@/components/shared/ExtensionCard.vue";
import StyledMenu from "@/components/shared/StyledMenu.vue"; import StyledMenu from "@/components/shared/StyledMenu.vue";
import defaultPluginIcon from "@/assets/images/plugin_icon.png"; import defaultPluginIcon from "@/assets/images/plugin_icon.png";
import { normalizeTextInput } from "@/utils/inputValue";
const props = defineProps({ const props = defineProps({
state: { state: {
@@ -165,12 +164,10 @@ const {
<div class="d-flex align-center flex-wrap ml-auto" style="gap: 8px"> <div class="d-flex align-center flex-wrap ml-auto" style="gap: 8px">
<v-text-field <v-text-field
:model-value="pluginSearch" v-model="pluginSearch"
@update:model-value="pluginSearch = normalizeTextInput($event)"
density="compact" density="compact"
:label="tm('search.placeholder')" :label="tm('search.placeholder')"
prepend-inner-icon="mdi-magnify" prepend-inner-icon="mdi-magnify"
clearable
variant="solo-filled" variant="solo-filled"
flat flat
hide-details hide-details
@@ -3,7 +3,6 @@ import MarketPluginCard from "@/components/extension/MarketPluginCard.vue";
import PluginSortControl from "@/components/extension/PluginSortControl.vue"; import PluginSortControl from "@/components/extension/PluginSortControl.vue";
import defaultPluginIcon from "@/assets/images/plugin_icon.png"; import defaultPluginIcon from "@/assets/images/plugin_icon.png";
import { computed } from "vue"; import { computed } from "vue";
import { normalizeTextInput } from "@/utils/inputValue";
const props = defineProps({ const props = defineProps({
state: { state: {
@@ -213,13 +212,11 @@ const marketSortItems = computed(() => [
</div> </div>
<v-text-field <v-text-field
:model-value="marketSearch" v-model="marketSearch"
@update:model-value="marketSearch = normalizeTextInput($event)"
class="ml-auto" class="ml-auto"
density="compact" density="compact"
:label="tm('search.marketPlaceholder')" :label="tm('search.marketPlaceholder')"
prepend-inner-icon="mdi-magnify" prepend-inner-icon="mdi-magnify"
clearable
variant="solo-filled" variant="solo-filled"
flat flat
hide-details hide-details
+2 -2
View File
@@ -245,7 +245,7 @@ export default defineConfig({
next: '下一篇' next: '下一篇'
}, },
editLink: { editLink: {
pattern: 'https://github.com/AstrBotdevs/AstrBot/edit/master/docs/:path', pattern: 'https://github.com/AstrBotdevs/AstrBot-docs/edit/v4/:path',
text: '发现文档有问题?在 GitHub 上编辑此页', text: '发现文档有问题?在 GitHub 上编辑此页',
}, },
logo: '/logo_prod.png', logo: '/logo_prod.png',
@@ -484,7 +484,7 @@ export default defineConfig({
next: 'Next' next: 'Next'
}, },
editLink: { editLink: {
pattern: 'https://github.com/AstrBotdevs/AstrBot/edit/master/docs/:path', pattern: 'https://github.com/AstrBotdevs/AstrBot-docs/edit/v4/:path',
text: 'Edit this page on GitHub', text: 'Edit this page on GitHub',
}, },
logo: '/logo_prod.png', logo: '/logo_prod.png',
+2
View File
@@ -14,6 +14,8 @@ Welcome to submit Issues or Pull Requests:
- [AstrBotDevs/AstrBot](https://github.com/AstrBotDevs/AstrBot) - [AstrBotDevs/AstrBot](https://github.com/AstrBotDevs/AstrBot)
- [AstrBotDevs/AstrBot-Docs](https://github.com/AstrBotDevs/AstrBot-docs)
### Tencent QQ Groups ### Tencent QQ Groups
> - All groups are available to join. If you find that the group size is below the limit, please feel free to join. > - All groups are available to join. If you find that the group size is below the limit, please feel free to join.
-8
View File
@@ -128,9 +128,6 @@ The default AstrBot configuration is as follows:
"telegram": { "telegram": {
"pre_ack_emoji": {"enable": False, "emojis": ["✍️"]}, "pre_ack_emoji": {"enable": False, "emojis": ["✍️"]},
}, },
"discord": {
"pre_ack_emoji": {"enable": False, "emojis": ["🤔"]},
},
}, },
"wake_prefix": ["/"], "wake_prefix": ["/"],
"log_level": "INFO", "log_level": "INFO",
@@ -514,11 +511,6 @@ When enabled, AstrBot sends a pre-reply emoji before requesting the LLM to infor
- `enable`: Whether to enable pre-reply emojis for Telegram messages. Default is `false`. - `enable`: Whether to enable pre-reply emojis for Telegram messages. Default is `false`.
- `emojis`: List of pre-reply emojis. Default is `["✍️"]`. Telegram only supports a fixed set of reactions; refer to [reactions.txt](https://gist.github.com/Soulter/3f22c8e5f9c7e152e967e8bc28c97fc9). - `emojis`: List of pre-reply emojis. Default is `["✍️"]`. Telegram only supports a fixed set of reactions; refer to [reactions.txt](https://gist.github.com/Soulter/3f22c8e5f9c7e152e967e8bc28c97fc9).
##### discord
- `enable`: Whether to enable pre-reply emojis for Discord messages. Default is `false`.
- `emojis`: List of pre-reply emojis. Default is `["🤔"]`. Refer to [Discord Reaction FAQ](https://support.discord.com/hc/en-us/articles/12102061808663-Reactions-and-Super-Reactions-FAQ).
### `wake_prefix` ### `wake_prefix`
Wake prefix. Default is `/`. When a message starts with `/`, AstrBot is awakened. Wake prefix. Default is `/`. When a message starts with `/`, AstrBot is awakened.
+2
View File
@@ -29,6 +29,8 @@ https://discord.gg/PxgzhmxJ
- [AstrBotDevs/AstrBot](https://github.com/AstrBotDevs/AstrBot) - [AstrBotDevs/AstrBot](https://github.com/AstrBotDevs/AstrBot)
- [AstrBotDevs/AstrBot-Docs](https://github.com/AstrBotDevs/AstrBot-docs)
## 成为 AstrBot 组织成员 ## 成为 AstrBot 组织成员
欢迎加入我们! 欢迎加入我们!
+1 -9
View File
@@ -128,9 +128,6 @@ AstrBot 默认配置如下:
"telegram": { "telegram": {
"pre_ack_emoji": {"enable": False, "emojis": ["✍️"]}, "pre_ack_emoji": {"enable": False, "emojis": ["✍️"]},
}, },
"discord": {
"pre_ack_emoji": {"enable": False, "emojis": ["🤔"]},
},
}, },
"wake_prefix": ["/"], "wake_prefix": ["/"],
"log_level": "INFO", "log_level": "INFO",
@@ -509,16 +506,11 @@ AstrBot WebUI 配置。
- `enable`: 是否启用飞书消息预回复表情。默认为 `false` - `enable`: 是否启用飞书消息预回复表情。默认为 `false`
- `emojis`: 预回复的表情列表。默认为 `["Typing"]`。表情枚举名参考:[表情文案说明](https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce) - `emojis`: 预回复的表情列表。默认为 `["Typing"]`。表情枚举名参考:[表情文案说明](https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce)
##### telegram #### telegram
- `enable`: 是否启用 Telegram 消息预回复表情。默认为 `false` - `enable`: 是否启用 Telegram 消息预回复表情。默认为 `false`
- `emojis`: 预回复的表情列表。默认为 `["✍️"]`。Telegram 仅支持固定反应集合,参考:[reactions.txt](https://gist.github.com/Soulter/3f22c8e5f9c7e152e967e8bc28c97fc9) - `emojis`: 预回复的表情列表。默认为 `["✍️"]`。Telegram 仅支持固定反应集合,参考:[reactions.txt](https://gist.github.com/Soulter/3f22c8e5f9c7e152e967e8bc28c97fc9)
##### discord
- `enable`: 是否启用 Discord 消息预回复表情。默认为 `false`
- `emojis`: 预回复的表情列表。默认为 `["🤔"]`。Discord反应支持参考:[Discord Reaction FAQ](https://support.discord.com/hc/en-us/articles/12102061808663-Reactions-and-Super-Reactions-FAQ)
### `wake_prefix` ### `wake_prefix`
唤醒前缀。默认为 `/`。当消息以 `/` 开头时,AstrBot 会被唤醒。 唤醒前缀。默认为 `/`。当消息以 `/` 开头时,AstrBot 会被唤醒。