mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 00:33:20 +08:00
feat: add message source context enhancement
This commit is contained in:
parent
5928b33057
commit
0cb79f74e9
69 changed files with 6217 additions and 597 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
||||
import { normalizeLastMessageError } from '../../last-message-error.js';
|
||||
import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
|
||||
|
||||
export const DINGTALK_RPC_CHANNEL = '/dingtalk';
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ export const DINGTALK_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: 'bot.workspace.set',
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: 'bot.context-enhancement.set',
|
||||
});
|
||||
|
||||
const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
|
||||
|
|
@ -162,6 +164,7 @@ function normalizeBot(value) {
|
|||
configured: value.configured !== false,
|
||||
workspace: optionalString(value.workspace, 4_096) ?? '',
|
||||
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
||||
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
|
||||
bot: {
|
||||
name: optionalString(bot.name, 100) ?? '钉钉机器人',
|
||||
clientIdMasked: optionalString(bot.clientIdMasked, 140) ?? '已安全保存',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
|||
import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
|
||||
import { h } from '../../i18n.js';
|
||||
import { WorkspaceEditor } from '../../workspace-editor.js';
|
||||
import { ContextEnhancementEditor } from '../../context-enhancement.js';
|
||||
import {
|
||||
AgentPresetCatalogContext,
|
||||
AgentPresetEditor,
|
||||
|
|
@ -220,6 +221,7 @@ export function AccountCard({
|
|||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
onAgentPresetSave,
|
||||
onContextEnhancementSave,
|
||||
onRequestRemove,
|
||||
onConfirmRemove,
|
||||
onCancelRemove,
|
||||
|
|
@ -254,6 +256,11 @@ export function AccountCard({
|
|||
disabled: Boolean(busy),
|
||||
onSave: onAgentPresetSave,
|
||||
}),
|
||||
h(ContextEnhancementEditor, {
|
||||
config: account.contextEnhancement,
|
||||
disabled: Boolean(busy),
|
||||
onSave: onContextEnhancementSave,
|
||||
}),
|
||||
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
||||
h('div', { className: 'dim-cardFooterLayout' },
|
||||
h('div', { className: 'ddt-actions dim-cardActions' },
|
||||
|
|
@ -294,6 +301,7 @@ function AccountList(props) {
|
|||
onReconnect: () => props.onReconnect(account),
|
||||
onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
|
||||
onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(account, agentPreset),
|
||||
onContextEnhancementSave: (config) => props.onContextEnhancementSave(account, config),
|
||||
onRequestRemove: () => props.onRequestRemove(account),
|
||||
onConfirmRemove: () => props.onConfirmRemove(account),
|
||||
onCancelRemove: props.onCancelRemove,
|
||||
|
|
@ -737,13 +745,13 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
}
|
||||
}, [discardStaleFeedback, invoke, loadStatus, setBotBusy, workspaceFence]);
|
||||
|
||||
const saveAgentPreset = React.useCallback(async (account, agentPreset) => {
|
||||
const saveBotSetting = React.useCallback(async (account, operation, endpoint, payload) => {
|
||||
const snapshotVersion = workspaceFence.beginMutation();
|
||||
setBotBusy(account.botId, 'preset');
|
||||
setBotBusy(account.botId, operation);
|
||||
try {
|
||||
const snapshot = normalizeSnapshot(await invoke(
|
||||
DINGTALK_ENDPOINTS.setAgentPreset,
|
||||
{ botId: account.botId, agentPreset },
|
||||
endpoint,
|
||||
{ botId: account.botId, ...payload },
|
||||
));
|
||||
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
||||
setModel({
|
||||
|
|
@ -853,7 +861,12 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
removeTarget,
|
||||
onReconnect: (account) => void reconnect(account),
|
||||
onWorkspaceSave: saveWorkspace,
|
||||
onAgentPresetSave: saveAgentPreset,
|
||||
onAgentPresetSave: (account, agentPreset) => saveBotSetting(
|
||||
account, 'preset', DINGTALK_ENDPOINTS.setAgentPreset, { agentPreset },
|
||||
),
|
||||
onContextEnhancementSave: (account, config) => saveBotSetting(
|
||||
account, 'context-enhancement', DINGTALK_ENDPOINTS.setContextEnhancement, { config },
|
||||
),
|
||||
onRequestRemove: (account) => setRemoveTarget(account.botId),
|
||||
onConfirmRemove: (account) => void remove(account),
|
||||
onCancelRemove: () => setRemoveTarget(null),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { normalizeAgentPresetCatalog, normalizeAgentPresetId } from "../../agent-preset.js";
|
||||
import { normalizeLastMessageError } from "../../last-message-error.js";
|
||||
import { normalizeContextEnhancementConfig } from "../../../../src/channels/shared/context-enhancement.mjs";
|
||||
|
||||
export const FEISHU_RPC_CHANNEL = "/feishu";
|
||||
|
||||
|
|
@ -24,6 +25,7 @@ export const FEISHU_ENDPOINTS = Object.freeze({
|
|||
deleteBot: "bot.delete",
|
||||
setWorkspace: "bot.workspace.set",
|
||||
setAgentPreset: "bot.preset.set",
|
||||
setContextEnhancement: "bot.context-enhancement.set",
|
||||
setGroupResponseMode: "bot.group-response-mode.set",
|
||||
// Kept for rolling upgrades. The multi-bot UI never calls these endpoints.
|
||||
testConnection: "connection.test",
|
||||
|
|
@ -203,6 +205,7 @@ export function normalizeBotConnection(value, fallbackBotId) {
|
|||
configured: value.configured !== false,
|
||||
workspace: optionalString(value.workspace)?.slice(0, 4_096) ?? "",
|
||||
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
||||
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
|
||||
groupResponseMode: normalizeGroupResponseMode(value.groupResponseMode),
|
||||
groupMessagePermissionGranted: value.groupMessagePermissionGranted === true,
|
||||
bot: normalizeBot(value.bot),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from "./api.js";
|
||||
import { useAnimationFrameScheduler } from "../../lifecycle.js";
|
||||
import { WorkspaceEditor } from "../../workspace-editor.js";
|
||||
import { ContextEnhancementEditor } from "../../context-enhancement.js";
|
||||
import {
|
||||
AgentPresetCatalogContext,
|
||||
AgentPresetEditor,
|
||||
|
|
@ -553,6 +554,7 @@ export function BotCard({
|
|||
onRepairCallback,
|
||||
onWorkspaceSave,
|
||||
onAgentPresetSave,
|
||||
onContextEnhancementSave,
|
||||
onGroupResponseModeSave,
|
||||
onGroupMessagePermissionAuthorize,
|
||||
onRequestRemove,
|
||||
|
|
@ -611,6 +613,11 @@ export function BotCard({
|
|||
disabled: Boolean(busy),
|
||||
onSave: onAgentPresetSave,
|
||||
}),
|
||||
h(ContextEnhancementEditor, {
|
||||
config: connection.contextEnhancement,
|
||||
disabled: Boolean(busy),
|
||||
onSave: onContextEnhancementSave,
|
||||
}),
|
||||
h(GroupResponseModeEditor, {
|
||||
value: connection.groupResponseMode,
|
||||
permissionGranted: connection.groupMessagePermissionGranted,
|
||||
|
|
@ -708,6 +715,7 @@ function BotList(props) {
|
|||
onRepairCallback: () => props.onRepairCallback(bot),
|
||||
onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
|
||||
onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(bot, agentPreset),
|
||||
onContextEnhancementSave: (config) => props.onContextEnhancementSave(bot, config),
|
||||
onGroupResponseModeSave: (groupResponseMode) => props.onGroupResponseModeSave(bot, groupResponseMode),
|
||||
onGroupMessagePermissionAuthorize: () => props.onGroupMessagePermissionAuthorize(bot),
|
||||
onRequestRemove: () => props.onRequestRemove(bot),
|
||||
|
|
@ -1296,15 +1304,15 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|||
}
|
||||
}, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
|
||||
|
||||
const saveAgentPreset = React.useCallback(async (connection, agentPreset) => {
|
||||
const saveBotSetting = React.useCallback(async (connection, operation, endpoint, payload) => {
|
||||
const { botId } = connection;
|
||||
const snapshotVersion = workspaceFence.beginMutation();
|
||||
setBotBusy(botId, "preset");
|
||||
setBotBusy(botId, operation);
|
||||
setBotError(botId, null);
|
||||
try {
|
||||
const snapshot = normalizeBotsSnapshot(await invoke(
|
||||
FEISHU_ENDPOINTS.setAgentPreset,
|
||||
{ botId, agentPreset },
|
||||
endpoint,
|
||||
{ botId, ...payload },
|
||||
));
|
||||
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
||||
mergeSnapshot(snapshot);
|
||||
|
|
@ -1521,7 +1529,12 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|||
onReconnect: (bot) => void reconnectOneBot(bot),
|
||||
onRepairCallback: repairCallback,
|
||||
onWorkspaceSave: saveWorkspace,
|
||||
onAgentPresetSave: saveAgentPreset,
|
||||
onAgentPresetSave: (connection, agentPreset) => saveBotSetting(
|
||||
connection, "preset", FEISHU_ENDPOINTS.setAgentPreset, { agentPreset },
|
||||
),
|
||||
onContextEnhancementSave: (connection, config) => saveBotSetting(
|
||||
connection, "context-enhancement", FEISHU_ENDPOINTS.setContextEnhancement, { config },
|
||||
),
|
||||
onGroupResponseModeSave: saveGroupResponseMode,
|
||||
onGroupMessagePermissionAuthorize: authorizeGroupMessages,
|
||||
onRequestRemove: requestRemove,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
||||
import { normalizeLastMessageError } from '../../last-message-error.js';
|
||||
import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
|
||||
|
||||
export const QQ_RPC_CHANNEL = '/qq';
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ export const QQ_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: 'bot.workspace.set',
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: 'bot.context-enhancement.set',
|
||||
});
|
||||
|
||||
const PROVISION_STATES = new Set(['starting', 'pending', 'refreshing', 'connecting', 'connected', 'failed', 'cancelled']);
|
||||
|
|
@ -87,6 +89,7 @@ function normalizeBot(value) {
|
|||
state: connected ? 'connected' : state,
|
||||
workspace: text(value.workspace, '', 4_096),
|
||||
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
||||
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
|
||||
bot: {
|
||||
name: text(value.bot?.name, 'QQ机器人', 100),
|
||||
appIdMasked: text(value.bot?.appIdMasked, '应用标识已安全保存', 140),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { QqLogoGlyph } from '../../channel-logos.js';
|
|||
import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
|
||||
import { h } from '../../i18n.js';
|
||||
import { WorkspaceEditor } from '../../workspace-editor.js';
|
||||
import { ContextEnhancementEditor } from '../../context-enhancement.js';
|
||||
import {
|
||||
AgentPresetCatalogContext,
|
||||
AgentPresetEditor,
|
||||
|
|
@ -166,6 +167,7 @@ export function AccountCard({
|
|||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
onAgentPresetSave,
|
||||
onContextEnhancementSave,
|
||||
onRequestRemove,
|
||||
onConfirmRemove,
|
||||
onCancelRemove,
|
||||
|
|
@ -198,6 +200,11 @@ export function AccountCard({
|
|||
disabled: Boolean(busy),
|
||||
onSave: onAgentPresetSave,
|
||||
}),
|
||||
h(ContextEnhancementEditor, {
|
||||
config: account.contextEnhancement,
|
||||
disabled: Boolean(busy),
|
||||
onSave: onContextEnhancementSave,
|
||||
}),
|
||||
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
||||
h('div', { className: 'dim-cardFooterLayout' },
|
||||
h('div', { className: 'ddt-actions dim-cardActions' },
|
||||
|
|
@ -468,6 +475,12 @@ export function QqSettingsTab({ rpcCall }) {
|
|||
QQ_ENDPOINTS.setAgentPreset,
|
||||
{ botId: account.botId, agentPreset },
|
||||
),
|
||||
onContextEnhancementSave: (config) => botAction(
|
||||
account,
|
||||
'context-enhancement',
|
||||
QQ_ENDPOINTS.setContextEnhancement,
|
||||
{ botId: account.botId, config },
|
||||
),
|
||||
onRequestRemove: () => setRemoveTarget(account.botId),
|
||||
onCancelRemove: () => setRemoveTarget(null),
|
||||
onConfirmRemove: async () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
||||
import { normalizeLastMessageError } from '../../last-message-error.js';
|
||||
import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
|
||||
|
||||
const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: 'bot.workspace.set',
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: 'bot.context-enhancement.set',
|
||||
});
|
||||
|
||||
export function createTokenChannelApi(channel, connectionSummary, {
|
||||
|
|
@ -57,6 +59,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|||
state: connected ? 'connected' : state,
|
||||
workspace: text(value.workspace, '', 4_096),
|
||||
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
||||
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
|
||||
bot: {
|
||||
name: text(value.bot?.name, `${channel}机器人`, 100),
|
||||
username: text(value.bot?.username, '', 100),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { CredentialActionIcon, CredentialBindingPanel } from '../../credential-b
|
|||
import { h } from '../../i18n.js';
|
||||
import { installDingtalkStyles } from '../dingtalk/styles.js';
|
||||
import { WorkspaceEditor } from '../../workspace-editor.js';
|
||||
import { ContextEnhancementEditor } from '../../context-enhancement.js';
|
||||
import {
|
||||
AgentPresetCatalogContext,
|
||||
AgentPresetEditor,
|
||||
|
|
@ -73,7 +74,7 @@ export function createTokenChannelSettings(definition) {
|
|||
accountSettingsEndpoint = null,
|
||||
} = definition;
|
||||
|
||||
function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAgentPresetSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
|
||||
function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
|
||||
const state = busy === 'reconnect' ? 'connecting' : account.state;
|
||||
const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning';
|
||||
const stateLabel = account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪';
|
||||
|
|
@ -105,6 +106,11 @@ export function createTokenChannelSettings(definition) {
|
|||
disabled: Boolean(busy),
|
||||
onSave: onAgentPresetSave,
|
||||
}),
|
||||
h(ContextEnhancementEditor, {
|
||||
config: account.contextEnhancement,
|
||||
disabled: Boolean(busy),
|
||||
onSave: onContextEnhancementSave,
|
||||
}),
|
||||
AccountSettings ? h(AccountSettings, {
|
||||
account,
|
||||
busy: Boolean(busy),
|
||||
|
|
@ -309,6 +315,12 @@ export function createTokenChannelSettings(definition) {
|
|||
endpoints.setAgentPreset,
|
||||
{ botId: account.botId, agentPreset },
|
||||
),
|
||||
onContextEnhancementSave: (config) => botAction(
|
||||
account,
|
||||
'context-enhancement',
|
||||
endpoints.setContextEnhancement,
|
||||
{ botId: account.botId, config },
|
||||
),
|
||||
onAccountSettingsSave: AccountSettings && accountSettingsEndpoint
|
||||
? (payload) => botAction(
|
||||
account,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
||||
import { normalizeLastMessageError } from '../../last-message-error.js';
|
||||
import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
|
||||
|
||||
export const WECOM_RPC_CHANNEL = '/wecom';
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ export const WECOM_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: 'bot.workspace.set',
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: 'bot.context-enhancement.set',
|
||||
});
|
||||
|
||||
const PROVISION_STATES = new Set(['starting', 'pending', 'refreshing', 'connecting', 'connected', 'failed', 'cancelled']);
|
||||
|
|
@ -96,6 +98,7 @@ function normalizeBot(value) {
|
|||
state: connected ? 'connected' : state,
|
||||
workspace: text(value.workspace, '', 4_096),
|
||||
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
||||
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
|
||||
bot: {
|
||||
name: text(value.bot?.name, '企业微信机器人', 100),
|
||||
appIdMasked: text(value.bot?.appIdMasked, '应用标识已安全保存', 140),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { WecomLogoGlyph } from '../../channel-logos.js';
|
|||
import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
|
||||
import { h } from '../../i18n.js';
|
||||
import { WorkspaceEditor } from '../../workspace-editor.js';
|
||||
import { ContextEnhancementEditor } from '../../context-enhancement.js';
|
||||
import {
|
||||
AgentPresetCatalogContext,
|
||||
AgentPresetEditor,
|
||||
|
|
@ -165,6 +166,7 @@ export function AccountCard({
|
|||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
onAgentPresetSave,
|
||||
onContextEnhancementSave,
|
||||
onRequestRemove,
|
||||
onConfirmRemove,
|
||||
onCancelRemove,
|
||||
|
|
@ -197,6 +199,11 @@ export function AccountCard({
|
|||
disabled: Boolean(busy),
|
||||
onSave: onAgentPresetSave,
|
||||
}),
|
||||
h(ContextEnhancementEditor, {
|
||||
config: account.contextEnhancement,
|
||||
disabled: Boolean(busy),
|
||||
onSave: onContextEnhancementSave,
|
||||
}),
|
||||
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
||||
h('div', { className: 'dim-cardFooterLayout' },
|
||||
h('div', { className: 'ddt-actions dim-cardActions' },
|
||||
|
|
@ -499,6 +506,12 @@ export function WecomSettingsTab({ rpcCall }) {
|
|||
WECOM_ENDPOINTS.setAgentPreset,
|
||||
{ botId: account.botId, agentPreset },
|
||||
),
|
||||
onContextEnhancementSave: (config) => botAction(
|
||||
account,
|
||||
'context-enhancement',
|
||||
WECOM_ENDPOINTS.setContextEnhancement,
|
||||
{ botId: account.botId, config },
|
||||
),
|
||||
onRequestRemove: () => setRemoveTarget(account.botId),
|
||||
onCancelRemove: () => setRemoveTarget(null),
|
||||
onConfirmRemove: async () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
||||
import { normalizeLastMessageError } from '../../last-message-error.js';
|
||||
import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
|
||||
|
||||
export const WEIXIN_RPC_CHANNEL = '/weixin';
|
||||
export const WEIXIN_ENDPOINTS = Object.freeze({
|
||||
|
|
@ -12,6 +13,7 @@ export const WEIXIN_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: 'bot.workspace.set',
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: 'bot.context-enhancement.set',
|
||||
});
|
||||
|
||||
const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
|
||||
|
|
@ -121,6 +123,7 @@ function normalizeBot(value) {
|
|||
configured: value.configured === true,
|
||||
workspace: string(value.workspace).slice(0, 4_096),
|
||||
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
||||
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
|
||||
bot: {
|
||||
name: string(value.bot.name, '微信机器人'),
|
||||
accountIdMasked: string(value.bot.accountIdMasked, '已安全保存'),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from './api.js';
|
||||
import { createPollScheduler, useAnimationFrameScheduler } from '../../lifecycle.js';
|
||||
import { WorkspaceEditor } from '../../workspace-editor.js';
|
||||
import { ContextEnhancementEditor } from '../../context-enhancement.js';
|
||||
import {
|
||||
AgentPresetCatalogContext,
|
||||
AgentPresetEditor,
|
||||
|
|
@ -207,6 +208,7 @@ export function AccountCard({
|
|||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
onAgentPresetSave,
|
||||
onContextEnhancementSave,
|
||||
onRequestRemove,
|
||||
onConfirmRemove,
|
||||
onCancelRemove,
|
||||
|
|
@ -238,6 +240,12 @@ export function AccountCard({
|
|||
disabled: Boolean(busy),
|
||||
onSave: onAgentPresetSave,
|
||||
}),
|
||||
h(ContextEnhancementEditor, {
|
||||
config: account.contextEnhancement,
|
||||
groupSupported: false,
|
||||
disabled: Boolean(busy),
|
||||
onSave: onContextEnhancementSave,
|
||||
}),
|
||||
h('div', { className: 'dxw-accountFooter dim-cardFooter' },
|
||||
h('div', { className: 'dim-cardFooterLayout' },
|
||||
h('div', { className: 'dxw-actions dim-cardActions' },
|
||||
|
|
@ -280,6 +288,7 @@ function AccountList(props) {
|
|||
onReconnect: () => props.onReconnect(account),
|
||||
onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
|
||||
onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(account, agentPreset),
|
||||
onContextEnhancementSave: (config) => props.onContextEnhancementSave(account, config),
|
||||
onRequestRemove: () => props.onRequestRemove(account),
|
||||
onConfirmRemove: () => props.onConfirmRemove(account),
|
||||
onCancelRemove: props.onCancelRemove,
|
||||
|
|
@ -594,13 +603,13 @@ export function WeixinSettingsTab({ rpcCall }) {
|
|||
}
|
||||
}, [invoke, loadStatus, setBotBusy, workspaceFence]);
|
||||
|
||||
const saveAgentPreset = React.useCallback(async (account, agentPreset) => {
|
||||
const saveBotSetting = React.useCallback(async (account, operation, endpoint, payload) => {
|
||||
const snapshotVersion = workspaceFence.beginMutation();
|
||||
setBotBusy(account.botId, 'preset');
|
||||
setBotBusy(account.botId, operation);
|
||||
try {
|
||||
const snapshot = normalizeSnapshot(await invoke(
|
||||
WEIXIN_ENDPOINTS.setAgentPreset,
|
||||
{ botId: account.botId, agentPreset },
|
||||
endpoint,
|
||||
{ botId: account.botId, ...payload },
|
||||
));
|
||||
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
||||
setModel({
|
||||
|
|
@ -703,7 +712,12 @@ export function WeixinSettingsTab({ rpcCall }) {
|
|||
removeTarget,
|
||||
onReconnect: (account) => void reconnect(account),
|
||||
onWorkspaceSave: saveWorkspace,
|
||||
onAgentPresetSave: saveAgentPreset,
|
||||
onAgentPresetSave: (account, agentPreset) => saveBotSetting(
|
||||
account, 'preset', WEIXIN_ENDPOINTS.setAgentPreset, { agentPreset },
|
||||
),
|
||||
onContextEnhancementSave: (account, config) => saveBotSetting(
|
||||
account, 'context-enhancement', WEIXIN_ENDPOINTS.setContextEnhancement, { config },
|
||||
),
|
||||
onRequestRemove: (account) => setRemoveTarget(account.botId),
|
||||
onConfirmRemove: (account) => void remove(account),
|
||||
onCancelRemove: () => setRemoveTarget(null),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
||||
import { normalizeLastMessageError } from '../../last-message-error.js';
|
||||
import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
|
||||
|
||||
export const WHATSAPP_RPC_CHANNEL = '/whatsapp';
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ export const WHATSAPP_ENDPOINTS = Object.freeze({
|
|||
setAccessPolicy: 'bot.access-policy.set',
|
||||
setWorkspace: 'bot.workspace.set',
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: 'bot.context-enhancement.set',
|
||||
});
|
||||
|
||||
const PROVISION_STATES = new Set(['starting', 'pending', 'connecting', 'connected', 'failed', 'cancelled']);
|
||||
|
|
@ -88,6 +90,7 @@ function normalizeBot(value) {
|
|||
state: connected ? 'connected' : state,
|
||||
workspace: text(value.workspace, '', 4_096),
|
||||
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
||||
contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
|
||||
accessPolicy: {
|
||||
accessMode: ['self-only', 'private-allowlist', 'open'].includes(
|
||||
value.accessPolicy?.accessMode,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { WhatsappLogoGlyph } from '../../channel-logos.js';
|
|||
import { QrActionIcon } from '../../credential-binding.js';
|
||||
import { h } from '../../i18n.js';
|
||||
import { WorkspaceEditor } from '../../workspace-editor.js';
|
||||
import { ContextEnhancementEditor } from '../../context-enhancement.js';
|
||||
import {
|
||||
AgentPresetCatalogContext,
|
||||
AgentPresetEditor,
|
||||
|
|
@ -297,6 +298,7 @@ export function WhatsappAccountCard({
|
|||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
onAgentPresetSave,
|
||||
onContextEnhancementSave,
|
||||
onAccessPolicySave,
|
||||
onRequestRemove,
|
||||
onConfirmRemove,
|
||||
|
|
@ -334,6 +336,11 @@ export function WhatsappAccountCard({
|
|||
disabled: Boolean(busy),
|
||||
onSave: onAgentPresetSave,
|
||||
}),
|
||||
h(ContextEnhancementEditor, {
|
||||
config: account.contextEnhancement,
|
||||
disabled: Boolean(busy),
|
||||
onSave: onContextEnhancementSave,
|
||||
}),
|
||||
h(WhatsappAccessSettings, {
|
||||
account,
|
||||
busy: Boolean(busy),
|
||||
|
|
@ -596,6 +603,12 @@ export function WhatsappSettingsTab({ rpcCall }) {
|
|||
WHATSAPP_ENDPOINTS.setAgentPreset,
|
||||
{ botId: account.botId, agentPreset },
|
||||
),
|
||||
onContextEnhancementSave: (config) => botAction(
|
||||
account,
|
||||
'context-enhancement',
|
||||
WHATSAPP_ENDPOINTS.setContextEnhancement,
|
||||
{ botId: account.botId, config },
|
||||
),
|
||||
onAccessPolicySave: (accessPolicy) => botAction(
|
||||
account,
|
||||
'access',
|
||||
|
|
|
|||
275
plugin-src/client/context-enhancement.js
Normal file
275
plugin-src/client/context-enhancement.js
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
import * as React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import {
|
||||
CONTEXT_ENHANCEMENT_FIELDS,
|
||||
CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH,
|
||||
CONTEXT_GUIDANCE_EXAMPLE,
|
||||
normalizeContextEnhancementConfig,
|
||||
validateContextEnhancementConfig,
|
||||
} from '../../src/channels/shared/context-enhancement.mjs';
|
||||
import { h, localizeText } from './i18n.js';
|
||||
|
||||
const FIELD_LABELS = Object.freeze({
|
||||
channel: '渠道',
|
||||
conversationType: '会话类型',
|
||||
senderId: '发送者标识',
|
||||
senderName: '发送者昵称',
|
||||
botId: '机器人标识',
|
||||
});
|
||||
|
||||
export function contextEnhancementLabel(config) {
|
||||
const { groupEnabled, directEnabled } = normalizeContextEnhancementConfig(config);
|
||||
if (groupEnabled && directEnabled) return '群聊和私聊';
|
||||
if (groupEnabled) return '仅群聊';
|
||||
if (directEnabled) return '仅私聊';
|
||||
return '未开启';
|
||||
}
|
||||
|
||||
function ContextIcon({ kind = 'sliders' }) {
|
||||
const path = kind === 'close' ? 'M6 6l12 12M6 18 18 6'
|
||||
: kind === 'chevron' ? 'm9 5 7 7-7 7'
|
||||
: 'M21 4h-7M10 4H3M21 12h-9M8 12H3M21 20h-3M14 20H3M14 2v4M8 10v4M18 18v4';
|
||||
return h('svg', {
|
||||
width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none',
|
||||
stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
|
||||
'aria-hidden': 'true', focusable: 'false',
|
||||
}, h('path', { d: path }));
|
||||
}
|
||||
|
||||
function ContextEnhancementDialog({ config, groupSupported, disabled, onSave, onClose, returnFocusRef, id }) {
|
||||
// A mounted dialog owns its draft; status refreshes must not replace unsaved edits.
|
||||
const [draft, setDraft] = React.useState(() => ({
|
||||
...normalizeContextEnhancementConfig(config),
|
||||
...(!groupSupported ? { groupEnabled: false } : {}),
|
||||
}));
|
||||
const [saving, setSaving] = React.useState(false);
|
||||
const [error, setError] = React.useState(null);
|
||||
const savingRef = React.useRef(false);
|
||||
const dialogRef = React.useRef(null);
|
||||
const mountedRef = React.useRef(true);
|
||||
const titleId = React.useId();
|
||||
const descriptionId = React.useId();
|
||||
const groupNoticeId = React.useId();
|
||||
const guidanceId = React.useId();
|
||||
const guidanceHelpId = React.useId();
|
||||
const fieldIdPrefix = React.useId();
|
||||
const fieldsHelpId = React.useId();
|
||||
const senderNameHelpId = React.useId();
|
||||
const guidanceExample = localizeText(CONTEXT_GUIDANCE_EXAMPLE);
|
||||
const busy = disabled || saving;
|
||||
|
||||
React.useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
dialogRef.current?.focus?.();
|
||||
const keepFocus = (event) => {
|
||||
if (dialogRef.current && !dialogRef.current.contains(event.target)) dialogRef.current.focus();
|
||||
};
|
||||
globalThis.document?.addEventListener?.('focusin', keepFocus);
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
globalThis.document?.removeEventListener?.('focusin', keepFocus);
|
||||
queueMicrotask(() => returnFocusRef.current?.focus?.());
|
||||
};
|
||||
}, [returnFocusRef]);
|
||||
|
||||
const change = (key, value) => {
|
||||
if (busy || savingRef.current) return;
|
||||
setDraft((current) => ({ ...current, [key]: value }));
|
||||
setError(null);
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
if (!savingRef.current) onClose();
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
if (busy || savingRef.current) return;
|
||||
savingRef.current = true;
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
dialogRef.current?.focus?.();
|
||||
try {
|
||||
const next = validateContextEnhancementConfig(draft);
|
||||
await onSave(next);
|
||||
if (mountedRef.current) onClose();
|
||||
} catch (cause) {
|
||||
if (mountedRef.current) setError(cause?.message ?? '上下文增强保存失败,请重试。');
|
||||
} finally {
|
||||
savingRef.current = false;
|
||||
if (mountedRef.current) setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const content = h('div', {
|
||||
className: 'dim-contextBackdrop',
|
||||
onMouseDown: (event) => { if (event.target === event.currentTarget) cancel(); },
|
||||
}, h('section', {
|
||||
id,
|
||||
ref: dialogRef,
|
||||
className: 'dim-contextDialog',
|
||||
role: 'dialog',
|
||||
'aria-modal': 'true',
|
||||
'aria-labelledby': titleId,
|
||||
'aria-describedby': descriptionId,
|
||||
'aria-busy': saving,
|
||||
tabIndex: -1,
|
||||
onKeyDown: (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
cancel();
|
||||
}
|
||||
if (event.key !== 'Tab') return;
|
||||
const controls = dialogRef.current?.querySelectorAll?.(
|
||||
'button:not(:disabled), input:not(:disabled), textarea:not(:disabled)',
|
||||
);
|
||||
if (!controls?.length) {
|
||||
event.preventDefault();
|
||||
dialogRef.current?.focus?.();
|
||||
return;
|
||||
}
|
||||
const first = controls[0];
|
||||
const last = controls[controls.length - 1];
|
||||
const active = globalThis.document?.activeElement;
|
||||
if (event.shiftKey && (active === first || active === dialogRef.current)) {
|
||||
event.preventDefault();
|
||||
last.focus();
|
||||
} else if (!event.shiftKey && (active === last || active === dialogRef.current)) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
h('header', { className: 'dim-contextHeader' },
|
||||
h('div', { className: 'dim-contextHeaderTitle' },
|
||||
h('h3', { id: titleId }, '上下文增强'),
|
||||
h('span', { className: 'dim-contextHelp dim-contextHeaderHelp' },
|
||||
h('button', {
|
||||
type: 'button', className: 'dim-contextHelpButton', disabled: busy,
|
||||
'aria-label': '查看上下文增强说明', 'aria-describedby': descriptionId,
|
||||
}, h('span', { 'aria-hidden': 'true' }, '?')),
|
||||
h('span', { id: descriptionId, className: 'dim-contextTooltip dim-contextHeaderTooltip', role: 'tooltip' },
|
||||
'选择在哪些会话中启用、提供哪些来源字段,以及如何使用这些信息。仅使用已有消息元数据,不查询平台 API。'))),
|
||||
h('button', {
|
||||
type: 'button', className: 'dim-contextClose', 'aria-label': '关闭弹窗',
|
||||
disabled: saving, onClick: cancel,
|
||||
}, h(ContextIcon, { kind: 'close' }))),
|
||||
h('fieldset', { className: 'dim-contextSection', disabled: busy },
|
||||
h('legend', null, '启用范围'),
|
||||
h('div', { className: 'dim-contextSwitches' },
|
||||
h('label', { className: 'dim-contextSwitchRow' },
|
||||
h('span', { className: 'dim-contextSwitchLabel' },
|
||||
h('span', null, '群聊中启用'),
|
||||
!groupSupported ? h('span', {
|
||||
id: groupNoticeId, className: 'dim-contextUnavailable',
|
||||
}, '(当前渠道不支持群聊)') : null),
|
||||
h('input', {
|
||||
type: 'checkbox', role: 'switch', className: 'dim-contextSwitch',
|
||||
checked: groupSupported && draft.groupEnabled,
|
||||
disabled: busy || !groupSupported,
|
||||
'aria-describedby': !groupSupported ? groupNoticeId : undefined,
|
||||
onChange: (event) => { if (groupSupported) change('groupEnabled', event.target.checked); },
|
||||
})),
|
||||
h('label', { className: 'dim-contextSwitchRow' },
|
||||
h('span', null, '私聊中启用'),
|
||||
h('input', {
|
||||
type: 'checkbox', role: 'switch', className: 'dim-contextSwitch',
|
||||
checked: draft.directEnabled, disabled: busy,
|
||||
onChange: (event) => change('directEnabled', event.target.checked),
|
||||
}))),
|
||||
),
|
||||
h('fieldset', { className: 'dim-contextSection', disabled: busy },
|
||||
h('legend', { className: 'dim-contextLegend' },
|
||||
h('span', null, '来源字段'),
|
||||
h('span', { className: 'dim-contextHelp dim-contextLegendHelp' },
|
||||
h('button', {
|
||||
type: 'button', className: 'dim-contextHelpButton', disabled: busy,
|
||||
'aria-label': '查看来源字段说明', 'aria-describedby': fieldsHelpId,
|
||||
}, h('span', { 'aria-hidden': 'true' }, '?')),
|
||||
h('span', { id: fieldsHelpId, className: 'dim-contextTooltip dim-contextLegendTooltip', role: 'tooltip' },
|
||||
'增强提示词中请使用字段名(如 senderId、conversationType)引用这些信息。只发送勾选且当前消息中可用的字段,不会额外查询或补全。'))),
|
||||
h('div', { className: 'dim-contextFields' }, CONTEXT_ENHANCEMENT_FIELDS.map((field) => {
|
||||
const fieldId = `${fieldIdPrefix}-${field}`;
|
||||
return h('div', { key: field, className: 'dim-contextField' },
|
||||
h('input', {
|
||||
id: fieldId, type: 'checkbox', name: field,
|
||||
checked: draft.fields.includes(field), disabled: busy,
|
||||
onChange: (event) => change('fields', event.target.checked
|
||||
? [...draft.fields, field] : draft.fields.filter((value) => value !== field)),
|
||||
}),
|
||||
h('span', { className: 'dim-contextFieldText' },
|
||||
h('label', { className: 'dim-contextFieldName', htmlFor: fieldId }, FIELD_LABELS[field]),
|
||||
field === 'senderName' ? h('span', { className: 'dim-contextHelp dim-contextFieldHelp' },
|
||||
h('button', {
|
||||
type: 'button', className: 'dim-contextHelpButton dim-contextFieldHelpButton', disabled: busy,
|
||||
'aria-label': '查看发送者昵称字段说明', 'aria-describedby': senderNameHelpId,
|
||||
}, h('span', { 'aria-hidden': 'true' }, '?')),
|
||||
h('span', {
|
||||
id: senderNameHelpId,
|
||||
className: 'dim-contextTooltip dim-contextFieldTooltip',
|
||||
role: 'tooltip',
|
||||
}, '该字段不是每个渠道都能提供。当前消息没有发送者昵称时,即使已选择该字段,<dsh_im_source> 中也会省略 senderName。')) : null,
|
||||
h('label', { className: 'dim-contextFieldKey', htmlFor: fieldId }, field)));
|
||||
}))),
|
||||
h('div', { className: 'dim-contextGuidance' },
|
||||
h('div', { className: 'dim-contextEditorHeader' },
|
||||
h('span', { className: 'dim-contextEditorTitle' },
|
||||
h('label', { htmlFor: guidanceId }, '增强提示词'),
|
||||
h('span', { className: 'dim-contextHelp' },
|
||||
h('button', {
|
||||
type: 'button', className: 'dim-contextHelpButton', disabled: busy,
|
||||
'aria-label': '查看增强提示词使用说明', 'aria-describedby': guidanceHelpId,
|
||||
}, h('span', { 'aria-hidden': 'true' }, '?')),
|
||||
h('span', { id: guidanceHelpId, className: 'dim-contextTooltip dim-contextGuidanceTooltip', role: 'tooltip' },
|
||||
h('strong', null, '使用说明'),
|
||||
h('span', null, '用于告诉模型如何使用 <dsh_im_source> 中已选择的来源字段。只填写正文,插件会自动添加 <dsh_im_source_guidance> 成对标签。'),
|
||||
h('strong', null, '生效规则'),
|
||||
h('span', null, '只需填写正文,插件自动添加 <dsh_im_source_guidance> 成对标签。清空并保存后,不再附加增强提示词,已选来源字段仍按开关设置发送。'),
|
||||
h('strong', null, '隐私提示'),
|
||||
h('span', null, '发送者标识可能包含平台用户 ID 或电话号码形式的标识。关闭开关不会删除已经写入会话历史的信息。'),
|
||||
h('strong', null, '使用示例'),
|
||||
h('span', { className: 'dim-contextTooltipExample' }, guidanceExample)))),
|
||||
h('div', { className: 'dim-contextTextActions' },
|
||||
h('button', { type: 'button', disabled: busy, onClick: () => change('guidance', guidanceExample) }, '填入示例'),
|
||||
h('button', { type: 'button', disabled: busy, onClick: () => change('guidance', '') }, '清空'))),
|
||||
h('textarea', {
|
||||
id: guidanceId, value: draft.guidance, placeholder: guidanceExample, rows: 4, disabled: busy,
|
||||
maxLength: CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH,
|
||||
'aria-describedby': guidanceHelpId,
|
||||
onChange: (event) => change('guidance', event.target.value),
|
||||
})),
|
||||
error ? h('p', { className: 'dim-contextError', role: 'alert' }, error) : null,
|
||||
h('footer', { className: 'dim-contextFooter' },
|
||||
h('button', { type: 'button', disabled: saving, onClick: cancel }, '取消'),
|
||||
h('button', {
|
||||
type: 'button', className: 'dim-contextSave', disabled: busy,
|
||||
onClick: () => { void save(); },
|
||||
}, saving ? '保存中…' : '保存'))));
|
||||
|
||||
return globalThis.document?.body ? createPortal(content, document.body) : content;
|
||||
}
|
||||
|
||||
export function ContextEnhancementEditor({ config, groupSupported = true, disabled = false, onSave }) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const entryRef = React.useRef(null);
|
||||
const dialogId = React.useId();
|
||||
const statusId = React.useId();
|
||||
const saved = normalizeContextEnhancementConfig(config);
|
||||
const label = contextEnhancementLabel(groupSupported ? saved : { ...saved, groupEnabled: false });
|
||||
|
||||
return h(React.Fragment, null,
|
||||
h('button', {
|
||||
type: 'button', ref: entryRef, className: 'dim-contextEntry', disabled,
|
||||
'aria-label': '上下文增强', 'aria-describedby': statusId,
|
||||
'aria-haspopup': 'dialog', 'aria-expanded': open, 'aria-controls': open ? dialogId : undefined,
|
||||
onClick: () => setOpen(true),
|
||||
}, h(ContextIcon),
|
||||
h('span', { className: 'dim-contextLabel' }, '上下文增强'),
|
||||
h('span', { id: statusId, className: 'dim-contextStatus', 'data-active': label !== '未开启', 'aria-live': 'polite' }, label),
|
||||
h(ContextIcon, { kind: 'chevron' })),
|
||||
open ? h(ContextEnhancementDialog, {
|
||||
id: dialogId, config, groupSupported, disabled, onSave,
|
||||
onClose: () => setOpen(false), returnFocusRef: entryRef,
|
||||
}) : null);
|
||||
}
|
||||
|
|
@ -208,6 +208,44 @@ const EN = Object.freeze({
|
|||
'/绝对路径/到/工作区': '/absolute/path/to/workspace',
|
||||
'修改': 'Change',
|
||||
'保存': 'Save',
|
||||
'上下文增强': 'Context enhancement',
|
||||
'未开启': 'Not enabled',
|
||||
'仅群聊': 'Groups only',
|
||||
'仅私聊': 'Direct only',
|
||||
'群聊和私聊': 'Groups and direct',
|
||||
'关闭弹窗': 'Close dialog',
|
||||
'查看上下文增强说明': 'View context enhancement details',
|
||||
'启用范围': 'Enable in',
|
||||
'群聊中启用': 'Enable in group chats',
|
||||
'私聊中启用': 'Enable in direct chats',
|
||||
'(当前渠道不支持群聊)': '(This channel does not support group chats)',
|
||||
'来源字段': 'Source fields',
|
||||
'查看来源字段说明': 'View source field details',
|
||||
'渠道': 'Channel',
|
||||
'会话类型': 'Conversation type',
|
||||
'发送者标识': 'Sender ID',
|
||||
'发送者昵称': 'Sender name',
|
||||
'查看发送者昵称字段说明': 'View sender name field details',
|
||||
'该字段不是每个渠道都能提供。当前消息没有发送者昵称时,即使已选择该字段,<dsh_im_source> 中也会省略 senderName。': 'This field is not available on every channel. If the current message has no sender name, <dsh_im_source> omits senderName even when the field is selected.',
|
||||
'机器人标识': 'Bot ID',
|
||||
'增强提示词': 'Guidance',
|
||||
'查看增强提示词使用说明': 'View guidance instructions',
|
||||
'使用说明': 'How to use',
|
||||
'用于告诉模型如何使用 <dsh_im_source> 中已选择的来源字段。只填写正文,插件会自动添加 <dsh_im_source_guidance> 成对标签。': 'Tell the model how to use the selected fields in <dsh_im_source>. Enter only the body; the plugin adds paired <dsh_im_source_guidance> tags.',
|
||||
'生效规则': 'Behavior',
|
||||
'隐私提示': 'Privacy',
|
||||
'使用示例': 'Example',
|
||||
'仅依据当前消息的 <dsh_im_source> 中实际提供的字段理解来源;没有提供的字段不要猜测或补全。\nconversationType是群聊时回复严肃一点,conversationType是私聊时回复一定要幽默搞笑,像周星驰的电影一样搞笑': 'Understand the source only from fields actually present in <dsh_im_source> for the current message; do not guess or fill in missing fields.\nWhen conversationType is group, reply more seriously. When conversationType is direct, make the reply humorous and funny.',
|
||||
'填入示例': 'Use example',
|
||||
'清空': 'Clear',
|
||||
'选择在哪些会话中启用、提供哪些来源字段,以及如何使用这些信息。仅使用已有消息元数据,不查询平台 API。': 'Choose which conversations to enhance, which source fields to include, and how to use them. Only existing message metadata is used; no platform APIs are queried.',
|
||||
'增强提示词中请使用字段名(如 senderId、conversationType)引用这些信息。只发送勾选且当前消息中可用的字段,不会额外查询或补全。': 'Reference these values by field name (such as senderId or conversationType) in the guidance. Only selected fields available in the current message are sent; no extra lookups are made.',
|
||||
'只需填写正文,插件自动添加 <dsh_im_source_guidance> 成对标签。清空并保存后,不再附加增强提示词,已选来源字段仍按开关设置发送。': 'Enter only the body; the plugin adds paired <dsh_im_source_guidance> tags. Clear and save to omit guidance. Selected source fields still follow the conversation switches.',
|
||||
'发送者标识可能包含平台用户 ID 或电话号码形式的标识。关闭开关不会删除已经写入会话历史的信息。': 'Sender IDs may contain platform user IDs or phone-number-like identifiers. Turning this off does not remove information already saved in conversation history.',
|
||||
'上下文增强保存失败,请重试。': 'Could not save context enhancement. Try again.',
|
||||
'请提交完整的上下文增强设置。': 'Submit the complete context enhancement settings.',
|
||||
'群聊和私聊开关必须是布尔值。': 'Group and direct switches must be booleans.',
|
||||
'来源字段只能选择已定义的五个字段。': 'Source fields must be chosen from the five defined fields.',
|
||||
'保存中…': 'Saving…',
|
||||
'未设置': 'Not set',
|
||||
'工作区修改失败,请重试。': 'Could not update the workspace. Try again.',
|
||||
|
|
@ -657,6 +695,8 @@ function channelName(value) {
|
|||
}
|
||||
|
||||
function translateDynamic(text) {
|
||||
const guidanceLimit = /^增强提示词不得超过 (\d+) 个字符。$/.exec(text);
|
||||
if (guidanceLimit) return `Guidance must not exceed ${guidanceLimit[1]} characters.`;
|
||||
let match = /^(\d+) \/ (\d+) 在线$/.exec(text);
|
||||
if (match) return `${match[1]}/${match[2]} online`;
|
||||
match = /^已接入 (\d+) 个机器人,其中 (\d+) 个在线$/.exec(text);
|
||||
|
|
|
|||
|
|
@ -211,6 +211,73 @@ const CSS = String.raw`
|
|||
.dim-panel .dim-presetSelect { min-width: 0; max-width: 100%; grid-column: 1 / -1; grid-row: 2; height: 30px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 12px; }
|
||||
.dim-panel .dim-presetSelect:disabled { cursor: not-allowed; opacity: .55; }
|
||||
.dim-panel .dim-presetError { grid-column: 1 / -1; grid-row: 3; margin: 0; color: var(--dsw-alias-state-error-primary, #d54941); font-size: 12px; line-height: 1.4; }
|
||||
.dim-contextEntry { width: 100%; min-height: 40px; display: grid; grid-template-columns: 16px minmax(0, 1fr) max-content 16px; align-items: center; gap: 9px; margin: 10px 0; padding: 8px 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-module-platform, #f7f8fa); font: inherit; font-size: 13px; line-height: 20px; text-align: left; cursor: pointer; }
|
||||
.dim-contextEntry:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
||||
.dim-contextEntry > svg { color: var(--dsw-alias-label-secondary, #646a73); }
|
||||
.dim-contextLabel { min-width: 0; font-weight: 500; overflow-wrap: anywhere; }
|
||||
.dim-contextStatus { padding: 2px 7px; border-radius: 5px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 11px; line-height: 16px; font-weight: 400; white-space: nowrap; }
|
||||
.dim-contextStatus[data-active="true"] { color: var(--dsw-alias-state-business-primary, #3370ff); background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 10%, var(--dsw-alias-bg-layer-1, #fff)); }
|
||||
.dim-contextBackdrop { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 12px; background: rgb(15 17 21 / 42%); }
|
||||
.dim-contextBackdrop, .dim-contextBackdrop *, .dim-contextBackdrop *::before, .dim-contextBackdrop *::after { box-sizing: border-box; }
|
||||
.dim-contextDialog { width: min(450px, 100%); min-width: 0; max-height: calc(100vh - 24px); max-height: calc(100dvh - 24px); overflow-y: auto; padding: 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 20px 70px rgb(0 0 0 / 20%); font-size: 13px; line-height: 1.5; text-align: left; }
|
||||
.dim-contextDialog:focus { outline: none; }
|
||||
.dim-contextHeader, .dim-contextEditorHeader { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
||||
.dim-contextHeader { position: relative; }
|
||||
.dim-contextHeaderTitle { min-width: 0; display: inline-flex; align-items: center; gap: 6px; }
|
||||
.dim-contextHeader h3 { margin: 0; font-size: 15px; line-height: 22px; font-weight: 500; }
|
||||
.dim-contextHeaderTooltip { width: min(340px, calc(100vw - 72px)); }
|
||||
.dim-contextClose { width: 30px; height: 30px; flex: none; display: grid; place-items: center; padding: 0; border: 0; border-radius: 6px; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; cursor: pointer; }
|
||||
.dim-contextClose:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
||||
.dim-contextSection { min-width: 0; margin: 14px 0; padding: 0; border: 0; }
|
||||
.dim-contextSection legend { margin-bottom: 8px; padding: 0; font-weight: 500; }
|
||||
.dim-contextLegend { position: relative; display: inline-flex; align-items: center; gap: 6px; }
|
||||
.dim-contextSwitches { display: grid; grid-template-columns: 1fr; gap: 2px; }
|
||||
.dim-contextSwitchRow { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 34px; cursor: pointer; }
|
||||
.dim-contextSwitchLabel { min-width: 0; display: inline-flex; align-items: baseline; gap: 5px; flex-wrap: wrap; }
|
||||
.dim-contextUnavailable { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 16px; font-weight: 400; }
|
||||
.dim-contextSwitch { appearance: none; flex: none; width: 32px; height: 19px; margin: 0; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-interactive-bg-hover, #eef0f3); cursor: pointer; }
|
||||
.dim-contextSwitch::before { content: ""; display: block; width: 13px; height: 13px; margin: 2px; border-radius: 50%; background: var(--dsw-alias-label-secondary, #646a73); }
|
||||
.dim-contextSwitch:checked { border-color: var(--dsw-alias-state-business-primary, #3370ff); background: var(--dsw-alias-state-business-primary, #3370ff); }
|
||||
.dim-contextSwitch:checked::before { transform: translateX(13px); background: #fff; }
|
||||
.dim-contextFields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3px 12px; }
|
||||
.dim-contextField { min-width: 0; min-height: 30px; display: flex; align-items: center; gap: 6px; }
|
||||
.dim-contextField input { flex: none; width: 14px; height: 14px; margin: 0; accent-color: var(--dsw-alias-state-business-primary, #3370ff); }
|
||||
.dim-contextFieldText { min-width: 0; display: grid; grid-template-columns: max-content max-content; align-items: center; column-gap: 5px; overflow-wrap: anywhere; }
|
||||
.dim-contextFieldName { min-width: 0; line-height: 17px; cursor: pointer; }
|
||||
.dim-contextFieldKey { min-width: 0; grid-column: 1 / -1; color: var(--dsw-alias-label-tertiary, #8f959e); font: 10px/14px ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; cursor: pointer; }
|
||||
.dim-contextFieldHelp { position: relative; }
|
||||
.dim-contextFieldHelpButton { width: 16px; height: 16px; font-size: 10px; }
|
||||
.dim-contextTooltip.dim-contextFieldTooltip { top: calc(100% + 6px); right: 0; left: auto; width: min(280px, calc(100vw - 72px)); }
|
||||
.dim-contextEditorHeader { position: relative; flex-wrap: wrap; }
|
||||
.dim-contextEditorTitle { min-width: 0; display: inline-flex; align-items: center; gap: 6px; }
|
||||
.dim-contextEditorTitle > label { font-weight: 500; }
|
||||
.dim-contextHelp { display: inline-flex; align-items: center; flex: none; }
|
||||
.dim-contextHelpButton { width: 18px; height: 18px; display: grid; place-items: center; padding: 0; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 30%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 50%; color: var(--dsw-alias-state-business-primary, #3370ff); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 11px; line-height: 1; font-weight: 700; cursor: help; transition: border-color .15s ease, color .15s ease, background .15s ease, box-shadow .15s ease; }
|
||||
.dim-contextHelpButton:hover:not(:disabled) { border-color: var(--dsw-alias-state-business-primary, #3370ff); background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 8%, var(--dsw-alias-bg-layer-1, #fff)); }
|
||||
.dim-contextTooltip { position: absolute; top: calc(100% + 7px); left: 0; z-index: 30; width: min(330px, calc(100vw - 72px)); display: grid; gap: 5px; padding: 10px 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 10px 28px rgb(31 35 41 / 16%); font-size: 11px; line-height: 16px; font-weight: 400; overflow-wrap: anywhere; white-space: normal; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
|
||||
.dim-contextTooltip strong { font-weight: 600; }
|
||||
.dim-contextTooltipExample { padding: 7px 8px; border-radius: 6px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; }
|
||||
.dim-contextTooltip.dim-contextLegendTooltip { width: min(350px, calc(100vw - 72px)); }
|
||||
.dim-contextTooltip.dim-contextGuidanceTooltip { top: auto; bottom: calc(100% + 7px); width: min(380px, calc(100vw - 72px)); max-height: calc(100dvh - 48px); overflow-y: auto; }
|
||||
.dim-contextHelp:hover .dim-contextTooltip, .dim-contextHelp:focus-within .dim-contextTooltip { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto; }
|
||||
.dim-contextTextActions { display: flex; gap: 10px; margin-left: auto; }
|
||||
.dim-contextTextActions button { min-height: 30px; padding: 4px 0; border: 0; border-radius: 4px; color: var(--dsw-alias-state-business-primary, #3370ff); background: transparent; font: inherit; font-size: 12px; cursor: pointer; }
|
||||
.dim-contextTextActions button:hover:not(:disabled) { text-decoration: underline; }
|
||||
.dim-contextGuidance textarea { display: block; width: 100%; min-height: 88px; margin-top: 7px; padding: 9px 10px; resize: vertical; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-module-platform, #f7f8fa); font: inherit; font-size: 12px; line-height: 1.6; }
|
||||
.dim-contextGuidance textarea::placeholder { color: var(--dsw-alias-label-tertiary, #8f959e); opacity: 1; }
|
||||
.dim-contextHint { margin: 5px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 1.5; overflow-wrap: anywhere; }
|
||||
.dim-contextError { margin: 12px 0 0; color: var(--dsw-alias-state-error-primary, #d54941); font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; }
|
||||
.dim-contextFooter { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
|
||||
.dim-contextFooter button { min-height: 34px; padding: 6px 15px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; cursor: pointer; }
|
||||
.dim-contextFooter button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
||||
.dim-contextFooter .dim-contextSave, .dim-contextFooter .dim-contextSave:hover:not(:disabled) { border-color: var(--dsw-alias-state-business-primary, #3370ff); color: #fff; background: var(--dsw-alias-state-business-primary, #3370ff); }
|
||||
.dim-contextEntry:focus-visible, .dim-contextDialog button:focus-visible, .dim-contextDialog input:focus-visible, .dim-contextDialog textarea:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
||||
.dim-contextEntry:disabled, .dim-contextDialog button:disabled, .dim-contextDialog input:disabled, .dim-contextDialog textarea:disabled { opacity: .55; cursor: not-allowed; }
|
||||
@media (pointer: coarse) {
|
||||
.dim-contextEntry, .dim-contextClose, .dim-contextFooter button, .dim-contextTextActions button, .dim-contextField, .dim-contextSwitchRow { min-height: 44px; }
|
||||
.dim-contextClose, .dim-contextTextActions button { min-width: 44px; }
|
||||
.dim-contextGuidance textarea { font-size: 16px; }
|
||||
}
|
||||
.dim-directoryPickerBackdrop { --dim-blue: var(--dsw-alias-state-business-primary, #3370ff); --dim-blue-soft: color-mix(in srgb, var(--dim-blue) 9%, transparent); position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 24px; background: rgb(15 17 21 / 42%); backdrop-filter: blur(3px); }
|
||||
.dim-directoryPickerBackdrop, .dim-directoryPickerBackdrop *, .dim-directoryPickerBackdrop *::before, .dim-directoryPickerBackdrop *::after { box-sizing: border-box; }
|
||||
.dim-directoryPicker { width: min(720px, 100%); height: min(620px, calc(100vh - 48px)); min-height: 420px; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 18px; outline: none; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 24px 72px rgb(15 17 21 / 24%); }
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|||
clientSecret,
|
||||
harness: workspaceScope.harness,
|
||||
state: workspaceScope.state,
|
||||
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
||||
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
||||
maxMessageChars: config.maxMessageChars ?? 4_000,
|
||||
connectTimeoutMs: config.connectTimeoutMs ?? 15_000,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import QRCode from 'qrcode';
|
||||
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
||||
|
|
@ -18,6 +19,7 @@ export const DINGTALK_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
||||
approveSender: 'bot.sender.approve',
|
||||
revokeSender: 'bot.sender.revoke',
|
||||
});
|
||||
|
|
@ -94,6 +96,10 @@ function payloadFailure(endpoint, payload) {
|
|||
return validAgentPresetPayload(payload)
|
||||
? null : '请选择 Agent Preset。';
|
||||
}
|
||||
if (endpoint === DINGTALK_ENDPOINTS.setContextEnhancement) {
|
||||
return validContextEnhancementPayload(payload)
|
||||
? null : '请提交有效的上下文增强设置。';
|
||||
}
|
||||
if (endpoint === DINGTALK_ENDPOINTS.approveSender) {
|
||||
return exactKeys(payload, ['botId', 'requestId', 'confirm'])
|
||||
&& validId(payload.botId)
|
||||
|
|
@ -251,6 +257,11 @@ export function createDingtalkRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|||
await controller.updateWorkspace(payload.botId, payload.workspace),
|
||||
cachedEncode,
|
||||
);
|
||||
} else if (endpoint === DINGTALK_ENDPOINTS.setContextEnhancement) {
|
||||
if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
|
||||
value = await controller.updateContextEnhancement(
|
||||
payload.botId, payload.config, (status) => publicStatus(status, cachedEncode),
|
||||
);
|
||||
} else if (endpoint === DINGTALK_ENDPOINTS.setAgentPreset) {
|
||||
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
||||
value = await publicStatus(
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|||
workspaces,
|
||||
method: 'removeBot',
|
||||
botIdFromRemoved: (removed) => removed.id,
|
||||
saveMethod: 'saveBot',
|
||||
botIdFromSave: (bot) => bot?.id,
|
||||
})
|
||||
: configStore;
|
||||
// State is lazy per bot. A corrupt legacy file can therefore fail only the
|
||||
|
|
@ -167,6 +169,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|||
ownerOpenIds: botConfig.ownerOpenIds ?? [botConfig.ownerOpenId],
|
||||
harness: workspaceScope.harness,
|
||||
state: workspaceScope.state,
|
||||
contextEnhancement: { botId: id, getSettings: () => workspaces.contextEnhancementFor(id) },
|
||||
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
||||
...(wsAgent ? { wsAgent } : {}),
|
||||
logger: {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { publicMessageFailure } from '../../../../src/channels/shared/message-fa
|
|||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
import { validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
||||
import { validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
||||
import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
|
||||
import {
|
||||
isFeishuGroupResponseMode,
|
||||
normalizeFeishuGroupResponseMode,
|
||||
|
|
@ -276,6 +278,7 @@ function publicBotEntry(entry) {
|
|||
connected,
|
||||
configured: source.configured === true,
|
||||
agentPreset: normalizeAgentPresetId(source.agentPreset),
|
||||
contextEnhancement: normalizeContextEnhancementConfig(source.contextEnhancement),
|
||||
groupResponseMode: normalizeFeishuGroupResponseMode(source.groupResponseMode),
|
||||
groupMessagePermissionGranted: source.groupMessagePermissionGranted === true,
|
||||
bot: publicBot(source.bot),
|
||||
|
|
@ -415,6 +418,10 @@ function validPayload(endpoint, payload) {
|
|||
return validAgentPresetPayload(payload)
|
||||
? null : '请选择 Agent Preset。';
|
||||
}
|
||||
if (endpoint === FEISHU_ENDPOINTS.setContextEnhancement) {
|
||||
return validContextEnhancementPayload(payload)
|
||||
? null : '请提交有效的上下文增强设置。';
|
||||
}
|
||||
if (endpoint === FEISHU_ENDPOINTS.setGroupResponseMode) {
|
||||
return hasOnlyKeys(payload, new Set(['botId', 'groupResponseMode']))
|
||||
&& safeOpaqueId(payload.botId)
|
||||
|
|
@ -666,6 +673,12 @@ export function createFeishuRpcHandler(controller, { encodeQr = qrCodeDataUrl }
|
|||
await controller.updateWorkspace(payload.botId, payload.workspace),
|
||||
{ encodeQr: cachedEncodeQr },
|
||||
);
|
||||
} else if (endpoint === FEISHU_ENDPOINTS.setContextEnhancement) {
|
||||
if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
|
||||
value = await controller.updateContextEnhancement(
|
||||
payload.botId, payload.config,
|
||||
(status) => toPublicFeishuStatus(status, { encodeQr: cachedEncodeQr }),
|
||||
);
|
||||
} else if (endpoint === FEISHU_ENDPOINTS.setAgentPreset) {
|
||||
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
||||
value = await toPublicFeishuStatus(
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|||
appSecret,
|
||||
harness: workspaceScope.harness,
|
||||
state: workspaceScope.state,
|
||||
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
||||
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
||||
connectTimeoutMs: config.connectTimeoutMs ?? 20_000,
|
||||
logger: {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
connectionTestTargetUnavailable,
|
||||
publicConnectionTestResult,
|
||||
} from '../../../../src/channels/shared/connection-test.mjs';
|
||||
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
||||
|
|
@ -18,6 +19,7 @@ export const QQ_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
||||
});
|
||||
export const QQ_RPC_ENDPOINTS = Object.freeze(Object.values(QQ_ENDPOINTS));
|
||||
|
||||
|
|
@ -76,6 +78,10 @@ function payloadFailure(endpoint, payload) {
|
|||
return validAgentPresetPayload(payload)
|
||||
? null : '请选择 Agent Preset。';
|
||||
}
|
||||
if (endpoint === QQ_ENDPOINTS.setContextEnhancement) {
|
||||
return validContextEnhancementPayload(payload)
|
||||
? null : '请提交有效的上下文增强设置。';
|
||||
}
|
||||
return 'Unknown QQ endpoint.';
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +177,11 @@ export function createQqRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|||
await controller.updateWorkspace(payload.botId, payload.workspace),
|
||||
cachedEncode,
|
||||
);
|
||||
} else if (endpoint === QQ_ENDPOINTS.setContextEnhancement) {
|
||||
if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
|
||||
value = await controller.updateContextEnhancement(
|
||||
payload.botId, payload.config, (status) => publicStatus(status, cachedEncode),
|
||||
);
|
||||
} else if (endpoint === QQ_ENDPOINTS.setAgentPreset) {
|
||||
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
||||
value = await publicStatus(
|
||||
|
|
|
|||
17
plugin-src/host/channels/shared/context-enhancement-rpc.mjs
Normal file
17
plugin-src/host/channels/shared/context-enhancement-rpc.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { validateContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
|
||||
|
||||
export const SET_CONTEXT_ENHANCEMENT_ENDPOINT = 'bot.context-enhancement.set';
|
||||
|
||||
export function validContextEnhancementPayload(payload) {
|
||||
try {
|
||||
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
||||
|| Reflect.ownKeys(payload).length !== 2
|
||||
|| !Object.hasOwn(payload, 'botId') || !Object.hasOwn(payload, 'config')
|
||||
|| typeof payload.botId !== 'string'
|
||||
|| !/^[A-Za-z0-9_-]{1,128}$/.test(payload.botId)) return false;
|
||||
validateContextEnhancementConfig(payload.config);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -102,6 +102,7 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|||
token,
|
||||
harness: workspaceScope.harness,
|
||||
state: workspaceScope.state,
|
||||
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
||||
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
||||
connectTimeoutMs: config.connectTimeoutMs ?? 20_000,
|
||||
logger: {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from './context-enhancement-rpc.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
||||
import {
|
||||
|
|
@ -17,6 +18,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
||||
});
|
||||
|
||||
const ENDPOINTS = Object.freeze(Object.values(TOKEN_BOT_ENDPOINTS));
|
||||
|
|
@ -71,6 +73,10 @@ function payloadFailure(endpoint, payload) {
|
|||
return validAgentPresetPayload(payload)
|
||||
? null : '请选择 Agent Preset。';
|
||||
}
|
||||
if (endpoint === TOKEN_BOT_ENDPOINTS.setContextEnhancement) {
|
||||
return validContextEnhancementPayload(payload)
|
||||
? null : '请提交有效的上下文增强设置。';
|
||||
}
|
||||
return 'Unknown bot endpoint.';
|
||||
}
|
||||
|
||||
|
|
@ -152,6 +158,9 @@ export function createTokenBotRpcHandler(controller, { channel }) {
|
|||
} else if (endpoint === TOKEN_BOT_ENDPOINTS.setWorkspace) {
|
||||
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
||||
value = await controller.updateWorkspace(payload.botId, payload.workspace);
|
||||
} else if (endpoint === TOKEN_BOT_ENDPOINTS.setContextEnhancement) {
|
||||
if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
|
||||
value = await controller.updateContextEnhancement(payload.botId, payload.config);
|
||||
} else if (endpoint === TOKEN_BOT_ENDPOINTS.setAgentPreset) {
|
||||
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
||||
value = await controller.updateAgentPreset(payload.botId, payload.agentPreset);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export function publicWorkspaceError(error) {
|
|||
'workspace-bot-not-found',
|
||||
'agent-preset-invalid',
|
||||
'agent-preset-unavailable',
|
||||
'context-enhancement-invalid',
|
||||
].includes(error?.code)) return null;
|
||||
return { code: error.code, message: error.message };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|||
appToken,
|
||||
harness: workspaceScope.harness,
|
||||
state: workspaceScope.state,
|
||||
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
||||
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
||||
connectTimeoutMs: config.connectTimeoutMs ?? 20_000,
|
||||
logger: {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
||||
import {
|
||||
|
|
@ -18,6 +19,7 @@ export const SLACK_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
||||
});
|
||||
export const SLACK_RPC_ENDPOINTS = Object.freeze(Object.values(SLACK_ENDPOINTS));
|
||||
|
||||
|
|
@ -75,6 +77,10 @@ function payloadFailure(endpoint, payload) {
|
|||
return validAgentPresetPayload(payload)
|
||||
? null : '请选择 Agent Preset。';
|
||||
}
|
||||
if (endpoint === SLACK_ENDPOINTS.setContextEnhancement) {
|
||||
return validContextEnhancementPayload(payload)
|
||||
? null : '请提交有效的上下文增强设置。';
|
||||
}
|
||||
return 'Unknown Slack endpoint.';
|
||||
}
|
||||
|
||||
|
|
@ -154,6 +160,10 @@ export function createSlackRpcHandler(controller) {
|
|||
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
||||
value = await controller.updateWorkspace(payload.botId, payload.workspace);
|
||||
}
|
||||
else if (endpoint === SLACK_ENDPOINTS.setContextEnhancement) {
|
||||
if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
|
||||
value = await controller.updateContextEnhancement(payload.botId, payload.config);
|
||||
}
|
||||
else if (endpoint === SLACK_ENDPOINTS.setAgentPreset) {
|
||||
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
||||
value = await controller.updateAgentPreset(payload.botId, payload.agentPreset);
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|||
secret,
|
||||
harness: workspaceScope.harness,
|
||||
state: workspaceScope.state,
|
||||
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
||||
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
||||
connectTimeoutMs: config.connectTimeoutMs ?? 20_000,
|
||||
maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import QRCode from 'qrcode';
|
||||
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
||||
|
|
@ -18,6 +19,7 @@ export const WECOM_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
||||
});
|
||||
export const WECOM_RPC_ENDPOINTS = Object.freeze(Object.values(WECOM_ENDPOINTS));
|
||||
|
||||
|
|
@ -76,6 +78,10 @@ function payloadFailure(endpoint, payload) {
|
|||
return validAgentPresetPayload(payload)
|
||||
? null : '请选择 Agent Preset。';
|
||||
}
|
||||
if (endpoint === WECOM_ENDPOINTS.setContextEnhancement) {
|
||||
return validContextEnhancementPayload(payload)
|
||||
? null : '请提交有效的上下文增强设置。';
|
||||
}
|
||||
return 'Unknown Enterprise WeChat endpoint.';
|
||||
}
|
||||
|
||||
|
|
@ -172,6 +178,11 @@ export function createWecomRpcHandler(controller, { encodeQr = qrDataUrl } = {})
|
|||
await controller.updateWorkspace(payload.botId, payload.workspace),
|
||||
cachedEncode,
|
||||
);
|
||||
} else if (endpoint === WECOM_ENDPOINTS.setContextEnhancement) {
|
||||
if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
|
||||
value = await controller.updateContextEnhancement(
|
||||
payload.botId, payload.config, (status) => publicStatus(status, cachedEncode),
|
||||
);
|
||||
} else if (endpoint === WECOM_ENDPOINTS.setAgentPreset) {
|
||||
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
||||
value = await publicStatus(
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|||
token,
|
||||
harness: workspaceScope.harness,
|
||||
state: workspaceScope.state,
|
||||
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
||||
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
||||
maxMessageChars: config.maxMessageChars ?? DEFAULT_WEIXIN_MAX_MESSAGE_CHARS,
|
||||
logger: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import QRCode from 'qrcode';
|
||||
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import {
|
||||
publicWorkspaceError,
|
||||
|
|
@ -25,6 +26,7 @@ export const WEIXIN_ENDPOINTS = Object.freeze({
|
|||
deleteBot: 'bot.delete',
|
||||
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
||||
});
|
||||
export const WEIXIN_RPC_ENDPOINTS = Object.freeze(Object.values(WEIXIN_ENDPOINTS));
|
||||
|
||||
|
|
@ -83,6 +85,10 @@ function payloadFailure(endpoint, payload) {
|
|||
return validAgentPresetPayload(payload)
|
||||
? null : '请选择 Agent Preset。';
|
||||
}
|
||||
if (endpoint === WEIXIN_ENDPOINTS.setContextEnhancement) {
|
||||
return validContextEnhancementPayload(payload)
|
||||
? null : '请提交有效的上下文增强设置。';
|
||||
}
|
||||
return 'Unknown Weixin endpoint.';
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +213,11 @@ export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl } = {}
|
|||
await controller.updateWorkspace(payload.botId, payload.workspace),
|
||||
cachedEncode,
|
||||
);
|
||||
} else if (endpoint === WEIXIN_ENDPOINTS.setContextEnhancement) {
|
||||
if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
|
||||
value = await controller.updateContextEnhancement(
|
||||
payload.botId, payload.config, (status) => publicStatus(status, cachedEncode),
|
||||
);
|
||||
} else if (endpoint === WEIXIN_ENDPOINTS.setAgentPreset) {
|
||||
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
||||
value = await publicStatus(
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|||
authDir,
|
||||
harness: workspaceScope.harness,
|
||||
state: workspaceScope.state,
|
||||
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
||||
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
||||
connectTimeoutMs: config.connectTimeoutMs ?? 30_000,
|
||||
createSession,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QRCode from 'qrcode';
|
|||
|
||||
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
||||
import { normalizeWhatsappAccessPolicy } from '../../../../src/channels/whatsapp/config-store.mjs';
|
||||
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
||||
|
|
@ -17,6 +18,7 @@ export const WHATSAPP_ENDPOINTS = Object.freeze({
|
|||
setAccessPolicy: 'bot.access-policy.set',
|
||||
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
||||
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
||||
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
||||
});
|
||||
export const WHATSAPP_RPC_ENDPOINTS = Object.freeze(Object.values(WHATSAPP_ENDPOINTS));
|
||||
|
||||
|
|
@ -71,6 +73,10 @@ function payloadFailure(endpoint, payload) {
|
|||
return validAgentPresetPayload(payload)
|
||||
? null : '请选择 Agent Preset。';
|
||||
}
|
||||
if (endpoint === WHATSAPP_ENDPOINTS.setContextEnhancement) {
|
||||
return validContextEnhancementPayload(payload)
|
||||
? null : '请提交有效的上下文增强设置。';
|
||||
}
|
||||
return 'Unknown WhatsApp endpoint.';
|
||||
}
|
||||
|
||||
|
|
@ -173,6 +179,11 @@ export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|||
await controller.updateWorkspace(payload.botId, payload.workspace),
|
||||
cachedEncode,
|
||||
);
|
||||
} else if (endpoint === WHATSAPP_ENDPOINTS.setContextEnhancement) {
|
||||
if (typeof controller.updateContextEnhancement !== 'function') throw new Error('Context enhancement update is unavailable');
|
||||
value = await controller.updateContextEnhancement(
|
||||
payload.botId, payload.config, (status) => publicStatus(status, cachedEncode),
|
||||
);
|
||||
} else if (endpoint === WHATSAPP_ENDPOINTS.setAgentPreset) {
|
||||
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
||||
value = await publicStatus(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue