diff --git a/.DS_Store b/.DS_Store
index c242eec..c358fbf 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/TalkingQ-H5.html b/TalkingQ-H5.html
index 545af1d..98a3805 100644
--- a/TalkingQ-H5.html
+++ b/TalkingQ-H5.html
@@ -1318,6 +1318,496 @@ body.login-mode .page-container {
gap: 5px;
}
+/* ========== SYSTEM SETTINGS ========== */
+.settings-page-header,
+.settings-layout {
+ width: 100%;
+ max-width: 1320px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.settings-page-header {
+ margin-bottom: 20px;
+}
+
+.settings-layout {
+ display: grid;
+ grid-template-columns: 220px minmax(0, 1fr);
+ gap: 20px;
+ align-items: start;
+}
+
+.settings-sidebar {
+ position: sticky;
+ top: 0;
+ padding: 14px;
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ background: var(--bg-card);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.03);
+}
+
+.settings-sidebar-title {
+ padding: 4px 8px 10px;
+ color: var(--text-muted);
+ font-size: 11px;
+ font-weight: 700;
+}
+
+.settings-sidebar-nav {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.settings-nav-item {
+ width: 100%;
+ min-height: 54px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 10px;
+ border: 1px solid transparent;
+ border-radius: 8px;
+ color: var(--text-secondary);
+ text-align: left;
+ transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
+}
+
+.settings-nav-item:hover {
+ color: var(--text-primary);
+ background: var(--bg-base);
+}
+
+.settings-nav-item.active {
+ color: var(--primary-dark);
+ border-color: rgba(82,196,26,0.22);
+ background: rgba(82,196,26,0.08);
+}
+
+.settings-nav-icon {
+ width: 32px;
+ height: 32px;
+ flex: 0 0 32px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 8px;
+ background: var(--bg-base);
+ color: var(--text-muted);
+}
+
+.settings-nav-item.active .settings-nav-icon {
+ color: var(--primary-dark);
+ background: rgba(82,196,26,0.12);
+}
+
+.settings-nav-icon svg {
+ width: 16px;
+ height: 16px;
+}
+
+.settings-nav-item > span:last-child {
+ min-width: 0;
+}
+
+.settings-nav-item strong,
+.settings-nav-item small {
+ display: block;
+}
+
+.settings-nav-item strong {
+ color: inherit;
+ font-size: 13px;
+ font-weight: 650;
+ line-height: 1.35;
+}
+
+.settings-nav-item small {
+ margin-top: 2px;
+ color: var(--text-muted);
+ font-size: 11px;
+ line-height: 1.3;
+}
+
+.settings-sidebar-status {
+ margin-top: 14px;
+ padding: 12px 10px 2px;
+ border-top: 1px solid var(--border);
+}
+
+.settings-status-line {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ color: var(--text-secondary);
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.settings-status-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: var(--primary);
+ box-shadow: 0 0 0 3px rgba(82,196,26,0.12);
+}
+
+.settings-sidebar-status small {
+ display: block;
+ margin-top: 6px;
+ color: var(--text-muted);
+ font-size: 11px;
+}
+
+.settings-content {
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.settings-two-column {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 16px;
+ align-items: stretch;
+}
+
+.settings-section-card {
+ min-width: 0;
+ overflow: hidden;
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ background: var(--bg-card);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.03);
+ scroll-margin-top: 16px;
+}
+
+.settings-two-column .settings-section-card {
+ display: flex;
+ flex-direction: column;
+}
+
+.settings-section-header {
+ min-height: 72px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--border);
+}
+
+.settings-section-heading {
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.settings-section-icon {
+ width: 36px;
+ height: 36px;
+ flex: 0 0 36px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 9px;
+ color: var(--primary-dark);
+ background: rgba(82,196,26,0.1);
+}
+
+.settings-section-icon svg {
+ width: 18px;
+ height: 18px;
+}
+
+.settings-section-heading h2 {
+ color: var(--text-primary);
+ font-size: 15px;
+ font-weight: 700;
+ line-height: 1.4;
+}
+
+.settings-section-heading p {
+ margin-top: 2px;
+ color: var(--text-muted);
+ font-size: 12px;
+ line-height: 1.4;
+}
+
+.settings-section-count {
+ flex-shrink: 0;
+ padding: 5px 9px;
+ border-radius: 999px;
+ color: var(--primary-dark);
+ background: rgba(82,196,26,0.09);
+ font-size: 11px;
+ font-weight: 650;
+}
+
+.settings-section-body {
+ min-width: 0;
+ padding: 20px;
+}
+
+.settings-two-column .settings-section-body {
+ flex: 1;
+}
+
+.settings-section-body > .form-group:last-child {
+ margin-bottom: 0;
+}
+
+.settings-section-footer {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-height: 62px;
+ padding: 12px 20px;
+ border-top: 1px solid var(--border);
+ background: rgba(0,0,0,0.012);
+}
+
+.settings-key-name {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ white-space: nowrap;
+}
+
+.settings-api-table {
+ border: 1px solid var(--border);
+ border-radius: 9px;
+}
+
+.settings-key-name strong {
+ font-size: 13px;
+ font-weight: 650;
+}
+
+.settings-key-avatar {
+ width: 30px;
+ height: 30px;
+ flex: 0 0 30px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 8px;
+ color: var(--primary-dark);
+ background: rgba(82,196,26,0.09);
+}
+
+.settings-key-avatar svg {
+ width: 14px;
+ height: 14px;
+}
+
+.settings-key-code {
+ display: inline-flex;
+ padding: 5px 8px;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ color: var(--text-secondary);
+ background: var(--bg-base);
+ font-size: 12px;
+ white-space: nowrap;
+}
+
+.settings-muted-cell {
+ color: var(--text-secondary);
+ font-size: 12px;
+ white-space: nowrap;
+}
+
+.settings-table-actions {
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ white-space: nowrap;
+}
+
+.settings-icon-btn {
+ width: 30px;
+ height: 30px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid transparent;
+ border-radius: 7px;
+ color: var(--text-muted);
+ transition: var(--transition);
+}
+
+.settings-icon-btn:hover {
+ color: var(--primary-dark);
+ border-color: rgba(82,196,26,0.24);
+ background: rgba(82,196,26,0.08);
+}
+
+.settings-icon-btn.danger:hover {
+ color: var(--danger);
+ border-color: rgba(255,77,79,0.22);
+ background: rgba(255,77,79,0.07);
+}
+
+.settings-icon-btn svg {
+ width: 15px;
+ height: 15px;
+}
+
+.settings-empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 190px;
+ padding: 28px 20px;
+ border: 1px dashed var(--border-dark);
+ border-radius: 9px;
+ text-align: center;
+ background: rgba(0,0,0,0.01);
+}
+
+.settings-empty-state > span {
+ width: 42px;
+ height: 42px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 12px;
+ border-radius: 10px;
+ color: var(--primary-dark);
+ background: rgba(82,196,26,0.1);
+}
+
+.settings-empty-state > span svg {
+ width: 20px;
+ height: 20px;
+}
+
+.settings-empty-state strong {
+ font-size: 14px;
+}
+
+.settings-empty-state p {
+ margin: 5px 0 14px;
+ color: var(--text-muted);
+ font-size: 12px;
+}
+
+.settings-security-note {
+ display: flex;
+ align-items: flex-start;
+ gap: 9px;
+ margin-top: 14px;
+ padding: 10px 12px;
+ border-radius: 8px;
+ color: var(--text-secondary);
+ background: rgba(82,196,26,0.055);
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+.settings-security-note svg {
+ width: 15px;
+ height: 15px;
+ flex-shrink: 0;
+ margin-top: 1px;
+ color: var(--primary-dark);
+}
+
+.settings-checkbox-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 12px 0 2px;
+ border-top: 1px solid var(--border);
+}
+
+.settings-checkbox-row strong,
+.settings-checkbox-row small {
+ display: block;
+}
+
+.settings-checkbox-row strong {
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.settings-checkbox-row small {
+ margin-top: 3px;
+ color: var(--text-muted);
+ font-size: 11px;
+}
+
+.settings-content .notification-principles {
+ gap: 8px;
+ margin-bottom: 14px;
+}
+
+.settings-content .notification-principles > div {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+ padding: 10px 12px;
+ border-radius: 8px;
+ background: var(--bg-base);
+}
+
+.settings-principle-icon {
+ width: 30px;
+ height: 30px;
+ flex: 0 0 30px;
+ display: inline-flex !important;
+ align-items: center;
+ justify-content: center;
+ border-radius: 7px;
+ color: var(--primary-dark);
+ background: var(--bg-card);
+}
+
+.settings-principle-icon svg {
+ width: 15px;
+ height: 15px;
+}
+
+.settings-content .notification-principles strong,
+.settings-content .notification-principles small {
+ display: block;
+}
+
+.settings-content .notification-principles strong {
+ margin: 0;
+ font-size: 12px;
+}
+
+.settings-content .notification-principles small {
+ margin-top: 2px;
+ overflow: hidden;
+ color: var(--text-muted);
+ font-size: 11px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.settings-content .notification-rule-row {
+ grid-template-columns: minmax(0, 1fr) 230px;
+ gap: 18px;
+ padding: 16px;
+ border-radius: 9px;
+ background: rgba(0,0,0,0.012);
+}
+
+.settings-content .notification-rule-controls {
+ padding-left: 16px;
+ border-left: 1px solid var(--border);
+}
+
.tenant-selector {
display: flex;
align-items: center;
@@ -2169,13 +2659,30 @@ select.form-control { appearance: none; cursor: pointer; }
display: flex;
align-items: center;
justify-content: center;
- z-index: 1000;
+ z-index: 20000;
backdrop-filter: blur(2px);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
+body.modal-open {
+ overflow: hidden;
+}
+
+body.modal-open .bailian-config-scroll,
+body.modal-open .page-content,
+body.modal-open .main-content {
+ scrollbar-width: none;
+}
+
+body.modal-open .bailian-config-scroll::-webkit-scrollbar,
+body.modal-open .page-content::-webkit-scrollbar,
+body.modal-open .main-content::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+}
+
.modal-overlay.open {
opacity: 1;
pointer-events: all;
@@ -2351,8 +2858,14 @@ select.form-control { appearance: none; cursor: pointer; }
line-height: 1.4;
}
-[data-tooltip]:hover::after {
- opacity: 1;
+[data-tooltip]:hover::after {
+ opacity: 1;
+ transform: translateX(-50%) translateY(-2px);
+}
+
+[data-tooltip]:focus::after,
+[data-tooltip]:focus-visible::after {
+ opacity: 1;
transform: translateX(-50%) translateY(-2px);
}
@@ -2367,6 +2880,38 @@ select.form-control { appearance: none; cursor: pointer; }
}
/* ========== RESPONSIVE ========== */
+@media (max-width: 1180px) {
+ .settings-layout {
+ grid-template-columns: minmax(0, 1fr);
+ }
+
+ .settings-sidebar {
+ position: sticky;
+ top: -1px;
+ z-index: 5;
+ padding: 8px;
+ }
+
+ .settings-sidebar-title,
+ .settings-sidebar-status {
+ display: none;
+ }
+
+ .settings-sidebar-nav {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 6px;
+ }
+
+ .settings-nav-item {
+ min-height: 48px;
+ }
+
+ .settings-section-card {
+ scroll-margin-top: 82px;
+ }
+}
+
@media (max-width: 1024px) {
.charts-grid { grid-template-columns: 1fr; }
.config-panels { grid-template-columns: 1fr; }
@@ -2376,6 +2921,18 @@ select.form-control { appearance: none; cursor: pointer; }
.session-filter-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.notification-principles { grid-template-columns: 1fr; }
.notification-rule-row { grid-template-columns: 1fr; }
+ .settings-content .notification-principles {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+ .settings-content .notification-rule-row {
+ grid-template-columns: 1fr;
+ }
+ .settings-content .notification-rule-controls {
+ padding-top: 12px;
+ padding-left: 0;
+ border-top: 1px solid var(--border);
+ border-left: 0;
+ }
.notif-detail-meta { grid-template-columns: 1fr; }
#replayPanel { position: relative !important; }
}
@@ -2390,6 +2947,21 @@ select.form-control { appearance: none; cursor: pointer; }
}
.sidebar.mobile-open { transform: translateX(0); }
.form-row { grid-template-columns: 1fr; }
+ .settings-two-column {
+ grid-template-columns: 1fr;
+ }
+ .settings-sidebar-nav {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ .settings-section-card {
+ scroll-margin-top: 138px;
+ }
+ .settings-content .notification-principles {
+ grid-template-columns: 1fr;
+ }
+ .settings-content .notification-principles small {
+ white-space: normal;
+ }
.kpi-grid { grid-template-columns: repeat(2, 1fr); }
.session-drilldown-banner {
align-items: flex-start;
@@ -2416,6 +2988,58 @@ select.form-control { appearance: none; cursor: pointer; }
}
}
+@media (max-width: 560px) {
+ .settings-page-header {
+ margin-bottom: 16px;
+ }
+
+ .settings-nav-item {
+ min-height: 44px;
+ padding: 6px 8px;
+ }
+
+ .settings-nav-icon {
+ width: 28px;
+ height: 28px;
+ flex-basis: 28px;
+ }
+
+ .settings-nav-item small {
+ display: none;
+ }
+
+ .settings-section-header {
+ min-height: 68px;
+ align-items: flex-start;
+ flex-direction: column;
+ padding: 14px 16px;
+ }
+
+ .settings-section-header > .btn {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .settings-section-body {
+ padding: 16px;
+ }
+
+ .settings-section-footer {
+ align-items: stretch;
+ flex-direction: column;
+ padding: 12px 16px;
+ }
+
+ .settings-section-footer .btn {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .settings-section-count {
+ margin-left: 48px;
+ }
+}
+
@media (max-width: 768px) {
.kpi-grid { grid-template-columns: 1fr; }
}
@@ -4733,24 +5357,24 @@ select.form-control { appearance: none; cursor: pointer; }
.kb-type-grid {
grid-template-columns: 1fr;
}
-
+
.file-compare {
grid-template-columns: 1fr;
}
-
+
.kb-detail-header {
flex-direction: column;
}
-
+
.kb-detail-actions {
width: 100%;
justify-content: flex-start;
}
-
+
.wizard-steps {
padding: 0;
}
-
+
.wizard-step-label {
display: none;
}
@@ -4763,12 +5387,12 @@ select.form-control { appearance: none; cursor: pointer; }
.no-print {
display: none !important;
}
-
+
.main-content {
width: 100% !important;
margin: 0 !important;
}
-
+
.page-container {
padding: 0 !important;
}
@@ -5775,225 +6399,6 @@ select.form-control { appearance: none; cursor: pointer; }
color: var(--text-muted);
}
-.selected-scene-card {
- width: 100%;
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 14px;
- border: 1px solid var(--border);
- border-radius: 8px;
- background: #fbfdfb;
- cursor: pointer;
- text-align: left;
- transition: var(--transition);
-}
-
-.selected-scene-card:hover {
- border-color: var(--primary-light);
- background: var(--primary-bg);
-}
-
-.selected-scene-icon,
-.scene-modal-icon {
- width: 38px;
- height: 38px;
- border-radius: 8px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--primary-bg);
- color: var(--primary);
- flex-shrink: 0;
-}
-
-.selected-scene-main {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 4px;
-}
-
-.selected-scene-name {
- font-size: 14px;
- font-weight: 700;
- color: var(--text-primary);
-}
-
-.selected-scene-version {
- font-size: 12px;
- line-height: 1.5;
- color: var(--text-muted);
-}
-
-.selected-scene-action {
- color: var(--text-muted);
- display: flex;
- align-items: center;
-}
-
-.scene-modal-content {
- display: flex;
- flex-direction: column;
- gap: 20px;
-}
-
-.scene-modal-section {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-.scene-modal-title {
- font-size: 13px;
- font-weight: 700;
- color: var(--text-primary);
-}
-
-.scene-modal-grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 10px;
-}
-
-.scene-modal-card {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 12px;
- border: 1px solid var(--border);
- border-radius: 8px;
- cursor: pointer;
- transition: var(--transition);
- background: var(--bg-white);
-}
-
-.scene-modal-card input,
-.scene-version-card input {
- display: none;
-}
-
-.scene-modal-card:hover,
-.scene-version-card:hover {
- border-color: var(--primary-light);
- background: var(--primary-bg);
-}
-
-.scene-modal-card.selected,
-.scene-version-card.selected {
- border-color: var(--primary);
- background: var(--primary-bg);
- box-shadow: 0 0 0 2px rgba(82,196,26,0.08);
-}
-
-.scene-modal-card.selected .scene-modal-icon {
- background: var(--primary);
- color: #fff;
-}
-
-.scene-modal-card[data-tooltip]::after {
- width: 220px;
- white-space: normal;
- text-align: left;
- line-height: 1.5;
- padding: 8px 10px;
-}
-
-.scene-modal-name {
- font-size: 13px;
- font-weight: 600;
- color: var(--text-primary);
-}
-
-.scene-version-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 10px;
-}
-
-.scene-version-card {
- display: flex;
- flex-direction: column;
- gap: 6px;
- padding: 14px;
- border: 1px solid var(--border);
- border-radius: 8px;
- cursor: pointer;
- transition: var(--transition);
- background: var(--bg-white);
-}
-
-.scene-version-name {
- font-size: 14px;
- font-weight: 700;
- color: var(--text-primary);
-}
-
-.scene-version-desc {
- font-size: 12px;
- line-height: 1.6;
- color: var(--text-secondary);
-}
-
-/* 场景模板网格 */
-.scene-template-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 8px;
-}
-
-.scene-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 6px;
- padding: 12px 8px;
- border: 1px solid var(--border);
- border-radius: var(--radius);
- cursor: pointer;
- transition: var(--transition);
- text-align: center;
-}
-
-.scene-item:hover {
- border-color: var(--primary-light);
- background: var(--primary-bg);
-}
-
-.scene-item.selected {
- border-color: var(--primary);
- background: var(--primary-bg);
-}
-
-.scene-item span:first-child {
- width: 32px;
- height: 32px;
- border-radius: var(--radius-sm);
- background: var(--bg-base);
- display: flex;
- align-items: center;
- justify-content: center;
- color: var(--text-secondary);
- transition: var(--transition);
-}
-
-.scene-item.selected span:first-child {
- background: var(--primary);
- color: #fff;
-}
-
-.scene-item span:last-child {
- font-size: 11px;
- color: var(--text-secondary);
- line-height: 1.3;
-}
-
-.scene-item.selected span:last-child {
- color: var(--primary-dark);
- font-weight: 500;
-}
-
/* 模块标签 */
.module-tabs {
display: flex;
@@ -6687,33 +7092,21 @@ select.form-control-sm {
.agent-config-left {
width: auto;
}
-
+
.agent-config-right {
height: 520px;
}
}
@media (max-width: 768px) {
- .scene-template-grid {
- grid-template-columns: repeat(2, 1fr);
- }
-
- .scene-modal-grid {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- .scene-version-grid {
- grid-template-columns: 1fr;
- }
-
.module-tabs {
flex-wrap: wrap;
}
-
+
.module-tab {
flex: 1 1 45%;
}
-
+
.skill-grid {
grid-template-columns: repeat(3, 1fr);
}
@@ -6736,13 +7129,11 @@ select.form-control-sm {
.bailian-split-grid,
.bailian-filter-summary,
- .bailian-edge-grid,
.bailian-check-grid,
.bailian-variable-grid,
.bailian-mode-grid,
.bailian-model-mode-grid,
- .bailian-mini-grid,
- .bailian-budget-grid {
+ .bailian-mini-grid {
grid-template-columns: 1fr;
}
@@ -6750,6 +7141,26 @@ select.form-control-sm {
grid-template-columns: 1fr;
}
+ .bailian-command-editor-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .bailian-command-library-card {
+ grid-template-columns: 18px minmax(0, 1fr);
+ }
+
+ .bailian-command-library-actions,
+ .bailian-command-editor-actions,
+ .bailian-command-head-actions {
+ width: 100%;
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ }
+
+ .bailian-command-library-actions {
+ grid-column: 2;
+ }
+
.bailian-filter-row {
grid-template-columns: 1fr;
}
@@ -6758,8 +7169,12 @@ select.form-control-sm {
grid-template-columns: 1fr;
}
+ .bailian-mcp-tool-row {
+ grid-template-columns: 1fr;
+ }
+
.bailian-section-inline-action,
- .bailian-filter-preview,
+ .bailian-filter-summary,
.bailian-feature-head,
.bailian-filter-head {
flex-direction: column;
@@ -6799,7 +7214,7 @@ select.form-control-sm {
.hotword-select-list {
grid-template-columns: 1fr;
}
-
+
.config-row {
flex-direction: column;
align-items: flex-start;
@@ -7762,6 +8177,12 @@ body.agent-workbench-mode .page-container {
.bailian-agent-config {
--agent-green: #52c41a;
--agent-green-dark: #389e0d;
+ --agent-primary: rgba(82,196,26,.82);
+ --agent-primary-dark: rgba(82,196,26,.88);
+ --agent-primary-hover: rgba(82,196,26,.96);
+ --agent-primary-soft: rgba(246,255,237,.78);
+ --agent-primary-border: rgba(82,196,26,.28);
+ --agent-accent: rgba(115,209,61,.62);
--agent-ink: #111827;
--agent-text: #263142;
--agent-muted: #7b8494;
@@ -7840,10 +8261,31 @@ body.agent-workbench-mode .page-container {
.bailian-agent-subtitle {
margin-top: 4px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
font-size: 12px;
color: var(--agent-muted);
}
+.bailian-autosave-status {
+ display: inline-flex;
+ align-items: center;
+ height: 22px;
+ padding: 0 8px;
+ border-radius: 999px;
+ background: var(--agent-primary-soft);
+ color: var(--agent-green-dark);
+ font-size: 11px;
+ font-weight: 700;
+}
+
+.bailian-autosave-status.saving {
+ background: #e6f4ff;
+ color: #096dd9;
+}
+
.bailian-header-actions {
justify-content: flex-end;
gap: 10px;
@@ -7857,7 +8299,6 @@ body.agent-workbench-mode .page-container {
.bailian-run-btn,
.bailian-add-round,
.bailian-add-card,
-.bailian-scene-card,
.bailian-model-select,
.bailian-step-tab {
font-family: inherit;
@@ -7943,6 +8384,18 @@ body.agent-workbench-mode .page-container {
font-size: 13px;
}
+.bailian-action-btn.danger {
+ color: var(--danger);
+ border-color: #ffccc7;
+ background: #fff;
+}
+
+.bailian-action-btn.danger:hover {
+ color: #cf1322;
+ border-color: #ffa39e;
+ background: #fff2f0;
+}
+
.bailian-action-icon {
width: 40px;
padding: 0;
@@ -7950,15 +8403,15 @@ body.agent-workbench-mode .page-container {
.bailian-primary-btn,
.bailian-run-btn {
- border-color: var(--agent-ink);
- background: var(--agent-ink);
+ border-color: var(--agent-primary-dark);
+ background: var(--agent-primary-dark);
color: #fff;
}
.bailian-primary-btn:hover,
.bailian-run-btn:hover {
- border-color: #243044;
- background: #243044;
+ border-color: var(--agent-primary-hover);
+ background: var(--agent-primary-hover);
color: #fff;
}
@@ -7977,8 +8430,8 @@ body.agent-workbench-mode .page-container {
.bailian-status-pill.published {
color: var(--agent-green-dark);
- background: #f6ffed;
- border: 1px solid #b7eb8f;
+ background: var(--agent-primary-soft);
+ border: 1px solid var(--agent-primary-border);
}
.bailian-status-pill.editing {
@@ -8014,7 +8467,7 @@ body.agent-workbench-mode .page-container {
height: 72px;
flex-shrink: 0;
display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
+ grid-template-columns: repeat(3, minmax(0, 1fr));
align-items: stretch;
border-bottom: 1px solid var(--agent-border);
background: #fff;
@@ -8023,11 +8476,11 @@ body.agent-workbench-mode .page-container {
.bailian-step-tab {
position: relative;
display: flex;
- flex-direction: column;
+ flex-direction: row;
align-items: center;
justify-content: center;
- gap: 6px;
- padding: 0 12px;
+ gap: 7px;
+ padding: 0 10px;
color: #8a93a3;
background: #fff;
transition: color .18s ease, background .18s ease;
@@ -8101,6 +8554,14 @@ body.agent-workbench-mode .page-container {
scrollbar-color: #dce1eb transparent;
}
+.bailian-config-footer {
+ flex-shrink: 0;
+ padding: 14px 24px 18px;
+ border-top: 1px solid var(--agent-border);
+ background: rgba(255, 255, 255, .96);
+ box-shadow: 0 -4px 14px rgba(17, 24, 39, .03);
+}
+
.bailian-config-scroll::-webkit-scrollbar,
.bailian-preview-pane::-webkit-scrollbar {
width: 4px;
@@ -8143,7 +8604,7 @@ body.agent-workbench-mode .page-container {
align-items: center;
justify-content: center;
border-radius: 50%;
- background: #111827;
+ background: var(--agent-primary-dark);
color: #fff;
font-size: 13px;
font-weight: 700;
@@ -8157,8 +8618,7 @@ body.agent-workbench-mode .page-container {
color: var(--agent-ink);
}
-.bailian-card-section,
-.bailian-scene-card {
+.bailian-card-section {
border: 1px solid var(--agent-border);
border-radius: 12px;
background: #fff;
@@ -8170,7 +8630,6 @@ body.agent-workbench-mode .page-container {
}
.bailian-card-section:hover,
-.bailian-scene-card:hover,
.bailian-model-select:hover,
.bailian-list-card:hover,
.bailian-kb-card:hover,
@@ -8189,6 +8648,44 @@ body.agent-workbench-mode .page-container {
color: var(--agent-ink);
}
+.bailian-card-section-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ margin: 0 0 14px;
+}
+
+.bailian-card-section-head h3 {
+ margin: 0;
+}
+
+.bailian-card-section-head .btn {
+ flex-shrink: 0;
+}
+
+.bailian-card-section.toggle-only {
+ padding-top: 16px;
+ padding-bottom: 16px;
+}
+
+.bailian-card-section.toggle-only .bailian-card-section-head {
+ min-height: 32px;
+ margin-bottom: 0;
+}
+
+.bailian-card-section.toggle-only .agent-switch {
+ flex-shrink: 0;
+}
+
+.bailian-card-section-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
.bailian-section-head {
display: flex;
align-items: center;
@@ -8208,23 +8705,6 @@ body.agent-workbench-mode .page-container {
gap: 12px;
}
-.bailian-scene-card {
- min-height: 72px;
- width: 100%;
- display: flex;
- align-items: center;
- gap: 14px;
- padding: 14px 16px;
- text-align: left;
- color: var(--agent-text);
- transition: border-color .18s ease, background .18s ease, transform .18s ease;
-}
-
-.bailian-scene-card:hover {
- transform: translateY(-1px);
-}
-
-.bailian-scene-icon,
.bailian-model-icon,
.bailian-list-icon,
.bailian-mini-card span {
@@ -8233,17 +8713,10 @@ body.agent-workbench-mode .page-container {
justify-content: center;
flex-shrink: 0;
color: var(--agent-green-dark);
- background: #f6ffed;
- border: 1px solid #e0f7d2;
+ background: var(--agent-primary-soft);
+ border: 1px solid var(--agent-primary-border);
}
-.bailian-scene-icon {
- width: 42px;
- height: 42px;
- border-radius: 12px;
-}
-
-.bailian-scene-icon i,
.bailian-model-icon i,
.bailian-list-icon i,
.bailian-mini-card i {
@@ -8251,40 +8724,6 @@ body.agent-workbench-mode .page-container {
height: 18px;
}
-.bailian-scene-main {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 5px;
-}
-
-.bailian-scene-title-row {
- display: flex;
- align-items: center;
- gap: 10px;
-}
-
-.bailian-scene-title-row strong {
- font-size: 15px;
- color: var(--agent-ink);
-}
-
-.bailian-scene-title-row em {
- font-style: normal;
- padding: 2px 9px;
- border-radius: 999px;
- color: var(--agent-green-dark);
- background: #f6ffed;
- font-size: 12px;
- font-weight: 600;
-}
-
-.bailian-scene-main span:last-child {
- font-size: 12px;
- color: var(--agent-muted);
-}
-
.bailian-field {
display: flex;
flex-direction: column;
@@ -8323,8 +8762,8 @@ body.agent-workbench-mode .page-container {
.bailian-input:focus,
.bailian-number-input:focus,
.bailian-prompt-editor:focus {
- border-color: var(--agent-ink);
- box-shadow: 0 0 0 3px rgba(17,24,39,0.06);
+ border-color: var(--agent-primary-dark);
+ box-shadow: 0 0 0 3px rgba(82,196,26,0.12);
}
.bailian-model-select {
@@ -8428,6 +8867,10 @@ body.agent-workbench-mode .page-container {
color: var(--agent-muted);
}
+.bailian-feature-manage {
+ width: fit-content;
+}
+
.bailian-model-desc,
.bailian-section-note {
font-size: 12px;
@@ -8435,6 +8878,10 @@ body.agent-workbench-mode .page-container {
color: var(--agent-muted);
}
+.bailian-model-field {
+ margin-top: 12px;
+}
+
.bailian-section-note {
padding: 10px 12px;
border-radius: 10px;
@@ -8442,6 +8889,298 @@ body.agent-workbench-mode .page-container {
border: 1px solid #edf0f6;
}
+.bailian-asr-manager {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.bailian-asr-form {
+ padding: 16px;
+ border: 1px solid var(--agent-border);
+ border-radius: 12px;
+ background: #fbfcff;
+}
+
+.bailian-asr-form-head,
+.bailian-asr-list-head,
+.bailian-asr-model-row,
+.bailian-asr-model-actions,
+.bailian-asr-form-actions {
+ display: flex;
+ align-items: center;
+}
+
+.bailian-asr-form-head,
+.bailian-asr-list-head,
+.bailian-asr-model-row {
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.bailian-asr-form-title {
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--agent-ink);
+}
+
+.bailian-asr-form-desc,
+.bailian-asr-list-head span,
+.bailian-asr-model-meta {
+ font-size: 12px;
+ line-height: 1.6;
+ color: var(--agent-muted);
+}
+
+.bailian-asr-form-grid {
+ margin-top: 14px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.bailian-asr-form-wide {
+ grid-column: 1 / -1;
+}
+
+.bailian-asr-form-actions {
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 14px;
+}
+
+.bailian-model-manager-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 14px;
+ padding: 14px 16px;
+ border: 1px solid var(--agent-border);
+ border-radius: 12px;
+ background: #fbfcff;
+}
+
+.bailian-model-manager-toolbar .bailian-action-btn,
+.bailian-model-manager-toolbar .btn {
+ flex-shrink: 0;
+}
+
+.bailian-empty-action {
+ margin-top: 6px;
+}
+
+.bailian-voice-mode-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.bailian-voice-mode-card {
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ padding: 12px;
+ border: 1px solid var(--agent-border);
+ border-radius: 12px;
+ background: #fff;
+ cursor: pointer;
+ transition: border-color .18s ease, background .18s ease, box-shadow .18s ease;
+}
+
+.bailian-voice-mode-card.active {
+ border-color: var(--agent-green);
+ background: var(--agent-primary-soft);
+ box-shadow: var(--agent-card-shadow);
+}
+
+.bailian-voice-mode-card input {
+ margin-top: 2px;
+ accent-color: var(--agent-primary);
+}
+
+.bailian-voice-mode-card strong,
+.bailian-voice-mode-card small {
+ display: block;
+}
+
+.bailian-voice-mode-card strong {
+ font-size: 13px;
+ color: var(--agent-ink);
+}
+
+.bailian-voice-mode-card small,
+.bailian-form-help {
+ margin-top: 4px;
+ font-size: 12px;
+ line-height: 1.5;
+ color: var(--agent-muted);
+}
+
+.bailian-voice-mode-panel {
+ margin-top: 10px;
+}
+
+.bailian-voice-mode-panel.hidden {
+ display: none;
+}
+
+.bailian-voice-batch-input {
+ min-height: 128px;
+ resize: vertical;
+ line-height: 1.55;
+}
+
+.bailian-inline-check {
+ margin-top: 14px;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 12px;
+ color: var(--agent-muted);
+ cursor: pointer;
+}
+
+.bailian-inline-check input {
+ width: 14px;
+ height: 14px;
+ accent-color: var(--agent-primary);
+}
+
+.bailian-asr-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.bailian-asr-model-card {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 14px;
+ padding: 14px;
+ border: 1px solid var(--agent-border);
+ border-radius: 12px;
+ background: #fff;
+}
+
+.bailian-asr-model-main {
+ flex: 1;
+ min-width: 0;
+}
+
+.bailian-asr-model-row strong {
+ font-size: 14px;
+ color: var(--agent-ink);
+}
+
+.bailian-asr-status {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ height: 22px;
+ padding: 0 8px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 700;
+ white-space: nowrap;
+}
+
+.bailian-asr-status.neutral {
+ color: #606a7b;
+ background: #f3f5f8;
+ border: 1px solid #e1e5ee;
+}
+
+.bailian-asr-status.pending {
+ color: #096dd9;
+ background: #e6f4ff;
+ border: 1px solid #91caff;
+}
+
+.bailian-asr-status.success {
+ color: var(--agent-green-dark);
+ background: var(--agent-primary-soft);
+ border: 1px solid var(--agent-primary-border);
+}
+
+.bailian-asr-status.danger {
+ color: #cf1322;
+ background: #fff1f0;
+ border: 1px solid #ffa39e;
+}
+
+.bailian-asr-status.disabled {
+ color: #7b8494;
+ background: #f7f8fc;
+ border: 1px solid #e5e7f0;
+}
+
+.bailian-asr-model-meta {
+ margin-top: 6px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px 14px;
+}
+
+.bailian-asr-error {
+ margin-top: 8px;
+ padding: 8px 10px;
+ border-radius: 10px;
+ border: 1px solid #ffd8d6;
+ background: #fff7f6;
+ color: #cf1322;
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+.bailian-asr-model-actions {
+ flex-shrink: 0;
+ gap: 8px;
+ opacity: 1 !important;
+ visibility: visible !important;
+}
+
+.bailian-model-manager-toolbar .bailian-action-btn,
+.bailian-model-manager-toolbar .btn,
+.bailian-asr-empty .bailian-action-btn,
+.bailian-asr-empty .btn,
+.bailian-asr-form-actions .bailian-action-btn,
+.bailian-asr-form-actions .btn,
+.bailian-asr-model-actions .bailian-action-btn,
+.bailian-asr-model-actions .bailian-plain-icon {
+ display: inline-flex !important;
+ opacity: 1 !important;
+ visibility: visible !important;
+ pointer-events: auto;
+}
+
+.bailian-asr-empty {
+ min-height: 150px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 24px;
+ border: 1px dashed var(--agent-border);
+ border-radius: 12px;
+ background: #fbfcff;
+ color: var(--agent-muted);
+ text-align: center;
+}
+
+.bailian-asr-empty i,
+.bailian-asr-empty svg {
+ width: 30px;
+ height: 30px;
+ color: var(--agent-muted);
+}
+
+.bailian-asr-empty strong {
+ color: var(--agent-ink);
+ font-size: 14px;
+}
+
.bailian-voice-disabled {
padding: 24px;
border: 1px dashed var(--agent-border);
@@ -8500,6 +9239,42 @@ body.agent-workbench-mode .page-container {
color: var(--agent-ink);
}
+.bailian-title-with-info {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.bailian-info-tip {
+ width: 18px;
+ height: 18px;
+ padding: 0;
+ border: 0;
+ border-radius: 999px;
+ background: transparent;
+ color: #8a94a6;
+ cursor: pointer;
+}
+
+.bailian-info-tip:hover,
+.bailian-info-tip:focus-visible {
+ color: var(--agent-ink);
+ outline: none;
+}
+
+.bailian-info-tip i,
+.bailian-info-tip svg {
+ width: 15px;
+ height: 15px;
+}
+
+.bailian-info-tip::after {
+ width: max-content;
+ max-width: 280px;
+ white-space: normal;
+ text-align: left;
+}
+
.bailian-hotword-desc,
.bailian-switch-desc,
.bailian-section-inline-action,
@@ -8521,8 +9296,8 @@ body.agent-workbench-mode .page-container {
padding: 0 10px;
border-radius: 8px;
color: var(--agent-green-dark);
- background: #f6ffed;
- border: 1px solid #d9f7be;
+ background: var(--agent-primary-soft);
+ border: 1px solid var(--agent-primary-border);
font-size: 13px;
font-weight: 600;
white-space: nowrap;
@@ -8530,7 +9305,7 @@ body.agent-workbench-mode .page-container {
}
.bailian-link-btn:hover {
- background: #edffd9;
+ background: #dffcef;
transform: translateY(-1px);
}
@@ -8541,19 +9316,30 @@ body.agent-workbench-mode .page-container {
margin-top: 12px;
}
+.bailian-token-row.compact {
+ margin-top: 8px;
+ gap: 6px;
+}
+
.bailian-token {
display: inline-flex;
align-items: center;
height: 28px;
padding: 0 10px;
border-radius: 999px;
- border: 1px solid #d9f7be;
- background: #f6ffed;
+ border: 1px solid var(--agent-primary-border);
+ background: var(--agent-primary-soft);
color: var(--agent-green-dark);
font-size: 12px;
font-weight: 600;
}
+.bailian-token.muted {
+ color: var(--agent-muted);
+ background: #f7f8fc;
+ border-color: #e5e7f0;
+}
+
.agent-switch {
position: relative;
width: 42px;
@@ -8589,7 +9375,7 @@ body.agent-workbench-mode .page-container {
}
.agent-switch input:checked + span {
- background: var(--agent-ink);
+ background: var(--agent-primary-dark);
}
.agent-switch input:checked + span::after {
@@ -8653,69 +9439,37 @@ body.agent-workbench-mode .page-container {
margin-left: 2px;
}
-.bailian-edge-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 10px;
-}
-
-.bailian-edge-card {
- padding: 14px;
+.bailian-filter-summary {
+ margin-top: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 12px;
border: 1px solid var(--agent-border);
border-radius: 12px;
- background: #fff;
+ background: #fbfcff;
}
-.bailian-edge-card strong {
- display: block;
- font-size: 14px;
+.bailian-filter-summary-main {
+ flex: 1;
+ min-width: 0;
+}
+
+.bailian-filter-summary-title,
+.bailian-filter-group-title {
+ font-size: 13px;
+ font-weight: 700;
color: var(--agent-ink);
}
-.bailian-edge-card span {
- display: block;
- margin-top: 6px;
+.bailian-filter-summary-meta {
+ margin-top: 3px;
font-size: 12px;
- line-height: 1.6;
+ line-height: 1.5;
color: var(--agent-muted);
}
-.bailian-collapse-head {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding: 0;
- border: 0;
- background: transparent;
- color: var(--agent-text);
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
-}
-
-.bailian-filter-preview {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- padding-top: 12px;
-}
-
-.bailian-filter-note {
- flex: 1;
- font-size: 12px;
- line-height: 1.6;
- color: var(--agent-muted);
-}
-
-.bailian-filter-summary {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 12px;
-}
-
.bailian-filter-group {
padding: 14px;
border: 1px solid var(--agent-border);
@@ -8724,9 +9478,6 @@ body.agent-workbench-mode .page-container {
}
.bailian-filter-group-title {
- font-size: 13px;
- font-weight: 700;
- color: var(--agent-ink);
margin-bottom: 10px;
}
@@ -8748,8 +9499,8 @@ body.agent-workbench-mode .page-container {
.bailian-radio-card:hover,
.bailian-radio-card:has(input:checked) {
- border-color: var(--agent-ink);
- background: #fbfcff;
+ border-color: var(--agent-green);
+ background: var(--agent-primary-soft);
}
.bailian-radio-dot {
@@ -8763,8 +9514,8 @@ body.agent-workbench-mode .page-container {
}
.bailian-radio-card:has(input:checked) .bailian-radio-dot {
- border-color: var(--agent-ink);
- background: var(--agent-ink);
+ border-color: var(--agent-primary-dark);
+ background: var(--agent-primary-dark);
}
.bailian-radio-card span:last-child {
@@ -8783,7 +9534,7 @@ body.agent-workbench-mode .page-container {
.bailian-run-btn:hover {
transform: translateY(-1px);
- box-shadow: 0 8px 18px rgba(17,24,39,.12);
+ box-shadow: 0 8px 18px rgba(82,196,26,.18);
}
.prompt-section {
@@ -8825,8 +9576,7 @@ body.agent-workbench-mode .page-container {
.bailian-variable-grid,
.bailian-mode-grid,
.bailian-model-mode-grid,
-.bailian-mini-grid,
-.bailian-budget-grid {
+.bailian-mini-grid {
display: grid;
gap: 12px;
}
@@ -8835,8 +9585,377 @@ body.agent-workbench-mode .page-container {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
-.bailian-mode-grid,
-.bailian-budget-grid {
+.bailian-variable-simple {
+ margin-top: 14px;
+ border: 1px solid var(--agent-border);
+ border-radius: 12px;
+ background: #fff;
+ overflow: hidden;
+}
+
+.bailian-variable-simple-head,
+.bailian-variable-simple-row {
+ display: grid;
+ grid-template-columns: minmax(150px, .72fr) minmax(220px, 1.28fr) 36px;
+ align-items: center;
+ gap: 10px;
+}
+
+.bailian-variable-simple-head {
+ min-height: 42px;
+ padding: 0 12px;
+ border-bottom: 1px solid var(--agent-border);
+ background: #fbfcff;
+ color: var(--agent-muted);
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.bailian-variable-simple-list {
+ display: flex;
+ flex-direction: column;
+}
+
+.bailian-variable-simple-row {
+ padding: 10px 12px;
+ border-bottom: 1px solid #f0f2f7;
+}
+
+.bailian-variable-simple-row:last-child {
+ border-bottom: 0;
+}
+
+.bailian-variable-simple-row .bailian-input {
+ height: 40px;
+}
+
+.bailian-variable-simple-empty {
+ min-height: 86px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--agent-muted);
+ font-size: 13px;
+}
+
+.bailian-variable-manager {
+ display: grid;
+ grid-template-columns: 238px minmax(0, 1fr);
+ gap: 12px;
+}
+
+.bailian-variable-list-pane,
+.bailian-variable-detail-pane {
+ min-height: 360px;
+ border: 1px solid var(--agent-border);
+ border-radius: 12px;
+ background: #fff;
+}
+
+.bailian-variable-pane-head {
+ height: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ padding: 0 12px;
+ border-bottom: 1px solid var(--agent-border);
+ font-size: 13px;
+ font-weight: 700;
+ color: var(--agent-ink);
+}
+
+.bailian-variable-pane-head strong {
+ min-width: 24px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 999px;
+ background: #f2f4f7;
+ color: var(--agent-muted);
+ font-size: 12px;
+}
+
+.bailian-variable-list {
+ max-height: 410px;
+ overflow: auto;
+ padding: 8px;
+}
+
+.bailian-variable-row {
+ width: 100%;
+ min-height: 58px;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 28px;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 8px;
+ border: 1px solid transparent;
+ border-radius: 10px;
+ background: transparent;
+ color: inherit;
+ text-align: left;
+ cursor: pointer;
+}
+
+.bailian-variable-row:hover,
+.bailian-variable-row.active {
+ border-color: var(--agent-border);
+ background: #fbfcff;
+}
+
+.bailian-variable-row.active {
+ border-color: #cbd5e1;
+ box-shadow: var(--agent-card-shadow);
+}
+
+.bailian-variable-select {
+ min-width: 0;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 8px;
+ padding: 0;
+ border: 0;
+ background: transparent;
+ color: inherit;
+ text-align: left;
+ cursor: pointer;
+}
+
+.bailian-variable-row-main {
+ min-width: 0;
+}
+
+.bailian-variable-row-main strong,
+.bailian-variable-row-main small {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.bailian-variable-row-main strong {
+ font-size: 13px;
+ color: var(--agent-ink);
+}
+
+.bailian-variable-row-main small {
+ margin-top: 3px;
+ font-size: 11px;
+ color: var(--agent-muted);
+}
+
+.bailian-variable-source {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ height: 22px;
+ padding: 0 8px;
+ border-radius: 999px;
+ border: 1px solid #e5e7f0;
+ background: #f7f8fc;
+ color: var(--agent-muted);
+ font-size: 11px;
+ font-weight: 700;
+ white-space: nowrap;
+}
+
+.bailian-variable-source.green {
+ color: var(--agent-green-dark);
+ background: var(--agent-primary-soft);
+ border-color: var(--agent-primary-border);
+}
+
+.bailian-variable-source.blue {
+ color: #0958d9;
+ background: #e6f4ff;
+ border-color: #91caff;
+}
+
+.bailian-variable-source.purple {
+ color: #722ed1;
+ background: #f9f0ff;
+ border-color: #d3adf7;
+}
+
+.bailian-variable-source.orange {
+ color: #ad4e00;
+ background: #fff7e6;
+ border-color: #ffd591;
+}
+
+.bailian-variable-source.cyan {
+ color: #08979c;
+ background: #e6fffb;
+ border-color: #87e8de;
+}
+
+.bailian-variable-source.red {
+ color: #cf1322;
+ background: #fff1f0;
+ border-color: #ffa39e;
+}
+
+.bailian-variable-delete {
+ width: 28px;
+ height: 28px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 0;
+ border-radius: 8px;
+ background: transparent;
+ color: #9aa3b2;
+ cursor: pointer;
+}
+
+.bailian-variable-delete:hover {
+ color: var(--danger);
+ background: #fff2f0;
+}
+
+.bailian-variable-delete i,
+.bailian-variable-delete svg {
+ width: 15px;
+ height: 15px;
+}
+
+.bailian-variable-detail-pane {
+ padding: 14px;
+}
+
+.bailian-variable-detail {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.bailian-variable-detail-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.bailian-variable-detail-name {
+ font-size: 16px;
+ font-weight: 800;
+ color: var(--agent-ink);
+}
+
+.bailian-variable-detail-desc {
+ margin-top: 6px;
+ font-size: 12px;
+ line-height: 1.6;
+ color: var(--agent-muted);
+}
+
+.bailian-variable-detail-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.bailian-variable-detail-grid > div {
+ min-width: 0;
+ padding: 10px;
+ border-radius: 10px;
+ background: #fbfcff;
+ border: 1px solid #eef1f6;
+}
+
+.bailian-variable-detail-grid span,
+.bailian-variable-example span,
+.bailian-variable-section-title {
+ display: block;
+ font-size: 11px;
+ color: var(--agent-muted);
+ margin-bottom: 5px;
+}
+
+.bailian-variable-detail-grid strong {
+ display: block;
+ overflow-wrap: anywhere;
+ font-size: 12px;
+ color: var(--agent-ink);
+}
+
+.bailian-variable-code {
+ font-family: "SF Mono", Monaco, Consolas, "Liberation Mono", monospace;
+}
+
+.bailian-variable-example {
+ padding: 10px;
+ border-radius: 10px;
+ background: #fbfcff;
+ border: 1px solid #eef1f6;
+}
+
+.bailian-variable-example code {
+ display: block;
+ overflow-wrap: anywhere;
+ color: var(--agent-ink);
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+.bailian-variable-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+}
+
+.bailian-variable-empty,
+.bailian-variable-detail-empty {
+ height: 100%;
+ min-height: 240px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 20px;
+ text-align: center;
+ color: var(--agent-muted);
+}
+
+.bailian-variable-detail-empty i,
+.bailian-variable-detail-empty svg {
+ width: 28px;
+ height: 28px;
+}
+
+.bailian-variable-empty strong,
+.bailian-variable-detail-empty strong {
+ color: var(--agent-ink);
+ font-size: 14px;
+}
+
+.bailian-variable-form {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.bailian-variable-form-wide {
+ grid-column: 1 / -1;
+}
+
+.bailian-variable-textarea {
+ min-height: 92px;
+ resize: vertical;
+ line-height: 1.6;
+}
+
+.bailian-variable-usage-checks {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px 14px;
+}
+
+.bailian-mode-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -8887,7 +10006,7 @@ body.agent-workbench-mode .page-container {
.bailian-add-card:hover {
color: var(--agent-green-dark);
border-color: var(--agent-green);
- background: #f6ffed;
+ background: var(--agent-primary-soft);
transform: translateY(-1px);
}
@@ -8934,14 +10053,220 @@ body.agent-workbench-mode .page-container {
.bailian-small-pill {
color: var(--agent-green-dark);
- background: #f6ffed;
- border: 1px solid #d9f7be;
+ background: var(--agent-primary-soft);
+ border: 1px solid var(--agent-primary-border);
}
.bailian-kb-weight {
margin-top: 6px;
}
+.bailian-kb-empty {
+ min-height: 96px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ border: 1px dashed var(--agent-border);
+ border-radius: 12px;
+ background: #fbfcff;
+ color: var(--agent-muted);
+ font-size: 13px;
+}
+
+.bailian-kb-empty i,
+.bailian-kb-empty svg {
+ width: 18px;
+ height: 18px;
+}
+
+.bailian-kb-picker {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.bailian-kb-picker-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.bailian-search-field {
+ flex: 1;
+ min-width: 240px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0 12px;
+ border: 1px solid var(--agent-border);
+ border-radius: 10px;
+ background: #fff;
+ color: var(--agent-muted);
+}
+
+.bailian-search-field i,
+.bailian-search-field svg {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+}
+
+.bailian-search-field input {
+ width: 100%;
+ border: 0;
+ outline: none;
+ background: transparent;
+ color: var(--agent-ink);
+ font-size: 13px;
+}
+
+.bailian-kb-picker-list {
+ max-height: 460px;
+ overflow: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding-right: 2px;
+}
+
+.bailian-kb-picker-card {
+ display: grid;
+ grid-template-columns: 18px minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 12px;
+ padding: 14px;
+ border: 1px solid var(--agent-border);
+ border-radius: 12px;
+ background: #fff;
+ cursor: pointer;
+ transition: border-color .18s ease, background .18s ease, box-shadow .18s ease;
+}
+
+.bailian-kb-picker-card:hover,
+.bailian-kb-picker-card.active {
+ border-color: #cbd5e1;
+ background: #fbfcff;
+ box-shadow: var(--agent-card-shadow);
+}
+
+.bailian-kb-picker-card input {
+ display: none;
+}
+
+.bailian-kb-picker-check {
+ width: 18px;
+ height: 18px;
+ border: 1px solid #cbd5e1;
+ border-radius: 6px;
+ background: #fff;
+ position: relative;
+}
+
+.bailian-kb-picker-card.active .bailian-kb-picker-check {
+ border-color: var(--agent-green);
+ background: var(--agent-green);
+}
+
+.bailian-kb-picker-card.active .bailian-kb-picker-check::after {
+ content: "";
+ position: absolute;
+ left: 5px;
+ top: 2px;
+ width: 5px;
+ height: 9px;
+ border: solid #fff;
+ border-width: 0 2px 2px 0;
+ transform: rotate(45deg);
+}
+
+.bailian-kb-picker-main {
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.bailian-kb-picker-main strong {
+ color: var(--agent-ink);
+ font-size: 14px;
+}
+
+.bailian-kb-picker-main small {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--agent-muted);
+ font-size: 12px;
+}
+
+.bailian-kb-picker-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.bailian-kb-picker-meta em {
+ font-style: normal;
+ color: var(--agent-muted);
+ font-size: 11px;
+}
+
+.bailian-kb-picker-status {
+ height: 24px;
+ display: inline-flex;
+ align-items: center;
+ padding: 0 8px;
+ border-radius: 999px;
+ border: 1px solid #e5e7f0;
+ background: #f7f8fc;
+ color: var(--agent-muted);
+ font-size: 11px;
+ font-weight: 700;
+ white-space: nowrap;
+}
+
+.bailian-kb-picker-status.ready {
+ color: var(--agent-green-dark);
+ background: var(--agent-primary-soft);
+ border-color: var(--agent-primary-border);
+}
+
+.bailian-kb-picker-status.processing,
+.bailian-kb-picker-status.uploading {
+ color: #0958d9;
+ background: #e6f4ff;
+ border-color: #91caff;
+}
+
+.bailian-kb-picker-status.error {
+ color: #cf1322;
+ background: #fff1f0;
+ border-color: #ffa39e;
+}
+
+.bailian-kb-picker-empty {
+ min-height: 180px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ border: 1px dashed var(--agent-border);
+ border-radius: 12px;
+ background: #fbfcff;
+ color: var(--agent-muted);
+ font-size: 13px;
+}
+
+.bailian-kb-picker-empty i,
+.bailian-kb-picker-empty svg {
+ width: 28px;
+ height: 28px;
+}
+
.bailian-mode-card {
cursor: pointer;
}
@@ -8970,8 +10295,8 @@ body.agent-workbench-mode .page-container {
.bailian-mode-card.active,
.bailian-mode-card:has(input:checked) {
border-color: var(--agent-green);
- background: #f6ffed;
- box-shadow: 0 0 0 3px rgba(82,196,26,.08);
+ background: var(--agent-primary-soft);
+ box-shadow: 0 0 0 3px rgba(82,196,26,.10);
}
.bailian-mini-grid {
@@ -9000,6 +10325,48 @@ body.agent-workbench-mode .page-container {
margin: 6px 0 0;
}
+.bailian-mcp-card-meta {
+ margin-top: 8px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.bailian-mcp-card-meta span {
+ max-width: 220px;
+ overflow: hidden;
+ color: var(--agent-muted);
+ font-size: 11px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.bailian-mcp-error {
+ margin-top: 8px;
+ color: #cf1322;
+ font-size: 12px;
+}
+
+.bailian-mcp-tool-block {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-top: 4px;
+}
+
+.bailian-mcp-tool-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, 1.2fr) 34px;
+ gap: 10px;
+ align-items: center;
+ padding: 10px 0;
+ border-bottom: 1px solid #edf0f6;
+}
+
+.bailian-mcp-tool-row:last-child {
+ border-bottom: 0;
+}
+
.bailian-empty-inline {
padding: 12px 0;
font-size: 12px;
@@ -9146,6 +10513,200 @@ body.agent-workbench-mode .page-container {
margin-top: 6px;
}
+.bailian-command-head-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.bailian-command-empty,
+.bailian-command-library-empty {
+ min-height: 150px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 24px;
+ border: 1px dashed var(--agent-border);
+ border-radius: 12px;
+ background: #fbfcff;
+ color: var(--agent-muted);
+ text-align: center;
+}
+
+.bailian-command-empty i,
+.bailian-command-empty svg,
+.bailian-command-library-empty i,
+.bailian-command-library-empty svg {
+ width: 28px;
+ height: 28px;
+}
+
+.bailian-command-empty strong,
+.bailian-command-library-empty strong {
+ color: var(--agent-ink);
+ font-size: 14px;
+}
+
+.bailian-command-empty span,
+.bailian-command-library-empty span {
+ font-size: 12px;
+ line-height: 1.6;
+}
+
+.bailian-command-library,
+.bailian-command-editor {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.bailian-command-library-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.bailian-command-library-card {
+ display: grid;
+ grid-template-columns: 18px minmax(0, 1fr) auto;
+ align-items: flex-start;
+ gap: 12px;
+ padding: 14px;
+ border: 1px solid var(--agent-border);
+ border-radius: 12px;
+ background: #fff;
+ transition: border-color .18s ease, background .18s ease, box-shadow .18s ease;
+}
+
+.bailian-command-library-card:hover,
+.bailian-command-library-card.active {
+ border-color: #cbd5e1;
+ background: #fbfcff;
+ box-shadow: var(--agent-card-shadow);
+}
+
+.bailian-command-select {
+ margin-top: 2px;
+ cursor: pointer;
+}
+
+.bailian-command-select input {
+ display: none;
+}
+
+.bailian-command-select span {
+ width: 18px;
+ height: 18px;
+ display: inline-flex;
+ border: 1px solid #cbd5e1;
+ border-radius: 6px;
+ background: #fff;
+ position: relative;
+}
+
+.bailian-command-select input:checked + span {
+ border-color: var(--agent-green);
+ background: var(--agent-green);
+}
+
+.bailian-command-select input:checked + span::after {
+ content: "";
+ position: absolute;
+ left: 5px;
+ top: 2px;
+ width: 5px;
+ height: 9px;
+ border: solid #fff;
+ border-width: 0 2px 2px 0;
+ transform: rotate(45deg);
+}
+
+.bailian-command-library-main {
+ min-width: 0;
+}
+
+.bailian-command-library-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.bailian-command-library-title strong {
+ color: var(--agent-ink);
+ font-size: 14px;
+}
+
+.bailian-command-library-title em {
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ padding: 0 8px;
+ border-radius: 999px;
+ background: var(--agent-primary-soft);
+ color: var(--agent-green-dark);
+ font-style: normal;
+ font-size: 11px;
+ font-weight: 700;
+}
+
+.bailian-command-library-main p {
+ margin: 6px 0 0;
+ color: var(--agent-muted);
+ font-size: 12px;
+ line-height: 1.6;
+}
+
+.bailian-command-library-meta {
+ margin-top: 8px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.bailian-command-library-meta span {
+ color: var(--agent-muted);
+ font-size: 11px;
+}
+
+.bailian-command-library-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.bailian-command-editor {
+ padding: 14px;
+ border: 1px solid var(--agent-border);
+ border-radius: 12px;
+ background: #fbfcff;
+}
+
+.bailian-command-editor-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.bailian-command-editor-wide {
+ grid-column: 1 / -1;
+}
+
+.bailian-command-editor-textarea {
+ min-height: 92px;
+ resize: vertical;
+ line-height: 1.6;
+}
+
+.bailian-command-editor-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 12px;
+}
+
.bailian-add-round {
width: 38px;
height: 38px;
@@ -9154,13 +10715,13 @@ body.agent-workbench-mode .page-container {
justify-content: center;
flex-shrink: 0;
border-radius: 50%;
- background: var(--agent-ink);
+ background: var(--agent-primary-dark);
color: #fff;
transition: background .18s ease, transform .18s ease;
}
.bailian-add-round:hover {
- background: #243044;
+ background: var(--agent-primary-hover);
transform: translateY(-1px);
}
@@ -9219,6 +10780,1110 @@ body.agent-workbench-mode .page-container {
color: #a5adbb;
}
+.bailian-preview-pane.is-runtime {
+ align-items: stretch;
+ justify-content: stretch;
+ padding: 36px;
+}
+
+.bailian-preview-pane.is-runtime.is-voice-runtime {
+ overflow: visible;
+ padding: 14px 16px 16px;
+}
+
+.bailian-preview-pane.is-runtime.is-text-runtime {
+ overflow: visible;
+ padding: 14px 16px 16px;
+}
+
+.bailian-preview-pane.is-runtime.is-voice-runtime .agent-voice-panel {
+ overflow: visible;
+ border-radius: 0;
+}
+
+.bailian-preview-pane.is-runtime.is-text-runtime .agent-text-panel {
+ overflow: visible;
+ border-radius: 0;
+}
+
+.agent-runtime-shell {
+ width: min(760px, 100%);
+ margin: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+}
+
+.agent-runtime-shell.voice {
+ min-height: min(720px, calc(100vh - 136px));
+}
+
+.agent-runtime-shell.text-chat {
+ min-height: min(720px, calc(100vh - 136px));
+}
+
+.agent-text-panel {
+ width: 100%;
+ height: 100%;
+ min-height: min(720px, calc(100vh - 136px));
+ margin: 0 auto;
+ display: grid;
+ grid-template-rows: auto minmax(0, 1fr) auto;
+ overflow: visible;
+ border: 0;
+ border-radius: 0;
+ background:
+ radial-gradient(circle at 50% 20%, rgba(82,196,26,.075), transparent 34%),
+ var(--agent-soft);
+ box-shadow: none;
+}
+
+.agent-text-topbar {
+ min-height: 52px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 8px clamp(12px, 1.8vw, 20px) 4px;
+ border-bottom: 0;
+ background: transparent;
+}
+
+.agent-text-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.agent-text-title h2 {
+ margin: 0;
+ color: #303846;
+ font-size: 16px;
+ line-height: 1.35;
+ font-weight: 650;
+ letter-spacing: 0;
+}
+
+.agent-text-key {
+ margin-top: 6px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: var(--agent-muted);
+ font-size: 11px;
+}
+
+.agent-text-key strong {
+ max-width: 340px;
+ overflow: hidden;
+ color: #4b5563;
+ font-weight: 700;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.agent-text-user {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ color: #4b5563;
+ font-size: 13px;
+ font-weight: 650;
+}
+
+.agent-text-user i,
+.agent-text-user svg {
+ width: 15px;
+ height: 15px;
+}
+
+.agent-text-settings {
+ width: 32px;
+ height: 32px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 0;
+ border-radius: 10px;
+ background: rgba(255,255,255,.82);
+ color: #6b7280;
+ cursor: pointer;
+ transition: background .18s ease, transform .18s ease;
+}
+
+.agent-text-settings:hover {
+ background: #fff;
+ transform: translateY(-1px);
+}
+
+.agent-text-message-area {
+ min-height: 0;
+ overflow-y: auto;
+ padding: clamp(28px, 6vh, 64px) clamp(12px, 1.8vw, 20px) 18px;
+ background: transparent;
+ scrollbar-width: none;
+}
+
+.agent-text-message-area::-webkit-scrollbar {
+ display: none;
+}
+
+.agent-text-message-stream {
+ width: 100%;
+ min-height: 100%;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+}
+
+.agent-text-empty {
+ flex: 1;
+ min-height: 260px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #a0a7b5;
+ font-size: 13px;
+}
+
+.agent-text-message-row {
+ display: flex;
+ width: 100%;
+}
+
+.agent-text-message-row.user {
+ justify-content: flex-end;
+}
+
+.agent-text-message-row.ai {
+ justify-content: flex-start;
+}
+
+.agent-text-user-bubble {
+ max-width: min(78%, 640px);
+ padding: 12px 16px;
+ border-radius: 18px 18px 4px 18px;
+ background: rgba(246,255,237,.88);
+ box-shadow: 0 2px 8px rgba(82,196,26,.045);
+ color: #303846;
+ font-size: 15px;
+ line-height: 1.65;
+ word-break: break-word;
+}
+
+.agent-text-ai-message {
+ max-width: min(82%, 700px);
+ color: #303846;
+}
+
+.agent-text-ai-label {
+ margin-bottom: 7px;
+ color: #8a93a3;
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.agent-text-ai-content {
+ color: #303846;
+ font-size: 15px;
+ line-height: 1.85;
+ word-break: break-word;
+ white-space: pre-wrap;
+}
+
+.agent-text-actions {
+ margin-top: 10px;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.agent-text-actions [data-tooltip]::after {
+ top: calc(100% + 8px);
+ bottom: auto;
+}
+
+.agent-text-actions [data-tooltip]:hover::after,
+.agent-text-actions [data-tooltip]:focus::after,
+.agent-text-actions [data-tooltip]:focus-visible::after {
+ transform: translateX(-50%) translateY(2px);
+}
+
+.agent-text-action-btn {
+ width: 30px;
+ height: 30px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid #e5e7f0;
+ border-radius: 9px;
+ background: #fff;
+ color: #8a93a3;
+ cursor: pointer;
+ transition: border-color .18s ease, color .18s ease, transform .18s ease;
+}
+
+.agent-text-action-btn:hover {
+ border-color: var(--agent-primary-border);
+ color: var(--agent-primary-dark);
+ transform: translateY(-1px);
+}
+
+.agent-text-action-btn i,
+.agent-text-action-btn svg {
+ width: 14px;
+ height: 14px;
+}
+
+.agent-text-input-area {
+ padding: 0 clamp(12px, 1.8vw, 20px) 14px;
+ border-top: 0;
+ background: transparent;
+}
+
+.agent-text-input-shell {
+ width: 100%;
+ min-height: 66px;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 10px;
+ align-items: center;
+ padding: 8px;
+ border: 1px solid rgba(229,231,240,.96);
+ border-radius: 999px;
+ background: rgba(255,255,255,.94);
+ box-shadow: 0 14px 40px rgba(31,41,55,.06);
+ transition: border-color .18s ease, box-shadow .18s ease;
+}
+
+.agent-text-input-shell:focus-within {
+ border-color: var(--agent-primary-border);
+ box-shadow: 0 14px 40px rgba(31,41,55,.06), 0 0 0 3px rgba(82,196,26,.10);
+}
+
+.agent-text-input {
+ width: 100%;
+ height: 48px;
+ padding: 0 16px;
+ border: 0;
+ border-radius: 999px;
+ outline: 0;
+ background: transparent;
+ color: #303846;
+ font-size: 14px;
+ transition: border-color .18s ease, box-shadow .18s ease;
+}
+
+.agent-text-input:focus {
+ box-shadow: none;
+}
+
+.agent-text-send {
+ width: 58px;
+ height: 48px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 0;
+ border-radius: 16px;
+ background: var(--agent-primary-dark);
+ color: #fff;
+ cursor: pointer;
+ transition: background .18s ease, transform .18s ease;
+}
+
+.agent-text-send:hover {
+ background: var(--agent-primary-hover);
+ transform: translateY(-1px);
+}
+
+.agent-text-send:disabled {
+ cursor: not-allowed;
+ opacity: .55;
+ transform: none;
+}
+
+.agent-text-send i,
+.agent-text-send svg {
+ width: 17px;
+ height: 17px;
+}
+
+.agent-voice-panel {
+ width: 100%;
+ height: 100%;
+ min-height: min(720px, calc(100vh - 136px));
+ margin: 0 auto;
+ display: grid;
+ grid-template-rows: auto minmax(0, 1fr) auto;
+ position: relative;
+ overflow: visible;
+ border-radius: 0;
+ background:
+ radial-gradient(circle at 50% 20%, rgba(82,196,26,.075), transparent 34%),
+ var(--agent-soft);
+ animation: agentRuntimeFade .32s ease both;
+}
+
+.agent-voice-panel.recording,
+.agent-voice-panel.has-conversation {
+ animation: none;
+}
+
+@keyframes agentRuntimeFade {
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.agent-voice-topbar {
+ min-height: 52px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 8px clamp(12px, 1.8vw, 20px) 4px;
+}
+
+.agent-voice-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.agent-voice-title h2 {
+ margin: 0;
+ color: #303846;
+ font-size: 16px;
+ line-height: 1.35;
+ font-weight: 650;
+}
+
+.agent-voice-user {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ color: #4b5563;
+ font-size: 13px;
+ font-weight: 650;
+}
+
+.agent-voice-user i,
+.agent-voice-user svg {
+ width: 15px;
+ height: 15px;
+}
+
+.agent-voice-settings {
+ width: 32px;
+ height: 32px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 0;
+ border-radius: 10px;
+ background: rgba(255,255,255,.82);
+ color: #6b7280;
+ cursor: pointer;
+ transition: background .18s ease, transform .18s ease;
+}
+
+.agent-voice-settings:hover {
+ background: #fff;
+ transform: translateY(-1px);
+}
+
+.agent-voice-stage {
+ min-height: 0;
+ position: relative;
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ overflow: auto;
+ padding: clamp(28px, 6vh, 64px) clamp(12px, 1.8vw, 20px) 18px;
+ scrollbar-width: none;
+}
+
+.agent-voice-stage::-webkit-scrollbar {
+ display: none;
+}
+
+.agent-voice-idle-center {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ padding-top: clamp(10px, 2vh, 24px);
+ animation: agentRuntimeFade .36s ease both;
+}
+
+.agent-voice-logo {
+ width: 92px;
+ height: 92px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 24px;
+ background: linear-gradient(135deg, var(--agent-green) 0%, var(--agent-accent) 100%);
+ color: #fff;
+ box-shadow: 0 18px 46px rgba(82,196,26,.16);
+ animation: agentVoiceGlow 2.8s ease-in-out infinite;
+}
+
+@keyframes agentVoiceGlow {
+ 0%, 100% {
+ transform: scale(1);
+ box-shadow: 0 18px 46px rgba(82,196,26,.15);
+ }
+ 50% {
+ transform: scale(1.025);
+ box-shadow: 0 22px 58px rgba(82,196,26,.22);
+ }
+}
+
+.agent-voice-logo i,
+.agent-voice-logo svg {
+ width: 38px;
+ height: 38px;
+}
+
+.agent-voice-idle-center h3 {
+ margin: 22px 0 8px;
+ color: #303846;
+ font-size: 18px;
+ line-height: 1.35;
+ font-weight: 700;
+}
+
+.agent-voice-idle-center p {
+ max-width: 420px;
+ margin: 0;
+ color: #8a93a3;
+ font-size: 13px;
+ line-height: 1.8;
+}
+
+.agent-voice-conversation {
+ width: 100%;
+ overflow: visible;
+ margin: 0 auto;
+ padding-top: clamp(8px, 2vh, 28px);
+}
+
+.agent-voice-message-stream {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+}
+
+.agent-voice-message-row {
+ display: flex;
+ width: 100%;
+}
+
+.agent-voice-message-row.user {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.agent-voice-message-row.ai {
+ justify-content: flex-start;
+}
+
+.agent-voice-user-bubble {
+ max-width: min(78%, 640px);
+ padding: 12px 16px;
+ border-radius: 18px 18px 4px 18px;
+ background: rgba(246,255,237,.90);
+ box-shadow: 0 4px 14px rgba(82,196,26,.05);
+ color: #303846;
+ font-size: 15px;
+ line-height: 1.65;
+}
+
+.agent-voice-ai-row,
+.agent-voice-ai-message {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ max-width: min(82%, 700px);
+}
+
+.agent-voice-ai-content {
+ color: #303846;
+ font-size: 15px;
+ line-height: 1.65;
+ white-space: pre-wrap;
+}
+
+.agent-voice-ai-actions {
+ margin-top: 12px;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.agent-voice-ai-actions:not(.is-visible) {
+ display: none;
+}
+
+.agent-voice-ai-actions [data-tooltip]::after {
+ top: calc(100% + 8px);
+ bottom: auto;
+}
+
+.agent-voice-ai-actions [data-tooltip]:hover::after,
+.agent-voice-ai-actions [data-tooltip]:focus::after,
+.agent-voice-ai-actions [data-tooltip]:focus-visible::after {
+ transform: translateX(-50%) translateY(2px);
+}
+
+.agent-voice-latency {
+ position: absolute;
+ right: clamp(12px, 1.8vw, 20px);
+ top: 48%;
+ transform: translateY(-50%);
+ padding: 8px 10px;
+ border: 1px solid #e5e7f0;
+ border-radius: 999px;
+ background: rgba(255,255,255,.72);
+ color: #6b7280;
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.agent-voice-control {
+ min-height: 66px;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 10px;
+ align-items: center;
+ margin: 0 clamp(12px, 1.8vw, 20px) 14px;
+ padding: 8px;
+ border: 1px solid rgba(229,231,240,.96);
+ border-radius: 999px;
+ background: rgba(255,255,255,.94);
+ box-shadow: 0 14px 40px rgba(31,41,55,.06);
+}
+
+.agent-voice-panel.recording .agent-voice-control {
+ grid-template-columns: 72px minmax(0, 1fr) auto;
+}
+
+.agent-voice-device {
+ height: 48px;
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+ padding: 0 16px;
+ border: 0;
+ border-radius: 999px;
+ background: transparent;
+ color: #303846;
+ cursor: pointer;
+ transition: background .18s ease;
+}
+
+.agent-voice-device:hover {
+ background: #f7f8fc;
+}
+
+.agent-voice-device span {
+ overflow: hidden;
+ font-size: 14px;
+ font-weight: 650;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.agent-voice-device i,
+.agent-voice-device svg {
+ width: 18px;
+ height: 18px;
+ flex-shrink: 0;
+ color: #6b7280;
+}
+
+.agent-voice-start-btn,
+.agent-voice-hangup-btn,
+.agent-voice-mic-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 0;
+ cursor: pointer;
+ transition: transform .18s ease, filter .18s ease, background .18s ease;
+}
+
+.agent-voice-start-btn {
+ width: 58px;
+ height: 48px;
+ border-radius: 16px;
+ background: linear-gradient(135deg, var(--agent-green) 0%, var(--agent-accent) 100%);
+ color: #fff;
+ box-shadow: 0 12px 26px rgba(82,196,26,.18);
+}
+
+.agent-voice-start-btn:hover,
+.agent-voice-hangup-btn:hover,
+.agent-voice-mic-btn:hover {
+ transform: translateY(-1px);
+}
+
+.agent-voice-start-btn i,
+.agent-voice-start-btn svg,
+.agent-voice-hangup-btn i,
+.agent-voice-hangup-btn svg,
+.agent-voice-mic-btn i,
+.agent-voice-mic-btn svg {
+ width: 20px;
+ height: 20px;
+}
+
+.agent-voice-record-time {
+ color: var(--agent-green-dark);
+ font-size: 16px;
+ font-weight: 800;
+ text-align: center;
+ font-variant-numeric: tabular-nums;
+}
+
+.agent-voice-wave {
+ height: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 5px;
+ overflow: hidden;
+}
+
+.agent-voice-wave span {
+ width: 6px;
+ height: 6px;
+ border-radius: 999px;
+ background: linear-gradient(180deg, var(--agent-green), var(--agent-accent));
+ opacity: .65;
+ animation: agentVoiceDotWave 920ms ease-in-out infinite;
+ animation-delay: calc(var(--i) * -42ms);
+}
+
+.agent-voice-wave.muted span {
+ background: #cbd5e1;
+ animation-play-state: paused;
+}
+
+@keyframes agentVoiceDotWave {
+ 0%, 100% {
+ height: 6px;
+ opacity: .45;
+ transform: translateY(0);
+ }
+ 40% {
+ height: 34px;
+ opacity: 1;
+ transform: translateY(-1px);
+ }
+ 70% {
+ height: 16px;
+ opacity: .82;
+ }
+}
+
+.agent-voice-control-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.agent-voice-mic-btn {
+ width: 44px;
+ height: 44px;
+ border-radius: 16px;
+ background: #f7f8fc;
+ color: #303846;
+}
+
+.agent-voice-mic-btn.muted {
+ background: #fff1f0;
+ color: #cf1322;
+}
+
+.agent-voice-hangup-btn {
+ width: 52px;
+ height: 44px;
+ border-radius: 16px;
+ background: #dc2626;
+ color: #fff;
+}
+
+.agent-runtime-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 18px;
+}
+
+.agent-runtime-kicker {
+ display: inline-flex;
+ align-items: center;
+ height: 24px;
+ padding: 0 9px;
+ border-radius: 999px;
+ background: var(--agent-primary-soft);
+ color: var(--agent-green-dark);
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0;
+}
+
+.agent-runtime-head h2 {
+ margin: 12px 0 8px;
+ color: var(--agent-ink);
+ font-size: 22px;
+ line-height: 1.32;
+}
+
+.agent-runtime-head p {
+ max-width: 520px;
+ margin: 0;
+ color: var(--agent-muted);
+ font-size: 13px;
+ line-height: 1.7;
+}
+
+.agent-runtime-metrics {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+}
+
+.agent-runtime-metrics span {
+ display: inline-flex;
+ align-items: center;
+ height: 30px;
+ padding: 0 10px;
+ border: 1px solid var(--agent-border);
+ border-radius: 999px;
+ background: #fff;
+ color: var(--agent-ink);
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.agent-runtime-auth-card,
+.agent-runtime-keybar,
+.agent-runtime-conversation,
+.agent-runtime-callbar,
+.agent-runtime-textbar,
+.agent-runtime-note {
+ border: 1px solid var(--agent-border);
+ border-radius: 14px;
+ background: rgba(255,255,255,.86);
+ box-shadow: var(--agent-card-shadow);
+}
+
+.agent-runtime-auth-card {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: 14px;
+ padding: 18px;
+}
+
+.agent-runtime-note {
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ padding: 12px 14px;
+ color: var(--agent-muted);
+ font-size: 12px;
+ line-height: 1.7;
+}
+
+.agent-runtime-note i,
+.agent-runtime-note svg {
+ width: 16px;
+ height: 16px;
+ margin-top: 2px;
+ color: var(--agent-green-dark);
+}
+
+.agent-runtime-auth-actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.agent-runtime-keybar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 13px 15px;
+}
+
+.agent-runtime-keybar span {
+ display: block;
+ color: var(--agent-muted);
+ font-size: 11px;
+ font-weight: 700;
+}
+
+.agent-runtime-keybar strong {
+ display: block;
+ margin-top: 4px;
+ color: var(--agent-ink);
+ font-size: 13px;
+ font-weight: 750;
+}
+
+.agent-runtime-conversation {
+ flex: 1;
+ min-height: 300px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding: 18px;
+}
+
+.agent-runtime-conversation.chat {
+ min-height: 360px;
+ max-height: min(520px, calc(100vh - 360px));
+ overflow-y: auto;
+}
+
+.agent-runtime-message {
+ display: grid;
+ grid-template-columns: 36px minmax(0, 1fr);
+ gap: 12px;
+ align-items: flex-start;
+}
+
+.agent-runtime-avatar {
+ width: 36px;
+ height: 36px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 11px;
+ background: #f7f8fc;
+ color: var(--agent-muted);
+}
+
+.agent-runtime-message.ai .agent-runtime-avatar {
+ background: var(--agent-primary-soft);
+ color: var(--agent-green-dark);
+}
+
+.agent-runtime-avatar i,
+.agent-runtime-avatar svg {
+ width: 17px;
+ height: 17px;
+}
+
+.agent-runtime-message span {
+ display: block;
+ margin-bottom: 6px;
+ color: var(--agent-muted);
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.agent-runtime-message p {
+ margin: 0;
+ color: var(--agent-ink);
+ font-size: 14px;
+ line-height: 1.75;
+}
+
+.agent-runtime-message.user {
+ align-self: flex-end;
+ max-width: 82%;
+}
+
+.agent-runtime-message.user > div:last-child {
+ padding: 10px 12px;
+ border-radius: 14px;
+ background: var(--agent-primary-dark);
+ color: #fff;
+}
+
+.agent-runtime-message.user span,
+.agent-runtime-message.user p {
+ color: #fff;
+}
+
+.agent-runtime-message.ai {
+ max-width: 86%;
+}
+
+.agent-runtime-message.ai > div:last-child {
+ padding: 10px 12px;
+ border: 1px solid #edf0f6;
+ border-radius: 14px;
+ background: #fff;
+}
+
+.agent-runtime-message.pending svg {
+ animation: spin 1s linear infinite;
+}
+
+.agent-runtime-typing {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ height: 18px;
+}
+
+.agent-runtime-typing i {
+ width: 6px;
+ height: 6px;
+ border-radius: 999px;
+ background: var(--agent-green);
+ animation: agentTyping 900ms ease-in-out infinite;
+}
+
+.agent-runtime-typing i:nth-child(2) {
+ animation-delay: .12s;
+}
+
+.agent-runtime-typing i:nth-child(3) {
+ animation-delay: .24s;
+}
+
+@keyframes agentTyping {
+ 0%, 100% {
+ transform: translateY(0);
+ opacity: .45;
+ }
+ 50% {
+ transform: translateY(-4px);
+ opacity: 1;
+ }
+}
+
+.agent-runtime-chat-meta {
+ align-self: center;
+ padding: 4px 8px;
+ border-radius: 999px;
+ background: #f7f8fc;
+ color: var(--agent-muted);
+ font-size: 11px;
+ font-weight: 700;
+}
+
+.agent-runtime-callbar {
+ min-height: 74px;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 44px 54px;
+ gap: 12px;
+ align-items: center;
+ padding: 12px;
+}
+
+.agent-runtime-wave {
+ height: 42px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 0 10px;
+ border-radius: 12px;
+ background: #f7f8fc;
+ overflow: hidden;
+}
+
+.agent-runtime-wave span {
+ width: 4px;
+ height: 10px;
+ border-radius: 999px;
+ background: #c6ceda;
+ transform-origin: center;
+}
+
+.agent-runtime-wave.active span {
+ background: var(--agent-green);
+ animation: agentWave 900ms ease-in-out infinite;
+ animation-delay: calc(var(--i) * -58ms);
+}
+
+@keyframes agentWave {
+ 0%, 100% { height: 8px; opacity: .55; }
+ 45% { height: 32px; opacity: 1; }
+ 70% { height: 18px; opacity: .78; }
+}
+
+.agent-runtime-round,
+.agent-runtime-call {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 0;
+ cursor: pointer;
+ transition: transform .18s ease, background .18s ease, color .18s ease;
+}
+
+.agent-runtime-round {
+ width: 44px;
+ height: 44px;
+ border-radius: 14px;
+ background: #f7f8fc;
+ color: var(--agent-ink);
+}
+
+.agent-runtime-round.muted {
+ background: #fff1f0;
+ color: #cf1322;
+}
+
+.agent-runtime-call {
+ width: 54px;
+ height: 54px;
+ border-radius: 18px;
+ background: var(--agent-primary-dark);
+ color: #fff;
+}
+
+.agent-runtime-call.danger {
+ background: #ff4d4f;
+}
+
+.agent-runtime-round:hover,
+.agent-runtime-call:hover {
+ transform: translateY(-1px);
+}
+
+.agent-runtime-round i,
+.agent-runtime-round svg,
+.agent-runtime-call i,
+.agent-runtime-call svg {
+ width: 20px;
+ height: 20px;
+}
+
+.agent-runtime-muted-tip {
+ align-self: flex-end;
+ color: #cf1322;
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.agent-runtime-textbar {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 10px;
+ padding: 12px;
+}
+
.bailian-cube {
position: relative;
width: 112px;
@@ -9262,163 +11927,6 @@ body.agent-workbench-mode .page-container {
background: linear-gradient(145deg, #f7f8fb, #e5e9f1);
}
-.bailian-scene-modal {
- display: flex;
- flex-direction: column;
- gap: 22px;
-}
-
-.bailian-scene-modal-section {
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-
-.bailian-scene-modal-title {
- font-size: 14px;
- font-weight: 700;
- color: #111827;
-}
-
-.bailian-scene-option-grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 12px;
-}
-
-.bailian-scene-option,
-.bailian-scene-version-option {
- border: 1px solid #e5e7f0;
- border-radius: 12px;
- background: #fff;
- cursor: pointer;
- transition: border-color .18s ease, background .18s ease, box-shadow .18s ease, transform .18s ease;
-}
-
-.bailian-scene-option {
- min-height: 112px;
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 10px;
- padding: 14px;
-}
-
-.bailian-scene-option input,
-.bailian-scene-version-option input {
- display: none;
-}
-
-.bailian-scene-option:hover,
-.bailian-scene-version-option:hover {
- border-color: #cfd5e3;
- background: #fbfcff;
- transform: translateY(-1px);
-}
-
-.bailian-scene-option.selected,
-.bailian-scene-version-option.selected {
- border-color: #52c41a;
- background: #f6ffed;
- box-shadow: 0 0 0 3px rgba(82,196,26,.08);
-}
-
-.bailian-scene-option-icon {
- width: 36px;
- height: 36px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- border-radius: 10px;
- color: #389e0d;
- background: #f6ffed;
- border: 1px solid #d9f7be;
-}
-
-.bailian-scene-option.selected .bailian-scene-option-icon {
- color: #fff;
- background: #52c41a;
- border-color: #52c41a;
-}
-
-.bailian-scene-option-icon i {
- width: 18px;
- height: 18px;
-}
-
-.bailian-scene-option-main {
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 5px;
-}
-
-.bailian-scene-option-main strong,
-.bailian-scene-version-option strong {
- font-size: 14px;
- font-weight: 700;
- color: #111827;
-}
-
-.bailian-scene-option-main small,
-.bailian-scene-version-option span {
- font-size: 12px;
- line-height: 1.55;
- color: #7b8494;
-}
-
-.bailian-scene-version-grid,
-.bailian-scene-language-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 12px;
-}
-
-.bailian-scene-version-option,
-.bailian-scene-language-option {
- min-height: 96px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- padding: 16px;
-}
-
-.bailian-scene-language-option {
- border: 1px solid #e5e7f0;
- border-radius: 12px;
- background: #fff;
- cursor: pointer;
- transition: border-color .18s ease, background .18s ease, box-shadow .18s ease, transform .18s ease;
-}
-
-.bailian-scene-language-option input {
- display: none;
-}
-
-.bailian-scene-language-option:hover {
- border-color: #cfd5e3;
- background: #fbfcff;
- transform: translateY(-1px);
-}
-
-.bailian-scene-language-option.selected {
- border-color: #52c41a;
- background: #f6ffed;
- box-shadow: 0 0 0 3px rgba(82,196,26,.08);
-}
-
-.bailian-scene-language-option strong {
- font-size: 14px;
- font-weight: 700;
- color: #111827;
-}
-
-.bailian-scene-language-option span {
- font-size: 12px;
- line-height: 1.55;
- color: #7b8494;
-}
-
.bailian-required {
color: #ff4d4f;
font-size: 13px;
@@ -9476,9 +11984,30 @@ body.agent-workbench-mode .page-container {
overflow: visible;
}
+ .bailian-config-footer {
+ padding: 14px 18px 18px;
+ box-shadow: none;
+ }
+
.bailian-preview-pane {
min-height: 420px;
}
+
+ .bailian-preview-pane.is-runtime {
+ padding: 24px;
+ }
+
+ .agent-runtime-head,
+ .agent-runtime-keybar,
+ .agent-text-topbar,
+ .agent-voice-topbar {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .agent-runtime-metrics {
+ justify-content: flex-start;
+ }
}
@media (max-width: 640px) {
@@ -9500,12 +12029,88 @@ body.agent-workbench-mode .page-container {
}
.bailian-step-tabs {
- height: auto;
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ height: 64px;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .agent-runtime-callbar,
+ .agent-runtime-textbar,
+ .agent-text-input-shell {
+ grid-template-columns: 1fr;
+ }
+
+ .agent-runtime-round,
+ .agent-runtime-call,
+ .agent-text-send {
+ width: 100%;
+ }
+
+ .agent-text-panel {
+ min-height: 640px;
+ border-radius: 12px;
+ }
+
+ .agent-voice-panel {
+ min-height: 640px;
+ border-radius: 12px;
+ }
+
+ .agent-text-message-area {
+ padding: 24px 14px;
+ }
+
+ .agent-text-user-bubble,
+ .agent-text-ai-message,
+ .agent-voice-user-bubble,
+ .agent-voice-ai-row,
+ .agent-voice-ai-message {
+ max-width: 88%;
+ }
+
+ .agent-voice-stage {
+ flex-direction: column;
+ padding: 22px 16px 12px;
+ }
+
+ .agent-voice-latency {
+ position: static;
+ align-self: center;
+ transform: none;
+ margin-top: 14px;
+ }
+
+ .agent-voice-panel.idle .agent-voice-control,
+ .agent-voice-panel.recording .agent-voice-control {
+ grid-template-columns: 1fr;
+ border-radius: 24px;
+ margin: 0 14px 12px;
+ padding: 8px;
+ }
+
+ .agent-voice-control-actions {
+ justify-content: stretch;
+ }
+
+ .agent-voice-mic-btn,
+ .agent-voice-hangup-btn,
+ .agent-voice-start-btn {
+ width: 100%;
}
.bailian-step-tab {
- min-height: 58px;
+ min-height: 64px;
+ gap: 4px;
+ padding: 0 4px;
+ }
+
+ .bailian-step-number {
+ width: 20px;
+ height: 20px;
+ font-size: 11px;
+ }
+
+ .bailian-step-title {
+ font-size: 12px;
}
.bailian-config-scroll {
@@ -9518,11 +12123,46 @@ body.agent-workbench-mode .page-container {
.bailian-variable-grid,
.bailian-mode-grid,
- .bailian-mini-grid,
- .bailian-budget-grid {
+ .bailian-model-mode-grid,
+ .bailian-mini-grid {
grid-template-columns: 1fr;
}
+ .bailian-variable-manager,
+ .bailian-variable-detail-grid,
+ .bailian-variable-form {
+ grid-template-columns: 1fr;
+ }
+
+ .bailian-variable-simple-head {
+ display: none;
+ }
+
+ .bailian-variable-simple-row {
+ grid-template-columns: 1fr 36px;
+ }
+
+ .bailian-variable-simple-row .bailian-input:first-child {
+ grid-column: 1 / -1;
+ }
+
+ .bailian-variable-list-pane,
+ .bailian-variable-detail-pane {
+ min-height: auto;
+ }
+
+ .bailian-variable-list {
+ max-height: none;
+ }
+
+ .bailian-variable-actions {
+ justify-content: stretch;
+ }
+
+ .bailian-variable-actions .bailian-action-btn {
+ flex: 1;
+ }
+
.bailian-hotword-box,
.bailian-switch-row,
.bailian-section-inline-action,
@@ -9534,10 +12174,61 @@ body.agent-workbench-mode .page-container {
.bailian-hotword-box,
.bailian-mcp-empty,
+ .bailian-model-manager-toolbar,
+ .bailian-kb-picker-toolbar,
.bailian-section-inline-action {
flex-direction: column;
}
+ .bailian-card-section-head {
+ align-items: flex-start;
+ flex-wrap: wrap;
+ }
+
+ .bailian-card-section-actions {
+ justify-content: flex-start;
+ }
+
+ .bailian-search-field {
+ min-width: 0;
+ width: 100%;
+ }
+
+ .bailian-kb-picker-toolbar .btn {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .bailian-kb-picker-card {
+ grid-template-columns: 18px minmax(0, 1fr);
+ }
+
+ .bailian-kb-picker-status {
+ grid-column: 2;
+ justify-self: flex-start;
+ }
+
+ .bailian-asr-form-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .bailian-voice-mode-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .bailian-asr-model-card,
+ .bailian-asr-model-actions,
+ .bailian-asr-form-actions {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .bailian-asr-model-actions .bailian-action-btn,
+ .bailian-asr-form-actions .bailian-action-btn,
+ .bailian-asr-form-actions .btn {
+ width: 100%;
+ }
+
.bailian-list-card,
.bailian-kb-card {
flex-wrap: wrap;
@@ -9548,12 +12239,6 @@ body.agent-workbench-mode .page-container {
min-height: 300px;
}
- .bailian-scene-option-grid,
- .bailian-scene-version-grid,
- .bailian-scene-language-grid {
- grid-template-columns: 1fr;
- }
-
.bailian-preview-pane {
padding: 32px 18px;
}
@@ -9789,6 +12474,32 @@ body.agent-workbench-mode .page-container {
previewVoice: (provider, voice, text) => new Promise(r => setTimeout(r, 600)),
testRAGQuery: (agentId, query) => new Promise(r => setTimeout(r, 1000)),
createKnowledgeBase: (data) => new Promise(r => setTimeout(r, 600)),
+ listASRModels: (params) => Promise.resolve({ success: true, data: window.__asrModelRegistry || [], params }),
+ createASRModel: (data) => new Promise(r => setTimeout(() => r({ success: true, data }), 300)),
+ updateASRModel: (id, data) => new Promise(r => setTimeout(() => r({ success: true, data: { id, ...data } }), 300)),
+ deleteASRModel: (id) => new Promise(r => setTimeout(() => r({ success: true, data: { id } }), 300)),
+ validateASRModel: (id, data) => new Promise(r => setTimeout(() => r({ success: true, data: { id, ...data } }), 600)),
+ testASRModel: (id, data) => new Promise(r => setTimeout(() => r({ success: true, data: { id, ...data } }), 500)),
+ setASRModelEnabled: (id, enabled) => new Promise(r => setTimeout(() => r({ success: true, data: { id, enabled } }), 250)),
+ listTTSModels: (params) => Promise.resolve({ success: true, data: window.__ttsModelRegistry || [], params }),
+ createTTSModel: (data) => new Promise(r => setTimeout(() => r({ success: true, data }), 300)),
+ updateTTSModel: (id, data) => new Promise(r => setTimeout(() => r({ success: true, data: { id, ...data } }), 300)),
+ deleteTTSModel: (id) => new Promise(r => setTimeout(() => r({ success: true, data: { id } }), 300)),
+ validateTTSModel: (id, data) => new Promise(r => setTimeout(() => r({ success: true, data: { id, ...data } }), 700)),
+ setTTSModelEnabled: (id, enabled) => new Promise(r => setTimeout(() => r({ success: true, data: { id, enabled } }), 250)),
+ listTextModels: (params) => Promise.resolve({ success: true, data: window.__textModelRegistry || [], params }),
+ createTextModel: (data) => new Promise(r => setTimeout(() => r({ success: true, data }), 300)),
+ updateTextModel: (id, data) => new Promise(r => setTimeout(() => r({ success: true, data: { id, ...data } }), 300)),
+ deleteTextModel: (id) => new Promise(r => setTimeout(() => r({ success: true, data: { id } }), 300)),
+ validateTextModel: (id, data) => new Promise(r => setTimeout(() => r({ success: true, data: { id, ...data } }), 700)),
+ setTextModelEnabled: (id, enabled) => new Promise(r => setTimeout(() => r({ success: true, data: { id, enabled } }), 250)),
+ listVariables: (agentId, params) => Promise.resolve({ success: true, data: window.__agentVariables || [], params: { agentId, ...params } }),
+ createVariable: (agentId, data) => new Promise(r => setTimeout(() => r({ success: true, data: { agentId, ...data } }), 300)),
+ updateVariable: (agentId, variableId, data) => new Promise(r => setTimeout(() => r({ success: true, data: { agentId, variableId, ...data } }), 300)),
+ deleteVariable: (agentId, variableId) => new Promise(r => setTimeout(() => r({ success: true, data: { agentId, variableId } }), 250)),
+ injectVariableToPrompt: (agentId, variableId, data) => new Promise(r => setTimeout(() => r({ success: true, data: { agentId, variableId, ...data } }), 250)),
+ getVariableState: (agentId, sessionId, params) => Promise.resolve({ success: true, data: {}, params: { agentId, sessionId, ...params } }),
+ updateVariableState: (agentId, sessionId, data) => new Promise(r => setTimeout(() => r({ success: true, data: { agentId, sessionId, ...data } }), 300)),
getHotwordGroups: (params) => Promise.resolve({ success: true, data: window.__agentHotwordGroups || [], params }),
createHotwordGroup: (data) => new Promise(r => setTimeout(() => r({ success: true, data }), 500)),
updateHotwordGroup: (id, data) => new Promise(r => setTimeout(() => r({ success: true, data: { id, ...data } }), 500)),
@@ -10121,10 +12832,16 @@ body.agent-workbench-mode .page-container {
overlay.className = 'modal-overlay';
overlay.innerHTML = `
`;
document.body.appendChild(overlay);
-
+ document.body.classList.add('modal-open');
+
const close = () => {
overlay.classList.remove('open');
- setTimeout(() => overlay.remove(), 200);
+ setTimeout(() => {
+ overlay.remove();
+ if (!document.querySelector('.modal-overlay')) {
+ document.body.classList.remove('modal-open');
+ }
+ }, 200);
};
overlay.querySelector('.modal-close').addEventListener('click', close);
@@ -10421,7 +13138,7 @@ body.agent-workbench-mode .page-container {
window.companies = loadCompanies();
const companies = window.companies;
-
+
// 当前选中的公司,默认为"全部公司"
window.currentCompany = 'all';
@@ -11675,7 +14392,7 @@ body.agent-workbench-mode .page-container {
}
});
}
-
+
try {
localStorage.removeItem('talkingq_theme');
document.documentElement.removeAttribute('data-theme');
@@ -13113,7 +15830,6 @@ const AGENT_CONFIG_STEPS = [
{ id: 'voice', index: 1, title: '语音交互' },
{ id: 'understand', index: 2, title: '理解和生成' },
{ id: 'skills', index: 3, title: '技能' },
- { id: 'advanced', index: 4, title: '高级设置' },
];
const AGENT_CONFIG_DEFAULT_APPS = [
@@ -13123,100 +15839,67 @@ const AGENT_CONFIG_DEFAULT_APPS = [
{ id: 'app-004', type: 'multimodal', name: '数学小课堂', status: 'published' },
];
-const AGENT_SCENE_TEMPLATES = [
- { id: 'ai-audio-glasses', name: 'AI音频眼镜', icon: 'headphones', desc: '随身携带的语音助手' },
- { id: 'ar-glasses', name: 'AR眼镜', icon: 'glasses', desc: '具备屏幕显示能力' },
- { id: 'desktop-robot', name: '桌面机器人', icon: 'bot', desc: '实时语音对话,日常情感陪伴' },
- { id: 'smart-home', name: '智能家居', icon: 'home', desc: '语音控制家中设备' },
- { id: 'toy', name: '儿童玩具', icon: 'star', desc: '陪伴孩子聊天、放音乐、讲故事' },
- { id: 'story-player', name: '儿童故事机', icon: 'book-open', desc: '给孩子讲故事、放儿歌' },
- { id: 'smart-speaker', name: '智能音箱', icon: 'volume-2', desc: '语音控制播放音乐' },
- { id: 'mobile-app', name: '手机应用', icon: 'smartphone', desc: '实时语音互动应用' },
+const AGENT_INTERACTION_LANGUAGES = [
+ { id: 'zh', name: '中文' },
+ { id: 'en', name: '英语' },
+ { id: 'fr', name: '法语' },
+ { id: 'de', name: '德语' },
+ { id: 'es', name: '西班牙语' },
+ { id: 'it', name: '意大利语' },
+ { id: 'ru', name: '俄语' },
+ { id: 'pt', name: '葡萄牙语' },
+ { id: 'ko', name: '韩语' },
+ { id: 'ja', name: '日语' },
+ { id: 'th', name: '泰语' },
+ { id: 'vi', name: '越南语' },
+ { id: 'id', name: '印度尼西亚语' },
+ { id: 'ms', name: '马来语' },
+ { id: 'fil', name: '菲律宾语' },
+ { id: 'yue', name: '粤语' },
+ { id: 'ar', name: '阿拉伯语' },
+ { id: 'hi', name: '印地语' },
+ { id: 'tr', name: '土耳其语' },
+ { id: 'uk', name: '乌克兰语' },
+ { id: 'cs', name: '捷克语' },
+ { id: 'da', name: '丹麦语' },
+ { id: 'fi', name: '芬兰语' },
+ { id: 'is', name: '冰岛语' },
+ { id: 'no', name: '挪威语' },
+ { id: 'pl', name: '波兰语' },
+ { id: 'nl', name: '荷兰语' },
+ { id: 'sv', name: '瑞典语' },
];
-const AGENT_SCENE_VERSIONS = [
- {
- id: 'full',
- name: '全能版',
- desc: '支持意图识别、工具调用、联网搜索、多场景 Agent,适合完整智能体体验。',
- },
- {
- id: 'lite',
- name: '轻量版',
- desc: '更快速、更低价的语音闲聊,不启用复杂工具调用和 Agent 编排。',
- },
-];
+const SPEECH_MODEL_STATUS_META = {
+ validating: { label: '连接中', className: 'pending' },
+ available: { label: '可用', className: 'success' },
+ failed: { label: '连接失败', className: 'danger' },
+ disabled: { label: '已禁用', className: 'disabled' },
+};
-const AGENT_SCENE_LANGUAGES = [
- { id: 'zh', name: '中文', desc: '适合中文普通话和中文儿童内容场景' },
- { id: 'en', name: '英文', desc: '适合英语对话、英语学习和双语启蒙场景' },
+const ASR_MODEL_STATUS_META = SPEECH_MODEL_STATUS_META;
+const TTS_MODEL_STATUS_META = SPEECH_MODEL_STATUS_META;
+const TEXT_MODEL_STATUS_META = SPEECH_MODEL_STATUS_META;
+
+const MCP_SERVICE_STATUS_META = {
+ pending: { label: '待连接', className: 'pending' },
+ validating: { label: '连接中', className: 'pending' },
+ available: { label: '可用', className: 'success' },
+ failed: { label: '连接失败', className: 'danger' },
+ disabled: { label: '已禁用', className: 'disabled' },
+};
+
+const TTS_AUDIO_FORMAT_OPTIONS = [
+ { id: 'mp3', name: 'MP3' },
+ { id: 'wav', name: 'WAV' },
+ { id: 'pcm', name: 'PCM' },
+ { id: 'opus', name: 'OPUS' },
+ { id: 'flac', name: 'FLAC' },
];
const AGENT_CONFIG_MOCK = {
voice: {
- languageOptions: [
- { id: 'zh', label: '中文' },
- { id: 'en', label: '英文' },
- ],
- asrModels: [
- {
- id: 'fun-asr',
- icon: 'audio-lines',
- label: 'Fun-ASR实时语音识别',
- desc: '中英文自由切换,多地区方言覆盖,适应多样复杂环境。',
- },
- {
- id: 'qwen3-asr',
- icon: 'audio-lines',
- label: '通义千问3-ASR-Flash-Realtime',
- desc: '高精度语音识别,中英日韩德法俄意西葡阿和多种方言。',
- },
- {
- id: 'paraformer',
- icon: 'audio-lines',
- label: 'Paraformer实时语音识别',
- desc: '新一代非自回归端到端模型,支持中英日韩德法俄等语言。',
- },
- {
- id: 'multimodal-lite-asr',
- icon: 'audio-lines',
- label: '多模态交互轻量版语音识别',
- desc: '超低价格,预算友好,支持中英等语言。',
- },
- ],
- ttsGroups: [
- {
- id: 'cosyvoice',
- title: 'CosyVoice-实时语音合成',
- models: [
- { id: 'cosyvoice-v3-flash', label: 'CosyVoice-v3-Flash大模型', desc: '独家音色,性价比高,支持情感控制。' },
- { id: 'cosyvoice-v3-plus', label: 'CosyVoice-v3-Plus大模型', desc: '情感表达最佳,支持声音复刻。' },
- { id: 'cosyvoice-v35-plus', label: 'CosyVoice-v3.5-Plus大模型', desc: '声音设计/复刻最佳,多种方言和外语。' },
- { id: 'cosyvoice-v35-flash', label: 'CosyVoice-v3.5-Flash大模型', desc: '声音设计/复刻性价比高,多种方言和外语。' },
- { id: 'cosyvoice-v2', label: 'CosyVoice-v2大模型', desc: '音色丰富,70+音色可选,一句话声音复刻。' },
- ],
- },
- {
- id: 'qwen-tts',
- title: '通义千问3-实时语音合成',
- models: [
- { id: 'qwen3-tts-flash', label: '通义千问3-TTS-Flash-Realtime', desc: '多语言,拥有17种高表现力的拟人音色。' },
- { id: 'qwen3-tts-instruct', label: '通义千问3-TTS-Instruct-Flash-Realtime', desc: '支持指令控制,调整音调、语速、情感、特点。' },
- { id: 'qwen3-tts-design', label: '通义千问3-TTS-声音设计', desc: '通过文本描述生成定制化音色,支持多维度定义。' },
- { id: 'qwen3-tts-clone', label: '通义千问3-TTS-声音复刻', desc: '10-20秒音频即可生成效果最佳的定制音色。' },
- ],
- },
- {
- id: 'others',
- title: '其他语音合成模型',
- models: [
- { id: 'sambert-tts', label: 'Sambert-实时语音合成', desc: '音色选择多,多语言,合成速度快。' },
- { id: 'lite-tts', label: '多模态交互轻量版语音合成', desc: '预算友好,支持多语种和方言。' },
- ],
- },
- ],
defaultHotwords: ['TalkingQ', '小Q', '白雪公主', '睡前故事', '恐龙百科'],
- correctionHint: '通过接口上传词表,对语音识别结果进行实时干预。',
},
understand: {
intentModel: {
@@ -13224,15 +15907,6 @@ const AGENT_CONFIG_MOCK = {
label: '意图识别',
desc: '关闭将不再触发调用工具/Agent 等基于语义理解的交互功能。',
},
- textModels: [
- { id: 'recommended-fast', group: '推荐模型', label: '多模态交互专有模型-高速版', desc: '对话速度更快,适合通用场景、闲聊陪伴场景。' },
- { id: 'recommended-tool', group: '推荐模型', label: '多模态交互专有模型', desc: '指令遵循能力更强,适合工作学习场景。' },
- { id: 'recommended-style', group: '推荐模型', label: '多模态交互专有模型-风格化版', desc: '风格化能力更强,适合角色扮演等场景。' },
- { id: 'qwen', group: '更多模型', label: '多千问', desc: '通用能力均衡,适合大多数任务。' },
- { id: 'deepseek', group: '更多模型', label: 'DeepSeek', desc: '推理和代码表现更强。' },
- { id: 'chatgpt', group: '更多模型', label: 'ChatGPT', desc: '多场景通用对话能力。' },
- { id: 'doubao', group: '更多模型', label: '豆包', desc: '适合中文对话和内容生成。' },
- ],
prompt: `# 角色
你是小Q,一个专为 3-10 岁儿童设计的 AI 玩具智能体。
@@ -13253,53 +15927,156 @@ const AGENT_CONFIG_MOCK = {
知识库优先级:高
\`\`\``,
variables: [
- { name: 'child_age', desc: '儿童年龄' },
- { name: 'device_mode', desc: '设备当前模式' },
- { name: 'ambient_noise', desc: '环境噪音等级' },
- { name: 'weather', desc: '当前位置天气' },
+ { id: 'var-empty-1', name: '', desc: '' },
],
knowledgeBases: [
- { name: '儿童科学百科', desc: '动物、太空、自然科学问答素材', weight: '0.72' },
- { name: '故事素材库 V2', desc: '睡前故事、角色设定、儿童互动剧情', weight: '0.68' },
+ { id: 'kb-001', name: '儿童科学百科', desc: '动物、太空、自然科学问答素材', weight: '0.72' },
+ { id: 'kb-002', name: '故事素材库 V2', desc: '睡前故事、角色设定、儿童互动剧情', weight: '0.68' },
],
},
skills: {
- commands: [
- { icon: 'phone-call', name: '打电话', count: 6, desc: '拨打联系人电话、语音拨号等电话相关控制' },
- { icon: 'alarm-clock', name: '定闹钟', count: 1, desc: '创建、关闭、修改闹钟' },
- { icon: 'volume-2', name: '音量设置', count: 7, desc: '调节媒体音量、铃声音量、静音等' },
- { icon: 'bluetooth', name: '蓝牙', count: 4, desc: '打开/关闭蓝牙、连接蓝牙设备' },
- { icon: 'toggle-right', name: '应用开关', count: 10, desc: '打开/关闭指定 App 或系统功能' },
- { icon: 'cpu', name: '设备控制', count: 5, desc: '系统级设备控制能力(当前已选中)' },
- { icon: 'music', name: '音乐播放', count: 5, desc: '播放音乐、暂停、下一首等' },
- { icon: 'circle-play', name: '多媒体控制', count: 6, desc: '控制媒体播放状态与音频输出' },
- { icon: 'camera', name: '拍照录像', count: 6, desc: '打开相机、拍照、录像' },
- { icon: 'mic', name: '录音', count: 2, desc: '开始/停止录音' },
- { icon: 'book-open', name: '朗读文字', count: 1, desc: '语音朗读输入文本' },
- { icon: 'message-circle', name: '发短信', count: 3, desc: '发送短信消息' },
- { icon: 'sun', name: '亮度设置', count: 5, desc: '调节屏幕亮度' },
- { icon: 'sun-medium', name: '色温设置', count: 3, desc: '调节屏幕色温/护眼模式' },
- { icon: 'video', name: '视频播放', count: 1, desc: '打开并播放视频' },
- { icon: 'map-pinned', name: '打开地图', count: 1, desc: '打开地图导航类应用' },
- { icon: 'monitor', name: '屏幕控制', count: 4, desc: '锁屏、亮屏、熄屏、旋转等屏幕操作' },
- ],
- plugins: [
- { icon: 'cloud-sun', name: '天气查询', desc: '查询实时天气、空气质量和未来预报', status: '系统执行' },
- { icon: 'calendar', name: '万年历', desc: '查询日期、农历、节假日和宜忌信息', status: '系统执行' },
- { icon: 'newspaper', name: '新闻资讯', desc: '获取最新新闻资讯和摘要播报', status: '系统执行' },
- { icon: 'navigation', name: '路线规划', desc: '规划出行路线、距离和预计耗时', status: '系统执行' },
- { icon: 'map', name: '本地生活', desc: '查询周边商户、生活服务和地点信息', status: '系统执行' },
- ],
- bailianApps: [
- { name: '故事生成器', desc: '基于主题生成短故事' },
- { name: '儿童内容安全', desc: '对回答内容进行安全审查' },
- ],
- thirdPartyApps: [
- { name: '音频内容库', desc: '接入第三方儿歌和故事资源' },
- ],
+ commands: [],
+ plugins: [],
},
};
+const AGENT_KNOWLEDGE_BASE_CATALOG = [
+ { id: 'kb-001', name: '儿童科学百科', description: '包含各类儿童科学知识的百科全书', docs: 2840, size: '128MB', typeName: '文档搜索', status: 'ready', tags: ['儿童', '科学'] },
+ { id: 'kb-002', name: '故事素材库 V2', description: '精选各类儿童故事素材', docs: 5200, size: '340MB', typeName: '文档搜索', status: 'ready', tags: ['故事', '儿童'] },
+ { id: 'kb-003', name: '小学数学知识点', description: '小学数学知识体系整理', docs: 1560, size: '86MB', typeName: '文档搜索', status: 'processing', tags: ['数学', '教育'] },
+ { id: 'kb-004', name: '英语启蒙词典', description: '儿童英语启蒙词汇', docs: 3200, size: '210MB', typeName: '图片问答', status: 'ready', tags: ['英语', '启蒙'] },
+ { id: 'kb-005', name: '唐诗宋词精选', description: '经典唐诗宋词收录', docs: 890, size: '45MB', typeName: '文档搜索', status: 'ready', tags: ['诗词', '文学'] },
+ { id: 'kb-006', name: '历史人物故事', description: '历史名人故事集', docs: 1200, size: '78MB', typeName: '音视频搜索', status: 'error', tags: ['历史', '故事'] },
+ { id: 'kb-007', name: '产品参数数据库', description: '公司产品技术参数', docs: 450, size: '32MB', typeName: '数据查询', status: 'ready', tags: ['产品', '技术'] },
+ { id: 'kb-008', name: '音乐教育资源库', description: '儿童音乐教育资源', docs: 560, size: '65MB', typeName: '音视频搜索', status: 'uploading', tags: ['音乐', '教育'] },
+];
+
+function ensureAsrModelRegistry() {
+ if (!Array.isArray(window.__asrModelRegistry)) {
+ window.__asrModelRegistry = [];
+ }
+ window.__asrModelRegistry.forEach(normalizeSpeechModelStatus);
+ return window.__asrModelRegistry;
+}
+
+function ensureTtsModelRegistry() {
+ if (!Array.isArray(window.__ttsModelRegistry)) {
+ window.__ttsModelRegistry = [];
+ }
+ window.__ttsModelRegistry.forEach(normalizeSpeechModelStatus);
+ return window.__ttsModelRegistry;
+}
+
+function ensureTextModelRegistry() {
+ if (!Array.isArray(window.__textModelRegistry)) {
+ window.__textModelRegistry = [];
+ }
+ window.__textModelRegistry.forEach(normalizeSpeechModelStatus);
+ return window.__textModelRegistry;
+}
+
+function ensureCommandLibrary() {
+ if (!Array.isArray(window.__agentCommandLibrary)) {
+ window.__agentCommandLibrary = [];
+ }
+ return window.__agentCommandLibrary;
+}
+
+function ensurePluginLibrary() {
+ if (!Array.isArray(window.__agentPluginLibrary)) {
+ window.__agentPluginLibrary = [];
+ }
+ return window.__agentPluginLibrary;
+}
+
+function ensureMcpServiceLibrary() {
+ if (!Array.isArray(window.__agentMcpServiceLibrary)) {
+ window.__agentMcpServiceLibrary = [];
+ }
+ return window.__agentMcpServiceLibrary;
+}
+
+function ensureAgentConfigAutosaves() {
+ if (!window.__agentConfigAutosaves || typeof window.__agentConfigAutosaves !== 'object') {
+ window.__agentConfigAutosaves = {};
+ }
+ return window.__agentConfigAutosaves;
+}
+
+function ensureAgentRunTimers() {
+ if (!window.__agentRunTimers || typeof window.__agentRunTimers !== 'object') {
+ window.__agentRunTimers = {};
+ }
+ return window.__agentRunTimers;
+}
+
+function normalizeSpeechModelStatus(model) {
+ if (!model) return model;
+ if (model.status === 'connected') model.status = 'available';
+ if (model.status === 'unconnected') {
+ model.status = 'failed';
+ model.errorMessage = model.errorMessage || '尚未完成连接校验';
+ }
+ if (model.enabled === false && model.status === 'available') {
+ model.status = 'disabled';
+ }
+ return model;
+}
+
+function getNowText() {
+ return new Date().toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: false,
+ }).replace(/\//g, '-');
+}
+
+function getTimeOnly(value) {
+ const text = String(value || '');
+ const match = text.match(/(\d{2}:\d{2}:\d{2})/);
+ return match ? match[1] : text;
+}
+
+function getAvailableAsrModels() {
+ return ensureAsrModelRegistry()
+ .filter(model => model.enabled !== false && model.status === 'available')
+ .map(model => ({
+ id: model.id,
+ label: model.name,
+ desc: `${model.provider} · ${model.baseUrl}`,
+ provider: model.provider,
+ }));
+}
+
+function getAvailableTtsModels() {
+ return ensureTtsModelRegistry()
+ .filter(model => model.enabled !== false && model.status === 'available')
+ .map(model => ({
+ id: model.id,
+ label: model.name,
+ desc: `${model.provider} · ${getLanguageTypeName(model.languageType)} · ${getTtsVoiceSummary(model)} · ${String(model.audioFormat || '').toUpperCase() || '默认格式'}`,
+ provider: model.provider,
+ audioFormat: model.audioFormat,
+ voiceId: model.voiceId,
+ }));
+}
+
+function getAvailableTextModels() {
+ return ensureTextModelRegistry()
+ .filter(model => model.enabled !== false && model.status === 'available')
+ .map(model => ({
+ id: model.id,
+ label: model.name,
+ desc: `${model.provider} · ${model.modelCode} · 上下文 ${model.contextWindow || '-'} tokens`,
+ provider: model.provider,
+ modelCode: model.modelCode,
+ }));
+}
+
registerPage('agent-config', function(container) {
if (typeof window.__ensureAgentHotwordGroups === 'function') {
window.__ensureAgentHotwordGroups();
@@ -13331,21 +16108,22 @@ registerPage('agent-config', function(container) {
const apps = window.__agentManagedApps || AGENT_CONFIG_DEFAULT_APPS;
const selectedApp = apps.find(app => app.id === window.__selectedAppId) || apps[0] || AGENT_CONFIG_DEFAULT_APPS[0];
const firstHotwordGroup = window.__agentHotwordGroups?.[0]?.id || '';
+ const availableAsrModels = getAvailableAsrModels();
+ const availableTtsModels = getAvailableTtsModels();
+ const availableTextModels = getAvailableTextModels();
const state = {
app: selectedApp,
activeStep: 'voice',
preview: 'empty',
- selectedSceneId: window.__selectedSceneTemplateId || 'toy',
- selectedSceneVersion: window.__selectedSceneVersion || 'full',
- selectedSceneLanguage: window.__selectedSceneLanguage || 'zh',
+ saveStatus: 'saved',
+ lastSavedAt: '',
+ selectedInteractionLanguage: window.__selectedInteractionLanguage || 'zh',
voiceEnabled: true,
asrEnabled: true,
ttsEnabled: true,
- selectedAsrModelId: AGENT_CONFIG_MOCK.voice.asrModels[0]?.id || '',
- selectedTtsGroupId: AGENT_CONFIG_MOCK.voice.ttsGroups[0]?.id || '',
- selectedTtsModelId: AGENT_CONFIG_MOCK.voice.ttsGroups[0]?.models[0]?.id || '',
+ selectedAsrModelId: availableAsrModels[0]?.id || '',
+ selectedTtsModelId: availableTtsModels[0]?.id || '',
selectedHotwordGroupId: firstHotwordGroup,
- realtimeCorrectionEnabled: true,
ttsFilterEnabled: false,
ttsFilterPairs: [
{ enabled: true, left: '/*', right: '*/' },
@@ -13357,19 +16135,40 @@ registerPage('agent-config', function(container) {
interruptSecondaryEnabled: true,
interruptMode: 'full',
semanticStopEnabled: true,
- edgeAlgorithmExpanded: true,
- selectedTextModelMode: 'recommended',
- selectedTextModelId: AGENT_CONFIG_MOCK.understand.textModels[0]?.id || '',
+ selectedTextModelId: availableTextModels[0]?.id || '',
intentEnabled: true,
dialogueContinuationEnabled: true,
knowledgeSearchEnabled: true,
- searchMode: 'fast',
longTermMemoryEnabled: false,
+ prompt: AGENT_CONFIG_MOCK.understand.prompt,
knowledgeBases: AGENT_CONFIG_MOCK.understand.knowledgeBases.map(item => ({ ...item })),
- customCommands: [],
- customPlugins: [],
+ variables: AGENT_CONFIG_MOCK.understand.variables.map(item => ({ ...item })),
+ selectedCommandIds: [],
+ selectedPluginIds: [],
+ selectedMcpServiceIds: [],
+ runSession: {
+ apiKey: '',
+ apiKeyConfirmed: false,
+ apiKeyName: '',
+ isRecording: false,
+ micMuted: false,
+ elapsedSeconds: 0,
+ latencyMs: null,
+ transcript: '',
+ aiResponse: '',
+ textInput: '',
+ textResponse: '',
+ textPending: false,
+ chatMessages: [],
+ startedAt: 0,
+ },
};
+ function renderAfterConfigChange() {
+ scheduleAgentConfigAutoSave(state);
+ render();
+ }
+
function render() {
const scrollArea = container.querySelector('.bailian-config-scroll');
const previousScrollTop = scrollArea?.scrollTop || 0;
@@ -13378,8 +16177,9 @@ registerPage('agent-config', function(container) {
}
container.innerHTML = renderAgentWorkbench(state);
renderAgentIcons(container);
- bindWorkbenchEvents(container, state, render);
- bindPromptCounter(container);
+ bindWorkbenchEvents(container, state, renderAfterConfigChange);
+ bindPromptCounter(container, state);
+ bindRuntimePreviewEvents(container, state, () => updatePreviewOnly(container, state));
requestAnimationFrame(() => {
const nextScrollArea = container.querySelector('.bailian-config-scroll');
if (nextScrollArea) {
@@ -13403,22 +16203,29 @@ registerPage('agent-config', function(container) {
function renderAgentWorkbench(state) {
return `
- ${renderAgentHeader(state.app)}
+ ${renderAgentHeader(state)}
- ${renderPreviewPane(state.preview)}
+ ${renderPreviewPane(state)}
`;
}
-function renderAgentHeader(app) {
+function renderAgentHeader(state) {
+ const app = state.app;
const status = getAgentStatus(app.status);
+ const saveText = state.saveStatus === 'saving'
+ ? '自动保存中'
+ : state.lastSavedAt ? `已自动保存 ${getTimeOnly(state.lastSavedAt)}` : '等待自动保存';
return `
@@ -13474,28 +16283,37 @@ function renderStepContent(stepId, state) {
voice: renderVoiceStep,
understand: renderUnderstandStep,
skills: renderSkillsStep,
- advanced: renderAdvancedStep,
};
return (renderers[stepId] || renderVoiceStep)(state);
}
function renderVoiceStep(state) {
const data = AGENT_CONFIG_MOCK.voice;
- const scene = getSelectedScene(state);
- const version = getSelectedSceneVersion(state);
- const language = getSelectedSceneLanguage(state);
const hotwordGroups = window.__agentHotwordGroups || [];
+ const asrModels = getAvailableAsrModels();
+ const ttsModels = getAvailableTtsModels();
+ if (state.selectedAsrModelId && !asrModels.some(model => model.id === state.selectedAsrModelId)) {
+ state.selectedAsrModelId = '';
+ }
+ if (state.selectedTtsModelId && !ttsModels.some(model => model.id === state.selectedTtsModelId)) {
+ state.selectedTtsModelId = '';
+ }
+ if (!state.selectedAsrModelId && asrModels.length) {
+ state.selectedAsrModelId = asrModels[0].id;
+ }
+ if (!state.selectedTtsModelId && ttsModels.length) {
+ state.selectedTtsModelId = ttsModels[0].id;
+ }
return `
语音交互
- ${renderSceneCard(scene, version, language)}
交互语言
-
- ${data.languageOptions.map(lang => `
- ${escapeHtml(lang.label)}
+
+ ${AGENT_INTERACTION_LANGUAGES.map(lang => `
+ ${escapeHtml(lang.name)}
`).join('')}
@@ -13519,17 +16337,22 @@ function renderVoiceStep(state) {
toggleKey: 'asr-enabled',
checked: state.asrEnabled,
activeId: state.selectedAsrModelId,
- options: data.asrModels,
- hint: '前面的开关决定是否启用 ASR,后面的下拉用于切换识别模型',
+ options: asrModels,
+ hint: 'API Key 统一在 ASR 模型管理中维护,连接成功且启用后才可选择',
+ emptyText: '暂无可用 ASR 模型,请先创建并通过自动校验',
+ manageAction: 'asr-model-manager',
+ manageText: '管理 ASR 模型',
})}
${renderFeatureSelect({
title: '语音合成',
toggleKey: 'tts-enabled',
checked: state.ttsEnabled,
activeId: state.selectedTtsModelId,
- options: data.ttsGroups.flatMap(group => group.models.map(model => ({ ...model, groupTitle: group.title }))),
- groupedOptions: data.ttsGroups,
- hint: '前面的开关决定是否启用 TTS,后面的下拉用于切换播报模型',
+ options: ttsModels,
+ hint: 'API Key、Voice ID 和音频格式统一在 TTS 模型管理中维护',
+ emptyText: '暂无可用 TTS 模型,请先创建并通过自动校验',
+ manageAction: 'tts-model-manager',
+ manageText: '管理 TTS 模型',
})}
@@ -13545,49 +16368,36 @@ function renderVoiceStep(state) {
选择热词组
- ${hotwordGroups.length ? hotwordGroups.map(group => `
+
+ ${hotwordGroups.map(group => `
${escapeHtml(group.name)}
- `).join('') : '暂无热词组 '}
+ `).join('')}
-
-
即时纠错
-
-
-
开启即时纠错
-
${escapeHtml(data.correctionHint)}
-
-
-
-
-
-
-
- 可上传词表对识别结果进行实时干预
- 上传词表
-
-
语音合成播报过滤
-
开启播报过滤
-
特定符号对中的内容将不会进行 TTS 播报
+
+ 开启播报过滤
+
+
+
+
- ${state.ttsFilterEnabled ? `
-
-
选中符号对内的文字将不会通过 TTS 播报,如 [动作/表情编码]、[设备参数] 及 $$公式$$ 等。
-
语音合成播报过滤设置
-
- ` : ''}
+ ${state.ttsFilterEnabled ? renderTtsFilterSummary(state) : ''}
- ${state.ttsFilterEnabled ? renderTtsFilterSummary(state) : ''}
对话打断
此配置仅用于效果体验
@@ -13616,46 +16426,30 @@ function renderVoiceStep(state) {
-
语义判停与拒识
-
开启语义判停与拒识
-
开启后可能导致对话延迟增加
+
+ 语义判停与拒识
+
+
+
+
-
基于用户语义,判断是否结束语音输入,以及是否需要模型回复。全双工对话场景下,本功能效果更显著。
-
-
端侧算法
-
- 展开/收起端侧算法
-
-
- ${state.edgeAlgorithmExpanded ? `
-
- ${[
- ['语音唤醒', '默认唤醒词“小云小云”'],
- ['端侧 VAD', '语音活性检测,判断是否存在语音信号'],
- ['AEC', '回声消除,减少自身播报的干扰'],
- ['定向拾音', '增强特定方向拾音,抑制其他方向'],
- ].map(([title, desc]) => `
-
- ${escapeHtml(title)}
- ${escapeHtml(desc)}
-
- `).join('')}
-
- ` : ''}
-
- 立即运行
` : `
当前为文字交互模式
- 关闭语音交互后,不展示语音 AI、热词、纠错、播报过滤、打断和端侧算法配置。
+ 关闭语音交互后,不展示语音 AI、热词、播报过滤和对话打断配置。
`}
@@ -13665,71 +16459,42 @@ function renderVoiceStep(state) {
function renderUnderstandStep(state) {
const data = AGENT_CONFIG_MOCK.understand;
- const selectedMode = state.selectedTextModelMode || 'recommended';
- const selectedModelId = state.selectedTextModelId || data.textModels[0]?.id || '';
- const selectedModel = data.textModels.find(item => item.id === selectedModelId)
- || data.textModels.find(item => item.group === '推荐模型')
- || data.textModels[0];
+ const textModels = getAvailableTextModels();
+ if (state.selectedTextModelId && !textModels.some(model => model.id === state.selectedTextModelId)) {
+ state.selectedTextModelId = '';
+ }
+ if (!state.selectedTextModelId && textModels.length) {
+ state.selectedTextModelId = textModels[0].id;
+ }
+ const selectedModel = textModels.find(item => item.id === state.selectedTextModelId) || textModels[0] || null;
return `
- ${renderSection('意图识别', `
-
-
-
开启意图识别
-
${escapeHtml(data.intentModel.desc)}
-
-
-
-
-
-
-
关闭将不再触发调用工具/Agent 等基于语义理解的交互功能,适用于低成本、轻量级语音交互场景。
+ ${renderSection('意图识别', '', `${data.intentModel.desc} 关闭将不再触发调用工具/Agent 等基于语义理解的交互功能,适用于低成本、轻量级语音交互场景。`, `
+
+
+
+
`)}
${renderSection('文本模型', `
模型选择
-
建议使用推荐模型,为用户提供流畅的对话体验
+
模型凭证统一在文本模型管理中维护,校验通过并启用后才可选择
-
推荐 / 更多
+
创建模型
-
-
-
- 推荐模型
- 适合通用对话、工作学习和角色扮演场景。
-
-
-
- 更多模型
- 适合需要切换不同模型供应商的场景。
-
-
-
- ${data.textModels.filter(item => item.group === (selectedMode === 'recommended' ? '推荐模型' : '更多模型')).map(item => `
- ${escapeHtml(item.label)}
- `).join('')}
+
+ ${textModels.length ? textModels.map(item => `
+ ${escapeHtml(item.label)}
+ `).join('') : '暂无可用文本模型,请先创建并通过自动校验 '}
- ${escapeHtml(selectedModel.desc)}
+ ${selectedModel ? `${escapeHtml(selectedModel.desc)}
` : ''}
`)}
- ${renderPromptEditor(data.prompt)}
+ ${renderPromptEditor(state.prompt || data.prompt)}
${renderSection('对话变量', `
-
- 支持在对话输入中添加变量,为模型提供用户语音之外的更多信息,如天气、周围环境等。
- 添加变量
-
-
- ${data.variables.map(item => `
-
-
${escapeHtml(item.name)}
-
${escapeHtml(item.desc)}
-
- `).join('')}
-
-
- 自定义变量
-
-
+ ${renderVariableSimpleEditor(state)}
+ `, '变量是智能体运行时的状态存储,可用于注入 Prompt、节点传递、流程控制和上下文记忆。', `
+ 添加变量
`)}
${renderSection('上下文轮数', `
@@ -13739,56 +16504,23 @@ function renderUnderstandStep(state) {
`)}
${renderKnowledgeBaseSection(state.knowledgeBases || data.knowledgeBases)}
- ${renderSection('对话承接语', `
-
-
-
开启对话承接语
-
大模型基于用户输入自动生成承接语,减少回复等待时间
-
-
-
-
-
-
+ ${renderSection('对话承接语', '', '大模型基于用户输入自动生成承接语,减少回复等待时间', `
+
+
+
+
`)}
- ${renderSection('互联网搜索', `
-
-
-
开启互联网搜索
-
关闭后不展示搜索模式,仅保留本地知识和模型能力
-
-
-
-
-
-
- ${state.knowledgeSearchEnabled !== false ? `
-
-
-
- 效率模式
- 搜索速度更快,对话延迟低。
-
-
-
- 效果模式
- 搜索质量更高,对话延迟可能高于效率模式。
-
-
- ` : ''}
+ ${renderSection('互联网搜索', '', '关闭后不展示搜索模式,仅保留本地知识和模型能力', `
+
+
+
+
`)}
- ${renderSection('长期记忆', `
-
-
-
开启长期记忆
-
基于对话历史形成专属记忆库,并在后续对话中体现
-
-
-
-
-
-
- 开启长期记忆后,结合记忆内容进行回复的对话会增加额外耗时。
+ ${renderSection('长期记忆', '', '基于对话历史形成专属记忆库,并在后续对话中体现。开启长期记忆后,结合记忆内容进行回复的对话会增加额外耗时。', `
+
+
+
+
`)}
`;
@@ -13796,104 +16528,93 @@ function renderUnderstandStep(state) {
function renderSkillsStep(state) {
const data = AGENT_CONFIG_MOCK.skills;
- const commands = [...data.commands, ...(state.customCommands || [])];
- const plugins = [...data.plugins, ...(state.customPlugins || [])];
+ const commandLibrary = ensureCommandLibrary();
+ const selectedCommandIds = new Set(state.selectedCommandIds || []);
+ const commands = commandLibrary.filter(item => selectedCommandIds.has(item.id));
+ const pluginLibrary = ensurePluginLibrary();
+ const selectedPluginIds = new Set(state.selectedPluginIds || []);
+ const plugins = pluginLibrary.filter(item => selectedPluginIds.has(item.id));
return `
${renderSection('指令', `
- 当前已选择的指令可以编辑,右侧加号可添加新的指令类型
+ 当前智能体只会调用已挂载的指令。指令创建后会保存在指令库中,可重复选择。
添加/管理指令
- ${commands.map(item => `
+ ${commands.length ? commands.map(item => `
-
+
-
${escapeHtml(item.name)} ${item.count || 1} 指令
+
${escapeHtml(item.name)} ${escapeHtml(item.commandType || '自定义指令')}
${escapeHtml(item.desc)}
-
-
+
+
- `).join('')}
+ `).join('') : `
+
+
+ 暂无已挂载指令
+ 从 0 开始创建指令,创建后可在指令库中重复选择。
+ 创建/选择指令
+
+ `}
`)}
${renderSection('插件', `
- 插件能力默认由系统执行,也可以独立配置启用状态
- 配置插件
+ 当前智能体只会调用已挂载的插件。插件创建后会保存在插件库中,可重复选择。
+ 添加/管理插件
- ${plugins.map(item => `
+ ${plugins.length ? plugins.map(item => `
-
+
-
${escapeHtml(item.name)}
+
${escapeHtml(item.name)} ${escapeHtml(item.pluginType || '自定义插件')}
${escapeHtml(item.desc)}
${escapeHtml(item.status || '系统执行')}
-
-
+
+
- `).join('')}
+ `).join('') : `
+
+
+ 暂无已挂载插件
+ 从 0 开始创建插件,创建后可在插件库中重复选择。
+ 创建/选择插件
+
+ `}
`)}
- ${renderMiniAppSection('百炼应用', data.bailianApps, 'boxes')}
- ${renderMiniAppSection('三方应用', data.thirdPartyApps, 'plug')}
- ${renderMcpSection()}
- ${renderSection('Planning / Function Calling', `
- ${renderSwitchRow('开启 Planning', '让智能体先规划步骤,再调用函数或插件完成复杂任务', true)}
- ${renderSwitchRow('开启 Function Calling', '允许模型按照结构化参数调用已授权函数', true)}
- `)}
+ ${renderMcpSection(state)}
`;
}
-function renderAdvancedStep() {
- return `
-
- ${renderSection('高级设置', `
- ${renderSwitchRow('儿童安全模式', '强制启用儿童内容安全策略、危险行为拒答和敏感信息保护', true)}
- ${renderSwitchRow('链路追踪', '记录 ASR、LLM、RAG、TTS 的耗时和调用链路', true)}
- ${renderSwitchRow('自动降级', '主模型异常时自动切换到备用模型,保证设备可用性', true)}
- `)}
- ${renderSection('调用预算', `
-
-
- 单次对话上限
-
-
-
- 每日调用上限
-
-
-
- `)}
- ${renderSection('发布策略', `
-
- ${renderRadio('release', 'gray', '灰度发布 10%', true)}
- ${renderRadio('release', 'full', '全量发布')}
- ${renderRadio('release', 'schedule', '定时发布')}
-
- `)}
-
- `;
-}
-
-function renderPreviewPane(previewState) {
- const title = previewState === 'running' ? '预览环境已准备好' : '请填好配置后点击运行';
- const desc = previewState === 'running'
- ? '当前智能体已加载最新 mock 配置,可继续调整参数后再次运行。'
- : '当前应用测试会产生实际调用,按对话次数计费';
+function renderPreviewPane(state) {
+ if (state.preview === 'voice-auth') {
+ return renderRuntimeAuthPreview(state, 'voice');
+ }
+ if (state.preview === 'text-auth') {
+ return renderRuntimeAuthPreview(state, 'text');
+ }
+ if (state.preview === 'voice-runtime') {
+ return renderVoiceRuntimePreview(state);
+ }
+ if (state.preview === 'text-runtime') {
+ return renderTextRuntimePreview(state);
+ }
return `
-
${title}
-
${desc}
+
请填好配置后点击运行
+
当前应用测试会产生实际调用,按对话次数计费
建议佩戴耳机,并在安静环境下测试,对话体验更佳。
@@ -13903,28 +16624,563 @@ function renderPreviewPane(previewState) {
`;
}
-function renderSceneCard(scene, version, language) {
+function renderRuntimeAuthPreview(state, mode = 'voice') {
+ const savedKeys = getRuntimeApiKeysForApp(state);
+ const hasKeys = savedKeys.length > 0;
+ const isVoice = mode === 'voice';
return `
-
-
-
-
- ${escapeHtml(scene.name)}
- ${escapeHtml(version.name)}
- ${escapeHtml(language.name)}
-
- ${escapeHtml(scene.desc)}
-
-
-
+
+
+
+
+
${isVoice ? 'Voice Runtime' : 'Text Runtime'}
+
配置运行 API Key
+
运行前已自动保存当前配置。请选择系统设置里已保存的 API Key 后进入${isVoice ? '语音测试' : '文字聊天测试'}。
+
+
配置已保存
+
+
+
+ 选择系统 API Key
+
+ ${hasKeys ? '请选择 API Key ' : '暂无 API Key,请先在系统设置添加 '}
+ ${savedKeys.map(key => `${escapeHtml(key.name)} · ${escapeHtml(maskSecret(key.value))} `).join('')}
+
+
+
+
+
+ 确认并进入${isVoice ? '语音测试' : '文字聊天'}
+
+ ${hasKeys ? '' : `
+
+ 去系统设置添加
+
+ `}
+
+
+
+
+ 运行面板只引用系统设置中已保存的 API Key。后端接入后,这里应只传递 Key ID,由后端安全读取密钥并创建运行会话。
+
+
+
`;
}
-function renderSection(title, content) {
+function renderVoiceRuntimePreview(state) {
+ const session = getRunSession(state);
+ const latency = session.latencyMs == null ? '3377ms' : `${session.latencyMs}ms`;
+ const recordText = session.isRecording ? formatRuntimeDuration(session.elapsedSeconds) : '00:00';
+ const transcript = session.transcript || '正在监听,请开始说话...';
+ const aiResponse = session.aiDisplayResponse !== undefined ? session.aiDisplayResponse : (session.aiResponse || '');
+ const userName = getAgentRuntimeUserName();
+ const hasVoiceConversation = session.isRecording || Boolean(session.transcript || session.aiResponse || session.aiDisplayResponse);
return `
-
- ${escapeHtml(title)}
- ${content}
+
+
+
+
+
语音交互体验
+
+
+
+
+
+ ${escapeHtml(userName)}
+
+
+
+
+
+
+
+
+ ${hasVoiceConversation ? `
+
+
+
+
${escapeHtml(transcript)}
+
+
+
+
+ ${aiResponse ? escapeHtml(aiResponse) : ' '}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
端到端首包延迟:${escapeHtml(latency)}
+ ` : `
+
+
+
+
+
${escapeHtml(state.app?.name || 'TalkingQ Agent')}
+
建议佩戴耳机,并在安静环境下测试,对话体验更佳。
+
+ `}
+
+
+
+ ${session.isRecording ? `
+
+
${escapeHtml(recordText)}
+
+
+ ${Array.from({ length: 28 }).map((_, index) => ` `).join('')}
+
+
+
+
+
+
+
+
+
+ ` : `
+
+
+ MacBook Pro麦克风
+
+
+
+
+
+ `}
+
+ ${session.micMuted ? '
麦克风已关闭,当前暂停收音。
' : ''}
+
+
+ `;
+}
+
+function renderTextRuntimePreview(state) {
+ const session = getRunSession(state);
+ const messages = Array.isArray(session.chatMessages) ? session.chatMessages : [];
+ const userName = getAgentRuntimeUserName();
+ return `
+
+
+
+
+
文本交互体验
+
+
+
+
+
+ ${escapeHtml(userName)}
+
+
+
+
+
+
+
+
+ ${messages.length ? messages.map((message, index) => renderTextChatMessage(message, index)).join('') : `
+
+ 开始输入第一条测试消息
+
+ `}
+ ${session.textPending ? `
+
+ ` : ''}
+ ${session.latencyMs ? `
+
端到端 ${escapeHtml(String(session.latencyMs))}ms
+ ` : ''}
+
+
+
+
+
+ `;
+}
+
+function renderTextChatMessage(message, index) {
+ if (message.role === 'user') {
+ return `
+
+
${escapeHtml(message.content)}
+
+ `;
+ }
+ const content = message.displayContent !== undefined ? message.displayContent : message.content;
+ return `
+
+
+
${escapeHtml(content || '')}
+ ${message.content ? `
+
+
+
+
+
+
+
+
+ ` : ''}
+
+
+ `;
+}
+
+function getRunSession(state) {
+ if (!state.runSession) {
+ state.runSession = {};
+ }
+ return state.runSession;
+}
+
+function getRuntimeApiKeysForApp(state) {
+ const settings = typeof loadSettings === 'function'
+ ? loadSettings()
+ : (window.__talkingqSettings || {});
+ if (Array.isArray(settings.apiKeyRecords)) {
+ const records = settings.apiKeyRecords
+ .filter(record => String(record.value || '').trim())
+ .map(record => ({
+ id: record.id,
+ name: record.name || 'API Key',
+ provider: record.provider || record.providerId || 'Custom',
+ value: record.value,
+ }));
+ if (records.length) return records;
+ }
+ const apiKeys = settings.apiKeys || {};
+ return [
+ { id: 'openai', name: 'OpenAI API Key', provider: 'OpenAI', value: apiKeys.openai || '' },
+ { id: 'ali', name: '阿里云百炼 API Key', provider: 'Alibaba Cloud Bailian', value: apiKeys.ali || '' },
+ { id: 'gemini', name: 'Google Gemini API Key', provider: 'Google Gemini', value: apiKeys.gemini || '' },
+ ].filter(item => String(item.value || '').trim());
+}
+
+function formatRuntimeDuration(seconds) {
+ const safe = Math.max(0, Number(seconds) || 0);
+ const min = Math.floor(safe / 60);
+ const sec = safe % 60;
+ return `${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
+}
+
+function getAgentRunTimerKey(state) {
+ return state.app?.id || 'default';
+}
+
+function clearAgentRunTimer(state) {
+ const timers = ensureAgentRunTimers();
+ const key = getAgentRunTimerKey(state);
+ if (timers[key]) {
+ window.clearInterval(timers[key]);
+ delete timers[key];
+ }
+}
+
+function buildAgentConfigSnapshot(state) {
+ return {
+ appId: state.app?.id,
+ appName: state.app?.name,
+ savedAt: state.lastSavedAt,
+ voiceEnabled: state.voiceEnabled,
+ asrEnabled: state.asrEnabled,
+ ttsEnabled: state.ttsEnabled,
+ selectedInteractionLanguage: state.selectedInteractionLanguage,
+ selectedAsrModelId: state.selectedAsrModelId,
+ selectedTtsModelId: state.selectedTtsModelId,
+ selectedTextModelId: state.selectedTextModelId,
+ selectedHotwordGroupId: state.selectedHotwordGroupId,
+ ttsFilterEnabled: state.ttsFilterEnabled,
+ interruptPrimaryEnabled: state.interruptPrimaryEnabled,
+ interruptSecondaryEnabled: state.interruptSecondaryEnabled,
+ semanticStopEnabled: state.semanticStopEnabled,
+ intentEnabled: state.intentEnabled,
+ dialogueContinuationEnabled: state.dialogueContinuationEnabled,
+ knowledgeSearchEnabled: state.knowledgeSearchEnabled,
+ longTermMemoryEnabled: state.longTermMemoryEnabled,
+ prompt: state.prompt,
+ variables: state.variables,
+ knowledgeBases: state.knowledgeBases,
+ selectedCommandIds: state.selectedCommandIds,
+ selectedPluginIds: state.selectedPluginIds,
+ selectedMcpServiceIds: state.selectedMcpServiceIds,
+ };
+}
+
+function updateAgentAutosaveIndicator(state) {
+ const indicator = document.querySelector('#agentAutosaveStatus');
+ if (!indicator) return;
+ indicator.classList.toggle('saving', state.saveStatus === 'saving');
+ indicator.textContent = state.saveStatus === 'saving'
+ ? '自动保存中'
+ : state.lastSavedAt ? `已自动保存 ${getTimeOnly(state.lastSavedAt)}` : '等待自动保存';
+}
+
+function autoSaveAgentConfig(state) {
+ state.saveStatus = 'saved';
+ state.lastSavedAt = getNowText();
+ const store = ensureAgentConfigAutosaves();
+ store[state.app?.id || 'default'] = buildAgentConfigSnapshot(state);
+ updateAgentAutosaveIndicator(state);
+}
+
+function scheduleAgentConfigAutoSave(state) {
+ if (state.__saveTimer) {
+ window.clearTimeout(state.__saveTimer);
+ }
+ state.saveStatus = 'saving';
+ updateAgentAutosaveIndicator(state);
+ state.__saveTimer = window.setTimeout(() => {
+ autoSaveAgentConfig(state);
+ state.__saveTimer = null;
+ }, 300);
+}
+
+function getMockTranscriptForSecond(second) {
+ const fragments = [
+ '你好小Q',
+ '你好小Q,今天可以给我讲一个太空故事吗',
+ '你好小Q,今天可以给我讲一个太空故事吗,里面要有机器人和小朋友',
+ '你好小Q,今天可以给我讲一个太空故事吗,里面要有机器人和小朋友,最后要开心一点',
+ ];
+ if (second <= 1) return fragments[0];
+ if (second <= 2) return fragments[1];
+ if (second <= 4) return fragments[2];
+ return fragments[3];
+}
+
+function getMockAiResponseForSecond(second) {
+ if (second < 2) return '';
+ if (second < 4) return '可以呀,我正在准备一个温柔的太空小故事。';
+ return '当然可以。小朋友坐上银色小飞船,和机器人小Q一起飞向月亮。他们发现一颗会唱歌的小星星,最后把星光带回家,送给每一个睡前还想听故事的小朋友。';
+}
+
+function getAgentRuntimeUserName() {
+ return window.__currentUser?.displayName
+ || window.__currentUser?.name
+ || window.currentUser?.displayName
+ || window.currentUser?.name
+ || 'Admin';
+}
+
+function syncVoiceRuntimeDom(container, state) {
+ const panel = container.querySelector('.agent-voice-panel.recording');
+ if (!panel || state.preview !== 'voice-runtime') return false;
+ const session = getRunSession(state);
+ const latency = session.latencyMs == null ? '3377ms' : `${session.latencyMs}ms`;
+ const recordText = session.isRecording ? formatRuntimeDuration(session.elapsedSeconds) : '00:00';
+ const transcript = session.transcript || '正在监听,请开始说话...';
+ const aiResponse = session.aiDisplayResponse !== undefined ? session.aiDisplayResponse : (session.aiResponse || '');
+
+ const transcriptNode = panel.querySelector('[data-voice-runtime="transcript"]');
+ if (transcriptNode) transcriptNode.textContent = transcript;
+
+ const aiContentNode = panel.querySelector('[data-voice-runtime="ai-content"]');
+ if (aiContentNode) {
+ if (aiResponse) {
+ aiContentNode.textContent = aiResponse;
+ } else if (!aiContentNode.querySelector('.agent-runtime-typing')) {
+ aiContentNode.innerHTML = ' ';
+ }
+ }
+
+ const actionsNode = panel.querySelector('[data-voice-runtime="ai-actions"]');
+ if (actionsNode) {
+ actionsNode.classList.toggle('is-visible', Boolean(session.aiResponse));
+ actionsNode.setAttribute('aria-hidden', session.aiResponse ? 'false' : 'true');
+ }
+
+ const latencyNode = panel.querySelector('[data-voice-runtime="latency"]');
+ if (latencyNode) latencyNode.textContent = `端到端首包延迟:${latency}`;
+
+ const recordNode = panel.querySelector('[data-voice-runtime="record-time"]');
+ if (recordNode) recordNode.textContent = recordText;
+
+ const waveNode = panel.querySelector('[data-voice-runtime="wave"]');
+ if (waveNode) waveNode.classList.toggle('muted', Boolean(session.micMuted));
+
+ const micButton = panel.querySelector('[data-voice-runtime="mic-button"]');
+ if (micButton) {
+ micButton.classList.toggle('muted', Boolean(session.micMuted));
+ micButton.setAttribute('aria-label', session.micMuted ? '打开麦克风' : '关闭麦克风');
+ const iconName = session.micMuted ? 'mic-off' : 'mic';
+ const icon = micButton.querySelector('i, svg');
+ if (icon?.getAttribute('data-lucide') !== iconName) {
+ micButton.innerHTML = ` `;
+ renderAgentIcons(micButton);
+ }
+ }
+
+ const voiceScrollArea = panel.querySelector('.agent-voice-stage') || panel.querySelector('.agent-voice-conversation');
+ if (voiceScrollArea) {
+ voiceScrollArea.scrollTop = voiceScrollArea.scrollHeight;
+ }
+
+ return true;
+}
+
+function revealVoiceReply(container, state, fullText) {
+ const session = getRunSession(state);
+ let cursor = 0;
+ const step = Math.max(1, Math.ceil(fullText.length / 36));
+ if (session.__voiceRevealTimer) {
+ window.clearInterval(session.__voiceRevealTimer);
+ }
+ session.__voiceRevealTimer = window.setInterval(() => {
+ cursor = Math.min(fullText.length, cursor + step);
+ session.aiDisplayResponse = fullText.slice(0, cursor);
+ if (!syncVoiceRuntimeDom(container, state)) {
+ updatePreviewOnly(container, state);
+ }
+ if (cursor >= fullText.length) {
+ session.aiDisplayResponse = fullText;
+ window.clearInterval(session.__voiceRevealTimer);
+ session.__voiceRevealTimer = null;
+ if (!syncVoiceRuntimeDom(container, state)) {
+ updatePreviewOnly(container, state);
+ }
+ }
+ }, 38);
+}
+
+function buildMockTextReply(value, retryCount = 0) {
+ const suffix = retryCount ? `这是第 ${retryCount + 1} 版重新生成结果。` : '这是当前前端 mock 回复效果。';
+ return `我收到了:“${value}”。${suffix} 后端接入后,这里会返回真实模型的流式回复。`;
+}
+
+function revealTextReply(container, state, messageIndex, fullText) {
+ const session = getRunSession(state);
+ let cursor = 0;
+ const step = Math.max(1, Math.ceil(fullText.length / 34));
+ const timer = window.setInterval(() => {
+ const messages = Array.isArray(session.chatMessages) ? session.chatMessages : [];
+ const message = messages[messageIndex];
+ if (!message) {
+ window.clearInterval(timer);
+ return;
+ }
+ cursor = Math.min(fullText.length, cursor + step);
+ message.displayContent = fullText.slice(0, cursor);
+ updatePreviewOnly(container, state);
+ if (cursor >= fullText.length) {
+ message.displayContent = fullText;
+ window.clearInterval(timer);
+ updatePreviewOnly(container, state);
+ }
+ }, 38);
+}
+
+function startVoiceRuntimeCall(container, state) {
+ const session = getRunSession(state);
+ clearAgentRunTimer(state);
+ if (session.__voiceRevealTimer) {
+ window.clearInterval(session.__voiceRevealTimer);
+ session.__voiceRevealTimer = null;
+ }
+ session.isRecording = true;
+ session.micMuted = false;
+ session.elapsedSeconds = 0;
+ session.latencyMs = 3377;
+ session.transcript = '正在监听,请开始说话...';
+ session.aiResponse = '';
+ session.aiDisplayResponse = '';
+ session.lastVoiceAiResponse = '';
+ session.startedAt = Date.now();
+ updatePreviewOnly(container, state);
+ const timers = ensureAgentRunTimers();
+ timers[getAgentRunTimerKey(state)] = window.setInterval(() => {
+ if (!session.isRecording) {
+ clearAgentRunTimer(state);
+ return;
+ }
+ session.elapsedSeconds = Math.floor((Date.now() - session.startedAt) / 1000);
+ if (!session.micMuted) {
+ const nextTranscript = getMockTranscriptForSecond(session.elapsedSeconds);
+ const nextResponse = getMockAiResponseForSecond(session.elapsedSeconds);
+ session.transcript = nextTranscript;
+ session.latencyMs = 3377 + ((session.elapsedSeconds * 83) % 420);
+ if (nextResponse && nextResponse !== session.lastVoiceAiResponse) {
+ session.aiResponse = nextResponse;
+ session.aiDisplayResponse = '';
+ session.lastVoiceAiResponse = nextResponse;
+ revealVoiceReply(container, state, nextResponse);
+ return;
+ }
+ }
+ if (!syncVoiceRuntimeDom(container, state)) {
+ updatePreviewOnly(container, state);
+ }
+ }, 1000);
+}
+
+function stopVoiceRuntimeCall(container, state) {
+ const session = getRunSession(state);
+ clearAgentRunTimer(state);
+ session.isRecording = false;
+ session.elapsedSeconds = Math.max(session.elapsedSeconds || 0, Math.floor((Date.now() - (session.startedAt || Date.now())) / 1000));
+ session.latencyMs = session.latencyMs || 3377;
+ session.transcript = session.transcript || '本轮没有检测到有效语音。';
+ session.aiResponse = session.aiResponse || '我已经结束本轮收音,可以再次点击电话按钮开始新的语音测试。';
+ session.aiDisplayResponse = session.aiDisplayResponse || session.aiResponse;
+ updatePreviewOnly(container, state);
+}
+
+function renderSection(title, content, tooltip = '', headerAction = '') {
+ const hasContent = Boolean(String(content || '').trim());
+ const titleInfo = tooltip ? `
+
+
+
+ ` : '';
+ const titleMarkup = `
+
+ `;
+ return `
+
`;
}
@@ -13951,6 +17207,9 @@ function renderFeatureSelect(config) {
options = [],
groupedOptions = null,
hint = '',
+ emptyText = '暂无可选模型',
+ manageAction = '',
+ manageText = '',
} = config;
const fieldAction = toggleKey === 'asr-enabled' ? 'asr-model'
: toggleKey === 'tts-enabled' ? 'tts-model'
@@ -13968,8 +17227,8 @@ function renderFeatureSelect(config) {
-
- ${groupedOptions ? groupedOptions.map(group => `
+
+ ${options.length === 0 ? `${escapeHtml(emptyText)} ` : groupedOptions ? groupedOptions.map(group => `
${group.models.map(model => `
${escapeHtml(model.label)}
@@ -13980,21 +17239,12 @@ function renderFeatureSelect(config) {
`).join('')}
${selectedOption ? `${escapeHtml(selectedOption.desc)}
` : ''}
-
- `;
-}
-
-function renderSwitchRow(label, desc, checked) {
- return `
-
-
-
${escapeHtml(label)}
-
${escapeHtml(desc)}
-
-
-
-
-
+ ${manageAction ? `
+
+
+ ${escapeHtml(manageText || '管理模型')}
+
+ ` : ''}
`;
}
@@ -14011,29 +17261,1147 @@ function renderRadio(name, value, label, checked = false) {
function renderTtsFilterSummary(state) {
if (!state.ttsFilterEnabled) return '';
- const commonPairs = (state.ttsFilterPairs || [])
- .filter(item => item.enabled !== false)
- .map(item => `${item.left} ${item.right}`.trim());
- const singles = state.ttsFilterSingles || [];
+ const pairKeys = new Set();
+ const pairs = (state.ttsFilterPairs || []).reduce((list, item) => {
+ if (item.enabled === false) return list;
+ const left = String(item.left || '').trim();
+ const right = String(item.right || '').trim();
+ if (!left && !right) return list;
+ const key = `${left}__${right}`;
+ if (pairKeys.has(key)) return list;
+ pairKeys.add(key);
+ list.push(`${left} ${right}`.trim());
+ return list;
+ }, []);
+ const singles = [...new Set((state.ttsFilterSingles || []).map(item => String(item || '').trim()).filter(Boolean))];
+ const tokens = [...pairs, ...singles.map(item => `字符 ${item}`)];
+ const visibleTokens = tokens.slice(0, 5);
+ const hiddenCount = Math.max(tokens.length - visibleTokens.length, 0);
return `
-
-
常用过滤符号对
-
- ${commonPairs.length ? commonPairs.map(item => `
${escapeHtml(item)} `).join('') : '
暂无 '}
+
+
播报过滤规则
+
${pairs.length} 组符号对 · ${singles.length} 个单字符
+
+ ${visibleTokens.length ? visibleTokens.map(item => `${escapeHtml(item)} `).join('') : '暂无规则 '}
+ ${hiddenCount ? `+${hiddenCount} ` : ''}
-
-
自定义过滤符号对与单字符
-
- ${singles.length ? singles.map(item => `${escapeHtml(item)} `).join('') : '暂无 '}
-
-
打开配置弹窗
+
配置
+
+ `;
+}
+
+function renderVariableSimpleEditor(state) {
+ const variables = state.variables || [];
+ return `
+
`;
}
+function getSpeechModelDisplayStatus(model) {
+ if (!model) return 'failed';
+ if (model.status === 'connected') return 'available';
+ if (model.status === 'unconnected') return 'failed';
+ if (model.status === 'available' && model.enabled === false) return 'disabled';
+ return model.status || 'failed';
+}
+
+function getAsrModelDisplayStatus(model) {
+ return getSpeechModelDisplayStatus(model);
+}
+
+function getTtsModelDisplayStatus(model) {
+ return getSpeechModelDisplayStatus(model);
+}
+
+function getAsrStatusMeta(model) {
+ return ASR_MODEL_STATUS_META[getAsrModelDisplayStatus(model)] || ASR_MODEL_STATUS_META.failed;
+}
+
+function getTtsStatusMeta(model) {
+ return TTS_MODEL_STATUS_META[getTtsModelDisplayStatus(model)] || TTS_MODEL_STATUS_META.failed;
+}
+
+function maskSecret(value) {
+ if (!value) return '未填写';
+ return `${String(value).slice(0, 4)}••••••••${String(value).slice(-3)}`;
+}
+
+function getLanguageTypeName(languageId) {
+ return AGENT_INTERACTION_LANGUAGES.find(item => item.id === languageId)?.name || languageId || '多语言';
+}
+
+function getTtsVoiceIds(model) {
+ if (Array.isArray(model?.voiceIds)) {
+ return model.voiceIds.filter(Boolean);
+ }
+ return model?.voiceId ? [model.voiceId] : [];
+}
+
+function getTtsVoiceSummary(model) {
+ const voiceIds = getTtsVoiceIds(model);
+ if (!voiceIds.length) return '未配置 Voice';
+ if (voiceIds.length === 1) return `Voice ${voiceIds[0]}`;
+ return `${voiceIds.length} 个 Voice`;
+}
+
+function parseTtsVoiceIds(value) {
+ return [...new Set(String(value || '')
+ .split(/[\n,,;;\s]+/)
+ .map(item => item.trim())
+ .filter(Boolean))];
+}
+
+function getSpeechModelValidationError(model, type) {
+ const apiKey = String(model.apiKey || '').trim().toLowerCase();
+ const baseUrl = String(model.baseUrl || '').trim().toLowerCase();
+ const voiceIds = getTtsVoiceIds(model).map(item => String(item).trim().toLowerCase());
+ if (!apiKey || apiKey.includes('invalid') || apiKey.startsWith('bad') || apiKey === 'sk-invalid') {
+ return 'Key 无效';
+ }
+ if (!/^https?:\/\//i.test(model.baseUrl || '') || /down|timeout|error|404|500/.test(baseUrl)) {
+ return '接口异常';
+ }
+ if (type === 'TTS' && voiceIds.some(voiceId => /missing|notfound|not-found|none/.test(voiceId))) {
+ return 'Voice 不存在';
+ }
+ if (type === 'TTS' && !TTS_AUDIO_FORMAT_OPTIONS.some(item => item.id === model.audioFormat)) {
+ return '返回格式错误';
+ }
+ return '';
+}
+
+function markSpeechModelValidating(model) {
+ model.status = 'validating';
+ model.enabled = false;
+ model.errorMessage = '';
+ model.updatedAt = getNowText();
+}
+
+function markSpeechModelAvailable(model, shouldEnable = true) {
+ model.status = shouldEnable ? 'available' : 'disabled';
+ model.enabled = Boolean(shouldEnable);
+ model.errorMessage = '';
+ model.validatedAt = getNowText();
+ model.updatedAt = getNowText();
+}
+
+function markSpeechModelFailed(model, message) {
+ model.status = 'failed';
+ model.enabled = false;
+ model.errorMessage = message || '连接校验失败';
+ model.updatedAt = getNowText();
+}
+
+function renderAsrModelManagerContent(showForm = false) {
+ const models = ensureAsrModelRegistry();
+ return `
+
+
+
+
ASR 模型凭证统一维护在这里。创建后系统会自动校验接口连通性,只有状态为「可用」且已启用的模型才会进入智能体的语音识别下拉框。
+
+
+ ${showForm ? `
+
+ ` : `
+
+ `}
+
+
+
已添加模型
+
${models.length} 个配置
+
+
+ ${models.length ? models.map(model => {
+ const meta = getAsrStatusMeta(model);
+ const status = getAsrModelDisplayStatus(model);
+ const canToggle = status === 'available' || status === 'disabled';
+ return `
+
+
+
+ ${escapeHtml(model.name)}
+ ${escapeHtml(meta.label)}
+
+
+ Provider:${escapeHtml(model.provider)}
+ Base URL:${escapeHtml(model.baseUrl)}
+ API Key:${escapeHtml(maskSecret(model.apiKey))}
+
+
+ Secret Key:${escapeHtml(maskSecret(model.secretKey))}
+ 更新时间:${escapeHtml(model.updatedAt || '-')}
+ ${model.validatedAt ? `最近校验:${escapeHtml(model.validatedAt)} ` : ''}
+
+ ${model.errorMessage ? `
失败原因:${escapeHtml(model.errorMessage)}
` : ''}
+
+
+ 重新校验
+
+ ${status === 'disabled' ? '启用' : '禁用'}
+
+
+
+ 删除
+
+
+
+ `;
+ }).join('') : `
+
+
+ 暂无 ASR 模型
+ 创建并自动校验成功后,智能体即可选择该识别模型。
+
+ 添加模型
+
+
+ `}
+
+
+ `;
+}
+
+function collectAsrModelForm(body) {
+ const name = body.querySelector('#asrModelName')?.value.trim();
+ const provider = body.querySelector('#asrProviderName')?.value.trim();
+ const baseUrl = body.querySelector('#asrBaseUrl')?.value.trim();
+ const apiKey = body.querySelector('#asrApiKey')?.value.trim();
+ const secretKey = body.querySelector('#asrSecretKey')?.value.trim();
+ const enableAfterValidation = body.querySelector('#asrEnableAfterValidation')?.checked !== false;
+ if (!name || !provider || !baseUrl || !apiKey) {
+ Toast.warning('请填写模型名称、Provider、Base URL 和 API Key');
+ return null;
+ }
+ if (!/^https?:\/\//i.test(baseUrl)) {
+ Toast.warning('Base URL 需要以 http:// 或 https:// 开头');
+ return null;
+ }
+ return {
+ id: `asr-${Date.now()}`,
+ name,
+ provider,
+ baseUrl,
+ apiKey,
+ secretKey,
+ enableAfterValidation,
+ status: 'validating',
+ enabled: false,
+ updatedAt: getNowText(),
+ };
+}
+
+function validateAsrModelConnection(model) {
+ const validationError = getSpeechModelValidationError(model, 'ASR');
+ if (validationError) {
+ return new Promise(resolve => setTimeout(() => resolve({ success: false, message: validationError }), 650));
+ }
+ const validate = API.agentDev.validateASRModel || API.agentDev.testASRModel;
+ return validate(model.id, {
+ provider: model.provider,
+ baseUrl: model.baseUrl,
+ });
+}
+
+function openAsrModelManagerModal(state, rerender) {
+ let showCreateForm = false;
+ const modal = createModal({
+ title: 'ASR 模型管理',
+ size: 'xl',
+ showCancel: false,
+ confirmText: '关闭',
+ content: renderAsrModelManagerContent(showCreateForm),
+ onConfirm: () => true,
+ });
+
+ const refreshModal = () => {
+ const body = modal.overlay.querySelector('.modal-body');
+ if (!body) return;
+ body.innerHTML = renderAsrModelManagerContent(showCreateForm);
+ lucide.createIcons({ elements: [body] });
+ bindAsrModelManagerEvents(body);
+ rerender();
+ };
+
+ const finishValidation = async (model, shouldEnable = true) => {
+ const result = await validateAsrModelConnection(model);
+ if (result.success === false) {
+ markSpeechModelFailed(model, result.message || 'ASR 模型连接失败');
+ Toast.error(model.errorMessage);
+ } else {
+ markSpeechModelAvailable(model, shouldEnable);
+ if (model.enabled && !state.selectedAsrModelId) state.selectedAsrModelId = model.id;
+ Toast.success(`ASR 模型「${model.name}」校验通过`);
+ }
+ };
+
+ const bindAsrModelManagerEvents = (body) => {
+ body.querySelectorAll('[data-asr-action="show-create"]').forEach(btn => btn.addEventListener('click', () => {
+ showCreateForm = true;
+ refreshModal();
+ }));
+
+ body.querySelectorAll('[data-asr-action="cancel-create"]').forEach(btn => btn.addEventListener('click', () => {
+ showCreateForm = false;
+ refreshModal();
+ }));
+
+ body.querySelectorAll('[data-asr-action="create-new"]').forEach(btn => btn.addEventListener('click', async () => {
+ const model = collectAsrModelForm(body);
+ if (!model) return;
+ markSpeechModelValidating(model);
+ ensureAsrModelRegistry().push(model);
+ await API.agentDev.createASRModel(model);
+ Toast.info(`ASR 模型「${model.name}」开始自动校验`, 800);
+ showCreateForm = false;
+ refreshModal();
+ await finishValidation(model, model.enableAfterValidation !== false);
+ refreshModal();
+ }));
+
+ body.querySelectorAll('[data-asr-action="validate-existing"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const model = ensureAsrModelRegistry().find(item => item.id === btn.dataset.id);
+ if (!model) return;
+ markSpeechModelValidating(model);
+ refreshModal();
+ await finishValidation(model, true);
+ refreshModal();
+ });
+ });
+
+ body.querySelectorAll('[data-asr-action="toggle-existing"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const model = ensureAsrModelRegistry().find(item => item.id === btn.dataset.id);
+ if (!model) return;
+ const status = getAsrModelDisplayStatus(model);
+ if (status !== 'available' && status !== 'disabled') {
+ Toast.warning('只有校验通过的模型才可以启用或禁用');
+ return;
+ }
+ if (status === 'disabled') {
+ await API.agentDev.setASRModelEnabled(model.id, true);
+ markSpeechModelAvailable(model, true);
+ if (!state.selectedAsrModelId) state.selectedAsrModelId = model.id;
+ Toast.success(`ASR 模型「${model.name}」已启用`);
+ } else {
+ await API.agentDev.setASRModelEnabled(model.id, false);
+ markSpeechModelAvailable(model, false);
+ if (state.selectedAsrModelId === model.id) state.selectedAsrModelId = '';
+ Toast.info(`ASR 模型「${model.name}」已禁用`);
+ }
+ refreshModal();
+ });
+ });
+
+ body.querySelectorAll('[data-asr-action="delete-existing"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const registry = ensureAsrModelRegistry();
+ const model = registry.find(item => item.id === btn.dataset.id);
+ if (!model) return;
+ await API.agentDev.deleteASRModel(model.id);
+ window.__asrModelRegistry = registry.filter(item => item.id !== model.id);
+ if (state.selectedAsrModelId === model.id) state.selectedAsrModelId = '';
+ Toast.success(`ASR 模型「${model.name}」已删除`);
+ refreshModal();
+ });
+ });
+ };
+
+ const body = modal.overlay.querySelector('.modal-body');
+ if (body) {
+ lucide.createIcons({ elements: [body] });
+ bindAsrModelManagerEvents(body);
+ }
+}
+
+function renderTtsModelManagerContent(showForm = false) {
+ const models = ensureTtsModelRegistry();
+ return `
+
+
+
+
TTS 模型凭证、Voice ID、音频格式和语言类型统一维护在这里。创建模型后系统会自动校验 Key、接口、Voice 与音频返回,校验通过后才会进入智能体的语音合成下拉框。
+
+
+ ${showForm ? `
+
+ ` : `
+
+ `}
+
+
+
已添加模型
+
${models.length} 个配置
+
+
+ ${models.length ? models.map(model => {
+ const meta = getTtsStatusMeta(model);
+ const status = getTtsModelDisplayStatus(model);
+ const canToggle = status === 'available' || status === 'disabled';
+ return `
+
+
+
+ ${escapeHtml(model.name)}
+ ${escapeHtml(meta.label)}
+
+
+ Provider:${escapeHtml(model.provider)}
+ Base URL:${escapeHtml(model.baseUrl)}
+ API Key:${escapeHtml(maskSecret(model.apiKey))}
+
+
+ Secret Key:${escapeHtml(maskSecret(model.secretKey))}
+ Voice:${escapeHtml(getTtsVoiceSummary(model))}
+ 音频格式:${escapeHtml(String(model.audioFormat || '').toUpperCase())}
+ 语言类型:${escapeHtml(getLanguageTypeName(model.languageType))}
+
+
+ 更新时间:${escapeHtml(model.updatedAt || '-')}
+ ${model.validatedAt ? `最近校验:${escapeHtml(model.validatedAt)} ` : ''}
+
+ ${model.errorMessage ? `
失败原因:${escapeHtml(model.errorMessage)}
` : ''}
+
+
+ 重新校验
+
+ ${status === 'disabled' ? '启用' : '禁用'}
+
+
+
+ 删除
+
+
+
+ `;
+ }).join('') : `
+
+
+ 暂无 TTS 模型
+ 创建并自动校验成功后,智能体即可选择该合成模型。
+
+ 添加模型
+
+
+ `}
+
+
+ `;
+}
+
+function collectTtsModelForm(body) {
+ const name = body.querySelector('#ttsModelName')?.value.trim();
+ const provider = body.querySelector('#ttsProviderName')?.value.trim();
+ const baseUrl = body.querySelector('#ttsBaseUrl')?.value.trim();
+ const apiKey = body.querySelector('#ttsApiKey')?.value.trim();
+ const secretKey = body.querySelector('#ttsSecretKey')?.value.trim();
+ const voiceMode = body.querySelector('input[name="ttsVoiceMode"]:checked')?.value || 'single';
+ const voiceId = body.querySelector('#ttsVoiceId')?.value.trim();
+ const voiceIds = voiceMode === 'batch'
+ ? parseTtsVoiceIds(body.querySelector('#ttsVoiceIds')?.value)
+ : (voiceId ? [voiceId] : []);
+ const audioFormat = body.querySelector('#ttsAudioFormat')?.value || 'mp3';
+ const languageType = body.querySelector('#ttsLanguageType')?.value || 'zh';
+ const enableAfterValidation = body.querySelector('#ttsEnableAfterValidation')?.checked !== false;
+ if (!name || !provider || !baseUrl || !apiKey || !audioFormat || !languageType) {
+ Toast.warning('请填写模型名称、Provider、Base URL、API Key、音频格式和语言类型');
+ return null;
+ }
+ if (!/^https?:\/\//i.test(baseUrl)) {
+ Toast.warning('Base URL 需要以 http:// 或 https:// 开头');
+ return null;
+ }
+ if (voiceMode === 'batch' && voiceIds.length === 0) {
+ Toast.warning('批量添加 Voice 时,请至少填写一个 Voice ID');
+ return null;
+ }
+ if (voiceIds.length > 50) {
+ Toast.warning('单次最多批量添加 50 个 Voice ID');
+ return null;
+ }
+ return {
+ id: `tts-${Date.now()}`,
+ name,
+ provider,
+ baseUrl,
+ apiKey,
+ secretKey,
+ voiceMode,
+ voiceId: voiceIds[0] || '',
+ voiceIds,
+ audioFormat,
+ languageType,
+ enableAfterValidation,
+ status: 'validating',
+ enabled: false,
+ updatedAt: getNowText(),
+ };
+}
+
+function validateTtsModelConnection(model) {
+ const validationError = getSpeechModelValidationError(model, 'TTS');
+ if (validationError) {
+ return new Promise(resolve => setTimeout(() => resolve({ success: false, message: validationError }), 750));
+ }
+ const validate = API.agentDev.validateTTSModel || API.agentDev.testTTS;
+ return validate(model.id, {
+ provider: model.provider,
+ baseUrl: model.baseUrl,
+ voiceId: model.voiceId,
+ voiceIds: getTtsVoiceIds(model),
+ audioFormat: model.audioFormat,
+ languageType: model.languageType,
+ });
+}
+
+function openTtsModelManagerModal(state, rerender) {
+ let showCreateForm = false;
+ const modal = createModal({
+ title: 'TTS 模型管理',
+ size: 'xl',
+ showCancel: false,
+ confirmText: '关闭',
+ content: renderTtsModelManagerContent(showCreateForm),
+ onConfirm: () => true,
+ });
+
+ const refreshModal = () => {
+ const body = modal.overlay.querySelector('.modal-body');
+ if (!body) return;
+ body.innerHTML = renderTtsModelManagerContent(showCreateForm);
+ lucide.createIcons({ elements: [body] });
+ bindTtsModelManagerEvents(body);
+ rerender();
+ };
+
+ const finishValidation = async (model, shouldEnable = true) => {
+ const result = await validateTtsModelConnection(model);
+ if (result.success === false) {
+ markSpeechModelFailed(model, result.message || 'TTS 模型连接失败');
+ Toast.error(model.errorMessage);
+ } else {
+ markSpeechModelAvailable(model, shouldEnable);
+ if (model.enabled && !state.selectedTtsModelId) state.selectedTtsModelId = model.id;
+ Toast.success(`TTS 模型「${model.name}」校验通过`);
+ }
+ };
+
+ const bindTtsModelManagerEvents = (body) => {
+ const syncTtsVoiceMode = () => {
+ const mode = body.querySelector('input[name="ttsVoiceMode"]:checked')?.value || 'single';
+ body.querySelectorAll('.bailian-voice-mode-card').forEach(card => {
+ const input = card.querySelector('input[name="ttsVoiceMode"]');
+ card.classList.toggle('active', input?.value === mode);
+ });
+ body.querySelectorAll('[data-tts-voice-panel]').forEach(panel => {
+ panel.classList.toggle('hidden', panel.dataset.ttsVoicePanel !== mode);
+ });
+ };
+
+ body.querySelectorAll('input[name="ttsVoiceMode"]').forEach(radio => {
+ radio.addEventListener('change', syncTtsVoiceMode);
+ });
+ syncTtsVoiceMode();
+
+ body.querySelectorAll('[data-tts-action="show-create"]').forEach(btn => btn.addEventListener('click', () => {
+ showCreateForm = true;
+ refreshModal();
+ }));
+
+ body.querySelectorAll('[data-tts-action="cancel-create"]').forEach(btn => btn.addEventListener('click', () => {
+ showCreateForm = false;
+ refreshModal();
+ }));
+
+ body.querySelectorAll('[data-tts-action="create-new"]').forEach(btn => btn.addEventListener('click', async () => {
+ const model = collectTtsModelForm(body);
+ if (!model) return;
+ markSpeechModelValidating(model);
+ ensureTtsModelRegistry().push(model);
+ await API.agentDev.createTTSModel(model);
+ Toast.info(`TTS 模型「${model.name}」开始自动校验`, 800);
+ showCreateForm = false;
+ refreshModal();
+ await finishValidation(model, model.enableAfterValidation !== false);
+ refreshModal();
+ }));
+
+ body.querySelectorAll('[data-tts-action="validate-existing"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const model = ensureTtsModelRegistry().find(item => item.id === btn.dataset.id);
+ if (!model) return;
+ markSpeechModelValidating(model);
+ refreshModal();
+ await finishValidation(model, true);
+ refreshModal();
+ });
+ });
+
+ body.querySelectorAll('[data-tts-action="toggle-existing"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const model = ensureTtsModelRegistry().find(item => item.id === btn.dataset.id);
+ if (!model) return;
+ const status = getTtsModelDisplayStatus(model);
+ if (status !== 'available' && status !== 'disabled') {
+ Toast.warning('只有校验通过的模型才可以启用或禁用');
+ return;
+ }
+ if (status === 'disabled') {
+ await API.agentDev.setTTSModelEnabled(model.id, true);
+ markSpeechModelAvailable(model, true);
+ if (!state.selectedTtsModelId) state.selectedTtsModelId = model.id;
+ Toast.success(`TTS 模型「${model.name}」已启用`);
+ } else {
+ await API.agentDev.setTTSModelEnabled(model.id, false);
+ markSpeechModelAvailable(model, false);
+ if (state.selectedTtsModelId === model.id) state.selectedTtsModelId = '';
+ Toast.info(`TTS 模型「${model.name}」已禁用`);
+ }
+ refreshModal();
+ });
+ });
+
+ body.querySelectorAll('[data-tts-action="delete-existing"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const registry = ensureTtsModelRegistry();
+ const model = registry.find(item => item.id === btn.dataset.id);
+ if (!model) return;
+ await API.agentDev.deleteTTSModel(model.id);
+ window.__ttsModelRegistry = registry.filter(item => item.id !== model.id);
+ if (state.selectedTtsModelId === model.id) state.selectedTtsModelId = '';
+ Toast.success(`TTS 模型「${model.name}」已删除`);
+ refreshModal();
+ });
+ });
+ };
+
+ const body = modal.overlay.querySelector('.modal-body');
+ if (body) {
+ lucide.createIcons({ elements: [body] });
+ bindTtsModelManagerEvents(body);
+ }
+}
+
+function getTextModelDisplayStatus(model) {
+ return getSpeechModelDisplayStatus(model);
+}
+
+function getTextStatusMeta(model) {
+ return TEXT_MODEL_STATUS_META[getTextModelDisplayStatus(model)] || TEXT_MODEL_STATUS_META.failed;
+}
+
+function getTextModelValidationError(model) {
+ const apiKey = String(model.apiKey || '').trim().toLowerCase();
+ const baseUrl = String(model.baseUrl || '').trim().toLowerCase();
+ const modelCode = String(model.modelCode || '').trim().toLowerCase();
+ if (!apiKey || apiKey.includes('invalid') || apiKey.startsWith('bad') || apiKey === 'sk-invalid') {
+ return 'Key 无效';
+ }
+ if (!/^https?:\/\//i.test(model.baseUrl || '') || /down|timeout|error|404|500/.test(baseUrl)) {
+ return '接口异常';
+ }
+ if (!modelCode || /missing|notfound|not-found|none|404/.test(modelCode)) {
+ return '模型 ID 不存在';
+ }
+ if (!Number.isFinite(Number(model.contextWindow)) || Number(model.contextWindow) < 1024) {
+ return '上下文长度无效';
+ }
+ return '';
+}
+
+function renderTextModelManagerContent(showForm = false) {
+ const models = ensureTextModelRegistry();
+ return `
+
+
+
+
文本模型凭证统一维护在这里。创建模型后系统会自动校验 API Key、接口连通性和模型 ID,状态为「可用」且已启用后才会进入智能体的文本模型下拉框。
+
+
+ ${showForm ? `
+
+ ` : `
+
+ `}
+
+
+
已添加模型
+
${models.length} 个配置
+
+
+ ${models.length ? models.map(model => {
+ const meta = getTextStatusMeta(model);
+ const status = getTextModelDisplayStatus(model);
+ const canToggle = status === 'available' || status === 'disabled';
+ return `
+
+
+
+ ${escapeHtml(model.name)}
+ ${escapeHtml(meta.label)}
+
+
+ Provider:${escapeHtml(model.provider)}
+ 模型 ID:${escapeHtml(model.modelCode)}
+ 协议:${escapeHtml(model.protocol || 'openai-compatible')}
+
+
+ Base URL:${escapeHtml(model.baseUrl)}
+ API Key:${escapeHtml(maskSecret(model.apiKey))}
+ 上下文:${escapeHtml(model.contextWindow || '-')} tokens
+ 最大输出:${escapeHtml(model.maxOutputTokens || '-')} tokens
+
+
+ Secret Key:${escapeHtml(maskSecret(model.secretKey))}
+ 更新时间:${escapeHtml(model.updatedAt || '-')}
+ ${model.validatedAt ? `最近校验:${escapeHtml(model.validatedAt)} ` : ''}
+
+ ${model.errorMessage ? `
失败原因:${escapeHtml(model.errorMessage)}
` : ''}
+
+
+ 重新校验
+
+ ${status === 'disabled' ? '启用' : '禁用'}
+
+
+
+ 删除
+
+
+
+ `;
+ }).join('') : `
+
+
+ 暂无文本模型
+ 创建并自动校验成功后,智能体即可选择该文本模型。
+
+ 添加模型
+
+
+ `}
+
+
+ `;
+}
+
+function collectTextModelForm(body) {
+ const name = body.querySelector('#textModelName')?.value.trim();
+ const provider = body.querySelector('#textProviderName')?.value.trim();
+ const modelCode = body.querySelector('#textModelCode')?.value.trim();
+ const protocol = body.querySelector('#textProtocol')?.value || 'openai-compatible';
+ const baseUrl = body.querySelector('#textBaseUrl')?.value.trim();
+ const apiKey = body.querySelector('#textApiKey')?.value.trim();
+ const secretKey = body.querySelector('#textSecretKey')?.value.trim();
+ const contextWindow = Number.parseInt(body.querySelector('#textContextWindow')?.value || '128000', 10);
+ const maxOutputTokens = Number.parseInt(body.querySelector('#textMaxOutputTokens')?.value || '4096', 10);
+ const enableAfterValidation = body.querySelector('#textEnableAfterValidation')?.checked !== false;
+ if (!name || !provider || !modelCode || !baseUrl || !apiKey) {
+ Toast.warning('请填写模型名称、Provider、模型 ID、Base URL 和 API Key');
+ return null;
+ }
+ if (!/^https?:\/\//i.test(baseUrl)) {
+ Toast.warning('Base URL 需要以 http:// 或 https:// 开头');
+ return null;
+ }
+ if (!Number.isFinite(contextWindow) || contextWindow < 1024) {
+ Toast.warning('上下文长度至少为 1024 tokens');
+ return null;
+ }
+ if (!Number.isFinite(maxOutputTokens) || maxOutputTokens < 256) {
+ Toast.warning('最大输出 Token 至少为 256');
+ return null;
+ }
+ return {
+ id: `text-${Date.now()}`,
+ name,
+ provider,
+ modelCode,
+ protocol,
+ baseUrl,
+ apiKey,
+ secretKey,
+ contextWindow,
+ maxOutputTokens,
+ enableAfterValidation,
+ status: 'validating',
+ enabled: false,
+ updatedAt: getNowText(),
+ };
+}
+
+function validateTextModelConnection(model) {
+ const validationError = getTextModelValidationError(model);
+ if (validationError) {
+ return new Promise(resolve => setTimeout(() => resolve({ success: false, message: validationError }), 750));
+ }
+ const validate = API.agentDev.validateTextModel || API.agentDev.testTextModel;
+ if (!validate) {
+ return Promise.resolve({ success: true, data: model });
+ }
+ return validate(model.id, {
+ provider: model.provider,
+ modelCode: model.modelCode,
+ protocol: model.protocol,
+ baseUrl: model.baseUrl,
+ contextWindow: model.contextWindow,
+ maxOutputTokens: model.maxOutputTokens,
+ });
+}
+
+function openTextModelManagerModal(state, rerender) {
+ let showCreateForm = false;
+ const modal = createModal({
+ title: '文本模型管理',
+ size: 'xl',
+ showCancel: false,
+ confirmText: '关闭',
+ content: renderTextModelManagerContent(showCreateForm),
+ onConfirm: () => true,
+ });
+
+ const refreshModal = () => {
+ const body = modal.overlay.querySelector('.modal-body');
+ if (!body) return;
+ body.innerHTML = renderTextModelManagerContent(showCreateForm);
+ lucide.createIcons({ elements: [body] });
+ bindTextModelManagerEvents(body);
+ rerender();
+ };
+
+ const finishValidation = async (model, shouldEnable = true) => {
+ const result = await validateTextModelConnection(model);
+ if (result.success === false) {
+ markSpeechModelFailed(model, result.message || '文本模型连接失败');
+ Toast.error(model.errorMessage);
+ } else {
+ markSpeechModelAvailable(model, shouldEnable);
+ if (model.enabled && !state.selectedTextModelId) state.selectedTextModelId = model.id;
+ Toast.success(`文本模型「${model.name}」校验通过`);
+ }
+ };
+
+ const bindTextModelManagerEvents = (body) => {
+ body.querySelectorAll('[data-text-action="show-create"]').forEach(btn => btn.addEventListener('click', () => {
+ showCreateForm = true;
+ refreshModal();
+ }));
+
+ body.querySelectorAll('[data-text-action="cancel-create"]').forEach(btn => btn.addEventListener('click', () => {
+ showCreateForm = false;
+ refreshModal();
+ }));
+
+ body.querySelectorAll('[data-text-action="create-new"]').forEach(btn => btn.addEventListener('click', async () => {
+ const model = collectTextModelForm(body);
+ if (!model) return;
+ markSpeechModelValidating(model);
+ ensureTextModelRegistry().push(model);
+ await API.agentDev.createTextModel(model);
+ Toast.info(`文本模型「${model.name}」开始自动校验`, 800);
+ showCreateForm = false;
+ refreshModal();
+ await finishValidation(model, model.enableAfterValidation !== false);
+ refreshModal();
+ }));
+
+ body.querySelectorAll('[data-text-action="validate-existing"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const model = ensureTextModelRegistry().find(item => item.id === btn.dataset.id);
+ if (!model) return;
+ markSpeechModelValidating(model);
+ refreshModal();
+ await finishValidation(model, true);
+ refreshModal();
+ });
+ });
+
+ body.querySelectorAll('[data-text-action="toggle-existing"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const model = ensureTextModelRegistry().find(item => item.id === btn.dataset.id);
+ if (!model) return;
+ const status = getTextModelDisplayStatus(model);
+ if (status !== 'available' && status !== 'disabled') {
+ Toast.warning('只有校验通过的模型才可以启用或禁用');
+ return;
+ }
+ if (status === 'disabled') {
+ await API.agentDev.setTextModelEnabled(model.id, true);
+ markSpeechModelAvailable(model, true);
+ if (!state.selectedTextModelId) state.selectedTextModelId = model.id;
+ Toast.success(`文本模型「${model.name}」已启用`);
+ } else {
+ await API.agentDev.setTextModelEnabled(model.id, false);
+ markSpeechModelAvailable(model, false);
+ if (state.selectedTextModelId === model.id) state.selectedTextModelId = '';
+ Toast.info(`文本模型「${model.name}」已禁用`);
+ }
+ refreshModal();
+ });
+ });
+
+ body.querySelectorAll('[data-text-action="delete-existing"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const registry = ensureTextModelRegistry();
+ const model = registry.find(item => item.id === btn.dataset.id);
+ if (!model) return;
+ await API.agentDev.deleteTextModel(model.id);
+ window.__textModelRegistry = registry.filter(item => item.id !== model.id);
+ if (state.selectedTextModelId === model.id) state.selectedTextModelId = '';
+ Toast.success(`文本模型「${model.name}」已删除`);
+ refreshModal();
+ });
+ });
+ };
+
+ const body = modal.overlay.querySelector('.modal-body');
+ if (body) {
+ lucide.createIcons({ elements: [body] });
+ bindTextModelManagerEvents(body);
+ }
+}
+
function renderPromptEditor(prompt) {
return `
@@ -14054,16 +18422,9 @@ function renderPromptEditor(prompt) {
}
function renderKnowledgeBaseSection(items) {
- return renderSection(`知识库 ${items.length}/10`, `
-
-
已挂载知识库会参与 RAG 检索和答案补充
-
- 知识库权重配置
- 添加知识库
-
-
+ return renderSection('知识库', `
- ${items.map(item => `
+ ${items.length ? items.map((item, index) => `
@@ -14071,25 +18432,165 @@ function renderKnowledgeBaseSection(items) {
${escapeHtml(item.desc)}
权重 ${escapeHtml(item.weight)}
-
+
- `).join('')}
+ `).join('') : `
+
+
+ 暂无挂载知识库
+
+ `}
+
+ `, '', `
+
+ 知识库权重配置
+ 添加知识库
`);
}
-function renderWebSearchSection(modes) {
- return renderSection('联网搜索', `
-
- ${modes.map(mode => `
-
-
- ${escapeHtml(mode.title)}
- ${escapeHtml(mode.desc)}
-
- `).join('')}
-
- `);
+function getKnowledgeBaseCatalog() {
+ const catalog = Array.isArray(window.__knowledgeBaseCatalog) && window.__knowledgeBaseCatalog.length
+ ? window.__knowledgeBaseCatalog
+ : AGENT_KNOWLEDGE_BASE_CATALOG;
+ return catalog.map(item => ({
+ id: item.id,
+ name: item.name,
+ description: item.description || item.desc || '',
+ docs: item.docs || item.docCount || 0,
+ size: item.size || '',
+ typeName: item.typeName || '文档搜索',
+ status: item.status || 'ready',
+ tags: Array.isArray(item.tags) ? item.tags : [],
+ }));
+}
+
+function getKnowledgeBaseStatusLabel(status) {
+ const map = {
+ ready: '已就绪',
+ processing: '处理中',
+ uploading: '上传中',
+ error: '异常',
+ };
+ return map[status] || status || '未知';
+}
+
+function openKnowledgeBasePickerModal(state, rerender) {
+ const catalog = getKnowledgeBaseCatalog();
+ const attached = state.knowledgeBases || [];
+ const selectedIds = new Set(attached.map(item => item.id || item.name));
+ let searchValue = '';
+
+ const modal = createModal({
+ title: '添加知识库',
+ size: 'xl',
+ confirmText: '挂载所选',
+ cancelText: '取消',
+ content: `
+
+ `,
+ onConfirm: () => {
+ if (!selectedIds.size) {
+ Toast.warning('请先选择需要挂载的知识库');
+ return false;
+ }
+ const previousById = new Map(attached.map(item => [item.id || item.name, item]));
+ state.knowledgeBases = catalog
+ .filter(item => selectedIds.has(item.id))
+ .map(item => {
+ const existing = previousById.get(item.id);
+ return {
+ id: item.id,
+ name: item.name,
+ desc: item.description,
+ weight: existing?.weight || '1.00',
+ docs: item.docs,
+ status: item.status,
+ };
+ });
+ Toast.success(`已挂载 ${state.knowledgeBases.length} 个知识库`);
+ rerender();
+ return true;
+ },
+ });
+
+ const overlay = modal.overlay;
+ const listEl = overlay.querySelector('#agentKbPickerList');
+ const searchInput = overlay.querySelector('#agentKbPickerSearch');
+
+ function getFilteredCatalog() {
+ const keyword = searchValue.trim().toLowerCase();
+ if (!keyword) return catalog;
+ return catalog.filter(item => [
+ item.name,
+ item.description,
+ item.typeName,
+ ...item.tags,
+ ].some(value => String(value || '').toLowerCase().includes(keyword)));
+ }
+
+ function renderList() {
+ const filtered = getFilteredCatalog();
+ listEl.innerHTML = filtered.length ? filtered.map(item => `
+
+
+
+
+ ${escapeHtml(item.name)}
+ ${escapeHtml(item.description)}
+
+ ${escapeHtml(item.typeName)}
+ ${Number(item.docs || 0).toLocaleString()} 文档
+ ${escapeHtml(item.size)}
+
+
+ ${escapeHtml(getKnowledgeBaseStatusLabel(item.status))}
+
+ `).join('') : `
+
+
+ 没有找到匹配的知识库
+
+ `;
+
+ listEl.querySelectorAll('input[type="checkbox"]').forEach(input => {
+ input.addEventListener('change', () => {
+ if (input.checked) {
+ selectedIds.add(input.value);
+ } else {
+ selectedIds.delete(input.value);
+ }
+ renderList();
+ renderAgentIcons(overlay);
+ });
+ });
+ }
+
+ searchInput?.addEventListener('input', event => {
+ searchValue = event.target.value;
+ renderList();
+ renderAgentIcons(overlay);
+ });
+
+ overlay.querySelector('#agentCreateKbFromPicker')?.addEventListener('click', () => {
+ modal.close?.();
+ navigateTo('kb-create');
+ });
+
+ renderList();
+ renderAgentIcons(overlay);
}
function renderMiniAppSection(title, items, iconName) {
@@ -14110,14 +18611,48 @@ function renderMiniAppSection(title, items, iconName) {
`);
}
-function renderMcpSection() {
- return renderSection('MCP服务 0', `
-
-
-
MCP服务 0
-
智能体可以通过标准化协议(MCP)连接企业内部服务API并发起调用
-
-
+function getMcpStatusMeta(service) {
+ return MCP_SERVICE_STATUS_META[service?.status || 'pending'] || MCP_SERVICE_STATUS_META.pending;
+}
+
+function renderMcpSection(state) {
+ const library = ensureMcpServiceLibrary();
+ const selectedIds = new Set(state.selectedMcpServiceIds || []);
+ const services = library.filter(item => selectedIds.has(item.id));
+ return renderSection('MCP服务', `
+
+ 智能体可以通过标准化协议 MCP 连接企业内部服务 API,并将服务工具暴露给模型调用。
+ 添加/管理 MCP 服务
+
+
+ ${services.length ? services.map(item => {
+ const meta = getMcpStatusMeta(item);
+ const tools = Array.isArray(item.tools) ? item.tools.filter(tool => tool.name) : [];
+ return `
+
+
+
+
${escapeHtml(item.name)} ${escapeHtml(item.namespace || 'default')}
+
${escapeHtml(item.desc)}
+
+ ${escapeHtml(item.transport || 'Streamable HTTP')}
+ ${tools.length} 个工具
+ ${escapeHtml(item.endpoint || '-')}
+
+
+
${escapeHtml(meta.label)}
+
+
+
+ `;
+ }).join('') : `
+
+
+ 暂无已挂载 MCP 服务
+ 从 0 开始创建 MCP 服务,连接通过后可挂载到当前智能体。
+ 创建/选择 MCP 服务
+
+ `}
`);
}
@@ -14141,7 +18676,6 @@ function bindWorkbenchEvents(container, state, rerender) {
'voice-enabled': 'voiceEnabled',
'asr-enabled': 'asrEnabled',
'tts-enabled': 'ttsEnabled',
- 'realtime-correction': 'realtimeCorrectionEnabled',
'tts-filter': 'ttsFilterEnabled',
'interrupt-primary': 'interruptPrimaryEnabled',
'interrupt-secondary': 'interruptSecondaryEnabled',
@@ -14159,30 +18693,9 @@ function bindWorkbenchEvents(container, state, rerender) {
});
});
- container.querySelectorAll('input[name="textModelMode"]').forEach(radio => {
- radio.addEventListener('change', () => {
- if (!radio.checked) return;
- state.selectedTextModelMode = radio.value;
- const group = radio.value === 'recommended' ? '推荐模型' : '更多模型';
- const candidates = AGENT_CONFIG_MOCK.understand.textModels.filter(item => item.group === group);
- state.selectedTextModelId = candidates[0]?.id || state.selectedTextModelId;
- rerender();
- });
- });
-
- container.querySelectorAll('input[name="webSearchMode"]').forEach(radio => {
- radio.addEventListener('change', () => {
- if (!radio.checked) return;
- state.searchMode = radio.value;
- rerender();
- });
- });
-
container.querySelectorAll('[data-action="text-model"]').forEach(select => {
select.addEventListener('change', () => {
state.selectedTextModelId = select.value;
- const current = AGENT_CONFIG_MOCK.understand.textModels.find(item => item.id === select.value);
- if (current) state.selectedTextModelMode = current.group === '更多模型' ? 'more' : 'recommended';
rerender();
});
});
@@ -14197,8 +18710,6 @@ function bindWorkbenchEvents(container, state, rerender) {
container.querySelectorAll('[data-action="tts-model"]').forEach(select => {
select.addEventListener('change', () => {
state.selectedTtsModelId = select.value;
- const matchedGroup = AGENT_CONFIG_MOCK.voice.ttsGroups.find(group => group.models.some(model => model.id === select.value));
- state.selectedTtsGroupId = matchedGroup?.id || state.selectedTtsGroupId;
rerender();
});
});
@@ -14210,10 +18721,81 @@ function bindWorkbenchEvents(container, state, rerender) {
});
});
- container.querySelectorAll('[data-action="scene-language"]').forEach(select => {
+ container.querySelectorAll('[data-action="interaction-language"]').forEach(select => {
select.addEventListener('change', () => {
- state.selectedSceneLanguage = select.value;
- window.__selectedSceneLanguage = select.value;
+ state.selectedInteractionLanguage = select.value;
+ window.__selectedInteractionLanguage = select.value;
+ rerender();
+ });
+ });
+
+ container.querySelectorAll('[data-action="delete-variable"]').forEach(btn => {
+ btn.addEventListener('click', event => {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ const variable = (state.variables || []).find(item => item.id === btn.dataset.variableId);
+ if (!variable) return;
+ state.variables = (state.variables || []).filter(item => item.id !== variable.id);
+ Toast.success(variable.name ? `变量 ${variable.name} 已删除` : '变量已删除');
+ rerender();
+ });
+ });
+
+ container.querySelectorAll('[data-variable-field]').forEach(input => {
+ input.addEventListener('input', () => {
+ const variable = (state.variables || []).find(item => item.id === input.dataset.variableId);
+ if (!variable) return;
+ variable[input.dataset.variableField] = input.value;
+ scheduleAgentConfigAutoSave(state);
+ });
+ });
+
+ container.querySelectorAll('[data-action="remove-kb"]').forEach(btn => {
+ btn.addEventListener('click', event => {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ const index = Number.parseInt(btn.dataset.kbIndex, 10);
+ const list = state.knowledgeBases || [];
+ const target = list[index];
+ if (!target) return;
+ state.knowledgeBases = list.filter((_, itemIndex) => itemIndex !== index);
+ Toast.success(`知识库「${target.name}」已移除`);
+ rerender();
+ });
+ });
+
+ container.querySelectorAll('[data-action="remove-command"]').forEach(btn => {
+ btn.addEventListener('click', event => {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ const commandId = btn.dataset.commandId;
+ const command = ensureCommandLibrary().find(item => item.id === commandId);
+ state.selectedCommandIds = (state.selectedCommandIds || []).filter(id => id !== commandId);
+ Toast.success(command?.name ? `指令「${command.name}」已从当前智能体移除` : '指令已移除');
+ rerender();
+ });
+ });
+
+ container.querySelectorAll('[data-action="remove-plugin"]').forEach(btn => {
+ btn.addEventListener('click', event => {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ const pluginId = btn.dataset.pluginId;
+ const plugin = ensurePluginLibrary().find(item => item.id === pluginId);
+ state.selectedPluginIds = (state.selectedPluginIds || []).filter(id => id !== pluginId);
+ Toast.success(plugin?.name ? `插件「${plugin.name}」已从当前智能体移除` : '插件已移除');
+ rerender();
+ });
+ });
+
+ container.querySelectorAll('[data-action="remove-mcp-service"]').forEach(btn => {
+ btn.addEventListener('click', event => {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ const serviceId = btn.dataset.mcpId;
+ const service = ensureMcpServiceLibrary().find(item => item.id === serviceId);
+ state.selectedMcpServiceIds = (state.selectedMcpServiceIds || []).filter(id => id !== serviceId);
+ Toast.success(service?.name ? `MCP 服务「${service.name}」已从当前智能体移除` : 'MCP 服务已移除');
rerender();
});
});
@@ -14231,26 +18813,63 @@ function bindWorkbenchEvents(container, state, rerender) {
}
if (action === 'publish') {
state.app.status = 'published';
+ autoSaveAgentConfig(state);
Toast.success('应用已提交发布,预计 2-5 分钟生效');
rerender();
return;
}
if (action === 'run') {
- state.preview = 'running';
- updatePreviewOnly(container, state.preview);
- Toast.success('运行预览已启动');
+ autoSaveAgentConfig(state);
+ clearAgentRunTimer(state);
+ const session = getRunSession(state);
+ const configuredKeys = getRuntimeApiKeysForApp(state);
+ const activeKeyStillExists = configuredKeys.some(key => key.value === session.apiKey);
+ if (!activeKeyStillExists) {
+ session.apiKeyConfirmed = false;
+ session.apiKey = '';
+ session.apiKeyName = '';
+ session.apiKeyProvider = '';
+ }
+ session.isRecording = false;
+ session.elapsedSeconds = 0;
+ session.latencyMs = null;
+ session.transcript = '';
+ session.aiResponse = '';
+ state.preview = state.voiceEnabled
+ ? (session.apiKeyConfirmed ? 'voice-runtime' : 'voice-auth')
+ : (session.apiKeyConfirmed ? 'text-runtime' : 'text-auth');
+ updatePreviewOnly(container, state);
+ Toast.success(state.voiceEnabled
+ ? '配置已自动保存,请配置运行 API Key'
+ : '配置已自动保存,请配置文字运行 API Key');
return;
}
if (action === 'add-kb') {
- navigateTo('knowledge-base');
+ openKnowledgeBasePickerModal(state, rerender);
return;
}
if (action === 'knowledge-weight') {
openKnowledgeWeightModal(state, rerender);
return;
}
- if (action === 'scene') {
- openSceneTemplateModal(state, rerender);
+ if (action === 'asr-model-manager') {
+ openAsrModelManagerModal(state, rerender);
+ return;
+ }
+ if (action === 'tts-model-manager') {
+ openTtsModelManagerModal(state, rerender);
+ return;
+ }
+ if (action === 'text-model-manager') {
+ openTextModelManagerModal(state, rerender);
+ return;
+ }
+ if (action === 'add-variable' || action === 'variable') {
+ state.variables = [
+ ...(state.variables || []),
+ { id: `var-${Date.now()}`, name: '', desc: '' },
+ ];
+ rerender();
return;
}
if (action === 'hotwords') {
@@ -14269,22 +18888,18 @@ function bindWorkbenchEvents(container, state, rerender) {
openPluginConfigModal(state, rerender);
return;
}
- if (action === 'edge-collapse') {
- state.edgeAlgorithmExpanded = !state.edgeAlgorithmExpanded;
- rerender();
+ if (action === 'mcp-config') {
+ openMcpConfigModal(state, rerender);
return;
}
const actionText = {
history: '打开历史记录',
api: '打开 API 接入文档',
- license: '打开 License 购买页',
model: '切换模型',
variable: '打开变量配置',
setting: '打开设置',
remove: '已移除当前项',
- mcp: '添加 MCP 服务',
'add-app': '添加应用',
- 'correction-upload': '上传词表',
};
Toast.info(actionText[action] || '操作已触发');
event.stopPropagation();
@@ -14292,6 +18907,183 @@ function bindWorkbenchEvents(container, state, rerender) {
});
}
+function bindRuntimePreviewEvents(container, state, refreshPreview) {
+ const pane = container.querySelector('.bailian-preview-pane');
+ if (!pane) return;
+ const session = getRunSession(state);
+
+ pane.querySelector('[data-preview-action="confirm-runtime-key"]')?.addEventListener('click', () => {
+ const select = pane.querySelector('[data-preview-field="runtime-key-select"]');
+ const keys = getRuntimeApiKeysForApp(state);
+ const selected = keys.find(item => item.id === select?.value);
+ if (!selected) {
+ Toast.warning('请先选择系统设置中已保存的 API Key');
+ select?.focus();
+ return;
+ }
+ session.apiKey = selected.value;
+ session.apiKeyName = `${selected.name} · ${maskSecret(selected.value)}`;
+ session.apiKeyProvider = selected.provider;
+ session.apiKeyConfirmed = true;
+ session.isRecording = false;
+ state.preview = state.preview === 'text-auth' || !state.voiceEnabled ? 'text-runtime' : 'voice-runtime';
+ Toast.success(state.preview === 'text-runtime' ? 'API Key 已配置,可以开始文字聊天' : 'API Key 已配置,可以开始语音测试');
+ refreshPreview();
+ });
+
+ pane.querySelector('[data-preview-action="go-settings-api-keys"]')?.addEventListener('click', () => {
+ clearAgentRunTimer(state);
+ session.isRecording = false;
+ Toast.info('请在系统设置中添加并保存 API Key');
+ navigateTo('settings');
+ });
+
+ pane.querySelector('[data-preview-action="change-runtime-key"]')?.addEventListener('click', () => {
+ clearAgentRunTimer(state);
+ session.isRecording = false;
+ session.apiKeyConfirmed = false;
+ state.preview = state.voiceEnabled ? 'voice-auth' : 'text-auth';
+ refreshPreview();
+ });
+
+ pane.querySelector('[data-preview-action="toggle-call"]')?.addEventListener('click', () => {
+ if (!session.apiKeyConfirmed) {
+ Toast.warning('请先配置运行 API Key');
+ state.preview = state.voiceEnabled ? 'voice-auth' : 'text-auth';
+ refreshPreview();
+ return;
+ }
+ if (session.isRecording) {
+ stopVoiceRuntimeCall(container, state);
+ Toast.info('本轮语音通话已结束', 900);
+ } else {
+ startVoiceRuntimeCall(container, state);
+ Toast.info('已开始录音,请开始说话', 900);
+ }
+ });
+
+ pane.querySelector('[data-preview-action="toggle-mic"]')?.addEventListener('click', () => {
+ session.micMuted = !session.micMuted;
+ Toast.info(session.micMuted ? '麦克风已关闭' : '麦克风已打开', 800);
+ if (!syncVoiceRuntimeDom(container, state)) {
+ refreshPreview();
+ }
+ });
+
+ pane.querySelector('[data-preview-action="voice-device"]')?.addEventListener('click', () => {
+ Toast.info('当前为 MacBook Pro 麦克风,后端接入后可读取浏览器音频设备列表', 1200);
+ });
+
+ pane.querySelector('[data-preview-action="voice-runtime-settings"]')?.addEventListener('click', () => {
+ Toast.info('语音体验设置入口,后续可接入采样率、降噪和播放设备配置', 1200);
+ });
+
+ pane.querySelector('[data-preview-action="copy-voice-ai"]')?.addEventListener('click', async () => {
+ const text = session.aiResponse || session.aiDisplayResponse || '';
+ if (!text) return;
+ try {
+ await navigator.clipboard?.writeText(text);
+ Toast.success('AI 回复已复制');
+ } catch (error) {
+ Toast.warning('当前浏览器不允许自动复制,请手动复制');
+ }
+ });
+
+ pane.querySelector('[data-preview-action="regenerate-voice-ai"]')?.addEventListener('click', () => {
+ const base = session.aiResponse || '我已经准备好继续语音对话。';
+ const next = `${base}(重新生成版本)`;
+ session.aiResponse = next;
+ session.aiDisplayResponse = '';
+ session.lastVoiceAiResponse = next;
+ Toast.info('正在重新生成语音回复', 800);
+ revealVoiceReply(container, state, next);
+ });
+
+ pane.querySelector('[data-preview-field="runtime-text-input"]')?.addEventListener('input', event => {
+ session.textInput = event.target.value;
+ });
+
+ pane.querySelector('[data-preview-field="runtime-text-input"]')?.addEventListener('keydown', event => {
+ if (event.key !== 'Enter' || event.shiftKey) return;
+ event.preventDefault();
+ pane.querySelector('[data-preview-action="send-text-runtime"]')?.click();
+ });
+
+ pane.querySelector('[data-preview-action="send-text-runtime"]')?.addEventListener('click', () => {
+ const input = pane.querySelector('[data-preview-field="runtime-text-input"]');
+ const value = input?.value.trim() || '';
+ if (!session.apiKeyConfirmed) {
+ Toast.warning('请先配置运行 API Key');
+ state.preview = 'text-auth';
+ refreshPreview();
+ return;
+ }
+ if (!value) {
+ Toast.warning('请输入测试内容');
+ input?.focus();
+ return;
+ }
+ session.chatMessages = [
+ ...(Array.isArray(session.chatMessages) ? session.chatMessages : []),
+ { role: 'user', content: value },
+ ];
+ session.textInput = '';
+ session.textPending = true;
+ session.latencyMs = null;
+ refreshPreview();
+ window.setTimeout(() => {
+ const reply = buildMockTextReply(value);
+ session.textPending = false;
+ session.latencyMs = 390 + (value.length * 13) % 260;
+ const messages = Array.isArray(session.chatMessages) ? session.chatMessages : [];
+ messages.push({
+ role: 'ai',
+ sourceText: value,
+ retryCount: 0,
+ content: reply,
+ displayContent: '',
+ });
+ session.chatMessages = messages;
+ const messageIndex = messages.length - 1;
+ refreshPreview();
+ revealTextReply(container, state, messageIndex, reply);
+ }, 850);
+ });
+
+ pane.querySelectorAll('[data-preview-action="copy-ai-message"]').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ const index = Number.parseInt(btn.dataset.messageIndex, 10);
+ const message = (session.chatMessages || [])[index];
+ const text = message?.content || '';
+ if (!text) return;
+ try {
+ await navigator.clipboard?.writeText(text);
+ Toast.success('AI 回复已复制');
+ } catch (error) {
+ Toast.warning('当前浏览器不允许自动复制,请手动复制');
+ }
+ });
+ });
+
+ pane.querySelectorAll('[data-preview-action="regenerate-ai-message"]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const index = Number.parseInt(btn.dataset.messageIndex, 10);
+ const message = (session.chatMessages || [])[index];
+ if (!message || message.role !== 'ai') return;
+ const nextRetry = (message.retryCount || 0) + 1;
+ const sourceText = message.sourceText || '这条消息';
+ const reply = buildMockTextReply(sourceText, nextRetry);
+ message.retryCount = nextRetry;
+ message.content = reply;
+ message.displayContent = '';
+ session.latencyMs = 420 + (nextRetry * 37) % 180;
+ Toast.info('正在重新生成回复', 800);
+ refreshPreview();
+ revealTextReply(container, state, index, reply);
+ });
+ });
+}
+
function updateActiveStepTab(container, stepId) {
container.querySelectorAll('.bailian-step-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.step === stepId);
@@ -14334,143 +19126,37 @@ function bindStepScrollSpy(container, state) {
});
}
-function updatePreviewOnly(container, previewState) {
+function updatePreviewOnly(container, state) {
const preview = container.querySelector('.bailian-preview-pane');
if (!preview) return;
const temp = document.createElement('div');
- temp.innerHTML = renderPreviewPane(previewState);
+ temp.innerHTML = renderPreviewPane(state);
preview.replaceWith(temp.firstElementChild);
renderAgentIcons(container.querySelector('.bailian-preview-pane'));
+ bindRuntimePreviewEvents(container, state, () => updatePreviewOnly(container, state));
+ const textMessageArea = container.querySelector('.agent-text-message-area');
+ if (textMessageArea) {
+ textMessageArea.scrollTop = textMessageArea.scrollHeight;
+ }
+ const voiceScrollArea = container.querySelector('.agent-voice-stage') || container.querySelector('.agent-voice-conversation');
+ if (voiceScrollArea) {
+ voiceScrollArea.scrollTop = voiceScrollArea.scrollHeight;
+ }
}
-function bindPromptCounter(container) {
+function bindPromptCounter(container, state) {
const editor = container.querySelector('#agentPromptEditor');
const counter = container.querySelector('#promptCounter');
if (!editor || !counter) return;
const update = () => {
counter.textContent = `${editor.value.length}/8000`;
+ if (state) {
+ state.prompt = editor.value;
+ scheduleAgentConfigAutoSave(state);
+ }
};
editor.addEventListener('input', update);
- update();
-}
-
-function openSceneTemplateModal(state, rerender) {
- let pendingSceneId = state.selectedSceneId;
- let pendingVersionId = state.selectedSceneVersion;
- let pendingLanguageId = state.selectedSceneLanguage;
-
- if (typeof createModal !== 'function') {
- Toast.info('场景模板选择器暂不可用');
- return;
- }
-
- const modal = createModal({
- title: '切换场景模板',
- size: 'xl',
- confirmText: '应用模板',
- cancelText: '取消',
- content: `
-
-
-
选择场景
-
- ${AGENT_SCENE_TEMPLATES.map(scene => `
-
-
-
-
- ${escapeHtml(scene.name)}
- ${escapeHtml(scene.desc)}
-
-
- `).join('')}
-
-
-
-
选择版本
-
- ${AGENT_SCENE_VERSIONS.map(version => `
-
-
- ${escapeHtml(version.name)}
- ${escapeHtml(version.desc)}
-
- `).join('')}
-
-
-
-
对话语言 *
-
- ${AGENT_SCENE_LANGUAGES.map(language => `
-
-
- ${escapeHtml(language.name)}
- ${escapeHtml(language.desc)}
-
- `).join('')}
-
-
-
- `,
- onConfirm: () => {
- if (!pendingLanguageId || !AGENT_SCENE_LANGUAGES.some(language => language.id === pendingLanguageId)) {
- Toast.warning('请选择对话语言');
- return false;
- }
- state.selectedSceneId = pendingSceneId;
- state.selectedSceneVersion = pendingVersionId;
- state.selectedSceneLanguage = pendingLanguageId;
- window.__selectedSceneTemplateId = pendingSceneId;
- window.__selectedSceneVersion = pendingVersionId;
- window.__selectedSceneLanguage = pendingLanguageId;
- const scene = getSelectedScene(state);
- const version = getSelectedSceneVersion(state);
- const language = getSelectedSceneLanguage(state);
- Toast.success(`已切换至「${scene.name} · ${version.name} · ${language.name}」`);
- rerender();
- return true;
- },
- });
-
- const overlay = modal.overlay;
- const syncSceneOptions = () => {
- overlay.querySelectorAll('.bailian-scene-option').forEach(option => {
- option.classList.toggle('selected', option.dataset.sceneId === pendingSceneId);
- option.querySelector('input').checked = option.dataset.sceneId === pendingSceneId;
- });
- };
- const syncVersionOptions = () => {
- overlay.querySelectorAll('.bailian-scene-version-option').forEach(option => {
- option.classList.toggle('selected', option.dataset.versionId === pendingVersionId);
- option.querySelector('input').checked = option.dataset.versionId === pendingVersionId;
- });
- };
- const syncLanguageOptions = () => {
- overlay.querySelectorAll('.bailian-scene-language-option').forEach(option => {
- option.classList.toggle('selected', option.dataset.languageId === pendingLanguageId);
- option.querySelector('input').checked = option.dataset.languageId === pendingLanguageId;
- });
- };
-
- overlay.querySelectorAll('.bailian-scene-option').forEach(option => {
- option.addEventListener('click', () => {
- pendingSceneId = option.dataset.sceneId;
- syncSceneOptions();
- });
- });
- overlay.querySelectorAll('.bailian-scene-version-option').forEach(option => {
- option.addEventListener('click', () => {
- pendingVersionId = option.dataset.versionId;
- syncVersionOptions();
- });
- });
- overlay.querySelectorAll('.bailian-scene-language-option').forEach(option => {
- option.addEventListener('click', () => {
- pendingLanguageId = option.dataset.languageId;
- syncLanguageOptions();
- });
- });
- renderAgentIcons(overlay);
+ counter.textContent = `${editor.value.length}/8000`;
}
function openKnowledgeWeightModal(state, rerender) {
@@ -14521,8 +19207,11 @@ function openTtsFilterModal(state, rerender) {
{ id: 'square', left: '[', right: ']', label: '[ ]' },
{ id: 'curly', left: '{', right: '}', label: '{ }' },
];
- const draftPairs = (state.ttsFilterPairs || []).map(item => ({ ...item }));
- const draftSingles = [...(state.ttsFilterSingles || [])];
+ const getPairKey = (pair) => `${String(pair.left || '').trim()}__${String(pair.right || '').trim()}`;
+ const commonPairKeys = new Set(commonPairs.map(getPairKey));
+ const savedPairs = (state.ttsFilterPairs || []).map(item => ({ ...item }));
+ const draftPairs = savedPairs.filter(pair => !commonPairKeys.has(getPairKey(pair)));
+ const draftSingles = [...new Set((state.ttsFilterSingles || []).map(item => String(item || '').trim()).filter(Boolean))];
const modal = createModal({
title: '语音合成播报过滤设置',
@@ -14537,7 +19226,7 @@ function openTtsFilterModal(state, rerender) {
${commonPairs.map(item => `
- pair.left === item.left && pair.right === item.right && pair.enabled !== false) ? 'checked' : ''}>
+ pair.left === item.left && pair.right === item.right && pair.enabled !== false) ? 'checked' : ''}>
${escapeHtml(item.label)}
将过滤 ${escapeHtml(item.left)} ... ${escapeHtml(item.right)} 内容
@@ -14566,16 +19255,26 @@ function openTtsFilterModal(state, rerender) {
const pairRows = Array.from(modal.overlay.querySelectorAll('[data-filter-pair-row]'));
const singleRows = Array.from(modal.overlay.querySelectorAll('[data-filter-single-row]'));
const nextPairs = [];
+ const nextPairKeys = new Set();
+ const pushPair = (leftValue, rightValue) => {
+ const left = String(leftValue || '').trim();
+ const right = String(rightValue || '').trim();
+ if (!left && !right) return;
+ const key = `${left}__${right}`;
+ if (nextPairKeys.has(key)) return;
+ nextPairKeys.add(key);
+ nextPairs.push({ enabled: true, left, right });
+ };
commonPairs.forEach(item => {
const enabled = modal.overlay.querySelector(`[data-common-pair="${item.id}"]`)?.checked;
- if (enabled) nextPairs.push({ enabled: true, left: item.left, right: item.right });
+ if (enabled) pushPair(item.left, item.right);
});
pairRows.forEach(row => {
const left = row.querySelector('[data-left]')?.value.trim() || '';
const right = row.querySelector('[data-right]')?.value.trim() || '';
- if (left || right) nextPairs.push({ enabled: true, left, right });
+ pushPair(left, right);
});
- const nextSingles = singleRows.map(row => row.querySelector('[data-single]')?.value.trim() || '').filter(Boolean);
+ const nextSingles = [...new Set(singleRows.map(row => row.querySelector('[data-single]')?.value.trim() || '').filter(Boolean))];
state.ttsFilterEnabled = true;
state.ttsFilterPairs = nextPairs;
state.ttsFilterSingles = nextSingles;
@@ -14651,200 +19350,909 @@ function openTtsFilterModal(state, rerender) {
}
function openCommandConfigModal(state, rerender) {
- const baseCommands = AGENT_CONFIG_MOCK.skills.commands;
- const customCommands = state.customCommands || [];
+ const library = ensureCommandLibrary().map(item => ({ ...item }));
+ const selectedIds = new Set((state.selectedCommandIds || []).filter(id => library.some(item => item.id === id)));
+ let draftForm = null;
+ const replyModes = ['根据执行情况回复', '执行前回复', '执行后回复', '不回复'];
+ const paramTypes = ['string', 'number', 'boolean', 'object', 'array'];
+
+ const createEmptyCommand = () => ({
+ id: '',
+ icon: 'terminal-square',
+ commandType: '自定义指令',
+ name: '',
+ desc: '',
+ example: '',
+ paramName: '',
+ paramType: 'string',
+ paramDesc: '',
+ replyMode: '根据执行情况回复',
+ count: 1,
+ });
+
const modal = createModal({
title: '指令配置',
size: 'xl',
- confirmText: '确定',
+ confirmText: '保存挂载',
cancelText: '取消',
content: `
-
下发设备控制指令由端侧执行,可在这里查看系统指令、编辑已选指令并添加自定义指令类型。
-
-
+
先创建指令到指令库,再勾选挂载到当前智能体。删除当前智能体里的指令只会移除挂载关系,不会删除指令库定义。
+
+
`,
onConfirm: () => {
- const rows = Array.from(modal.overlay.querySelectorAll('[data-custom-command-row]'));
- const next = rows.map(row => ({
- icon: 'settings-2',
- name: row.querySelector('[data-name]')?.value.trim() || '',
- desc: row.querySelector('[data-desc]')?.value.trim() || '',
- example: row.querySelector('[data-example]')?.value.trim() || '',
- params: row.querySelector('[data-params]')?.value.trim() || '',
- count: 1,
- })).filter(item => item.name);
- state.customCommands = next;
- Toast.success('自定义指令已保存');
+ if (draftForm) {
+ Toast.warning('请先保存或取消正在编辑的指令');
+ return false;
+ }
+ window.__agentCommandLibrary = library.map(item => ({ ...item }));
+ state.selectedCommandIds = Array.from(selectedIds).filter(id => library.some(item => item.id === id));
+ Toast.success(`已保存 ${state.selectedCommandIds.length} 个已挂载指令`);
rerender();
return true;
},
});
const overlay = modal.overlay;
- const list = overlay.querySelector('#customCommandList');
- const draft = (state.customCommands || []).map(item => ({ ...item }));
+ const libraryPanel = overlay.querySelector('#commandLibraryPanel');
+ const editorPanel = overlay.querySelector('#commandEditorPanel');
- function renderRows() {
- list.innerHTML = draft.length ? draft.map((item, index) => `
-
-
-
-
-
-
+ function renderLibraryPanel() {
+ libraryPanel.innerHTML = `
+
+
+
指令库
+
+ ${library.length} 条
+
+
+ 创建指令
+
+
+
+
+ ${library.length ? library.map(item => `
+
+
+
+
+
+
+
+ ${escapeHtml(item.name)}
+ ${escapeHtml(item.commandType || '自定义指令')}
+
+
${escapeHtml(item.desc)}
+
+ 示例:${escapeHtml(item.example || '-')}
+ 参数:${escapeHtml(item.paramName || '-')}
+ 回复:${escapeHtml(item.replyMode || '根据执行情况回复')}
+
+
+
+
+
+
+
+ `).join('') : `
+
+
+ 暂无可用指令
+ 点击创建指令,从 0 开始配置当前智能体可调用的能力。
+ 创建指令
+
+ `}
+
- `).join('') : '
暂无自定义指令
';
+ `;
+ }
+
+ function renderEditorPanel() {
+ if (!draftForm) {
+ editorPanel.innerHTML = '';
+ return;
+ }
+ editorPanel.innerHTML = `
+
+ `;
+ }
+
+ function readDraftForm() {
+ overlay.querySelectorAll('[data-command-field]').forEach(field => {
+ draftForm[field.dataset.commandField] = field.value.trim();
+ });
+ }
+
+ function saveDraftForm() {
+ readDraftForm();
+ if (!draftForm.name || !draftForm.desc || !draftForm.commandType) {
+ Toast.warning('请填写指令类型、指令名称和指令说明');
+ return;
+ }
+ const duplicate = library.some(item => item.id !== draftForm.id && item.name === draftForm.name);
+ if (duplicate) {
+ Toast.warning('指令名称已存在');
+ return;
+ }
+ if (!draftForm.id) {
+ draftForm.id = `cmd-${Date.now()}`;
+ library.push({ ...draftForm, icon: 'terminal-square', count: 1 });
+ } else {
+ const index = library.findIndex(item => item.id === draftForm.id);
+ if (index >= 0) library[index] = { ...library[index], ...draftForm, icon: library[index].icon || 'terminal-square', count: 1 };
+ }
+ selectedIds.add(draftForm.id);
+ Toast.success(`指令「${draftForm.name}」已保存`);
+ draftForm = null;
+ renderAll();
}
function bindCommandEvents() {
- overlay.querySelectorAll('[data-delete-command]').forEach(btn => {
- btn.addEventListener('click', () => {
- draft.splice(Number(btn.dataset.deleteCommand), 1);
- renderRows();
- bindCommandEvents();
- renderAgentIcons(overlay);
+ overlay.querySelector('#createCommandBtn')?.addEventListener('click', () => {
+ draftForm = createEmptyCommand();
+ renderAll();
+ });
+ overlay.querySelector('#createCommandEmptyBtn')?.addEventListener('click', () => {
+ draftForm = createEmptyCommand();
+ renderAll();
+ });
+ overlay.querySelectorAll('.bailian-command-select input').forEach(input => {
+ input.addEventListener('change', () => {
+ if (input.checked) selectedIds.add(input.value);
+ else selectedIds.delete(input.value);
+ renderAll();
});
});
+ overlay.querySelectorAll('.bailian-command-library-card').forEach(card => {
+ card.addEventListener('click', event => {
+ if (event.target.closest('button') || event.target.closest('label')) return;
+ const input = card.querySelector('.bailian-command-select input');
+ if (!input) return;
+ if (selectedIds.has(input.value)) selectedIds.delete(input.value);
+ else selectedIds.add(input.value);
+ renderAll();
+ });
+ });
+ overlay.querySelectorAll('[data-edit-command]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const target = library.find(item => item.id === btn.dataset.editCommand);
+ if (!target) return;
+ draftForm = { ...target };
+ renderAll();
+ });
+ });
+ overlay.querySelectorAll('[data-delete-library-command]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const commandId = btn.dataset.deleteLibraryCommand;
+ const index = library.findIndex(item => item.id === commandId);
+ if (index < 0) return;
+ const [deleted] = library.splice(index, 1);
+ selectedIds.delete(commandId);
+ if (draftForm?.id === commandId) draftForm = null;
+ Toast.success(`指令「${deleted.name}」已删除`);
+ renderAll();
+ });
+ });
+ overlay.querySelector('#cancelCommandEditBtn')?.addEventListener('click', () => {
+ draftForm = null;
+ renderAll();
+ });
+ overlay.querySelector('#cancelCommandEditBottomBtn')?.addEventListener('click', () => {
+ draftForm = null;
+ renderAll();
+ });
+ overlay.querySelector('#saveCommandEditBtn')?.addEventListener('click', saveDraftForm);
}
- overlay.querySelector('#addCustomCommandBtn')?.addEventListener('click', () => {
- draft.push({ name: '', desc: '', example: '', params: '' });
- renderRows();
+ function renderAll() {
+ renderLibraryPanel();
+ renderEditorPanel();
bindCommandEvents();
renderAgentIcons(overlay);
- });
+ }
- renderRows();
- bindCommandEvents();
- renderAgentIcons(overlay);
+ renderAll();
}
function openPluginConfigModal(state, rerender) {
- const basePlugins = AGENT_CONFIG_MOCK.skills.plugins;
- const customPlugins = (state.customPlugins || []).map(item => ({ ...item }));
+ const library = ensurePluginLibrary().map(item => ({ ...item }));
+ const selectedIds = new Set((state.selectedPluginIds || []).filter(id => library.some(item => item.id === id)));
+ let draftForm = null;
+ const authTypes = ['无鉴权', 'API Key', 'Bearer Token', 'OAuth2', '自定义签名'];
+ const executionModes = ['系统执行', '端侧执行', '工作流执行', '第三方服务执行'];
+ const statusOptions = ['系统执行', '已启用', '待接入', '已禁用'];
+
+ const createEmptyPlugin = () => ({
+ id: '',
+ icon: 'plug',
+ pluginType: '自定义插件',
+ name: '',
+ desc: '',
+ provider: '',
+ endpoint: '',
+ authType: 'API Key',
+ executionMode: '系统执行',
+ status: '系统执行',
+ });
+
const modal = createModal({
title: '插件配置',
size: 'xl',
- confirmText: '确定',
+ confirmText: '保存挂载',
cancelText: '取消',
content: `
-
插件默认由系统执行,也可以在这里单独增删自定义插件并控制是否启用。
-
-
系统插件
- ${basePlugins.length} 项
-
-
- ${basePlugins.map(item => `
-
-
-
- ${escapeHtml(item.name)}
- ${escapeHtml(item.desc)}
-
- ${escapeHtml(item.status || '系统执行')}
-
- `).join('')}
-
-
-
自定义插件
- + 添加
-
-
+
先创建插件到插件库,再勾选挂载到当前智能体。删除当前智能体里的插件只会移除挂载关系,不会删除插件库定义。
+
+
`,
onConfirm: () => {
- const rows = Array.from(modal.overlay.querySelectorAll('[data-custom-plugin-row]'));
- state.customPlugins = rows.map(row => ({
- icon: row.querySelector('[data-icon]')?.value.trim() || 'plug',
- name: row.querySelector('[data-name]')?.value.trim() || '',
- desc: row.querySelector('[data-desc]')?.value.trim() || '',
- status: row.querySelector('[data-status]')?.value.trim() || '系统执行',
- })).filter(item => item.name);
- Toast.success('插件配置已保存');
+ if (draftForm) {
+ Toast.warning('请先保存或取消正在编辑的插件');
+ return false;
+ }
+ window.__agentPluginLibrary = library.map(item => ({ ...item }));
+ state.selectedPluginIds = Array.from(selectedIds).filter(id => library.some(item => item.id === id));
+ Toast.success(`已保存 ${state.selectedPluginIds.length} 个已挂载插件`);
rerender();
return true;
},
});
- renderAgentIcons(modal.overlay);
const overlay = modal.overlay;
- const list = overlay.querySelector('#customPluginList');
- const draft = customPlugins;
+ const libraryPanel = overlay.querySelector('#pluginLibraryPanel');
+ const editorPanel = overlay.querySelector('#pluginEditorPanel');
- function renderRows() {
- list.innerHTML = draft.length ? draft.map((item, index) => `
-
-
-
-
-
-
-
+ function renderLibraryPanel() {
+ libraryPanel.innerHTML = `
+
+
+
插件库
+
+ ${library.length} 个
+
+
+ 创建插件
+
+
+
+
+ ${library.length ? library.map(item => `
+
+
+
+
+
+
+
+ ${escapeHtml(item.name)}
+ ${escapeHtml(item.pluginType || '自定义插件')}
+
+
${escapeHtml(item.desc)}
+
+ Provider:${escapeHtml(item.provider || '-')}
+ 调用地址:${escapeHtml(item.endpoint || '-')}
+ 鉴权:${escapeHtml(item.authType || '-')}
+ 执行:${escapeHtml(item.executionMode || '系统执行')}
+
+
+
+
+
+
+
+ `).join('') : `
+
+
+ 暂无可用插件
+ 点击创建插件,从 0 开始配置当前智能体可调用的外部能力。
+ 创建插件
+
+ `}
+
- `).join('') : '
暂无自定义插件
';
+ `;
}
- function bindRows() {
- overlay.querySelectorAll('[data-delete-plugin]').forEach(btn => {
+ function renderEditorPanel() {
+ if (!draftForm) {
+ editorPanel.innerHTML = '';
+ return;
+ }
+ editorPanel.innerHTML = `
+
+ `;
+ }
+
+ function readDraftForm() {
+ overlay.querySelectorAll('[data-plugin-field]').forEach(field => {
+ draftForm[field.dataset.pluginField] = field.value.trim();
+ });
+ }
+
+ function saveDraftForm() {
+ readDraftForm();
+ if (!draftForm.name || !draftForm.desc || !draftForm.pluginType) {
+ Toast.warning('请填写插件类型、插件名称和插件说明');
+ return;
+ }
+ const duplicate = library.some(item => item.id !== draftForm.id && item.name === draftForm.name);
+ if (duplicate) {
+ Toast.warning('插件名称已存在');
+ return;
+ }
+ if (!draftForm.id) {
+ draftForm.id = `plugin-${Date.now()}`;
+ library.push({ ...draftForm, icon: draftForm.icon || 'plug' });
+ } else {
+ const index = library.findIndex(item => item.id === draftForm.id);
+ if (index >= 0) library[index] = { ...library[index], ...draftForm, icon: draftForm.icon || library[index].icon || 'plug' };
+ }
+ selectedIds.add(draftForm.id);
+ Toast.success(`插件「${draftForm.name}」已保存`);
+ draftForm = null;
+ renderAll();
+ }
+
+ function bindPluginEvents() {
+ overlay.querySelector('#createPluginBtn')?.addEventListener('click', () => {
+ draftForm = createEmptyPlugin();
+ renderAll();
+ });
+ overlay.querySelector('#createPluginEmptyBtn')?.addEventListener('click', () => {
+ draftForm = createEmptyPlugin();
+ renderAll();
+ });
+ overlay.querySelectorAll('.bailian-command-select input').forEach(input => {
+ input.addEventListener('change', () => {
+ if (input.checked) selectedIds.add(input.value);
+ else selectedIds.delete(input.value);
+ renderAll();
+ });
+ });
+ overlay.querySelectorAll('.bailian-command-library-card').forEach(card => {
+ card.addEventListener('click', event => {
+ if (event.target.closest('button') || event.target.closest('label')) return;
+ const input = card.querySelector('.bailian-command-select input');
+ if (!input) return;
+ if (selectedIds.has(input.value)) selectedIds.delete(input.value);
+ else selectedIds.add(input.value);
+ renderAll();
+ });
+ });
+ overlay.querySelectorAll('[data-edit-plugin]').forEach(btn => {
btn.addEventListener('click', () => {
- draft.splice(Number(btn.dataset.deletePlugin), 1);
- renderRows();
- bindRows();
- renderAgentIcons(overlay);
+ const target = library.find(item => item.id === btn.dataset.editPlugin);
+ if (!target) return;
+ draftForm = { ...target };
+ renderAll();
+ });
+ });
+ overlay.querySelectorAll('[data-delete-library-plugin]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const pluginId = btn.dataset.deleteLibraryPlugin;
+ const index = library.findIndex(item => item.id === pluginId);
+ if (index < 0) return;
+ const [deleted] = library.splice(index, 1);
+ selectedIds.delete(pluginId);
+ if (draftForm?.id === pluginId) draftForm = null;
+ Toast.success(`插件「${deleted.name}」已删除`);
+ renderAll();
+ });
+ });
+ overlay.querySelector('#cancelPluginEditBtn')?.addEventListener('click', () => {
+ draftForm = null;
+ renderAll();
+ });
+ overlay.querySelector('#cancelPluginEditBottomBtn')?.addEventListener('click', () => {
+ draftForm = null;
+ renderAll();
+ });
+ overlay.querySelector('#savePluginEditBtn')?.addEventListener('click', saveDraftForm);
+ }
+
+ function renderAll() {
+ renderLibraryPanel();
+ renderEditorPanel();
+ bindPluginEvents();
+ renderAgentIcons(overlay);
+ }
+
+ renderAll();
+}
+
+function getMcpConnectionError(service) {
+ const endpoint = String(service.endpoint || '').trim();
+ const authType = String(service.authType || '').trim();
+ const credential = String(service.credential || '').trim();
+ if (!service.name || !service.desc) return '服务名称和服务说明不能为空';
+ if (!endpoint) return 'Server URL 不能为空';
+ if (!/^https?:\/\//i.test(endpoint) && !/^wss?:\/\//i.test(endpoint) && !/^stdio:/i.test(endpoint)) {
+ return 'Server URL 格式不正确';
+ }
+ if (/fail|error|timeout|invalid|404|500/i.test(endpoint)) {
+ return '服务不可访问';
+ }
+ if (authType !== '无鉴权' && !credential) {
+ return '缺少鉴权凭证';
+ }
+ return '';
+}
+
+function openMcpConfigModal(state, rerender) {
+ const library = ensureMcpServiceLibrary().map(item => ({
+ ...item,
+ tools: Array.isArray(item.tools) ? item.tools.map(tool => ({ ...tool })) : [],
+ }));
+ const selectedIds = new Set((state.selectedMcpServiceIds || []).filter(id => library.some(item => item.id === id)));
+ let draftForm = null;
+ const transports = ['Streamable HTTP', 'SSE', 'WebSocket', 'stdio(后端代理)'];
+ const authTypes = ['无鉴权', 'API Key', 'Bearer Token', 'OAuth2', '自定义 Header'];
+ const statusOptions = [
+ { id: 'pending', name: '待连接' },
+ { id: 'available', name: '可用' },
+ { id: 'failed', name: '连接失败' },
+ { id: 'disabled', name: '已禁用' },
+ ];
+
+ const createEmptyTool = () => ({
+ id: `mcp-tool-${Date.now()}-${Math.random().toString(16).slice(2)}`,
+ name: '',
+ desc: '',
+ inputSchema: '',
+ });
+
+ const createEmptyService = () => ({
+ id: '',
+ name: '',
+ desc: '',
+ transport: 'Streamable HTTP',
+ endpoint: '',
+ namespace: '',
+ authType: '无鉴权',
+ credential: '',
+ status: 'pending',
+ errorMessage: '',
+ tools: [createEmptyTool()],
+ });
+
+ const modal = createModal({
+ title: 'MCP 服务配置',
+ size: 'xl',
+ confirmText: '保存挂载',
+ cancelText: '取消',
+ content: `
+
+
先创建 MCP 服务到服务库,再测试连接。连接可用后可挂载到当前智能体,模型运行时可调用该服务暴露的工具。
+
+
+
+ `,
+ onConfirm: () => {
+ if (draftForm) {
+ Toast.warning('请先保存或取消正在编辑的 MCP 服务');
+ return false;
+ }
+ window.__agentMcpServiceLibrary = library.map(item => ({
+ ...item,
+ tools: Array.isArray(item.tools) ? item.tools.map(tool => ({ ...tool })) : [],
+ }));
+ state.selectedMcpServiceIds = Array.from(selectedIds).filter(id => library.some(item => item.id === id));
+ Toast.success(`已保存 ${state.selectedMcpServiceIds.length} 个已挂载 MCP 服务`);
+ rerender();
+ return true;
+ },
+ });
+
+ const overlay = modal.overlay;
+ const libraryPanel = overlay.querySelector('#mcpLibraryPanel');
+ const editorPanel = overlay.querySelector('#mcpEditorPanel');
+
+ function renderLibraryPanel() {
+ libraryPanel.innerHTML = `
+
+
+
MCP 服务库
+
+ ${library.length} 个
+
+
+ 创建服务
+
+
+
+
+ ${library.length ? library.map(item => {
+ const meta = getMcpStatusMeta(item);
+ const tools = Array.isArray(item.tools) ? item.tools.filter(tool => tool.name) : [];
+ return `
+
+
+
+
+
+
+
+ ${escapeHtml(item.name)}
+ ${escapeHtml(item.namespace || 'default')}
+ ${escapeHtml(meta.label)}
+
+
${escapeHtml(item.desc)}
+
+ 连接:${escapeHtml(item.transport || 'Streamable HTTP')}
+ URL:${escapeHtml(item.endpoint || '-')}
+ 鉴权:${escapeHtml(item.authType || '无鉴权')}
+ 工具:${tools.length} 个
+
+ ${item.status === 'failed' && item.errorMessage ? `
${escapeHtml(item.errorMessage)}
` : ''}
+
+
+ 测试连接
+
+
+
+
+ `;
+ }).join('') : `
+
+
+ 暂无可用 MCP 服务
+ 点击创建服务,配置连接地址、鉴权方式和工具清单。
+ 创建服务
+
+ `}
+
+
+ `;
+ }
+
+ function renderEditorPanel() {
+ if (!draftForm) {
+ editorPanel.innerHTML = '';
+ return;
+ }
+ editorPanel.innerHTML = `
+
+ `;
+ renderToolRows();
+ }
+
+ function renderToolRows() {
+ const list = overlay.querySelector('#mcpToolList');
+ if (!list || !draftForm) return;
+ const tools = Array.isArray(draftForm.tools) ? draftForm.tools : [];
+ list.innerHTML = tools.length ? tools.map((tool, index) => `
+
+
+
+
+
+
+ `).join('') : '
暂无工具,可先保存服务,后续由后端自动同步 MCP tools/list。
';
+ bindToolEvents();
+ renderAgentIcons(overlay);
+ }
+
+ function readDraftForm() {
+ overlay.querySelectorAll('[data-mcp-field]').forEach(field => {
+ draftForm[field.dataset.mcpField] = field.value.trim();
+ });
+ draftForm.tools = Array.from(overlay.querySelectorAll('[data-mcp-tool-row]')).map(row => ({
+ id: draftForm.tools?.[Number(row.dataset.mcpToolRow)]?.id || createEmptyTool().id,
+ name: row.querySelector('[data-mcp-tool-field="name"]')?.value.trim() || '',
+ desc: row.querySelector('[data-mcp-tool-field="desc"]')?.value.trim() || '',
+ inputSchema: row.querySelector('[data-mcp-tool-field="inputSchema"]')?.value.trim() || '',
+ })).filter(tool => tool.name || tool.desc || tool.inputSchema);
+ }
+
+ function saveDraftForm() {
+ readDraftForm();
+ if (!draftForm.name || !draftForm.desc || !draftForm.endpoint) {
+ Toast.warning('请填写服务名称、服务说明和 Server URL');
+ return;
+ }
+ const duplicate = library.some(item => item.id !== draftForm.id && item.name === draftForm.name);
+ if (duplicate) {
+ Toast.warning('MCP 服务名称已存在');
+ return;
+ }
+ if (!draftForm.id) {
+ draftForm.id = `mcp-${Date.now()}`;
+ library.push({ ...draftForm, tools: draftForm.tools.map(tool => ({ ...tool })) });
+ } else {
+ const index = library.findIndex(item => item.id === draftForm.id);
+ if (index >= 0) library[index] = { ...library[index], ...draftForm, tools: draftForm.tools.map(tool => ({ ...tool })) };
+ }
+ if (draftForm.status === 'available') selectedIds.add(draftForm.id);
+ else selectedIds.delete(draftForm.id);
+ Toast.success(`MCP 服务「${draftForm.name}」已保存`);
+ draftForm = null;
+ renderAll();
+ }
+
+ function testService(serviceId) {
+ const target = library.find(item => item.id === serviceId);
+ if (!target) return;
+ target.status = 'validating';
+ target.errorMessage = '连接测试中';
+ renderAll();
+ window.setTimeout(() => {
+ const service = library.find(item => item.id === serviceId);
+ if (!service) return;
+ const error = getMcpConnectionError(service);
+ if (error) {
+ service.status = 'failed';
+ service.errorMessage = error;
+ selectedIds.delete(service.id);
+ Toast.warning(`MCP 服务「${service.name}」连接失败:${error}`);
+ } else {
+ service.status = 'available';
+ service.errorMessage = '';
+ selectedIds.add(service.id);
+ Toast.success(`MCP 服务「${service.name}」连接可用,已挂载到当前智能体`);
+ }
+ renderAll();
+ }, 450);
+ }
+
+ function bindToolEvents() {
+ overlay.querySelectorAll('[data-delete-mcp-tool]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ readDraftForm();
+ draftForm.tools.splice(Number(btn.dataset.deleteMcpTool), 1);
+ renderToolRows();
});
});
}
- overlay.querySelector('#addCustomPluginBtn')?.addEventListener('click', () => {
- draft.push({ icon: 'plug', name: '', desc: '', status: '系统执行' });
- renderRows();
- bindRows();
- renderAgentIcons(overlay);
- });
+ function bindMcpEvents() {
+ overlay.querySelector('#createMcpBtn')?.addEventListener('click', () => {
+ draftForm = createEmptyService();
+ renderAll();
+ });
+ overlay.querySelector('#createMcpEmptyBtn')?.addEventListener('click', () => {
+ draftForm = createEmptyService();
+ renderAll();
+ });
+ overlay.querySelectorAll('.bailian-command-select input').forEach(input => {
+ input.addEventListener('change', () => {
+ const service = library.find(item => item.id === input.value);
+ if (!service || service.status !== 'available') {
+ input.checked = false;
+ Toast.warning('请先测试连接通过后再挂载 MCP 服务');
+ return;
+ }
+ if (input.checked) selectedIds.add(input.value);
+ else selectedIds.delete(input.value);
+ renderAll();
+ });
+ });
+ overlay.querySelectorAll('.bailian-command-library-card').forEach(card => {
+ card.addEventListener('click', event => {
+ if (event.target.closest('button') || event.target.closest('label')) return;
+ const input = card.querySelector('.bailian-command-select input');
+ if (!input || input.disabled) {
+ Toast.warning('请先测试连接通过后再挂载 MCP 服务');
+ return;
+ }
+ if (selectedIds.has(input.value)) selectedIds.delete(input.value);
+ else selectedIds.add(input.value);
+ renderAll();
+ });
+ });
+ overlay.querySelectorAll('[data-test-mcp]').forEach(btn => {
+ btn.addEventListener('click', () => testService(btn.dataset.testMcp));
+ });
+ overlay.querySelectorAll('[data-edit-mcp]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const target = library.find(item => item.id === btn.dataset.editMcp);
+ if (!target) return;
+ draftForm = {
+ ...target,
+ tools: Array.isArray(target.tools) && target.tools.length ? target.tools.map(tool => ({ ...tool })) : [createEmptyTool()],
+ };
+ renderAll();
+ });
+ });
+ overlay.querySelectorAll('[data-delete-library-mcp]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const serviceId = btn.dataset.deleteLibraryMcp;
+ const index = library.findIndex(item => item.id === serviceId);
+ if (index < 0) return;
+ const [deleted] = library.splice(index, 1);
+ selectedIds.delete(serviceId);
+ if (draftForm?.id === serviceId) draftForm = null;
+ Toast.success(`MCP 服务「${deleted.name}」已删除`);
+ renderAll();
+ });
+ });
+ overlay.querySelector('#cancelMcpEditBtn')?.addEventListener('click', () => {
+ draftForm = null;
+ renderAll();
+ });
+ overlay.querySelector('#cancelMcpEditBottomBtn')?.addEventListener('click', () => {
+ draftForm = null;
+ renderAll();
+ });
+ overlay.querySelector('#saveMcpEditBtn')?.addEventListener('click', saveDraftForm);
+ overlay.querySelector('#addMcpToolBtn')?.addEventListener('click', () => {
+ readDraftForm();
+ draftForm.tools = [...(draftForm.tools || []), createEmptyTool()];
+ renderToolRows();
+ });
+ }
- renderRows();
- bindRows();
- renderAgentIcons(overlay);
+ function renderAll() {
+ renderLibraryPanel();
+ renderEditorPanel();
+ bindMcpEvents();
+ renderAgentIcons(overlay);
+ }
+
+ renderAll();
}
function openRenameModal(state, rerender) {
@@ -14891,22 +20299,6 @@ function getAgentStatus(status) {
return map[status] || map.draft;
}
-function getSelectedScene(state) {
- return AGENT_SCENE_TEMPLATES.find(scene => scene.id === state.selectedSceneId)
- || AGENT_SCENE_TEMPLATES.find(scene => scene.id === 'toy')
- || AGENT_SCENE_TEMPLATES[0];
-}
-
-function getSelectedSceneVersion(state) {
- return AGENT_SCENE_VERSIONS.find(version => version.id === state.selectedSceneVersion)
- || AGENT_SCENE_VERSIONS[0];
-}
-
-function getSelectedSceneLanguage(state) {
- return AGENT_SCENE_LANGUAGES.find(language => language.id === state.selectedSceneLanguage)
- || AGENT_SCENE_LANGUAGES[0];
-}
-
function escapeHtml(value) {
const div = document.createElement('div');
div.textContent = String(value ?? '');
@@ -15733,7 +21125,7 @@ registerPage('agent-manage-voice', function(container) {
/**
* TalkingQ Platform - 知识库管理页面
* 包含知识库列表、添加知识库弹窗、筛选、分页等功能
- *
+ *
* API预留:
* - GET /api/v1/knowledge-bases - 获取知识库列表
* - POST /api/v1/knowledge-bases - 创建知识库
@@ -15751,6 +21143,7 @@ registerPage('knowledge-base', function(container) {
let sortField = 'updatedAt';
let sortOrder = 'desc';
let isLoading = true;
+ let hasStartedInitialLoad = false;
let kbData = [];
// ==================== API预留 - 后端接口定义 ====================
@@ -15761,21 +21154,21 @@ registerPage('knowledge-base', function(container) {
* Response: { list: [], total, page, pageSize }
*/
const API_GET_LIST = '/api/v1/knowledge-bases';
-
+
/**
* 删除知识库
* DELETE /api/v1/knowledge-bases/:id
* Response: { success: true }
*/
const API_DELETE = '/api/v1/knowledge-bases/:id';
-
+
/**
* 重建索引
* POST /api/v1/knowledge-bases/:id/rebuild
* Response: { success: true, taskId: string }
*/
const API_REBUILD = '/api/v1/knowledge-bases/:id/rebuild';
-
+
/**
* 批量删除
* DELETE /api/v1/knowledge-bases/batch
@@ -15783,7 +21176,7 @@ registerPage('knowledge-base', function(container) {
* Response: { success: true, deleted: number }
*/
const API_BATCH_DELETE = '/api/v1/knowledge-bases/batch';
-
+
/**
* 导出知识库
* GET /api/v1/knowledge-bases/:id/export
@@ -15803,11 +21196,40 @@ registerPage('knowledge-base', function(container) {
{ id: 'kb-008', name: '音乐教育资源库', description: '儿童音乐教育资源', docs: 560, size: '65MB', type: 'video_search', typeName: '音视频搜索', status: 'uploading', createdAt: '2026-05-12 10:00:00', updatedAt: '2026-05-12 18:00:00', tags: ['音乐', '教育'] },
];
- // 初始化数据
- kbData = [...mockKbData];
+ // 初始化数据。页面刷新后回到 mock;SPA 内新建/编辑/删除会保留在全局目录中。
+ kbData = Array.isArray(window.__knowledgeBaseCatalog) && window.__knowledgeBaseCatalog.length
+ ? window.__knowledgeBaseCatalog.map(item => ({ ...item, tags: [...(item.tags || [])] }))
+ : mockKbData.map(item => ({ ...item, tags: [...(item.tags || [])] }));
+ window.__knowledgeBaseCatalog = kbData;
+
+ function syncKnowledgeBaseCatalog() {
+ window.__knowledgeBaseCatalog = kbData;
+ }
+
+ function getKbById(id) {
+ return kbData.find(kb => kb.id === id);
+ }
+
+ function getNowText() {
+ const date = new Date();
+ const pad = value => String(value).padStart(2, '0');
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
+ }
+
+ function downloadTextFile(filename, content, mime = 'application/json;charset=utf-8') {
+ const blob = new Blob([content], { type: mime });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
+ }
// ==================== 工具函数 ====================
-
+
/**
* 获取状态标签信息
*/
@@ -15871,7 +21293,7 @@ registerPage('knowledge-base', function(container) {
*/
function generatePagination(current, total) {
if (total <= 1) return '';
-
+
let html = '';
const maxVisible = 5;
let start = Math.max(1, current - Math.floor(maxVisible / 2));
@@ -15899,7 +21321,7 @@ registerPage('knowledge-base', function(container) {
}
// ==================== 数据处理 ====================
-
+
/**
* 筛选数据
*/
@@ -15929,6 +21351,13 @@ registerPage('knowledge-base', function(container) {
if (sortField === 'name' || sortField === 'typeName') {
return sortOrder === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA);
}
+ if (sortField === 'createdAt' || sortField === 'updatedAt') {
+ valA = Date.parse(valA || '') || 0;
+ valB = Date.parse(valB || '') || 0;
+ } else {
+ valA = Number(valA) || 0;
+ valB = Number(valB) || 0;
+ }
return sortOrder === 'asc' ? valA - valB : valB - valA;
});
@@ -15936,7 +21365,7 @@ registerPage('knowledge-base', function(container) {
}
// ==================== 渲染函数 ====================
-
+
/**
* 渲染页面
*/
@@ -15984,7 +21413,7 @@ registerPage('knowledge-base', function(container) {
- 添加知识库
+ 创建知识库
@@ -16177,7 +21606,7 @@ registerPage('knowledge-base', function(container) {
暂无知识库
点击上方「添加知识库」开始创建您的第一个知识库
-
+
创建知识库
@@ -16206,10 +21635,10 @@ registerPage('knowledge-base', function(container) {
}
// ==================== 事件绑定 ====================
-
+
function bindEvents() {
// 添加知识库
- container.querySelector('#addKbBtn')?.addEventListener('click', showCreateKbModal);
+ container.querySelector('#addKbBtn')?.addEventListener('click', () => navigateTo('kb-create'));
// 搜索
container.querySelector('#kbSearchInput')?.addEventListener('input', debounce((e) => {
@@ -16219,11 +21648,14 @@ registerPage('knowledge-base', function(container) {
renderPage();
}, 300));
- // 模拟加载
- setTimeout(() => {
- isLoading = false;
- renderPage();
- }, 500);
+ // 模拟加载,只在首次进入页面时触发一次,避免重渲染打断下拉菜单等交互。
+ if (isLoading && !hasStartedInitialLoad) {
+ hasStartedInitialLoad = true;
+ setTimeout(() => {
+ isLoading = false;
+ renderPage();
+ }, 500);
+ }
}
/**
@@ -16238,7 +21670,7 @@ registerPage('knowledge-base', function(container) {
}
// ==================== 添加知识库弹窗 ====================
-
+
/**
* 显示创建知识库弹窗
*/
@@ -16371,11 +21803,17 @@ registerPage('knowledge-base', function(container) {
`;
document.body.appendChild(modalOverlay);
+ document.body.classList.add('modal-open');
lucide.createIcons({ elements: [modalOverlay] });
const close = () => {
modalOverlay.classList.remove('open');
- setTimeout(() => modalOverlay.remove(), 200);
+ setTimeout(() => {
+ modalOverlay.remove();
+ if (!document.querySelector('.modal-overlay')) {
+ document.body.classList.remove('modal-open');
+ }
+ }, 200);
};
// 关闭事件
@@ -16423,7 +21861,7 @@ registerPage('knowledge-base', function(container) {
} else {
selectedForAdd.add(id);
}
-
+
// 更新确认按钮状态
const confirmBtn = document.getElementById('confirmAddKbBtn');
const countBadge = document.getElementById('selectedCountBadge');
@@ -16433,7 +21871,7 @@ registerPage('knowledge-base', function(container) {
if (countBadge) {
countBadge.textContent = selectedForAdd.size > 0 ? `(${selectedForAdd.size})` : '';
}
-
+
renderKbList();
};
@@ -16442,12 +21880,12 @@ registerPage('knowledge-base', function(container) {
Toast.warning('请先选择知识库');
return;
}
-
+
const selectedNames = Array.from(selectedForAdd).map(id => {
const kb = kbData.find(k => k.id === id);
return kb ? kb.name : id;
});
-
+
Toast.success(`已添加 ${selectedForAdd.size} 个知识库到当前应用`);
close();
};
@@ -16461,7 +21899,7 @@ registerPage('knowledge-base', function(container) {
}
// ==================== 全局操作函数 ====================
-
+
// 筛选
window.__kbFilter = function(filter) {
currentFilter = filter;
@@ -16520,7 +21958,7 @@ registerPage('knowledge-base', function(container) {
window.__kbToggleDropdown = function(id, btn, e) {
if (e) e.stopPropagation();
-
+
const dropdown = btn.closest('.dropdown');
const menu = dropdown.querySelector('.dropdown-menu');
const isOpen = dropdown.classList.contains('open');
@@ -16536,7 +21974,7 @@ registerPage('knowledge-base', function(container) {
if (!isOpen) {
dropdown.classList.add('open');
menu.style.display = 'block';
-
+
__kbCloseDropdownHandler = (ev) => {
if (!dropdown.contains(ev.target)) {
dropdown.classList.remove('open');
@@ -16550,6 +21988,148 @@ registerPage('knowledge-base', function(container) {
}
};
+ function openEditKbModal(id) {
+ const kb = getKbById(id);
+ if (!kb) {
+ Toast.warning('未找到该知识库');
+ return;
+ }
+
+ createModal({
+ title: '编辑知识库',
+ size: 'lg',
+ content: `
+
+ 知识库名称 *
+
+
+
+ 知识库描述
+
+
+
+
+ 知识库类型
+
+ 文档搜索
+ 数据查询
+ 图片问答
+ 音视频搜索
+
+
+
+ 状态
+
+ 已就绪
+ 处理中
+ 上传中
+ 异常
+
+
+
+
+ 标签
+
+
+
+
+ 前端 mock 会立即更新当前列表;后端接入后调用 PUT /api/v1/knowledge-bases/:id。
+
+ `,
+ confirmText: '保存',
+ onConfirm: () => {
+ const name = document.getElementById('kbEditName')?.value.trim();
+ if (!name) {
+ Toast.warning('请输入知识库名称');
+ return false;
+ }
+ const type = document.getElementById('kbEditType')?.value || kb.type;
+ const tags = (document.getElementById('kbEditTags')?.value || '')
+ .split(/[,,]/)
+ .map(tag => tag.trim())
+ .filter(Boolean);
+
+ kb.name = name;
+ kb.description = document.getElementById('kbEditDesc')?.value.trim() || '';
+ kb.type = type;
+ kb.typeName = getTypeTag(type).label;
+ kb.status = document.getElementById('kbEditStatus')?.value || kb.status;
+ kb.tags = tags;
+ kb.updatedAt = getNowText();
+ syncKnowledgeBaseCatalog();
+ renderPage();
+ Toast.success('知识库信息已更新');
+ }
+ });
+ lucide.createIcons();
+ }
+
+ function exportKnowledgeBases(ids) {
+ const items = ids.map(id => getKbById(id)).filter(Boolean);
+ if (!items.length) {
+ Toast.warning('请先选择要导出的知识库');
+ return;
+ }
+
+ createModal({
+ title: items.length > 1 ? '批量导出知识库' : '导出知识库',
+ content: `
+
+
+ 导出格式
+
+ JSON 配置数据
+ CSV 列表数据
+
+
+
+
+ 包含文档数量、数据量、状态和标签
+
+
+ 浏览器会下载到默认下载目录;后端接入后替换为 GET /api/v1/knowledge-bases/:id/export。
+
+ `,
+ confirmText: '确认导出',
+ onConfirm: () => {
+ const format = document.getElementById('kbExportFormat')?.value || 'json';
+ const includeStats = document.getElementById('kbExportIncludeStats')?.checked !== false;
+ const exportRows = items.map(item => ({
+ id: item.id,
+ name: item.name,
+ description: item.description,
+ type: item.type,
+ typeName: item.typeName,
+ ...(includeStats ? {
+ docs: item.docs,
+ size: item.size,
+ status: item.status,
+ tags: item.tags,
+ } : {}),
+ createdAt: item.createdAt,
+ updatedAt: item.updatedAt,
+ }));
+ const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
+ if (format === 'csv') {
+ const headers = Object.keys(exportRows[0]);
+ const csv = [
+ headers.join(','),
+ ...exportRows.map(row => headers.map(key => `"${String(Array.isArray(row[key]) ? row[key].join('|') : row[key] ?? '').replace(/"/g, '""')}"`).join(',')),
+ ].join('\n');
+ downloadTextFile(`knowledge-bases-${stamp}.csv`, csv, 'text/csv;charset=utf-8');
+ } else {
+ downloadTextFile(`knowledge-bases-${stamp}.json`, JSON.stringify(exportRows, null, 2));
+ }
+ Toast.success(`已导出 ${items.length} 个知识库`);
+ }
+ });
+ }
+
// 操作
window.__kbDetail = function(id) {
navigateTo('kb-detail?id=' + id);
@@ -16560,7 +22140,7 @@ registerPage('knowledge-base', function(container) {
};
window.__kbEdit = function(id) {
- Toast.info('编辑知识库:' + id);
+ openEditKbModal(id);
};
/**
@@ -16587,6 +22167,8 @@ registerPage('knowledge-base', function(container) {
// TODO: 调用 API DELETE /api/v1/knowledge-bases/:id
const idx = kbData.findIndex(k => k.id === id);
if (idx !== -1) kbData.splice(idx, 1);
+ selectedRows.delete(id);
+ syncKnowledgeBaseCatalog();
renderPage();
Toast.success('知识库已删除');
}
@@ -16611,6 +22193,12 @@ registerPage('knowledge-base', function(container) {
confirmText: '重建',
onConfirm: () => {
// TODO: 调用 API POST /api/v1/knowledge-bases/:id/rebuild
+ if (kb) {
+ kb.status = 'processing';
+ kb.updatedAt = getNowText();
+ syncKnowledgeBaseCatalog();
+ renderPage();
+ }
Toast.success('索引重建任务已启动');
}
});
@@ -16621,8 +22209,7 @@ registerPage('knowledge-base', function(container) {
* API: GET /api/v1/knowledge-bases/:id/export
*/
window.__kbExport = function(id) {
- // TODO: 调用 API GET /api/v1/knowledge-bases/:id/export
- Toast.info('正在导出知识库...');
+ exportKnowledgeBases([id]);
};
/**
@@ -16630,8 +22217,7 @@ registerPage('knowledge-base', function(container) {
* API: GET /api/v1/knowledge-bases/batch/export
*/
window.__kbBatchExport = function() {
- // TODO: 调用 API GET /api/v1/knowledge-bases/batch/export
- Toast.info(`正在导出 ${selectedRows.size} 个知识库...`);
+ exportKnowledgeBases(Array.from(selectedRows));
};
/**
@@ -16639,6 +22225,11 @@ registerPage('knowledge-base', function(container) {
* API: DELETE /api/v1/knowledge-bases/batch
*/
window.__kbBatchDelete = function() {
+ if (selectedRows.size === 0) {
+ Toast.warning('请先选择要删除的知识库');
+ return;
+ }
+ const deleteCount = selectedRows.size;
createModal({
title: '批量删除',
content: `
@@ -16660,14 +22251,17 @@ registerPage('knowledge-base', function(container) {
if (idx !== -1) kbData.splice(idx, 1);
});
selectedRows.clear();
+ syncKnowledgeBaseCatalog();
renderPage();
- Toast.success(`已删除 ${selectedRows.size + 1} 个知识库`);
+ Toast.success(`已删除 ${deleteCount} 个知识库`);
}
});
};
// 导出弹窗函数
- window.__showCreateKbModal = showCreateKbModal;
+ window.__showCreateKbModal = function() {
+ navigateTo('kb-create');
+ };
// ==================== 初始化 ====================
renderPage();
@@ -16679,7 +22273,7 @@ registerPage('knowledge-base', function(container) {
/**
* TalkingQ Platform - 创建知识库向导页面
* 包含3步流程:基础信息 -> 数据选择 -> 索引设置
- *
+ *
* API预留:
* - POST /api/v1/knowledge-bases - 创建知识库
* - POST /api/v1/knowledge-bases/validate-name - 验证名称唯一性
@@ -16702,7 +22296,7 @@ registerPage('kb-create', function(container) {
* Response: { id: string, name: string, status: string }
*/
const API_CREATE = '/api/v1/knowledge-bases';
-
+
/**
* 验证名称唯一性
* POST /api/v1/knowledge-bases/validate-name
@@ -16710,7 +22304,7 @@ registerPage('kb-create', function(container) {
* Response: { valid: boolean, message?: string }
*/
const API_VALIDATE_NAME = '/api/v1/knowledge-bases/validate-name';
-
+
/**
* 文件上传
* POST /api/v1/files/upload
@@ -16718,14 +22312,14 @@ registerPage('kb-create', function(container) {
* Response: { fileId: string, fileName: string, status: string }
*/
const API_UPLOAD = '/api/v1/files/upload';
-
+
/**
* 获取上传/解析状态
* GET /api/v1/files/status/:fileId
* Response: { status: 'uploading'|'processing'|'ready'|'error', progress?: number, error?: string }
*/
const API_FILE_STATUS = '/api/v1/files/status/:fileId';
-
+
/**
* 获取类目列表
* GET /api/v1/categories
@@ -16842,7 +22436,7 @@ registerPage('kb-create', function(container) {
];
// ==================== 工具函数 ====================
-
+
function escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g, '&')
@@ -16888,7 +22482,7 @@ registerPage('kb-create', function(container) {
}
// ==================== 草稿管理 ====================
-
+
function loadDraft() {
try {
const draft = localStorage.getItem(DRAFT_KEY);
@@ -16922,37 +22516,37 @@ registerPage('kb-create', function(container) {
}
// ==================== 验证函数 ====================
-
+
function validateStep1() {
const errors = [];
-
+
if (!formData.name.trim()) {
errors.push('请输入知识库名称');
} else if (formData.name.length < 2 || formData.name.length > 64) {
errors.push('知识库名称长度需在2-64字符之间');
}
-
+
if (!formData.type) {
errors.push('请选择知识库类型');
}
-
+
return errors;
}
function validateStep2() {
const errors = [];
-
+
if (formData.sourceType === 'upload' && formData.uploadedFiles.length === 0) {
errors.push('请上传至少一个文件');
}
-
+
if (formData.sourceType === 'upload') {
const failedFiles = formData.uploadedFiles.filter(f => f.status === 'error');
if (failedFiles.length > 0) {
errors.push(`有 ${failedFiles.length} 个文件上传失败,请处理后重试`);
}
}
-
+
return errors;
}
@@ -16961,7 +22555,7 @@ registerPage('kb-create', function(container) {
}
// ==================== 渲染函数 ====================
-
+
/**
* 渲染向导步骤
*/
@@ -16992,7 +22586,7 @@ registerPage('kb-create', function(container) {
基础知识信息
?
-
+