([]);
+ const [isKbSearching, setIsKbSearching] = useState(false);
+ const saasReady = useSaaSStore((s) => s.isLoggedIn);
+
const loadStatus = async () => {
setIsLoading(true);
setMessage(null);
@@ -88,6 +98,19 @@ export function VikingPanel() {
}
};
+ const handleKbSearch = async () => {
+ if (!kbQuery.trim()) return;
+ setIsKbSearching(true);
+ try {
+ const results = await saasClient.searchKnowledge(kbQuery, { limit: 10 });
+ setKbResults(results);
+ } catch {
+ setKbResults([]);
+ } finally {
+ setIsKbSearching(false);
+ }
+ };
+
const handleExpandL2 = async (uri: string) => {
if (expandedUri === uri) {
setExpandedUri(null);
@@ -299,6 +322,68 @@ export function VikingPanel() {
)}
+ {/* SaaS Knowledge Base Search */}
+ {saasReady && (
+
+
+
+ 知识库搜索
+
+
+ 搜索 SaaS 端共享知识库
+
+
+ setKbQuery(e.target.value)}
+ onKeyDown={(e) => e.key === 'Enter' && handleKbSearch()}
+ placeholder="搜索知识库..."
+ className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
+ />
+
+
+ {kbResults.length > 0 && (
+
+ {kbResults.map((r) => (
+
+
+
+ {r.item_title}
+
+ {r.category_name && (
+
+ {r.category_name}
+
+ )}
+ {Math.round(r.score * 100)}%
+
+
{r.content}
+ {r.keywords.length > 0 && (
+
+ {r.keywords.slice(0, 5).map((kw) => (
+ {kw}
+ ))}
+
+ )}
+
+ ))}
+
+ )}
+
+ )}
+
{/* Summary Generation */}
{status?.available && (
diff --git a/desktop/src/lib/saas-client.ts b/desktop/src/lib/saas-client.ts
index 28462b6..bea8d7f 100644
--- a/desktop/src/lib/saas-client.ts
+++ b/desktop/src/lib/saas-client.ts
@@ -70,6 +70,7 @@ export type {
IndustryInfo,
IndustryFullConfig,
AccountIndustryItem,
+ KnowledgeSearchResult,
} from './saas-types';
export { SaaSApiError } from './saas-errors';
@@ -114,6 +115,7 @@ import { installPromptMethods } from './saas-prompt';
import { installTelemetryMethods } from './saas-telemetry';
import { installBillingMethods } from './saas-billing';
import { installIndustryMethods } from './saas-industry';
+import { installKnowledgeMethods } from './saas-knowledge';
export type { UsageIncrementResult } from './saas-billing';
// Re-export billing types for convenience
@@ -448,6 +450,7 @@ installPromptMethods(SaaSClient);
installTelemetryMethods(SaaSClient);
installBillingMethods(SaaSClient);
installIndustryMethods(SaaSClient);
+installKnowledgeMethods(SaaSClient);
export { installBillingMethods };
// === API Method Type Declarations ===
@@ -511,6 +514,9 @@ export interface SaaSClient {
getIndustryFullConfig(industryId: string): Promise;
getMyIndustries(): Promise;
getAccountIndustries(accountId: string): Promise;
+
+ // --- Knowledge (saas-knowledge.ts) ---
+ searchKnowledge(query: string, opts?: { category_id?: string; limit?: number }): Promise;
}
// === Singleton ===
diff --git a/desktop/src/lib/saas-knowledge.ts b/desktop/src/lib/saas-knowledge.ts
new file mode 100644
index 0000000..4e6b3d9
--- /dev/null
+++ b/desktop/src/lib/saas-knowledge.ts
@@ -0,0 +1,25 @@
+/**
+ * SaaS Knowledge Methods — Mixin
+ *
+ * Installs knowledge-search methods onto SaaSClient.prototype.
+ */
+
+import type { KnowledgeSearchResult } from './saas-types';
+
+export function installKnowledgeMethods(ClientClass: { prototype: any }): void {
+ const proto = ClientClass.prototype;
+
+ /**
+ * Search the SaaS knowledge base.
+ */
+ proto.searchKnowledge = async function (
+ this: { request(method: string, path: string, body?: unknown): Promise },
+ query: string,
+ opts?: { category_id?: string; limit?: number },
+ ): Promise {
+ return this.request('POST', '/api/v1/knowledge/search', {
+ query,
+ ...opts,
+ });
+ };
+}
diff --git a/desktop/src/lib/saas-types.ts b/desktop/src/lib/saas-types.ts
index 7da6658..33e3504 100644
--- a/desktop/src/lib/saas-types.ts
+++ b/desktop/src/lib/saas-types.ts
@@ -272,6 +272,17 @@ export interface AccountIndustryItem {
industry_icon: string;
}
+/** Knowledge search result from POST /api/v1/knowledge/search */
+export interface KnowledgeSearchResult {
+ chunk_id: string;
+ item_id: string;
+ item_title: string;
+ category_name: string;
+ content: string;
+ score: number;
+ keywords: string[];
+}
+
/** Provider info from GET /api/v1/providers */
export interface ProviderInfo {
id: string;