Skip to content

Commit e3a2624

Browse files
add landing page chat interface demo
1 parent 46ac051 commit e3a2624

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1556
-227
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- **Privacy-First**: All processing is local; no data is sent to external servers
1111
- **Mental Health Companion**: Supports gratitude, mindfulness, mood, stress, anxiety, and depression—with RAG over your knowledge base. Not a substitute for therapy or professional care.
1212
- **Local LLM**: Runs optimized models locally (Llama-3.2-3B default, Mistral-7B option)
13+
- **Streaming responses**: Chat replies stream token-by-token for a responsive experience
1314
- **RAG System**: Retrieval Augmented Generation with ChromaDB and sentence-transformers
1415
- **Open Source**: MIT license; fully auditable codebase
1516

@@ -36,11 +37,12 @@ Both models use GGUF quantization and run completely offline via llama.cpp. The
3637
This repository contains:
3738

3839
- **`desktop/`** – Desktop app (Tauri 2.0 + React + TypeScript + Rust), **primary application**
40+
- **`landing/`** – Marketing and download landing page (Next.js, shadcn/ui, Tailark). Built as a static site and deployed to **`docs/`** for [GitHub Pages](docs/GITHUB_PAGES.md). The deploy workflow (`.github/workflows/deploy-landing.yml`) builds `landing/` and copies output into `docs/` on pushes that touch `landing/`.
3941
- **`scripts/`** – Python scripts for building knowledge bases and downloading models
40-
- **`docs/`** – Design and migration documentation
42+
- **`docs/`** – Design and migration documentation; also the **GitHub Pages root** (serves the built landing site)
4143
- **`archive/`** – Previous implementations (Python, web) and planning docs
4244

43-
The desktop app uses a Rust backend for Tauri and file operations, and calls Python (llama-cpp-python, ChromaDB, sentence-transformers) via subprocess for LLM inference, embeddings, and vector search.
45+
The desktop app uses a Rust backend for Tauri and file operations, and calls Python (llama-cpp-python, ChromaDB, sentence-transformers) via subprocess for LLM inference, embeddings, and vector search. Chat responses stream token-by-token from the LLM to the UI.
4446

4547
## Quick Start
4648

@@ -61,7 +63,7 @@ See [desktop/SETUP_INSTRUCTIONS.md](desktop/SETUP_INSTRUCTIONS.md) for detailed
6163

6264
### Download (Beta)
6365

64-
Pre-built installers for macOS and Windows are available via [GitHub Releases](https://github.com/andresarbelaez/confidant/releases). A small [download page](docs/index.html) is in `docs/` for use with [GitHub Pages](docs/GITHUB_PAGES.md); enable Pages from the `docs/` folder to serve it.
66+
Pre-built installers for macOS and Windows are available via [GitHub Releases](https://github.com/andresarbelaez/confidant/releases). The project includes a [landing page](landing/README.md) (Next.js, in `landing/`) that is built and served from `docs/` via [GitHub Pages](docs/GITHUB_PAGES.md). Enable Pages with source **Branch: main**, **Folder: /docs** to serve the site.
6567

6668
## Contributing
6769

desktop/ACCESSIBILITY_AUDIT.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Desktop App — Accessibility Audit
2+
3+
WCAG 2.1 Level AA and general legibility review. Last updated: Jan 2026.
4+
5+
---
6+
7+
## Strengths
8+
9+
### Semantic structure
10+
- **Modals**: `role="dialog"`, `aria-modal="true"`, `aria-labelledby`, `aria-describedby` where helpful (CreateUserModal, PasswordPrompt, LogOutConfirmModal, DeleteChatHistoryConfirmModal, SetupModal, UserSettingsModal)
11+
- **Forms**: Labels with `htmlFor` on CreateUserModal, LanguageSelector, SetupModal, SetupSection
12+
- **Navigation**: Sidebar uses `<nav aria-label={t('ui.navAccountAndSettings')}>`
13+
- **Decorative icons**: Lucide icons in sidebar use `aria-hidden`
14+
15+
### Keyboard and focus
16+
- **Modal focus trap**: `useModalFocusTrap` in CreateUserModal, PasswordPrompt, LogOutConfirmModal, DeleteChatHistoryConfirmModal, UserSettingsModal; SetupModal uses it
17+
- **Escape to close**: Modals respond to Escape
18+
- **Tab trapping**: Focus stays inside open modal
19+
- **Focus restore**: Focus returns to trigger element when modal closes
20+
21+
### Touch targets
22+
- `--touch-target-min: 2.75rem` (44px) for primary controls (WCAG 2.5.5)
23+
- Send button, modal actions, sidebar buttons meet minimum size
24+
25+
### Screen reader support
26+
- Chat send: `aria-label={t('ui.send')}`
27+
- Copy message: `aria-label` + `role="status"` on tooltip
28+
- Modal close (SetupModal): `aria-label={t('ui.close')}`
29+
- Form errors: `role="alert"` on CreateUserModal, PasswordPrompt
30+
31+
---
32+
33+
## Issues
34+
35+
### Critical
36+
37+
#### 1. Focus indicator removed (WCAG 2.4.7 — Focus Visible)
38+
Several inputs and controls use `outline: none` on `:focus` with no visible replacement:
39+
40+
| File | Selector | Issue |
41+
|------|----------|-------|
42+
| ChatInterface.css | `.chat-input:focus` | `outline: none` |
43+
| CreateUserModal.css | `.form-input:focus` | `outline: none` |
44+
| PasswordPrompt.css | `.form-input:focus` | `outline: none` |
45+
| SetupScreen.css | `.selector-dropdown:focus` | `outline: none` |
46+
| SetupSection.css | `.selector-dropdown:focus`, `.existing-model-selector:focus` | `outline: none` |
47+
| LanguageSelector.css | `.language-select:focus` | `outline: none` (warm.css adds box-shadow) |
48+
49+
**Recommendation:** Add `:focus-visible` styles: `outline: 2px solid var(--color-primary); outline-offset: 2px` or equivalent. Reserve `outline: none` only for cases where another visible focus style exists.
50+
51+
---
52+
53+
#### 2. Chat input has no accessible name (WCAG 1.3.1, 4.1.2)
54+
The chat textarea relies solely on a placeholder. Placeholders are not sufficient as labels.
55+
56+
```
57+
<textarea placeholder={t('ui.askHealthQuestion')} ... />
58+
```
59+
60+
**Recommendation:** Add `aria-label={t('ui.askHealthQuestion')}` or a visually hidden `<label>` with `htmlFor`.
61+
62+
---
63+
64+
#### 3. Password input has no label (WCAG 1.3.1, 4.1.2)
65+
PasswordPrompt uses a placeholder instead of an associated label. The subtitle explains context but does not provide an accessible name for the input.
66+
67+
**Recommendation:** Add `aria-label={t('ui.password')}` or a visually hidden label.
68+
69+
---
70+
71+
### High
72+
73+
#### 4. Password visibility toggle not keyboard accessible
74+
The show/hide password buttons use `tabIndex={-1}`, so they are skipped in the tab order.
75+
76+
**Location:** CreateUserModal (2 instances), PasswordPrompt
77+
78+
**Recommendation:** Remove `tabIndex={-1}` so the toggle is focusable. Add `aria-label`:
79+
- `aria-label={showPassword ? t('ui.hidePassword') : t('ui.showPassword')}`
80+
81+
---
82+
83+
#### 5. Chat error not announced to screen readers
84+
When an error appears in the chat, it is not announced.
85+
86+
```
87+
{error && <div className="chat-error">{error}</div>}
88+
```
89+
90+
**Recommendation:** Add `role="alert"` so it is announced when it appears.
91+
92+
---
93+
94+
#### 6. Loading spinner not announced
95+
The loading spinner is decorative and has no accessible alternative.
96+
97+
**Recommendation:** Add `aria-live="polite"` region with `aria-busy="true"` on the loading container, or a `sr-only` message such as "Loading…" that updates when loading completes.
98+
99+
---
100+
101+
### Medium
102+
103+
#### 7. Potential color contrast in warm theme
104+
Warm palette uses oklch. Verify contrast ratios:
105+
106+
- **--color-text-muted** (oklch 0.48 0.035 100) on **--color-bg** (0.985 0.008 100) — may be close to 4.5:1
107+
- **--color-text-faint** (0.55) — may fall below 4.5:1 for normal text
108+
- **Assistant bubble** — Very low contrast by design; may not meet 1.4.3 for users with low vision
109+
110+
**Recommendation:** Run a contrast checker on warm theme combinations. Consider bumping `--color-text-muted` and `--color-text-faint` chroma/lightness if needed.
111+
112+
---
113+
114+
#### 8. Document language
115+
`index.html` has `lang="en"` and the app supports multiple languages. When the user switches language, the document `lang` is not updated.
116+
117+
**Recommendation:** Set `document.documentElement.lang` when language changes, or ensure initial load matches user preference.
118+
119+
---
120+
121+
### Low
122+
123+
#### 9. Skip link
124+
No skip-to-content link. For a single-main-view desktop app this may be acceptable, but a skip link can still help keyboard users when multiple regions exist.
125+
126+
---
127+
128+
#### 10. Heading hierarchy
129+
Multiple screens use `h1` or `h2`. Ensure a single `h1` per view and a logical order (h1 → h2 → h3). Error screen uses `h1`; Loading and UserProfileSelector use `h1`-style titles. Verify order when views switch.
130+
131+
---
132+
133+
## Summary checklist
134+
135+
| Criterion | Status |
136+
|----------|--------|
137+
| 1.1.1 Non-text content | Partial — icons have aria-hidden where decorative |
138+
| 1.3.1 Info and relationships | Issues — chat input, password input need labels |
139+
| 1.4.3 Contrast (minimum) | Verify — warm theme, assistant bubble |
140+
| 2.1.1 Keyboard | Pass — modals, forms keyboard accessible |
141+
| 2.4.7 Focus visible | Fail — many focus outlines removed |
142+
| 4.1.2 Name, role, value | Partial — several inputs need accessible names |
143+
| 4.1.3 Status messages | Partial — chat error needs role="alert" |
144+
145+
---
146+
147+
## Recommended fixes (priority order)
148+
149+
1. ~~Add visible focus indicators for all interactive elements (replace `outline: none` with `:focus-visible` styles).~~ **Done**
150+
2. ~~Add `aria-label` or label to chat textarea and PasswordPrompt input.~~ **Done**
151+
3. ~~Make password visibility toggles keyboard accessible and add `aria-label`.~~ **Done**
152+
4. ~~Add `role="alert"` to the chat error container.~~ **Done**
153+
5. Run contrast checks on the warm theme and tweak if needed.

desktop/STYLE_GUIDE.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
Short reference for colors, typography, spacing, and component patterns. Use this to keep UI changes consistent.
44

5+
## Theme experiments
6+
7+
A warm (cream/yellow) palette can be enabled to match the landing page. It lives in `src/themes/warm.css`.
8+
9+
- **Enable:** Ensure `main.tsx` imports `./themes/warm.css`.
10+
- **Revert:** Comment out that import in `main.tsx`.
11+
512
## Color variables
613

714
Defined in `src/index.css` (`:root` and `@media (prefers-color-scheme: light)`). Prefer variables over hardcoded hex/rgba.
@@ -43,7 +50,7 @@ Defined in `src/index.css` (`:root` and `@media (prefers-color-scheme: light)`).
4350

4451
## Typography
4552

46-
- **Base:** 1rem (16px), line-height 1.5, font-weight 400. Font stack: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif.
53+
- **Base:** 1rem (16px), line-height 1.5, font-weight 400. Font stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif.
4754
- **Headings:** Use a clear hierarchy (e.g. one `h1` per view, then `h2` for sections). Heading scale:
4855
- `h1`: ~3.2em (index.css).
4956
- Modal/section titles: 1.5em (e.g. `.modal-header h2`).

desktop/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Confidant - Offline AI Assistant</title>
88
</head>
9-
<body>
9+
<body style="margin:0;background:#242424">
1010
<div id="root"></div>
1111
<script type="module" src="/src/main.tsx"></script>
1212
</body>

desktop/package-lock.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

desktop/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
},
1818
"dependencies": {
1919
"@tauri-apps/api": "^2.0.0",
20+
"confidant-chat-ui": "file:../packages/confidant-chat-ui",
2021
"lucide-react": "^0.563.0",
2122
"react": "^18.2.0",
2223
"react-dom": "^18.2.0",

desktop/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ function App() {
124124
/>
125125
{userSelectionTransitioning && (
126126
<div className="App-loading-overlay" aria-hidden="true">
127-
<LoadingScreen onComplete={() => {}} />
127+
<LoadingScreen onComplete={() => {}} showLanguageSelector={false} />
128128
</div>
129129
)}
130130
{showSettingsModal && (
@@ -158,7 +158,7 @@ function App() {
158158
/>
159159
{chatTransitioning && (
160160
<div className="App-loading-overlay" aria-hidden="true">
161-
<LoadingScreen onComplete={() => {}} />
161+
<LoadingScreen onComplete={() => {}} showLanguageSelector={false} />
162162
</div>
163163
)}
164164
</main>

desktop/src/agent/dant-agent.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,22 @@ export class DantAgent {
210210
// Use a clear prompt format that encourages direct, concise responses
211211
const languageName = LANGUAGE_DISPLAY_NAMES[language] ?? 'English';
212212
const languageInstruction = ` Always respond in ${languageName}. The user's preferred language is ${languageName}.`;
213-
const systemContext = `You are Confidant, a supportive mental health companion. You help with gratitude, mindfulness, mood, stress, anxiety, and depression—in a supportive, clarifying way. Ask brief clarifying questions when it helps. Acknowledge limits and suggest professional help when appropriate. Never diagnose. You are a supportive companion, not a therapist or substitute for professional mental health care. Keep responses brief (2-3 sentences when possible). Use plain language. Respond directly to the user without meta-dialogue or example conversations.${languageInstruction}`;
213+
const systemContext = `You are Confidant, a supportive mental health companion. You help with gratitude, mindfulness, mood, stress, anxiety, and depression—in a supportive, clarifying way.
214+
215+
Psychological best practices:
216+
- Validation before problem-solving: Acknowledge and validate what the user is feeling before offering any perspective or suggestion. Emotions are always valid—you need not agree, only recognize they are real. Reflect back your understanding when appropriate ("It sounds like...", "That sounds really hard").
217+
- Avoid toxic positivity: Never minimize, dismiss, or rush past difficult emotions. Do not say things like "just stay positive," "look on the bright side," or "it could be worse." Take the user's experience seriously.
218+
- Unconditional positive regard: Accept the user non-judgmentally. Create a safe space where their feelings and experiences are valued as-is.
219+
220+
Use clinical psychology best practices for follow-up questions: ask open-ended questions (What, How, Tell me about) that invite reflection without leading. Draw from evidence-based approaches:
221+
- Emotional exploration: "How does that make you feel?" "What's come up for you around that?"
222+
- Clarification: "What would be most helpful to talk about right now?" "What's been on your mind lately?"
223+
- Strengths-forward: "What has helped before when you've felt this way?" "What feels manageable today?"
224+
- Motivational interviewing style: one brief question at a time, non-judgmental, inviting the user to elaborate.
225+
226+
Often end your response with one such follow-up question. Avoid "why" questions (can feel accusatory).
227+
228+
Acknowledge limits and suggest professional help when appropriate. Never diagnose. You are a supportive companion, not a therapist or substitute for professional mental health care. Keep responses brief (2-3 sentences when possible). Use plain language. Respond directly to the user without meta-dialogue or example conversations.${languageInstruction}`;
214229

215230
// Build conversation history for context (reduced for faster processing)
216231
let conversationContext = '';

desktop/src/components/ChatInterface.css

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
/* Wrapper so chat-interface can use height: 100% (fills flex parent) */
2+
.chat-interface-wrapper {
3+
flex: 1;
4+
min-height: 0;
5+
display: flex;
6+
width: 100%;
7+
}
8+
19
.chat-interface {
210
display: flex;
311
flex-direction: row;
@@ -402,6 +410,7 @@
402410
border: 1px solid var(--color-chat-input-wrap-border);
403411
border-radius: 9999px;
404412
box-sizing: border-box;
413+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
405414
}
406415

407416
.chat-input {
@@ -430,6 +439,10 @@
430439
.chat-input:focus {
431440
outline: none;
432441
}
442+
.chat-input:focus-visible {
443+
outline: 2px solid -webkit-focus-ring-color;
444+
outline-offset: 2px;
445+
}
433446

434447
.chat-input:disabled {
435448
opacity: 0.5;
@@ -586,7 +599,7 @@
586599
}
587600

588601
.chat-input-form {
589-
background: var(--color-surface-subtle);
602+
background: var(--color-bg);
590603
}
591604

592605
.chat-input-wrap {

0 commit comments

Comments
 (0)