SafeQueryAI Documentation

Privacy-first document Q&A with local RAG

View the Project on GitHub JYOshiro/SafeQueryAI

Development Guide

This guide covers development setup, contributing guidelines, and code structure for SafeQueryAI.

Development Environment Setup

Prerequisites

Clone & Setup

# Clone repository
git clone https://github.com/yourusername/SafeQueryAI.git
cd SafeQueryAI

# Start Ollama
ollama serve

# In another terminal, pull models
ollama pull nomic-embed-text
ollama pull llama3.2

Run Development Servers

Terminal 1 — Backend:

cd backend
dotnet watch run

Terminal 2 — Frontend:

cd frontend
npm install
npm run dev

Application should be available at http://localhost:5173

Project Structure

Backend Architecture

backend/
├── Controllers/          # HTTP endpoints
│   ├── FilesController.cs
│   ├── QuestionsController.cs
│   ├── SessionsController.cs
│   └── HealthController.cs
├── Services/             # Business logic
│   ├── DocumentIndexingService.cs
│   ├── OllamaService.cs
│   ├── QuestionAnsweringService.cs
│   ├── SessionService.cs
│   ├── TextExtractionService.cs
│   ├── VectorStoreService.cs
│   └── Interfaces/       # Service contracts
├── Models/               # Domain entities
│   ├── SessionInfo.cs
│   ├── DocumentChunk.cs
│   └── StoredFileInfo.cs
├── Contracts/            # DTOs for API
│   ├── AskQuestionRequest.cs
│   ├── AnswerStreamChunk.cs
│   └── ...
├── Program.cs            # DI and middleware setup
└── appsettings.json      # Configuration

Frontend Architecture

frontend/
├── src/
│   ├── components/       # React components
│   │   ├── App.tsx       # Root component
│   │   ├── QuestionForm.tsx
│   │   ├── FileUploadPanel.tsx
│   │   └── AnswerPanel.tsx
│   ├── services/         # API client
│   │   └── api.ts        # Typed API wrapper
│   ├── types/            # TypeScript interfaces
│   │   └── api.ts        # API types
│   ├── styles/           # CSS files
│   └── main.tsx          # Entry point
├── vite.config.ts        # Build config
├── tsconfig.json         # TS config
└── package.json          # Dependencies

Code Style Guidelines

C# (.NET)

Example:

public class DocumentIndexingService : IDocumentIndexingService
{
    private readonly ILogger<DocumentIndexingService> _logger;
    private readonly IOllamaService _ollamaService;

    public DocumentIndexingService(
        ILogger<DocumentIndexingService> logger,
        IOllamaService ollamaService)
    {
        _logger = logger;
        _ollamaService = ollamaService;
    }

    public async Task<List<DocumentChunk>> IndexDocumentAsync(string filePath)
    {
        try
        {
            _logger.LogInformation("Indexing document: {FilePath}", filePath);
            // Implementation
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to index document");
            throw;
        }
    }
}

TypeScript/React

Example:

interface QuestionFormProps {
  sessionId: string;
  onSubmit: (question: string) => void;
  isLoading: boolean;
}

export const QuestionForm: React.FC<QuestionFormProps> = ({
  sessionId,
  onSubmit,
  isLoading
}) => {
  const [question, setQuestion] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    onSubmit(question);
    setQuestion('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={question}
        onChange={(e) => setQuestion(e.target.value)}
        disabled={isLoading}
      />
      <button type="submit" disabled={isLoading}>
        Ask
      </button>
    </form>
  );
};

Testing

Backend Unit Tests

Create test files in backend/Tests/:

[TestFixture]
public class DocumentIndexingServiceTests
{
    private Mock<IOllamaService> _ollamaServiceMock;
    private DocumentIndexingService _service;

    [SetUp]
    public void Setup()
    {
        _ollamaServiceMock = new Mock<IOllamaService>();
        _service = new DocumentIndexingService(_ollamaServiceMock.Object);
    }

    [Test]
    public async Task IndexDocument_WithValidFile_ReturnsChunks()
    {
        // Arrange
        var filePath = "test.pdf";

        // Act
        var result = await _service.IndexDocumentAsync(filePath);

        // Assert
        Assert.IsNotEmpty(result);
    }
}

Run tests:

dotnet test backend/

Frontend Unit Tests

Add Vitest to frontend/package.json:

npm install -D vitest @testing-library/react @testing-library/jest-dom

Create test file:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QuestionForm } from './QuestionForm';

describe('QuestionForm', () => {
  it('submits question when form is submitted', async () => {
    const onSubmit = vi.fn();
    render(
      <QuestionForm
        sessionId="test-id"
        onSubmit={onSubmit}
        isLoading={false}
      />
    );

    const input = screen.getByRole('textbox');
    await userEvent.type(input, 'What is AI?');
    await userEvent.click(screen.getByRole('button'));

    expect(onSubmit).toHaveBeenCalledWith('What is AI?');
  });
});

Run tests:

npm run test

Common Development Tasks

Add a New API Endpoint

  1. Create request contract in Contracts/:
    public class MyRequest { }
    
  2. Create response contract in Contracts/:
    public class MyResponse { }
    
  3. Add controller method:
    [HttpPost("my-endpoint")]
    public async Task<MyResponse> MyEndpoint(MyRequest request)
    {
        // Implementation
    }
    
  4. Update frontend API client in services/api.ts:
    myEndpoint: (data: MyRequest): Promise<MyResponse> => 
      fetch('/api/my-endpoint', { method: 'POST', body: JSON.stringify(data) })
    

Add a New React Component

  1. Create file in src/components/:
    interface MyComponentProps { }
       
    export const MyComponent: React.FC<MyComponentProps> = (props) => {
      return <div>Component</div>;
    };
    
  2. Import and use in App.tsx:
    import { MyComponent } from './components/MyComponent';
    
  3. Add styling to styles/app.css

Update Dependencies

Backend:

cd backend
dotnet add package NameOfPackage

Frontend:

cd frontend
npm install package-name

Debugging

Backend Debugging in VS Code

Create .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": ".NET Core Launch (web)",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "build",
      "program": "${workspaceFolder}/backend/bin/Debug/net8.0/SafeQueryAI.Api.dll",
      "args": [],
      "cwd": "${workspaceFolder}/backend",
      "stopAtEntry": false,
      "serverReadyAction": {
        "port": 5000,
        "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
      }
    }
  ]
}

Frontend Debugging

Use React DevTools browser extension or:

// In component
console.log('Debug info:', value);
debugger; // Pauses execution

Logging

Backend:

_logger.LogInformation("Info message");
_logger.LogWarning("Warning: {Details}", details);
_logger.LogError(ex, "Error occurred");

Frontend:

console.log('Message');
console.debug('Debug info', obj);
console.error('Error', error);

Contributing

Workflow

  1. Create branch: git checkout -b feature/my-feature
  2. Make changes: Follow code style guidelines
  3. Test locally: dotnet test and npm test
  4. Commit: git commit -m "Add feature: description"
  5. Push: git push origin feature/my-feature
  6. Create PR: Open pull request with description

PR Checklist

Build & Release

Build for Production

Backend:

cd backend
dotnet publish -c Release -o ../publish

Frontend:

cd frontend
npm run build

Version Bumping

Update version in:

Tag and push:

git tag v1.0.0
git push origin v1.0.0

Performance & Optimization

Backend

Frontend

Resources

Getting Help