Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import { registerGenerateScorecard } from "./tools/generate-scorecard.js";
import { registerSearchContracts } from "./tools/search-contracts.js";
import { registerEstimateCost } from "./tools/estimate-cost.js";
import { registerExportReport } from "./tools/export-report.js";

// Validate related projects from config
function validateRelatedProjects(): void {
Expand All @@ -73,7 +74,7 @@
}

// Load config and validate related projects on startup
const config = getConfig();

Check warning on line 77 in src/index.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20)

'config' is assigned a value but never used

Check warning on line 77 in src/index.ts

View workflow job for this annotation

GitHub Actions / build-and-test (22)

'config' is assigned a value but never used
validateRelatedProjects();

const profile = getProfile();
Expand Down Expand Up @@ -110,6 +111,7 @@
["generate_scorecard", registerGenerateScorecard],
["estimate_cost", registerEstimateCost],
["search_contracts", registerSearchContracts],
["export_report", registerExportReport],
];

let registered = 0;
Expand Down
15 changes: 13 additions & 2 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { PROJECT_DIR } from "./files.js";

export type Profile = "minimal" | "standard" | "full";
export type EmbeddingProvider = "local" | "openai";
export type EmbeddingProvider = "local" | "openai" | "ollama";
export type TriageStrictness = "relaxed" | "standard" | "strict";

export interface RelatedProject {
Expand All @@ -30,6 +30,9 @@
embeddings: {
provider: EmbeddingProvider;
openai_api_key?: string;
ollama_base_url?: string;
ollama_model?: string;
ollama_dimensions?: number;
};
triage: {
rules: {
Expand Down Expand Up @@ -78,7 +81,7 @@
if (existsSync(configPath)) {
try {
const configYaml = readFileSync(configPath, "utf-8");
const configData = yamlLoad(configYaml) as any;

Check warning on line 84 in src/lib/config.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20)

Unexpected any. Specify a different type

Check warning on line 84 in src/lib/config.ts

View workflow job for this annotation

GitHub Actions / build-and-test (22)

Unexpected any. Specify a different type

if (configData) {
// Merge config data with defaults
Expand All @@ -96,7 +99,7 @@
if (existsSync(triagePath)) {
try {
const triageYaml = readFileSync(triagePath, "utf-8");
const triageData = yamlLoad(triageYaml) as any;

Check warning on line 102 in src/lib/config.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20)

Unexpected any. Specify a different type

Check warning on line 102 in src/lib/config.ts

View workflow job for this annotation

GitHub Actions / build-and-test (22)

Unexpected any. Specify a different type

if (triageData) {
if (triageData.rules) config.triage.rules = { ...config.triage.rules, ...triageData.rules };
Expand Down Expand Up @@ -125,14 +128,22 @@

// Embedding provider
const envProvider = process.env.EMBEDDING_PROVIDER?.toLowerCase();
if (envProvider === "local" || envProvider === "openai") {
if (envProvider === "local" || envProvider === "openai" || envProvider === "ollama") {
config.embeddings.provider = envProvider;
}

// OpenAI API key
if (process.env.OPENAI_API_KEY) {
config.embeddings.openai_api_key = process.env.OPENAI_API_KEY;
}

// Ollama config
if (process.env.OLLAMA_BASE_URL) {
config.embeddings.ollama_base_url = process.env.OLLAMA_BASE_URL;
}
if (process.env.OLLAMA_EMBED_MODEL) {
config.embeddings.ollama_model = process.env.OLLAMA_EMBED_MODEL;
}
}

return config;
Expand Down
61 changes: 60 additions & 1 deletion src/lib/embeddings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@

// --- Local Provider (Xenova/transformers) ---

let extractor: any = null;

Check warning on line 29 in src/lib/embeddings.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20)

Unexpected any. Specify a different type

Check warning on line 29 in src/lib/embeddings.ts

View workflow job for this annotation

GitHub Actions / build-and-test (22)

Unexpected any. Specify a different type

async function getExtractor(): Promise<any> {

Check warning on line 31 in src/lib/embeddings.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20)

Unexpected any. Specify a different type

Check warning on line 31 in src/lib/embeddings.ts

View workflow job for this annotation

GitHub Actions / build-and-test (22)

Unexpected any. Specify a different type
if (!extractor) {
extractor = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
}
Expand Down Expand Up @@ -92,7 +92,7 @@

const data = await resp.json();
// Sort by index to preserve order
const sorted = data.data.sort((a: any, b: any) => a.index - b.index);

Check warning on line 95 in src/lib/embeddings.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20)

Unexpected any. Specify a different type

Check warning on line 95 in src/lib/embeddings.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20)

Unexpected any. Specify a different type

Check warning on line 95 in src/lib/embeddings.ts

View workflow job for this annotation

GitHub Actions / build-and-test (22)

Unexpected any. Specify a different type

Check warning on line 95 in src/lib/embeddings.ts

View workflow job for this annotation

GitHub Actions / build-and-test (22)

Unexpected any. Specify a different type
for (const item of sorted) {
results.push(item.embedding);
}
Expand All @@ -102,17 +102,76 @@
}
}

// --- Ollama Provider ---

class OllamaEmbeddingProvider implements EmbeddingProvider {
dimensions: number;
private baseUrl: string;
private model: string;

constructor(options?: { baseUrl?: string; model?: string; dimensions?: number }) {
this.baseUrl = (options?.baseUrl ?? "http://localhost:11434").replace(/\/$/, "");
this.model = options?.model ?? "nomic-embed-text";
// nomic-embed-text = 768, all-minilm = 384, mxbai-embed-large = 1024
this.dimensions = options?.dimensions ?? 768;
}

async embed(text: string): Promise<number[]> {
const processed = preprocessText(text);
const resp = await fetch(`${this.baseUrl}/api/embed`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: this.model, input: processed }),
});

if (!resp.ok) {
const err = await resp.text();
throw new Error(`Ollama embeddings API error ${resp.status}: ${err}`);
}

const data = await resp.json();
return data.embeddings[0];
}

async embedBatch(texts: string[]): Promise<number[][]> {
const processed = texts.map(preprocessText);
const resp = await fetch(`${this.baseUrl}/api/embed`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: this.model, input: processed }),
});

if (!resp.ok) {
const err = await resp.text();
throw new Error(`Ollama embeddings API error ${resp.status}: ${err}`);
}

const data = await resp.json();
return data.embeddings;
}
}

// --- Factory ---

export interface EmbeddingConfig {
provider: "local" | "openai";
provider: "local" | "openai" | "ollama";
apiKey?: string;
ollamaBaseUrl?: string;
ollamaModel?: string;
ollamaDimensions?: number;
}

export function createEmbeddingProvider(config: EmbeddingConfig): EmbeddingProvider {
if (config.provider === "openai") {
if (!config.apiKey) throw new Error("OpenAI API key required for openai embedding provider");
return new OpenAIEmbeddingProvider(config.apiKey);
}
if (config.provider === "ollama") {
return new OllamaEmbeddingProvider({
baseUrl: config.ollamaBaseUrl,
model: config.ollamaModel,
dimensions: config.ollamaDimensions,
});
}
return new LocalEmbeddingProvider();
}
8 changes: 7 additions & 1 deletion src/lib/timeline-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ export interface ProjectInfo {
}

export interface TimelineConfig {
embedding_provider: "local" | "openai";
embedding_provider: "local" | "openai" | "ollama";
embedding_model: string;
openai_api_key?: string;
ollama_base_url?: string;
ollama_model?: string;
ollama_dimensions?: number;
indexed_projects: Record<string, {
last_session_index: string;
last_git_index: string;
Expand Down Expand Up @@ -186,6 +189,9 @@ async function getEmbedder(): Promise<EmbeddingProvider> {
_embedder = createEmbeddingProvider({
provider: config.embedding_provider,
apiKey: config.openai_api_key,
ollamaBaseUrl: config.ollama_base_url,
ollamaModel: config.ollama_model,
ollamaDimensions: config.ollama_dimensions,
});
}
return _embedder;
Expand Down
Loading
Loading