Skip to content

feat(ai): add local RAG vector store and interactive RAGChat widget#779

Merged
Karanjot786 merged 6 commits into
Karanjot786:mainfrom
Tejas9406:feat/rag-integration
Jun 9, 2026
Merged

feat(ai): add local RAG vector store and interactive RAGChat widget#779
Karanjot786 merged 6 commits into
Karanjot786:mainfrom
Tejas9406:feat/rag-integration

Conversation

@Tejas9406

@Tejas9406 Tejas9406 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Description

This PR implements a lightweight, local, zero-dependency RAG (Retrieval-Augmented Generation) system. It introduces a pure-TypeScript vector database (LocalVectorStore) utilizing in-memory cosine similarity search, an automated directory indexer (indexDirectory) that chunks markdown and text files, and an interactive TUI <RAGChat> assistant widget supporting scrollable histories, multiline query input, and real-time streaming answers.

Related Issue

Closes #777

Which package(s)?

@termuijs/adapters, @termuijs/ui

Type of Change

  • 🐛 Bug fix (type:bug)
  • ✨ Feature (type:feature)
  • 📝 Docs (type:docs)
  • 🧪 Tests (type:testing)
  • ♻️ Refactor (type:refactor)
  • 🎨 Design / UX (type:design)
  • ♿ Accessibility (type:accessibility)
  • ⚡ Performance (type:performance)
  • 🔧 DevOps / CI (type:devops)
  • 🔒 Security (type:security)

Checklist

  • ⭐ You starred the repo. The needs-star check blocks your merge otherwise.
  • Tests pass locally: bun vitest run
  • Build passes: bun run build
  • Typecheck passes: bun run typecheck
  • You read CONTRIBUTING.md.
  • Your PR title follows type: short description.
  • Widget state mutators call markDirty() (if your change affects rendering).
  • No new any types without an inline comment explaining why.
  • No unrelated refactors bundled into this PR.

GSSoC 2026 Participation

  • You are a GSSoC 2026 contributor.
  • Your GSSoC profile: https://gssoc.girlscript.org/profile/666f63ca-ed6a-4d0f-8ac8-1518b06ec839

Screenshots / Recordings (UI changes)

(Terminal screenshots / recordings can be dropped here if needed)

Notes for the Reviewer

  • All tests in @termuijs/adapters and @termuijs/ui pass.
  • OpenAI embeddings are used for vectorization. A mock AI adapter was configured in unit tests to bypass external API network requests.
  • Fixed a pre-existing type mismatch warning in packages/adapters/src/commander/index.ts so the workspace TypeScript declarations compile cleanly.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added text embedding support to AI adapter operations
    • Introduced local vector store for document similarity search and retrieval
    • Added RAGChat widget: interactive chat interface that queries local documents to provide context-aware AI responses
    • Added text chunking and directory indexing utilities for organizing and processing document collections

@Tejas9406 Tejas9406 requested a review from Karanjot786 as a code owner June 5, 2026 14:26
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a local retrieval-augmented generation (RAG) system to TermUI by extending the AIAdapter with embeddings, implementing a local vector store with document chunking and cosine similarity search, and creating an interactive RAGChat terminal widget that indexes documents, handles queries, and streams AI responses.

Changes

RAG Vector Store and Chat Feature

Layer / File(s) Summary
AI Embedding Capability
packages/adapters/src/ai/index.ts
AIAdapter interface gains optional embed(text): Promise<number[]>. useAI() conditionally attaches embed for OpenAI provider only, dynamically loading the SDK and calling embeddings.create() with text-embedding-3-small model.
Vector Store Implementation
packages/adapters/src/ai/vectorStore.ts
New DocumentChunk and VectorStoreOptions interfaces; cosineSimilarity() function; chunkText() splits text into overlapping chunks; indexDirectory() walks .md/.txt files and indexes them; LocalVectorStore class provides addDocuments(), query() (with cosine ranking), load()/save() for JSON persistence.
Vector Store Tests
packages/adapters/src/ai/vectorStore.test.ts
Mocked AIAdapter and test cases validating chunking behavior, cosine similarity retrieval, JSON persistence, and directory indexing with temporary file cleanup.
RAGChat Widget Implementation
packages/adapters/src/ai/RAGChat.ts
New RAGChat widget extends Widget; accepts AIAdapter, LocalVectorStore, and docs path; indexes on construction; provides multiline keyboard-driven input, query submission that retrieves context chunks, streams AI responses token-by-token; renders bordered TUI with scrollable messages, status badge, and input box; error handling via onError callback and error event.
RAGChat Tests
packages/adapters/src/ai/RAGChat.test.ts
Vitest suite covering mount-time indexing/persistence, query submission and vector store retrieval, streaming responses, and error propagation scenarios.
Package Exports and Wiring
packages/adapters/package.json, packages/adapters/src/index.ts, packages/ui/src/index.ts, packages/adapters/src/commander/index.ts
Add @termuijs/core and @termuijs/widgets to adapters dependencies; re-export LocalVectorStore, indexDirectory, chunkText, RAGChat from public barrels; minor commander opts typing adjustment.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Karanjot786/TermUI#562: Modifies AIAdapter and useAI() implementation in packages/adapters/src/ai/index.ts; the main PR extends that surface with optional embed() for OpenAI embeddings.

Suggested labels

gssoc:approved, quality:clean

Suggested reviewers

  • Karanjot786

Poem

🐰 A vector store hatched from whispers and chunks,
With embeddings dancing through cosine funks,
The RAGChat widget chirps queries with glee,
Streaming responses for all eyes to see—
Local docs, no calls, just terminal spree!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Code changes address most issue requirements: LocalVectorStore with cosine similarity and JSON persistence, directory indexer with overlapping chunks, RAGChat widget with streaming, and comprehensive tests. However, two blocking issues remain unresolved per reviewer feedback. Make embed optional on AIAdapter (embed?: (text: string) => Promise<number[]>) with LocalVectorStore throwing descriptive error when undefined, and move RAGChat out of packages/ui into packages/adapters to eliminate inverted package dependency.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR title clearly and specifically describes the main feature: adding a RAG vector store and interactive RAGChat widget for AI-assisted local document search.
Description check ✅ Passed PR description comprehensively addresses the template with clear objectives, package scope, type selection, checklist completion, GSSoC participation metadata, and reviewer notes identifying two architectural blockers.
Out of Scope Changes check ✅ Passed All code changes align with stated objectives and issue requirements for RAG vector store and RAGChat widget. Only two changes are architectural concerns, not out-of-scope additions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added type:feature +10 pts. New feature. area:ui @termuijs/ui type:testing +10 pts. Tests. labels Jun 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
packages/adapters/src/commander/index.ts (1)

14-14: ⚡ Quick win

Add an inline justification for the type assertion on Line 14.

as T currently violates the repo rule requiring an inline explanation for type assertions.

Suggested patch
-    options: program.opts() as T,
+    options: program.opts() as T, // Commander returns an untyped options bag; caller-provided generic `T` defines the expected shape.

As per coding guidelines, "No type assertions without an inline comment explaining why."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/commander/index.ts` at line 14, The type assertion on
the options assignment (options: program.opts() as T) needs an inline comment
explaining why the assertion is safe; update the line to keep the assertion but
add a brief justification comment after it (e.g., stating that commander
validates/returns the expected shape or that upstream type inference cannot
express the generic T but runtime checks cover it), referencing the exact
expression "options: program.opts() as T" so future readers know why the
assertion is allowed.
packages/ui/src/RAGChat.ts (1)

98-133: ⚡ Quick win

Remove console.error from library source code.

Line 124 logs errors directly to the console. Library code should propagate errors to the caller (e.g., via a callback or event) so consumers control logging behavior.

As per coding guidelines: packages/**/src/**/*.{ts,tsx}: No console.log in source files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/RAGChat.ts` around lines 98 - 133, The catch block in
_submitQuery currently calls console.error('RAG query failed:', error) which
violates the no-console rule; remove that console call and instead propagate the
error to the consumer by invoking a dedicated error-handling hook (e.g., call
this._handleError(error) or emit an 'error' event via this.emit('error', error))
and keep the existing push of an assistant error message; update or add the
handler (e.g., _handleError or an EventEmitter hookup) so callers can subscribe
and control logging behavior and ensure _submitQuery still sets _loading = false
and markDirty() in finally.
packages/adapters/src/ai/vectorStore.ts (1)

98-150: ⚡ Quick win

Remove console.error calls from library source code.

Lines 90 and 124 use console.error to log initialization and query failures. Library code should avoid writing directly to console; instead, propagate errors to the caller or emit events/callbacks so consumers can handle logging.

As per coding guidelines: packages/**/src/**/*.{ts,tsx}: No console.log in source files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/ai/vectorStore.ts` around lines 98 - 150, Remove any
console.error calls in LocalVectorStore (e.g., in the constructor, load(),
query() or any other methods) and stop swallowing errors there; instead either
rethrow the caught error or let promise rejections propagate to the caller so
callers can handle logging. Concretely, in load() keep the ENOENT branch that
sets this._documents = [], but for other errors remove console.error and throw
the error (or do not catch it). In query() and addDocuments() remove any
console.error around AI calls and allow embed() failures to surface (or emit an
error via an EventEmitter you add to LocalVectorStore) so consumers control
logging. Ensure no console.* remains in LocalVectorStore, and preserve existing
behavior for ENOENT only.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/adapters/src/ai/index.ts`:
- Around line 118-129: The embed method currently returns an empty array when
OpenAI's response lacks an embedding, which yields a zero-magnitude vector and
corrupts cosineSimilarity; modify the embed function (the async embed(text:
string) implementation for provider === 'openai') to detect missing
response.data[0]?.embedding and throw a descriptive Error (or rethrow the
provider error) instead of returning [] so callers can surface the failure
rather than treat it as a valid zero vector.

In `@packages/adapters/src/ai/vectorStore.ts`:
- Around line 74-96: The for-loop inside indexDirectory is shadowing the
chunkText function by using the loop variable name `chunkText`; rename that loop
variable (e.g., to `chunk`, `chunkStr`, or `chunkTextSlice`) and update its
usage in the docs.push call so it no longer conflicts with the `chunkText`
function name defined earlier, leaving the rest of indexDirectory and the call
to store.addDocuments(ai) unchanged.

In `@packages/ui/src/RAGChat.ts`:
- Around line 45-96: The constructor uses a type coercion for borderClr when
assigning this._borderColor (see RAGChat constructor and the borderClr ->
this._borderColor code) — replace the bare "as any" with a short inline comment
explaining why the runtime value cannot be typed more precisely (e.g., it can be
a named color string or a Style['fg'] object from upstream libs) and keep the
cast only with that justification; and in _initIndex replace the silent
console.error('Failed to initialize RAG index:', error) with error propagation
(either rethrow the caught error or rethrow after logging) so callers can handle
initialization failures instead of swallowing them (modify _initIndex's catch to
rethrow or remove console.error and throw the original error).
- Around line 187-293: In _renderSelf add short inline comments next to every
"as any" explaining why the cast is required and why it is safe: annotate the ba
assignment (ba = { ... } as any) to state the border color union mismatch and
why runtime value is valid; annotate fgColor (fgColor = ... as any) to explain
the intended CellAttr shape for user messages; annotate the two places in the
indexing/loading screen.writeString calls where fg is cast as any to say the
color literal is intentionally used despite TypeScript mismatch; keep each
comment one short sentence referencing the variable (ba, fgColor,
indexing/loading writeString fg) and the reason (third‑party type mismatch or
known runtime shape).

---

Nitpick comments:
In `@packages/adapters/src/ai/vectorStore.ts`:
- Around line 98-150: Remove any console.error calls in LocalVectorStore (e.g.,
in the constructor, load(), query() or any other methods) and stop swallowing
errors there; instead either rethrow the caught error or let promise rejections
propagate to the caller so callers can handle logging. Concretely, in load()
keep the ENOENT branch that sets this._documents = [], but for other errors
remove console.error and throw the error (or do not catch it). In query() and
addDocuments() remove any console.error around AI calls and allow embed()
failures to surface (or emit an error via an EventEmitter you add to
LocalVectorStore) so consumers control logging. Ensure no console.* remains in
LocalVectorStore, and preserve existing behavior for ENOENT only.

In `@packages/adapters/src/commander/index.ts`:
- Line 14: The type assertion on the options assignment (options: program.opts()
as T) needs an inline comment explaining why the assertion is safe; update the
line to keep the assertion but add a brief justification comment after it (e.g.,
stating that commander validates/returns the expected shape or that upstream
type inference cannot express the generic T but runtime checks cover it),
referencing the exact expression "options: program.opts() as T" so future
readers know why the assertion is allowed.

In `@packages/ui/src/RAGChat.ts`:
- Around line 98-133: The catch block in _submitQuery currently calls
console.error('RAG query failed:', error) which violates the no-console rule;
remove that console call and instead propagate the error to the consumer by
invoking a dedicated error-handling hook (e.g., call this._handleError(error) or
emit an 'error' event via this.emit('error', error)) and keep the existing push
of an assistant error message; update or add the handler (e.g., _handleError or
an EventEmitter hookup) so callers can subscribe and control logging behavior
and ensure _submitQuery still sets _loading = false and markDirty() in finally.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f3111de-6329-41bf-ab60-dfae7340e8a1

📥 Commits

Reviewing files that changed from the base of the PR and between f098137 and 161ff20.

📒 Files selected for processing (9)
  • packages/adapters/src/ai/index.ts
  • packages/adapters/src/ai/vectorStore.test.ts
  • packages/adapters/src/ai/vectorStore.ts
  • packages/adapters/src/commander/index.ts
  • packages/adapters/src/index.ts
  • packages/ui/package.json
  • packages/ui/src/RAGChat.test.ts
  • packages/ui/src/RAGChat.ts
  • packages/ui/src/index.ts

Comment thread packages/adapters/src/ai/index.ts Outdated
Comment thread packages/adapters/src/ai/vectorStore.ts
Comment thread packages/adapters/src/ai/RAGChat.ts
Comment thread packages/adapters/src/ai/RAGChat.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/ui/src/RAGChat.ts (1)

87-93: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an inline justification for the event emitter as any cast.

Line 92 uses as any without an inline rationale. Add a short inline comment (or a typed wrapper) to justify why this cast is required.

Suggested minimal fix
-        (this.events as any).emit('error', err);
+        // Cast needed: Widget event typings do not include custom 'error', but runtime emitter supports it.
+        (this.events as any).emit('error', err);

As per coding guidelines: **/*.{ts,tsx}: Use TypeScript strict mode. No any without an inline comment explaining why. No type assertions without an inline comment explaining why.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/RAGChat.ts` around lines 87 - 93, In _handleError, the cast
(this.events as any) is used to access emit but lacks justification; either add
a short inline comment explaining why a typed cast is necessary (e.g., events
uses a dynamic/emitter API not expressible in current Events type) or introduce
a small typed wrapper/override that exposes emit (e.g., a local variable typed
as EventEmitter-like) and use that instead; update references in _handleError to
use the justified cast or wrapper and mention the matching reason and link to
the underlying type limitation (keep comment concise and adjacent to the
cast/wrapper).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/src/RAGChat.test.ts`:
- Line 145: Add short inline comments next to each use of `any`/`as any` in
RAGChat.test.ts to justify the cast: explain that the test is wiring a
lightweight mock that doesn't implement the full EventEmitter/interface so `} as
any` (around the mocked props) is used to bypass strict typing, that `store:
any` is a minimal partial store used only for test setup, and that `('error' as
any, ...)`/similar casts are to satisfy the event API signature on a mocked
emitter rather than the real typed EventEmitter; place each comment directly
after the cast (near the symbols in the file) so future readers know these are
deliberate test-only loosenings.

---

Duplicate comments:
In `@packages/ui/src/RAGChat.ts`:
- Around line 87-93: In _handleError, the cast (this.events as any) is used to
access emit but lacks justification; either add a short inline comment
explaining why a typed cast is necessary (e.g., events uses a dynamic/emitter
API not expressible in current Events type) or introduce a small typed
wrapper/override that exposes emit (e.g., a local variable typed as
EventEmitter-like) and use that instead; update references in _handleError to
use the justified cast or wrapper and mention the matching reason and link to
the underlying type limitation (keep comment concise and adjacent to the
cast/wrapper).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0de60ec6-6a7c-4e25-a249-97b81f1891a2

📥 Commits

Reviewing files that changed from the base of the PR and between 161ff20 and d243551.

📒 Files selected for processing (5)
  • packages/adapters/src/ai/index.ts
  • packages/adapters/src/ai/vectorStore.ts
  • packages/adapters/src/commander/index.ts
  • packages/ui/src/RAGChat.test.ts
  • packages/ui/src/RAGChat.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/adapters/src/commander/index.ts
  • packages/adapters/src/ai/vectorStore.ts

Comment thread packages/ui/src/RAGChat.test.ts Outdated
@Karanjot786 Karanjot786 added the quality:needs-work Needs changes before merge. label Jun 6, 2026
@Karanjot786

Copy link
Copy Markdown
Owner

Hi @Tejas9406,

The RAG vector store concept is interesting, but there are two architectural issues that must be resolved before this can be approved:

Breaking change to AIAdapter interface
Adding embed(text: string): Promise<number[]> as a required method breaks every existing implementation of AIAdapter across the codebase and in downstream consumer code. This will cause TypeScript errors for anyone who has implemented the interface.

The fix is to make it optional: embed?: (text: string) => Promise<number[]>. Or better, define a separate EmbeddingAdapter interface that extends AIAdapter and add embed there. Only LocalVectorStore needs it.

Dependency inversion: ui should not depend on adapters
Adding @termuijs/adapters to packages/ui/package.json inverts the package dependency layering. The current architecture is ui → core/jsx/widgets. Adapters is a leaf package. If ui starts importing from adapters, and adapters ever needs something from ui, you get a circular dependency.

Move RAGChat to the adapters package or to a new dedicated @termuijs/ai package to avoid this.

Please address both issues and push a new commit.

@Karanjot786

Copy link
Copy Markdown
Owner

Hi @Tejas9406, thanks for iterating on this. CI passes and the test coverage looks solid. However, the two architectural blockers from the earlier review are still present. Please address both before requesting another review.

Issue 1: embed() is a required method on AIAdapter

The interface still reads:

embed(text: string): Promise<number[]>

This breaks every existing implementation of AIAdapter across the codebase and in downstream consumer code. TypeScript will error for anyone who already implements the interface.

Fix: make it optional or extract it into a separate interface.

Option A (minimal change):

embed?: (text: string) => Promise<number[]>

Then update LocalVectorStore.addDocuments and LocalVectorStore.query to throw a descriptive error when embed is undefined rather than calling it blindly.

Option B (cleaner): define a separate EmbeddingAdapter interface that extends AIAdapter and add embed there. LocalVectorStore accepts EmbeddingAdapter instead of AIAdapter.

Issue 2: packages/ui depends on packages/adapters

packages/ui/package.json still contains:

"@termuijs/adapters": "workspace:*"

This inverts the package layering. The established dependency graph flows ui -> core/jsx/widgets. Adapters is a leaf package. If ui imports from adapters and adapters ever needs something from ui, the repo gets a circular dependency that breaks the build.

Fix: move RAGChat.ts and RAGChat.test.ts out of packages/ui. Place them in packages/adapters/src/ai/ (or a new packages/ai/ package if preferred). Export from the adapters barrel. Remove the @termuijs/adapters dependency from packages/ui/package.json.

Minor items (fix alongside the above):

  • _initIndex in RAGChat.ts has a try/catch that only rethrows. The catch block does nothing extra. Remove it and let the promise reject naturally so the .catch() in the constructor handles it.
  • packages/adapters/src/ai/index.ts and packages/adapters/src/commander/index.ts are both missing a newline at end of file.

The concept and test suite are solid. Once the two architectural issues are resolved, this is close to mergeable.

@Karanjot786 Karanjot786 added the level:advanced +55 pts. Complex task. label Jun 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/adapters/src/ai/RAGChat.ts (1)

1-1: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Critical: Dependency inversion—adapters should not import from widgets.

RAGChat extends Widget from @termuijs/widgets, which means packages/adapters now depends on packages/widgets. Adapters are intended to be low-level, thin wrappers around external libraries; widgets are higher-level UI components. This inverts the expected package layering.

The PR objectives mention the reviewer suggested either moving RAGChat to packages/adapters or creating a new @termuijs/ai package. Moving it to adapters solves the packages/uipackages/adapters dependency issue but introduces a new packages/adapterspackages/widgets dependency.

Recommended fix: Create a new @termuijs/ai package (or move RAGChat to packages/ui and address the original dependency issue differently). Adapters should remain dependency-light.

Based on learnings: "Follow src/commander/index.ts as the reference shape for adapters: a thin function that takes the external object and returns TermUI-friendly data"—adapters should not extend Widget or depend on the widgets package.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/ai/RAGChat.ts` at line 1, RAGChat currently extends
Widget (importing Widget from '`@termuijs/widgets`'), which creates an unwanted
packages/adapters → packages/widgets dependency; refactor RAGChat into a thin
adapter function (following the shape of src/commander/index.ts) that accepts
the external AI object and returns TermUI-friendly data rather than extending
Widget, or move the component into a new package `@termuijs/ai` (or into
packages/ui) so adapters remain dependency-light; update any exports and
references to use the new adapter function name (RAGChat adapter) or the new
package, and remove the import of Widget from RAGChat to eliminate the inverse
dependency.

Source: Learnings

🧹 Nitpick comments (1)
packages/adapters/src/ai/RAGChat.test.ts (1)

73-88: ⚡ Quick win

Consider asserting rendered output, not just mock calls.

The test verifies that load and save were called, but doesn't verify what was actually rendered to the screen. While this isn't a placeholder test, asserting on screen.getCell() or similar would better test observable behavior.

💡 Example enhanced assertion
     expect(mockVectorStore.load).toHaveBeenCalled();
     expect(mockVectorStore.save).toHaveBeenCalled();
+    // Verify placeholder is rendered when not focused
+    const cell = screen.getCell(2, inputY);
+    expect(cell?.char).toBeTruthy();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/ai/RAGChat.test.ts` around lines 73 - 88, The test
currently only asserts that mockVectorStore.load/save were called; instead
assert rendered output from the Screen after RAGChat.render to verify visible
UI. After calling chat.render(screen) and awaiting awaitIndex(mockVectorStore),
use Screen methods (e.g., screen.getCell or screen.getRow/getText) to check that
the chat history area and input area contain expected markers/labels or
placeholder text produced by RAGChat (look for strings rendered by RAGChat such
as the chat panel title, input prompt, or column separators). Keep the existing
assertions for mockVectorStore.load/save but add assertions that
screen.getCell/row/text at the coordinates RAGChat uses contains the expected
characters so the test verifies observable rendering as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/adapters/src/ai/index.ts`:
- Around line 32-33: Add an inline comment explaining why the "as any" cast is
required on the dynamic require result: annotate the declaration "const mod =
req('openai') as any" with a short comment such as "Cast needed:
createRequire/require returns an opaque module whose shape varies by export
style (CJS vs ESM), so we treat it as any to access exported members safely."
This satisfies the ESLint rule justification and documents why we cannot use a
stricter type for mod.
- Around line 48-49: Add a brief inline comment explaining why the
eslint-disable and the `as any` cast are required for the dynamic require of the
Anthropic SDK: annotate the line with `const mod = req('`@anthropic-ai/sdk`') as
any` to state that the module has no TypeScript typings (or typings are
incompatible) and explain that a dynamic runtime require is used to avoid
conditional import/side-effects, so a temporary `any` cast is intentional until
proper typings are added; reference the `mod` variable and the
`req('`@anthropic-ai/sdk`')` call so reviewers can see the rationale.

---

Outside diff comments:
In `@packages/adapters/src/ai/RAGChat.ts`:
- Line 1: RAGChat currently extends Widget (importing Widget from
'`@termuijs/widgets`'), which creates an unwanted packages/adapters →
packages/widgets dependency; refactor RAGChat into a thin adapter function
(following the shape of src/commander/index.ts) that accepts the external AI
object and returns TermUI-friendly data rather than extending Widget, or move
the component into a new package `@termuijs/ai` (or into packages/ui) so adapters
remain dependency-light; update any exports and references to use the new
adapter function name (RAGChat adapter) or the new package, and remove the
import of Widget from RAGChat to eliminate the inverse dependency.

---

Nitpick comments:
In `@packages/adapters/src/ai/RAGChat.test.ts`:
- Around line 73-88: The test currently only asserts that
mockVectorStore.load/save were called; instead assert rendered output from the
Screen after RAGChat.render to verify visible UI. After calling
chat.render(screen) and awaiting awaitIndex(mockVectorStore), use Screen methods
(e.g., screen.getCell or screen.getRow/getText) to check that the chat history
area and input area contain expected markers/labels or placeholder text produced
by RAGChat (look for strings rendered by RAGChat such as the chat panel title,
input prompt, or column separators). Keep the existing assertions for
mockVectorStore.load/save but add assertions that screen.getCell/row/text at the
coordinates RAGChat uses contains the expected characters so the test verifies
observable rendering as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 283916eb-25a6-446c-b4bc-0b1df8b98cd1

📥 Commits

Reviewing files that changed from the base of the PR and between ad950dc and 3102ebe.

📒 Files selected for processing (8)
  • packages/adapters/package.json
  • packages/adapters/src/ai/RAGChat.test.ts
  • packages/adapters/src/ai/RAGChat.ts
  • packages/adapters/src/ai/index.ts
  • packages/adapters/src/ai/vectorStore.ts
  • packages/adapters/src/commander/index.ts
  • packages/adapters/src/index.ts
  • packages/ui/src/index.ts
💤 Files with no reviewable changes (1)
  • packages/ui/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/adapters/src/commander/index.ts
  • packages/adapters/src/ai/vectorStore.ts

Comment thread packages/adapters/src/ai/index.ts
Comment thread packages/adapters/src/ai/index.ts
@Tejas9406

Copy link
Copy Markdown
Contributor Author

Hell @Karanjot786 ,

Sorry for the delay—I am currently quite busy with my ongoing exams. I have resolved the requested issues and pushed the updates. Please take a look when you get a chance. Thanks!

@Karanjot786

Copy link
Copy Markdown
Owner

Hi @Tejas9406, thanks for the updates. Making embed? optional is the right call — Issue 1 is now fixed. Good work on that.

Issue 2 is still present in a different form. Here is what remains:

Issue 2: RAGChat is still in packages/ui

RAGChat.ts and RAGChat.test.ts remain in packages/ui/src/. Removing @termuijs/adapters from packages/ui/package.json avoids a direct import cycle, but the underlying architectural problem is that the widget has a hard dependency on AI adapter and vector store concepts that belong in the adapters layer, not the UI layer.

The fix from the previous comment still applies: move RAGChat.ts and RAGChat.test.ts to packages/adapters/src/ai/ and export them from the adapters barrel. Then remove the packages/ui/src/RAGChat.ts file and the RAGChat export from packages/ui/src/index.ts.

Issue 3: Duplicate AIAdapter interface

packages/ui/src/RAGChat.ts re-declares AIAdapter locally:

export interface AIAdapter {
  generate(prompt: string): Promise<string>;
  chat(messages: ...): AsyncIterable<string>;
  embed?(text: string): Promise<number[]>;
}

This is already defined in packages/adapters/src/ai/index.ts. Two definitions of the same interface will drift over time. Import it instead:

import type { AIAdapter } from '@termuijs/adapters';

This is one reason the widget belongs in packages/adapters — it avoids the need to duplicate or cross-import these types.

any types still missing explanatory comments

The convention requires a comment on every any explaining why it cannot be avoided. These are missing:

  • indexFn?: (docsPath: string, store: any, ai: any) => Promise<void> — add a comment explaining why store and ai are typed as any.
  • private _indexFn?: (docsPath: string, store: any, ai: any) => Promise<void> — same.
  • chunks.map((c: any) => c.text) — add a comment or type c as DocumentChunk.
  • VectorStore.query(...): Promise<any[]> — use Promise<DocumentChunk[]> instead, or add a comment.
  • Test file: let mockAI: any, let mockVectorStore: any, let mockIndexFn: any — use proper types or add comments. The typed mockAI: AIAdapter pattern used in vectorStore.test.ts is the right approach.

Once you move RAGChat to packages/adapters, the duplicate interface and most of the any issues will resolve naturally. That is the cleanest path forward.

@Karanjot786 Karanjot786 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: CI is green and the implementation looks comprehensive with tests. However, this adds a new AI/RAG feature to @termuijs/adapters which is a significant scope addition. Two questions:

  1. Is there a linked issue approving this feature direction?
  2. The LocalVectorStore uses cosine similarity with in-memory vectors — is this the intended approach for a terminal UI framework?

The code quality itself is good (proper types, tests, node: prefixed imports). Waiting on direction confirmation before approving.

@Tejas9406

Copy link
Copy Markdown
Contributor Author

Hello @Karanjot786 ,

Thank you for the re-review,To answer your questions:
1.Regarding the Linked Issue: This PR implements the AI and local RAG widget feature direction in connection with Issue #779 ("feat(ai): add local RAG vector store and interactive RAGChat widget") non other. We have relocated the widget and its test suite to packages/adapters/src/ai/ per previous review feedback to keep the UI package clean and maintain dependency separation.

2.Regarding the in-memory LocalVectorStore: Yes, this is the intended approach for a Terminal UI framework.
Lightweight & Portable: CLI/TUI applications need to remain lightweight and self-contained. A lightweight, in-memory store that serializes vectors to a local JSON file prevents the need for heavy native dependencies or running an external vector database server (like Qdrant, Chroma, or Pinecone).
Performance: For typical CLI tool documentation, help contexts, or local codebase indexing (which typically range from a few dozen to a few hundred markdown/text files), in-memory cosine similarity checks execute in a few milliseconds, making it highly performant for this scale.
Let me know if this aligns with your expectations or if you'd like to adjust this direction!

@Karanjot786

Copy link
Copy Markdown
Owner

Thanks for the contribution! Reviewed the RAGChat widget + LocalVectorStore. Several issues need to be fixed before this can merge.


BLOCKING

1. Constructor signature breaks widget conventions

// Current — style first, opts optional:
constructor(style: Partial<Style> = {}, opts?: RAGChatOptions)

But opts is not actually optional — the constructor throws immediately if !opts. This is a confusing API. Widget constructors across the codebase take a single options object (see Gauge, Table, Tree). Use:

constructor(opts: RAGChatOptions)

Callers who need custom style can include it in RAGChatOptions.

2. Adding @termuijs/core and @termuijs/widgets as runtime deps to packages/adapters without prior discussion

Per AGENTS.md:

Ask first (comment on the issue before doing it): Adding a new dependency.

packages/adapters is intentionally a thin adapter layer. Adding core and widgets as hard dependencies expands its footprint for all users, even those not using RAG. This needs maintainer approval before the dep is added. Please comment on the linked issue before proceeding.


MINOR

3. Polling loop in awaitIndex test helper is fragile

const awaitIndex = async (store: MockVectorStore) => {
    await new Promise<void>(resolve => {
        const timer = setInterval(() => {
            if (store.save.mock.calls.length > 0) {

Polling with setInterval in tests is flaky and slow. Since save is called as part of the async init flow, you can await it directly by capturing the promise or using vi.waitFor(() => expect(store.save).toHaveBeenCalled()).

4. No linked issue

The PR body does not link a GitHub issue with Closes #NNN. PRs without a linked issue get gssoc:invalid per the template.


Please fix the constructor API and remove the new deps (or get explicit maintainer approval for them on the issue) before re-requesting review.

@Karanjot786 Karanjot786 force-pushed the feat/rag-integration branch from ebd62ef to 40d7316 Compare June 9, 2026 16:02
@Karanjot786 Karanjot786 merged commit f34f340 into Karanjot786:main Jun 9, 2026
6 checks passed
@Karanjot786 Karanjot786 added gssoc:approved Approved PR. Earns +50 base points. quality:clean x 1.2 multiplier. Clean implementation. and removed quality:needs-work Needs changes before merge. labels Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:ui @termuijs/ui gssoc:approved Approved PR. Earns +50 base points. level:advanced +55 pts. Complex task. quality:clean x 1.2 multiplier. Clean implementation. type:feature +10 pts. New feature. type:testing +10 pts. Tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(ai): add local RAG vector store and interactive RAGChat widget

2 participants