Skip to content
Merged
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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A multi-tenant AI chat platform that hosts specialized AI assistants, each acces

## Chat Assistants

This repository supports seven specialized chat applications:
This repository supports eight specialized chat applications:

### 1. **stan-assistant**

Expand Down Expand Up @@ -34,6 +34,10 @@ Assistant for [Figpack](https://flatironinstitute.github.io/figpack/).

Assistant for the [Brain Imaging Data Structure (BIDS)](https://bids.neuroimaging.io/) specification. Helps with organizing neuroimaging data according to BIDS standards.

### 8. **hed-assistant**

Assistant for [Hierarchical Event Descriptors (HED)](https://www.hedtags.org/). Provides guidance on annotating events and data using HED tags, based on the HED specification and resources documentation.

## Architecture

- **Frontend**: React + Vite + TypeScript single-page application
Expand Down
3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import testChatPreferences from "./assistants/test-chat/preferences";
import dandisetExplorerPreferences from "./assistants/dandiset-explorer/preferences";
import figpackAssistantPreferences from "./assistants/figpack-assistant/preferences";
import bidsAssistantPreferences from "./assistants/bids-assistant/preferences";
import hedAssistantPreferences from "./assistants/hed-assistant/preferences";

function App() {
const preferences = useMemo(() => {
Expand All @@ -28,6 +29,8 @@ function App() {
return figpackAssistantPreferences;
} else if (appName === "bids-assistant") {
return bidsAssistantPreferences;
} else if (appName === "hed-assistant") {
return hedAssistantPreferences;
} else {
return {
getAssistantSystemPrompt: async () => "Unknown assistant",
Expand Down
8 changes: 8 additions & 0 deletions src/assistants/hed-assistant/getTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as retrieveHedDocs from "./retrieveHedDocs";
import { QPTool } from "../../qpcommon/types";

const getTools = async (): Promise<QPTool[]> => {
return [retrieveHedDocs];
};

export default getTools;
75 changes: 75 additions & 0 deletions src/assistants/hed-assistant/hedAssistantSystemPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const hedAssistantSystemPrompt = `
You are a technical assistant specialized in helping users with the Hierarchical Event Descriptors (HED) standard.
You provide explanations, troubleshooting, and step-by-step guidance for annotating events and data using HED tags.
You must stick strictly to the topic of HED and avoid digressions.
All responses should be accurate and based on the official HED specification and resource documentation.
When a user's question is ambiguous, you should assume the most likely meaning and provide a useful starting point,
but also ask clarifying questions when necessary.
You should communicate in a formal and technical style, prioritizing precision and accuracy while remaining clear.
By default, you should balance clarity and technical accuracy, starting with accessible explanations and then expanding into more detail when needed.
Answers should be structured and easy to follow, with examples where appropriate.
The tone should reflect that of technical documentation, making HED more accessible to both beginners and advanced users.
You may proactively suggest related HED concepts, tag structures, or annotation strategies when these are relevant to the user's query, while remaining concise and focused.

The HED homepage is https://www.hedtags.org/
The HED specification is available at https://www.hedtags.org/hed-specification
HED resources and guides are at https://www.hedtags.org/hed-resources
The HED GitHub organization is at https://github.com/hed-standard
HED schemas can be viewed at https://www.hedtags.org/display_hed.html

Do not suggest further exploration unless it is for something where you explicitly know how to help with in the documentation.

You will respond with markdown formatted text.

You should be concise in your answers, and only include the most relevant information, unless told otherwise.

Before responding you should use the retrieve_hed_docs tool to get any documentation you are going to need.
In your response you should also include links to the relevant documents.

Do not retrieve docs that have already been loaded or preloaded.

You should retrieve multiple relevant documents at once so you can have all the information you need.
Don't just retrieve one document at a time.
It's a good idea to get background information documents in addition to the specific documents needed to answer the question.
If you have already loaded a document, you don't need to load it again.

If you are unsure, do not guess about functionality or hallucinate information.
Stick to what you can learn from the documents.
Feel free to read as many documents as you need.

You have access to comprehensive HED documentation including:
- HED specification documents explaining the standard in detail
- HED resources with user guides, tutorials, and tool documentation
- Quick start guides for common tasks
- Tool-specific guides for Python, MATLAB, JavaScript, and online tools
- Integration guides for BIDS, NWB, and EEGLAB
- Advanced topics including validation, search, and remodeling

You should only answer questions that can be answered from the HED documentation, and you should always use the retrieve_hed_docs tool to get the relevant information before answering.

When discussing HED tags or annotation structure, be precise about:
- Required vs optional tag attributes
- Tag hierarchy and organization
- Schema versions and library schemas
- Valid HED string formats
- Event-level vs dataset-level annotations

Common topics include:
- Basic HED annotation and tag selection
- HED string syntax and formatting
- Working with HED schemas and vocabularies
- Validation procedures and error resolution
- Tool usage (Python, MATLAB, JavaScript, online)
- Integration with BIDS, NWB, and EEGLAB
- Event categorization and experimental design
- Advanced features like definitions and temporal scope

The markdown renderer supports LaTeX math enclosed in dollar signs, e.g. $\\alpha + \\beta = 1$.
So please use LaTeX math when appropriate.
For separate line math, use double dollar signs. Never use \\begin{eqnarray*} or any other LaTeX environment.
Just use dollar signs.

When providing examples of HED annotations, use code blocks for clarity.
`;

export default hedAssistantSystemPrompt;
143 changes: 143 additions & 0 deletions src/assistants/hed-assistant/integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { describe, expect, it } from 'vitest';

describe('HED Assistant - Integration', () => {
describe('File Structure', () => {
const assistantPath = 'src/assistants/hed-assistant';

it('should have all required assistant files', () => {
const requiredFiles = [
'hedAssistantSystemPrompt.ts',
'getTools.ts',
'preferences.tsx',
'retrieveHedDocs.tsx',
];

requiredFiles.forEach(file => {
const filePath = join(process.cwd(), assistantPath, file);
expect(() => readFileSync(filePath, 'utf8')).not.toThrow();
});
});

it('should have system prompt exported', () => {
const filePath = join(process.cwd(), assistantPath, 'hedAssistantSystemPrompt.ts');
const content = readFileSync(filePath, 'utf8');

expect(content).toContain('export default');
expect(content).toContain('HED');
expect(content.length).toBeGreaterThan(1000); // Ensure it's substantial
});

it('should export getTools function', () => {
const filePath = join(process.cwd(), assistantPath, 'getTools.ts');
const content = readFileSync(filePath, 'utf8');

expect(content).toContain('export default');
expect(content).toContain('retrieveHedDocs');
});

it('should export preferences object', () => {
const filePath = join(process.cwd(), assistantPath, 'preferences.tsx');
const content = readFileSync(filePath, 'utf8');

expect(content).toContain('export default');
expect(content).toContain('Preferences');
expect(content).toContain('hedAssistantSystemPrompt');
});
});

describe('App Integration', () => {
it('should be registered in App.tsx', () => {
const appPath = join(process.cwd(), 'src/App.tsx');
const content = readFileSync(appPath, 'utf8');

expect(content).toContain('hed-assistant');
expect(content).toContain('hedAssistantPreferences');
expect(content).toContain('from "./assistants/hed-assistant/preferences"');
});

it('should be registered in tools/getTools.ts', () => {
const toolsPath = join(process.cwd(), 'src/tools/getTools.ts');
const content = readFileSync(toolsPath, 'utf8');

expect(content).toContain('hed-assistant');
expect(content).toContain('getHedAssistantTools');
expect(content).toContain('from "../assistants/hed-assistant/getTools"');
});
});

describe('Documentation Tool', () => {
it('should have retrieveHedDocs tool with proper structure', () => {
const toolPath = join(process.cwd(), 'src/assistants/hed-assistant/retrieveHedDocs.tsx');
const content = readFileSync(toolPath, 'utf8');

expect(content).toContain('export const toolFunction');
expect(content).toContain('retrieve_hed_docs');
expect(content).toContain('export const execute');
expect(content).toContain('export const getDocPages');
expect(content).toContain('export const getDetailedDescription');
});

it('should have comprehensive documentation pages defined', () => {
const toolPath = join(process.cwd(), 'src/assistants/hed-assistant/retrieveHedDocs.tsx');
const content = readFileSync(toolPath, 'utf8');

// Check for key HED resources
expect(content).toContain('hed-specification');
expect(content).toContain('hed-resources');
expect(content).toContain('Introduction');
expect(content).toContain('Terminology');
expect(content).toContain('Basic');

// Check for categories
expect(content).toContain('specification');
expect(content).toContain('quickstart');
expect(content).toContain('tools');
expect(content).toContain('advanced');
expect(content).toContain('integration');

// Verify we have substantial number of docs (should be 41)
const docMatches = content.match(/title:/g);
expect(docMatches).toBeDefined();
expect(docMatches!.length).toBeGreaterThan(35); // At least 35 docs
});

it('should have preloaded documents configured', () => {
const toolPath = join(process.cwd(), 'src/assistants/hed-assistant/retrieveHedDocs.tsx');
const content = readFileSync(toolPath, 'utf8');

expect(content).toContain('includeFromStart: true');

// Should have at least 3 preloaded docs
const preloadMatches = content.match(/includeFromStart: true/g);
expect(preloadMatches).toBeDefined();
expect(preloadMatches!.length).toBeGreaterThanOrEqual(3);
});
});

describe('Assistant Content', () => {
it('should have HED-specific content in system prompt', () => {
const promptPath = join(process.cwd(), 'src/assistants/hed-assistant/hedAssistantSystemPrompt.ts');
const content = readFileSync(promptPath, 'utf8');

// HED-specific terms
expect(content).toContain('Hierarchical Event Descriptors');
expect(content).toContain('HED');
expect(content).toContain('annotation');
expect(content).toContain('hedtags.org');

// Documentation requirements
expect(content).toContain('retrieve_hed_docs');
expect(content).toContain('documentation');
});

it('should have suggested prompts in preferences', () => {
const prefsPath = join(process.cwd(), 'src/assistants/hed-assistant/preferences.tsx');
const content = readFileSync(prefsPath, 'utf8');

expect(content).toContain('suggestedPrompts');
expect(content).toContain('HED');
});
});
});
128 changes: 128 additions & 0 deletions src/assistants/hed-assistant/preferences.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useMemo } from "react";
import { Preferences } from "../../qpcommon/MainWindow";
import hedAssistantSystemPrompt from "./hedAssistantSystemPrompt";
import { getDocPages } from "./retrieveHedDocs";

// eslint-disable-next-line react-refresh/only-export-components
const AssistantDisplayInfo = () => {
const hedUrl = "https://www.hedtags.org/";
const hedSpecUrl = "https://www.hedtags.org/hed-specification";
const hedResourcesUrl = "https://www.hedtags.org/hed-resources";
const hedGithubUrl = "https://github.com/hed-standard";
const hedSchemaBrowserUrl = "https://www.hedtags.org/display_hed.html";
const docPages = useMemo(() => getDocPages(), []);

// Get categories for displaying
const categories = [
"specification",
"specification-details",
"introductory",
"quickstart",
"core-concepts",
"tools",
"advanced",
"integration",
"reference",
];

const categoryNames: { [key: string]: string } = {
specification: "Specification",
"specification-details": "Specification Details",
introductory: "Introductory",
quickstart: "Quick Starts",
"core-concepts": "Core Concepts",
tools: "Tools",
advanced: "Advanced",
integration: "Integration",
reference: "Reference",
};

return (
<div style={{ maxWidth: "600px", margin: "0 auto" }}>
<p>
This is a technical assistant for{" "}
<a href={hedUrl} target="_blank" rel="noopener noreferrer">
Hierarchical Event Descriptors (HED)
</a>
, providing guidance, troubleshooting, and explanations based on the
official{" "}
<a href={hedSpecUrl} target="_blank" rel="noopener noreferrer">
HED Specification
</a>{" "}
and{" "}
<a href={hedResourcesUrl} target="_blank" rel="noopener noreferrer">
HED Resources
</a>
.
</p>
<p>
Additional resources:{" "}
<a href={hedGithubUrl} target="_blank" rel="noopener noreferrer">
HED GitHub Organization
</a>{" "}
|{" "}
<a
href={hedSchemaBrowserUrl}
target="_blank"
rel="noopener noreferrer"
>
HED Schema Browser
</a>
</p>
<p>
<strong>Available Documentation ({docPages.length} documents):</strong>
</p>
<details style={{ marginTop: "10px" }}>
<summary style={{ cursor: "pointer", fontWeight: "bold" }}>
View all documentation categories
</summary>
<div style={{ marginLeft: "20px", marginTop: "10px" }}>
{categories.map((category) => {
const categoryDocs = docPages.filter(
(doc) => doc.category === category,
);
if (categoryDocs.length === 0) return null;
return (
<div key={category} style={{ marginBottom: "10px" }}>
<strong>{categoryNames[category]}:</strong> (
{categoryDocs.length} docs)
<ul style={{ marginTop: "5px", marginBottom: "5px" }}>
{categoryDocs.slice(0, 3).map((doc) => (
<li key={doc.url}>
<a
href={doc.url}
target="_blank"
rel="noopener noreferrer"
>
{doc.title}
</a>
{doc.includeFromStart && " (preloaded)"}
</li>
))}
{categoryDocs.length > 3 && (
<li>...and {categoryDocs.length - 3} more</li>
)}
</ul>
</div>
);
})}
</div>
</details>
</div>
);
};

const preferences: Preferences = {
getAssistantSystemPrompt: async () => hedAssistantSystemPrompt,
assistantDisplayInfo: <AssistantDisplayInfo />,
suggestedPrompts: [
"What is HED and how is it used?",
"How do I annotate an event with HED tags?",
"Explain the difference between HED short and long forms.",
"How do I validate HED annotations?",
"What tools are available for working with HED?",
],
requiresJupyter: false,
};

export default preferences;
Loading