feat: update auto reply and packaging

This commit is contained in:
ly1213
2026-06-29 17:44:22 +08:00
parent 1ca66dc0af
commit 2d5ee7f08d
19 changed files with 1147 additions and 227 deletions

View File

@@ -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);

View File

@@ -211,6 +211,7 @@ if (typeof window.go === 'undefined') {
identity: {
unknownPolicy: 'customer',
unknownHandoffPolicy: 'hold',
replyExternalOnly: false,
refreshOnStart: true,
refreshIntervalMinutes: 30,
pageSize: 200,

View File

@@ -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

View File

@@ -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);
}