feat: update auto reply and packaging
This commit is contained in:
@@ -98,7 +98,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sectionAlerts('listen').length" class="section-alerts">
|
||||
<div v-for="alert in sectionAlerts('listen')" :key="alert" class="section-alert">{{ alert }}</div>
|
||||
<div v-for="alert in sectionAlerts('listen')" :key="alert.text" class="section-alert" :class="alert.type">{{ alert.text }}</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<label class="check-row">
|
||||
@@ -117,6 +117,10 @@
|
||||
<input type="checkbox" v-model="form.listen.ignoreSelfMessage">
|
||||
<span>忽略自己发送的消息</span>
|
||||
</label>
|
||||
<label class="check-row">
|
||||
<input type="checkbox" v-model="form.identity.replyExternalOnly">
|
||||
<span>只回复外部客户,忽略企业内部成员</span>
|
||||
</label>
|
||||
<label>
|
||||
<span>群聊触发</span>
|
||||
<select v-model="form.listen.groupTriggerMode">
|
||||
@@ -169,7 +173,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sectionAlerts('ai').length" class="section-alerts">
|
||||
<div v-for="alert in sectionAlerts('ai')" :key="alert" class="section-alert">{{ alert }}</div>
|
||||
<div v-for="alert in sectionAlerts('ai')" :key="alert.text" class="section-alert" :class="alert.type">{{ alert.text }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 万川平台配置卡片 -->
|
||||
@@ -345,7 +349,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sectionAlerts('knowledge').length" class="section-alerts">
|
||||
<div v-for="alert in sectionAlerts('knowledge')" :key="alert" class="section-alert">{{ alert }}</div>
|
||||
<div v-for="alert in sectionAlerts('knowledge')" :key="alert.text" class="section-alert" :class="alert.type">{{ alert.text }}</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<label class="wide">
|
||||
@@ -473,7 +477,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sectionAlerts('handoff').length" class="section-alerts">
|
||||
<div v-for="alert in sectionAlerts('handoff')" :key="alert" class="section-alert">{{ alert }}</div>
|
||||
<div v-for="alert in sectionAlerts('handoff')" :key="alert.text" class="section-alert" :class="alert.type">{{ alert.text }}</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
@@ -549,7 +553,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sectionAlerts('identity').length" class="section-alerts">
|
||||
<div v-for="alert in sectionAlerts('identity')" :key="alert" class="section-alert">{{ alert }}</div>
|
||||
<div v-for="alert in sectionAlerts('identity')" :key="alert.text" class="section-alert" :class="alert.type">{{ alert.text }}</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
@@ -770,7 +774,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sectionAlerts('records').length" class="section-alerts">
|
||||
<div v-for="alert in sectionAlerts('records')" :key="alert" class="section-alert">{{ alert }}</div>
|
||||
<div v-for="alert in sectionAlerts('records')" :key="alert.text" class="section-alert" :class="alert.type">{{ alert.text }}</div>
|
||||
</div>
|
||||
<div class="record-table">
|
||||
<div class="record-row header">
|
||||
@@ -1056,46 +1060,72 @@ function classifyStatusErrorScope(text, scope) {
|
||||
return knownSectionScope(scope) || classifyErrorScope(text)
|
||||
}
|
||||
|
||||
function addUniqueAlert(alerts, text) {
|
||||
function normalizeMessageType(type) {
|
||||
const normalized = String(type || '').trim().toLowerCase()
|
||||
if (normalized === 'success' || normalized === 'warn' || normalized === 'warning') {
|
||||
return normalized === 'warning' ? 'warn' : normalized
|
||||
}
|
||||
return 'error'
|
||||
}
|
||||
|
||||
function addUniqueAlert(alerts, text, type = 'error') {
|
||||
const normalized = String(text || '').trim()
|
||||
if (!normalized) return
|
||||
const index = alerts.findIndex(item => item === normalized || item.includes(normalized) || normalized.includes(item))
|
||||
const messageType = normalizeMessageType(type)
|
||||
const index = alerts.findIndex(item => item.text === normalized || item.text.includes(normalized) || normalized.includes(item.text))
|
||||
if (index < 0) {
|
||||
alerts.push(normalized)
|
||||
} else if (normalized.length > alerts[index].length) {
|
||||
alerts[index] = normalized
|
||||
alerts.push({ text: normalized, type: messageType })
|
||||
} else if (normalized.length > alerts[index].text.length || alerts[index].type !== 'error') {
|
||||
alerts[index] = { text: normalized, type: messageType }
|
||||
}
|
||||
}
|
||||
|
||||
function sectionAlerts(sectionId) {
|
||||
const scope = normalizeSectionScope(sectionId)
|
||||
const alerts = []
|
||||
addUniqueAlert(alerts, scopedMessages.value[scope])
|
||||
const scopedMessage = scopedMessages.value[scope]
|
||||
addUniqueAlert(alerts, scopedMessage?.text || scopedMessage, scopedMessage?.type || 'error')
|
||||
if (scope === 'identity') {
|
||||
addUniqueAlert(alerts, status.value.identityRefreshError)
|
||||
addUniqueAlert(alerts, status.value.internalGroupMemberSyncError)
|
||||
addUniqueAlert(alerts, status.value.identityRefreshError, 'error')
|
||||
addUniqueAlert(alerts, status.value.internalGroupMemberSyncError, 'error')
|
||||
}
|
||||
const lastError = String(status.value.lastError || '').trim()
|
||||
if (lastError && classifyStatusErrorScope(lastError, status.value.lastErrorScope) === scope) {
|
||||
addUniqueAlert(alerts, lastError)
|
||||
addUniqueAlert(alerts, lastError, 'error')
|
||||
}
|
||||
return alerts
|
||||
}
|
||||
|
||||
function setScopedMessage(scope, text) {
|
||||
function clearScopedMessage(scope) {
|
||||
const target = normalizeSectionScope(scope)
|
||||
const normalized = String(text || '').trim()
|
||||
if (!normalized) return
|
||||
scopedMessages.value = { ...scopedMessages.value, [target]: normalized }
|
||||
if (scopedMessageTimers[target]) {
|
||||
clearTimeout(scopedMessageTimers[target])
|
||||
scopedMessageTimers[target] = null
|
||||
}
|
||||
scopedMessageTimers[target] = setTimeout(() => {
|
||||
if (scopedMessages.value[target] !== normalized) return
|
||||
if (scopedMessages.value[target]) {
|
||||
const next = { ...scopedMessages.value }
|
||||
delete next[target]
|
||||
scopedMessages.value = next
|
||||
}
|
||||
}
|
||||
|
||||
function setScopedMessage(scope, text, type = 'error') {
|
||||
const target = normalizeSectionScope(scope)
|
||||
const normalized = String(text || '').trim()
|
||||
if (!normalized) return
|
||||
const messageType = normalizeMessageType(type)
|
||||
scopedMessages.value = { ...scopedMessages.value, [target]: { text: normalized, type: messageType } }
|
||||
if (scopedMessageTimers[target]) {
|
||||
clearTimeout(scopedMessageTimers[target])
|
||||
}
|
||||
if (messageType === 'error') {
|
||||
scopedMessageTimers[target] = null
|
||||
return
|
||||
}
|
||||
scopedMessageTimers[target] = setTimeout(() => {
|
||||
const current = scopedMessages.value[target]
|
||||
if (!current || current.text !== normalized || current.type !== messageType) return
|
||||
clearScopedMessage(target)
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
@@ -1181,6 +1211,7 @@ function defaultConfig() {
|
||||
identity: {
|
||||
unknownPolicy: 'customer',
|
||||
unknownHandoffPolicy: 'hold',
|
||||
replyExternalOnly: false,
|
||||
refreshOnStart: true,
|
||||
refreshIntervalMinutes: 30,
|
||||
pageSize: 200,
|
||||
@@ -2399,7 +2430,7 @@ async function saveConfig(scope = 'listen', silentSuccess = false) {
|
||||
return false
|
||||
}
|
||||
if (!silentSuccess) {
|
||||
notify('自动客服配置已保存', 'success')
|
||||
notify('自动客服配置已保存', 'success', scope)
|
||||
}
|
||||
await loadStatus()
|
||||
return true
|
||||
@@ -2421,7 +2452,7 @@ async function handleStart() {
|
||||
await SaveAutoReplyConfig(JSON.stringify(form))
|
||||
await SetAutoReplyEnabled(true)
|
||||
await SendWxWorkData('0', JSON.stringify({ type: 10000, data: {} }))
|
||||
notify('自动客服已开启,正在监听当前接管账号', 'success')
|
||||
notify('自动客服已开启,正在监听当前接管账号', 'success', 'listen')
|
||||
await loadStatus()
|
||||
} catch (err) {
|
||||
notify(`开启失败: ${err.message || err}`, 'error', 'listen')
|
||||
@@ -2439,7 +2470,7 @@ async function handleDisable() {
|
||||
normalizeHandoffBeforeSave()
|
||||
await SaveAutoReplyConfig(JSON.stringify(form))
|
||||
await SetAutoReplyEnabled(false)
|
||||
notify('自动客服已关闭', 'success')
|
||||
notify('自动客服已关闭', 'success', 'listen')
|
||||
await loadStatus()
|
||||
} catch (err) {
|
||||
notify(`关闭失败: ${err.message || err}`, 'error', 'listen')
|
||||
@@ -2463,6 +2494,7 @@ async function rebuildKnowledge() {
|
||||
notify('知识库索引已重建,但未扫描到任何知识文件,请确认知识目录和文件格式是否正确。', 'error', 'knowledge')
|
||||
} else {
|
||||
const failedSuffix = failedCount > 0 ? `,${failedCount} 个文件解析失败` : ''
|
||||
if (failedCount === 0) clearScopedMessage('knowledge')
|
||||
notify(`知识库索引已重建:${fileCount} 个文件、${chunkCount} 个分片${failedSuffix}。`, failedCount > 0 ? 'error' : 'success', 'knowledge')
|
||||
}
|
||||
} else {
|
||||
@@ -2574,8 +2606,14 @@ async function testHandoff() {
|
||||
function notify(text, type = 'success', scope = '') {
|
||||
const normalized = String(text || '').trim()
|
||||
if (!normalized) return
|
||||
const target = knownSectionScope(scope)
|
||||
if (target) {
|
||||
if (type !== 'error') clearScopedMessage(target)
|
||||
setScopedMessage(target, normalized, type)
|
||||
return
|
||||
}
|
||||
if (type === 'error') {
|
||||
setScopedMessage(scope || classifyErrorScope(normalized), normalized)
|
||||
setScopedMessage(classifyErrorScope(normalized), normalized, type)
|
||||
return
|
||||
}
|
||||
message.value = normalized
|
||||
@@ -2897,6 +2935,21 @@ button:disabled {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.section-alert.success {
|
||||
background: #e8f7ef;
|
||||
color: #17633a;
|
||||
}
|
||||
|
||||
.section-alert.warn {
|
||||
background: #fff7df;
|
||||
color: #7a4a00;
|
||||
}
|
||||
|
||||
.section-alert.error {
|
||||
background: #fdecec;
|
||||
color: #9b1c1c;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
@@ -3597,6 +3650,24 @@ button:disabled {
|
||||
border: 1px solid rgba(255, 107, 125, 0.28);
|
||||
}
|
||||
|
||||
.section-alert.success {
|
||||
color: var(--cmd-green);
|
||||
background: rgba(63, 255, 162, 0.1);
|
||||
border-color: rgba(63, 255, 162, 0.28);
|
||||
}
|
||||
|
||||
.section-alert.warn {
|
||||
color: var(--cmd-amber);
|
||||
background: rgba(255, 209, 102, 0.1);
|
||||
border-color: rgba(255, 209, 102, 0.3);
|
||||
}
|
||||
|
||||
.section-alert.error {
|
||||
color: var(--cmd-red);
|
||||
background: rgba(255, 107, 125, 0.1);
|
||||
border-color: rgba(255, 107, 125, 0.28);
|
||||
}
|
||||
|
||||
.field-warning {
|
||||
border-color: rgba(255, 209, 102, 0.34);
|
||||
color: var(--cmd-amber);
|
||||
|
||||
@@ -211,6 +211,7 @@ if (typeof window.go === 'undefined') {
|
||||
identity: {
|
||||
unknownPolicy: 'customer',
|
||||
unknownHandoffPolicy: 'hold',
|
||||
replyExternalOnly: false,
|
||||
refreshOnStart: true,
|
||||
refreshIntervalMinutes: 30,
|
||||
pageSize: 200,
|
||||
|
||||
83
frontend/wailsjs/runtime/runtime.d.ts
vendored
83
frontend/wailsjs/runtime/runtime.d.ts
vendored
@@ -246,85 +246,4 @@ export function OnFileDropOff() :void
|
||||
export function CanResolveFilePaths(): boolean;
|
||||
|
||||
// Resolves file paths for an array of files
|
||||
export function ResolveFilePaths(files: File[]): void
|
||||
|
||||
// Notification types
|
||||
export interface NotificationOptions {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle?: string; // macOS and Linux only
|
||||
body?: string;
|
||||
categoryId?: string;
|
||||
data?: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface NotificationAction {
|
||||
id?: string;
|
||||
title?: string;
|
||||
destructive?: boolean; // macOS-specific
|
||||
}
|
||||
|
||||
export interface NotificationCategory {
|
||||
id?: string;
|
||||
actions?: NotificationAction[];
|
||||
hasReplyField?: boolean;
|
||||
replyPlaceholder?: string;
|
||||
replyButtonTitle?: string;
|
||||
}
|
||||
|
||||
// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
|
||||
// Initializes the notification service for the application.
|
||||
// This must be called before sending any notifications.
|
||||
export function InitializeNotifications(): Promise<void>;
|
||||
|
||||
// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
|
||||
// Cleans up notification resources and releases any held connections.
|
||||
export function CleanupNotifications(): Promise<void>;
|
||||
|
||||
// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
|
||||
// Checks if notifications are available on the current platform.
|
||||
export function IsNotificationAvailable(): Promise<boolean>;
|
||||
|
||||
// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
|
||||
// Requests notification authorization from the user (macOS only).
|
||||
export function RequestNotificationAuthorization(): Promise<boolean>;
|
||||
|
||||
// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
|
||||
// Checks the current notification authorization status (macOS only).
|
||||
export function CheckNotificationAuthorization(): Promise<boolean>;
|
||||
|
||||
// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
|
||||
// Sends a basic notification with the given options.
|
||||
export function SendNotification(options: NotificationOptions): Promise<void>;
|
||||
|
||||
// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
|
||||
// Sends a notification with action buttons. Requires a registered category.
|
||||
export function SendNotificationWithActions(options: NotificationOptions): Promise<void>;
|
||||
|
||||
// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
|
||||
// Registers a notification category that can be used with SendNotificationWithActions.
|
||||
export function RegisterNotificationCategory(category: NotificationCategory): Promise<void>;
|
||||
|
||||
// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
|
||||
// Removes a previously registered notification category.
|
||||
export function RemoveNotificationCategory(categoryId: string): Promise<void>;
|
||||
|
||||
// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
|
||||
// Removes all pending notifications from the notification center.
|
||||
export function RemoveAllPendingNotifications(): Promise<void>;
|
||||
|
||||
// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
|
||||
// Removes a specific pending notification by its identifier.
|
||||
export function RemovePendingNotification(identifier: string): Promise<void>;
|
||||
|
||||
// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
|
||||
// Removes all delivered notifications from the notification center.
|
||||
export function RemoveAllDeliveredNotifications(): Promise<void>;
|
||||
|
||||
// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
|
||||
// Removes a specific delivered notification by its identifier.
|
||||
export function RemoveDeliveredNotification(identifier: string): Promise<void>;
|
||||
|
||||
// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
|
||||
// Removes a notification by its identifier (cross-platform convenience function).
|
||||
export function RemoveNotification(identifier: string): Promise<void>;
|
||||
export function ResolveFilePaths(files: File[]): void
|
||||
@@ -48,10 +48,6 @@ export function EventsOff(eventName, ...additionalEventNames) {
|
||||
return window.runtime.EventsOff(eventName, ...additionalEventNames);
|
||||
}
|
||||
|
||||
export function EventsOffAll() {
|
||||
return window.runtime.EventsOffAll();
|
||||
}
|
||||
|
||||
export function EventsOnce(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, 1);
|
||||
}
|
||||
@@ -239,60 +235,4 @@ export function CanResolveFilePaths() {
|
||||
|
||||
export function ResolveFilePaths(files) {
|
||||
return window.runtime.ResolveFilePaths(files);
|
||||
}
|
||||
|
||||
export function InitializeNotifications() {
|
||||
return window.runtime.InitializeNotifications();
|
||||
}
|
||||
|
||||
export function CleanupNotifications() {
|
||||
return window.runtime.CleanupNotifications();
|
||||
}
|
||||
|
||||
export function IsNotificationAvailable() {
|
||||
return window.runtime.IsNotificationAvailable();
|
||||
}
|
||||
|
||||
export function RequestNotificationAuthorization() {
|
||||
return window.runtime.RequestNotificationAuthorization();
|
||||
}
|
||||
|
||||
export function CheckNotificationAuthorization() {
|
||||
return window.runtime.CheckNotificationAuthorization();
|
||||
}
|
||||
|
||||
export function SendNotification(options) {
|
||||
return window.runtime.SendNotification(options);
|
||||
}
|
||||
|
||||
export function SendNotificationWithActions(options) {
|
||||
return window.runtime.SendNotificationWithActions(options);
|
||||
}
|
||||
|
||||
export function RegisterNotificationCategory(category) {
|
||||
return window.runtime.RegisterNotificationCategory(category);
|
||||
}
|
||||
|
||||
export function RemoveNotificationCategory(categoryId) {
|
||||
return window.runtime.RemoveNotificationCategory(categoryId);
|
||||
}
|
||||
|
||||
export function RemoveAllPendingNotifications() {
|
||||
return window.runtime.RemoveAllPendingNotifications();
|
||||
}
|
||||
|
||||
export function RemovePendingNotification(identifier) {
|
||||
return window.runtime.RemovePendingNotification(identifier);
|
||||
}
|
||||
|
||||
export function RemoveAllDeliveredNotifications() {
|
||||
return window.runtime.RemoveAllDeliveredNotifications();
|
||||
}
|
||||
|
||||
export function RemoveDeliveredNotification(identifier) {
|
||||
return window.runtime.RemoveDeliveredNotification(identifier);
|
||||
}
|
||||
|
||||
export function RemoveNotification(identifier) {
|
||||
return window.runtime.RemoveNotification(identifier);
|
||||
}
|
||||
Reference in New Issue
Block a user