fix(安全): 修复HTML导出中的XSS漏洞并清理调试日志
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
refactor(日志): 替换console.log为tracing日志系统 style(代码): 移除未使用的代码和依赖项 feat(测试): 添加端到端测试文档和CI工作流 docs(变更日志): 更新CHANGELOG.md记录0.1.0版本变更 perf(构建): 更新依赖版本并优化CI流程
This commit is contained in:
183
desktop/src/lib/embedding-client.ts
Normal file
183
desktop/src/lib/embedding-client.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* Embedding Client - Vector Embedding Operations
|
||||
*
|
||||
* Client for interacting with embedding APIs via Tauri backend.
|
||||
* Supports multiple providers: OpenAI, Zhipu, Doubao, Qwen, DeepSeek.
|
||||
*/
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
export interface EmbeddingConfig {
|
||||
provider: string;
|
||||
model: string;
|
||||
apiKey: string;
|
||||
endpoint: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface EmbeddingResponse {
|
||||
embedding: number[];
|
||||
model: string;
|
||||
usage?: {
|
||||
prompt_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EmbeddingProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
defaultModel: string;
|
||||
dimensions: number;
|
||||
}
|
||||
|
||||
const EMBEDDING_STORAGE_KEY = 'zclaw-embedding-config';
|
||||
|
||||
export function loadEmbeddingConfig(): EmbeddingConfig {
|
||||
try {
|
||||
const stored = localStorage.getItem(EMBEDDING_STORAGE_KEY);
|
||||
if (stored) {
|
||||
return JSON.parse(stored);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return {
|
||||
provider: 'local',
|
||||
model: 'tfidf',
|
||||
apiKey: '',
|
||||
endpoint: '',
|
||||
enabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function saveEmbeddingConfig(config: EmbeddingConfig): void {
|
||||
try {
|
||||
localStorage.setItem(EMBEDDING_STORAGE_KEY, JSON.stringify(config));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEmbeddingProviders(): Promise<EmbeddingProvider[]> {
|
||||
const result = await invoke<[string, string, string, number][]>('embedding_providers');
|
||||
return result.map(([id, name, defaultModel, dimensions]) => ({
|
||||
id,
|
||||
name,
|
||||
defaultModel,
|
||||
dimensions,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function createEmbedding(
|
||||
text: string,
|
||||
config?: Partial<EmbeddingConfig>
|
||||
): Promise<EmbeddingResponse> {
|
||||
const savedConfig = loadEmbeddingConfig();
|
||||
const provider = config?.provider ?? savedConfig.provider;
|
||||
const apiKey = config?.apiKey ?? savedConfig.apiKey;
|
||||
const model = config?.model ?? savedConfig.model;
|
||||
const endpoint = config?.endpoint ?? savedConfig.endpoint;
|
||||
|
||||
if (provider === 'local') {
|
||||
throw new Error('Local TF-IDF mode does not support API embedding');
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error('API Key is required for embedding');
|
||||
}
|
||||
|
||||
return invoke<EmbeddingResponse>('embedding_create', {
|
||||
provider,
|
||||
apiKey,
|
||||
text,
|
||||
model: model || undefined,
|
||||
endpoint: endpoint || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export async function createEmbeddings(
|
||||
texts: string[],
|
||||
config?: Partial<EmbeddingConfig>
|
||||
): Promise<EmbeddingResponse[]> {
|
||||
const results: EmbeddingResponse[] = [];
|
||||
for (const text of texts) {
|
||||
const result = await createEmbedding(text, config);
|
||||
results.push(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export function cosineSimilarity(a: number[], b: number[]): number {
|
||||
if (a.length !== b.length) {
|
||||
throw new Error('Vectors must have the same length');
|
||||
}
|
||||
|
||||
let dotProduct = 0;
|
||||
let normA = 0;
|
||||
let normB = 0;
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
dotProduct += a[i] * b[i];
|
||||
normA += a[i] * a[i];
|
||||
normB += b[i] * b[i];
|
||||
}
|
||||
|
||||
const denom = Math.sqrt(normA * normB);
|
||||
if (denom === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dotProduct / denom;
|
||||
}
|
||||
|
||||
export class EmbeddingClient {
|
||||
private config: EmbeddingConfig;
|
||||
|
||||
constructor(config?: EmbeddingConfig) {
|
||||
this.config = config ?? loadEmbeddingConfig();
|
||||
}
|
||||
|
||||
get isApiMode(): boolean {
|
||||
return this.config.provider !== 'local' && this.config.enabled && !!this.config.apiKey;
|
||||
}
|
||||
|
||||
async embed(text: string): Promise<number[]> {
|
||||
const response = await createEmbedding(text, this.config);
|
||||
return response.embedding;
|
||||
}
|
||||
|
||||
async embedBatch(texts: string[]): Promise<number[][]> {
|
||||
const responses = await createEmbeddings(texts, this.config);
|
||||
return responses.map(r => r.embedding);
|
||||
}
|
||||
|
||||
similarity(vec1: number[], vec2: number[]): number {
|
||||
return cosineSimilarity(vec1, vec2);
|
||||
}
|
||||
|
||||
updateConfig(config: Partial<EmbeddingConfig>): void {
|
||||
this.config = { ...this.config, ...config };
|
||||
if (config.provider !== undefined || config.apiKey !== undefined) {
|
||||
this.config.enabled = this.config.provider !== 'local' && !!this.config.apiKey;
|
||||
}
|
||||
saveEmbeddingConfig(this.config);
|
||||
}
|
||||
|
||||
getConfig(): EmbeddingConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
}
|
||||
|
||||
let embeddingClientInstance: EmbeddingClient | null = null;
|
||||
|
||||
export function getEmbeddingClient(): EmbeddingClient {
|
||||
if (!embeddingClientInstance) {
|
||||
embeddingClientInstance = new EmbeddingClient();
|
||||
}
|
||||
return embeddingClientInstance;
|
||||
}
|
||||
|
||||
export function resetEmbeddingClient(): void {
|
||||
embeddingClientInstance = null;
|
||||
}
|
||||
Reference in New Issue
Block a user