diff --git a/.env.example b/.env.example index 9ebb01af..21b50f17 100644 --- a/.env.example +++ b/.env.example @@ -13,7 +13,7 @@ PUBLIC_PROJECT_REPO_BRANCH_REF="refs/heads/main" # LLM and Embedding Model - Optional to run jAI locally - Get keys https://platform.openai.com OPENAI_API_KEY=sk-proj-************************************* -OPENAI_CHAT_MODEL=gpt-4.1 +OPENAI_CHAT_MODEL=gpt-4.1-mini OPENAI_EMBEDDINGS_MODEL=text-embedding-3-small # Vector Store - Optional to run jAI locally - Get Keys https://qdrant.tech diff --git a/.prettierignore b/.prettierignore index c21b790a..d5625402 100644 --- a/.prettierignore +++ b/.prettierignore @@ -31,9 +31,6 @@ package-lock.json .DS_Store Thumbs.db -# SVG files with CDATA that cause parsing issues -src/components/jargonsdev-logo.astro - # Files with parsing issues in inline scripts src/layouts/base.astro diff --git a/apps/jai/README.md b/apps/jai/README.md index 23ba094f..d0593b74 100644 --- a/apps/jai/README.md +++ b/apps/jai/README.md @@ -12,6 +12,7 @@ Unlike standalone AI applications, ✨jAI is deeply integrated into the jargons.dev ecosystem, powering features like: +- **AI-powered word definitions**: Generate instant definitions for technical terms not yet in the dictionary - Intelligent word explanations and follow-up conversations - Semantic search across the dictionary - Context-aware responses based on the curated dictionary content @@ -31,9 +32,12 @@ The ✨jAI module is organized into focused utility files: ``` apps/jai/ -├── index.js # Main exports and module interface +├── index.js # Main exports and module interface +├── components/ # React components for UI/feature integration +│ ├── logo.jsx # jAI Logo +│ └── word-search.jsx # jAI Word Search feature └── lib/ - ├── jai-prompt.js # AI personality and prompt templates + ├── jai-prompts.js # AI personality and prompt templates ├── model.js # OpenAI model configuration ├── utils.js # Utility functions for message formatting └── vector-store.js # Qdrant vector store integration @@ -49,7 +53,7 @@ Main module interface that exports all ✨jAI utilities: export { jAIPrompt, formatMessage, model, vectorStore }; ``` -#### `lib/jai-prompt.js` +#### `lib/jai-prompts.js` Defines ✨jAI's personality and conversation templates. The AI assistant is designed to: @@ -57,6 +61,8 @@ Defines ✨jAI's personality and conversation templates. The AI assistant is des - Use relatable analogies and developer-friendly examples - Maintain a friendly, witty personality - Encourage follow-up questions and deeper exploration +- Generate accurate, SEO-friendly definitions for technical terms +- Provide structured responses optimized for dictionary content #### `lib/model.js` @@ -78,6 +84,20 @@ Manages the Qdrant vector database integration: Utility functions for message processing and formatting. +#### `components/logo.jsx` + +SVG React component for the ✨jAI logo with customizable styling. Used throughout the application for branding and visual identity. + +#### `components/word-search.jsx` + +React component that powers the AI-driven word definition feature. Includes: + +- **JAIWordSearch**: Main component for generating and displaying AI-powered word definitions +- **JAIWordSearchTrigger**: UI trigger component for initiating word searches with ✨jAI +- Streaming response handling for real-time definition generation +- Error handling and loading states +- Integration with the `/api/jai/search` endpoint + ## Environment Variables ✨jAI requires the following environment variables: @@ -85,7 +105,7 @@ Utility functions for message processing and formatting. ```bash # OpenAI Configuration OPENAI_API_KEY=your_openai_api_key -OPENAI_CHAT_MODEL=gpt-4-turbo-preview # or your preferred model +OPENAI_CHAT_MODEL=gpt-4.1-mini # or your preferred model OPENAI_EMBEDDINGS_MODEL=text-embedding-3-small # Qdrant Vector Database @@ -111,22 +131,32 @@ This command processes all dictionary entries and creates embeddings for semanti ## Architecture Integration -✨jAI is designed as a utility module that integrates seamlessly with the main jargons.dev application. The module is consumed in two primary areas: +✨jAI is designed as a utility module that integrates seamlessly with the main jargons.dev application. The module is consumed in three primary areas: ### 1. Vector Store Seeding (`dev/seed-vector-store.js`) Uses the `vectorStore` utility to populate the database with dictionary content. The script fetches dictionary entries from the jargons.dev API, processes them into document chunks, and creates vector embeddings for semantic search capabilities. -### 2. API Endpoint (`src/pages/api/jai/follow-up-chat.js`) +### 2. Word Search API (`src/pages/api/jai/search.js`) + +Dedicated endpoint for AI-powered word definitions that: + +- Uses the `SEARCH_WORD` prompt template for structured, dictionary-optimized responses +- Streams AI-generated definitions in real-time +- Provides fallback definitions for terms not yet in the dictionary +- Powers the `/browse/with-jai` page and word search components + + ### Integration Flow 1. **Data Preparation**: `seed-vector-store.js` populates the vector database with dictionary content -2. **Runtime Processing**: API endpoints use ✨jAI utilities for semantic search and AI response generation -3. **Real-time Interaction**: Streaming responses provide immediate feedback to users -4. **Context Awareness**: Vector search ensures AI responses are grounded in dictionary content +2. **Word Search Flow**: Users can search for undefined terms via `/browse/with-jai`, which uses the search API to generate instant definitions +3. **Runtime Processing**: API endpoints use ✨jAI utilities for semantic search and AI response generation +4. **Real-time Interaction**: Streaming responses provide immediate feedback to users +5. **Context Awareness**: Vector search ensures AI responses are grounded in dictionary content ## Development diff --git a/apps/jai/components/logo.jsx b/apps/jai/components/logo.jsx new file mode 100644 index 00000000..5c3fd8c2 --- /dev/null +++ b/apps/jai/components/logo.jsx @@ -0,0 +1,40 @@ +export default function JAILogo({ className }) { + return ( + + + + + + + + + + + + + + ); +} diff --git a/apps/jai/components/word-search.jsx b/apps/jai/components/word-search.jsx new file mode 100644 index 00000000..08379ce6 --- /dev/null +++ b/apps/jai/components/word-search.jsx @@ -0,0 +1,108 @@ +/** + * JAI Word Search Feature Components + * @exports JAIWordSearch - Fetches and displays AI-generated word definitions with loading and error states + * @exports JAIWordSearchTrigger - Link component to initiate a word search with jAI + */ + +import { useEffect, useState } from "react"; +import Markdown from "react-markdown"; +import { useChat } from "@ai-sdk/react"; +import JAILogo from "./logo.jsx"; +import { capitalizeText } from "../../../src/lib/utils/index.js"; + +/** + * JAI Word Search Component + * @param {Object} props + * @param {string} props.word - The word to search + * @returns {JSX.Element} + */ +export default function JAIWordSearch({ word }) { + const [error, setError] = useState(null); + + /** + * Initialize useChat hook + */ + const { messages, status, append } = useChat({ + api: "/api/jai/search", + onError: (e) => { + setError(JSON.parse(e.message)); + console.error(e); + }, + }); + + /** + * Handle Asking jAI for the word definition + */ + useEffect(() => { + append({ + role: "user", + content: `define ${word}`, + }); + }, [word]); + + /** + * Loading State + */ + if (status === "submitted" || (status === "ready" && messages.length === 0)) + return ( +
+
+
+
+
+
+ ); + + /** + * Error State + */ + if (error) + return ( +
+
+ + + +

+ An Error Occured while generating the definition for{" "} + {word}. +

+
+
+ ); + + return messages + .filter((msg) => msg.role !== "user") + .map((msg, index) => {msg.content}); +} + +/** + * JAI Word Search Trigger Component + * @param {Object} props + * @param {string} props.word - The word to search + * @param {number} props.cursor - The cursor position for keyboard navigation + * @returns {JSX.Element} + */ +export const JAIWordSearchTrigger = ({ word, cursor }) => ( + + {capitalizeText(word)} + + Search with + + + +); diff --git a/apps/jai/index.js b/apps/jai/index.js index 4f902be5..2c28f5e3 100644 --- a/apps/jai/index.js +++ b/apps/jai/index.js @@ -1,6 +1,6 @@ import model from "./lib/model.js"; import { formatMessage } from "./lib/utils.js"; -import { jAIPrompt } from "./lib/jai-prompt.js"; +import { jAIPrompts } from "./lib/jai-prompts.js"; import vectorStore from "./lib/vector-store.js"; -export { jAIPrompt, formatMessage, model, vectorStore }; +export { jAIPrompts, formatMessage, model, vectorStore }; diff --git a/apps/jai/lib/jai-prompt.js b/apps/jai/lib/jai-prompt.js deleted file mode 100644 index c888855a..00000000 --- a/apps/jai/lib/jai-prompt.js +++ /dev/null @@ -1,36 +0,0 @@ -import { PromptTemplate } from "@langchain/core/prompts"; - -const TEMPLATE = `You are jAI, an AI-powered assistant for jargons.dev, a dictionary for developers and tech enthusiasts. -Your job is to explain technical jargon in a clear, concise, and engaging way. You have a friendly, slightly witty personality, -and you relate to developers by using analogies, code examples, and real-world comparisons. - -Your tone should be knowledgeable yet casual—think of yourself as a coding buddy who can break down complex terms without being overly technical. - -Follow these guidelines when responding: -1. **Explain concisely**: Keep it short, clear, and to the point. -2. **Use relatable analogies**: Compare tech concepts to real-world scenarios when possible. -3. **Inject light humor**: A sprinkle of wit is welcome but keep it professional and helpful. -4. **Encourage follow-up questions**: Suggest deeper dives where relevant. -5. **Provide developer-centric examples**: Preferably in JavaScript, unless another language is more appropriate. -6. **Vary your responses**: Avoid repetitive explanations—offer multiple phrasings when possible. -7. **Use friendly but smart language**: Sound like an experienced dev friend, not a rigid encyclopedia. - -Examples of your style: -- Instead of just saying "An API is a way for two systems to communicate," say: - _"An API is like a restaurant menu—you see what’s available and place an order. The kitchen (server) then prepares your dish (response). No peeking inside!"_ -- Instead of saying "Metadata is data about data," say: - _"Metadata is like a README file—it doesn’t change the code, but it tells you what’s inside."_ -- Instead of a generic error message, say: - _"Oops! Looks like I just ran out of memory. Try again?"_ - -Now, answer the user's question based only on the following context. If the answer is not in the context, go ahead and provide an answer using your own knowledge; but lightly mention that the information was not available in the context. - ------------------------------- -Context: {context} ------------------------------- -Current conversation: {chat_history} - -User: {question} -jAI:`; - -export const jAIPrompt = PromptTemplate.fromTemplate(TEMPLATE); diff --git a/apps/jai/lib/jai-prompts.js b/apps/jai/lib/jai-prompts.js new file mode 100644 index 00000000..c38583b6 --- /dev/null +++ b/apps/jai/lib/jai-prompts.js @@ -0,0 +1,74 @@ +import { PromptTemplate } from "@langchain/core/prompts"; + +const PERSONALITY = `You are jAI, an AI-powered assistant for jargons.dev, a dictionary for developers and tech enthusiasts. +Your job is to explain technical jargon in a clear, concise, and engaging way. You have a friendly, slightly witty personality, +and you relate to developers by using analogies, code examples, and real-world comparisons. + +Your tone should be knowledgeable yet casual—think of yourself as a coding buddy who can break down complex terms without being overly technical. + +Follow these guidelines when responding: +1. **Explain concisely**: Keep it short, clear, and to the point. +2. **Use relatable analogies**: Compare tech concepts to real-world scenarios when possible. +3. **Inject light humor**: A sprinkle of wit is welcome but keep it professional and helpful. +4. **Encourage follow-up questions**: Suggest deeper dives where relevant. +5. **Provide developer-centric examples**: Preferably in JavaScript, unless another language is more appropriate. +6. **Vary your responses**: Avoid repetitive explanations—offer multiple phrasings when possible. +7. **Use friendly but smart language**: Sound like an experienced dev friend, not a rigid encyclopedia. + +Examples of your style: +- Instead of just saying "An API is a way for two systems to communicate," say: + _"An API is like a restaurant menu—you see what’s available and place an order. The kitchen (server) then prepares your dish (response). No peeking inside!"_ +- Instead of saying "Metadata is data about data," say: + _"Metadata is like a README file—it doesn’t change the code, but it tells you what’s inside."_ +- Instead of a generic error message, say: + _"Oops! Looks like I just ran out of memory. Try again?"_ + +Now, answer the user's question based only on the following context. If the answer is not in the context, go ahead and provide an answer using your own knowledge; but lightly mention that the information was not available in the context. + +------------------------------ +Context: {context} +------------------------------ +Current conversation: {chat_history} + +User: {question} +jAI:`; + +const SEARCH_WORD = `You are jAI, an AI-powered assistant for jargons.dev, a dictionary of technical terms for developers, software engineers, and technology professionals. Your task is to write definitions for technical words or jargons I provide, following these rules: + +- **Style & Tone:** + - Keep the meaning **formal, clear, and simplified**. + - Write in a way that is beginner-friendly but precise, avoiding overly technical jargon or complex language that might confuse beginners. + - Ensure content is **SEO-friendly** (natural use of keywords, clarity, and directness). + - Ensure the definition is **accurate and up-to-date** with current industry standards. + - Use **simple language** and short sentences for clarity. + - If the term has multiple meanings, focus on the one most relevant to software, programming, or technology. + - If the term is an acronym, always include the **full form or origin of the term** first if it exists and adds value. + - If the term is a programming language or framework, include its primary use case, purpose or/and code snippet. + - If the term is a concept (like "Agile" or "DevOps"), briefly mention its significance in the tech industry. + - No fluff, just professional and useful content. + +- **Structure (always in this order):** + 1. **Meaning** → A clear, concise definition of the term. + 2. **Further Explanation** → A short expansion with more detail or context (keep it brief). + 3. **Example** → Only include this if it is absolutely necessary (such as code snippets or practical use cases). + +- **Formatting Rules:** + - Do **not** repeat the keyword as a heading — the webpage already has it as the H1 title. + - Write directly into the sections (Meaning → Explanation → Example if needed). + - Do **not** title the specific sections: "Meaning", "Explanation". + - If you include an example, label it clearly (e.g., "Example:") and format the title in bold. + +- **Out-of-scope words:** + - If the word is not technical or relevant to software, programming, or technology, respond with a short, polite statement that it is out of scope for jargons.dev. + - Do **not** ask the user for clarification or another word. + - Instead, suggest that they try searching again if they meant something different. + +------------------------------ + +User: {question} +jAI:`; + +export const jAIPrompts = { + PERSONALITY: PromptTemplate.fromTemplate(PERSONALITY), + SEARCH_WORD: PromptTemplate.fromTemplate(SEARCH_WORD), +}; diff --git a/apps/jai/lib/model.js b/apps/jai/lib/model.js index e6bd646a..2853ab15 100644 --- a/apps/jai/lib/model.js +++ b/apps/jai/lib/model.js @@ -1,3 +1,32 @@ +/** + * jAI Model Selection Notes + * + * Different OpenAI models fit different parts of the jAI ecosystem. + * This file currently exports a singleton chat model, but future adjustments + * may dynamically select models depending on usecase. + * + * 📌 Recommended Defaults: + * - gpt-4.1-mini → Balanced, tunable (supports temperature < 1), cost-efficient. + * Ideal for most jAI features: search fallback, follow-up chat, contribution copilot. + * + * - gpt-5-mini → Ultra-fast, cheaper at scale, but temperature is fixed (1). + * Use when speed/cost dominate and variability is acceptable. + * Good for lightweight lookups (e.g., extensions, quick responses). + * + * - gpt-4.1 or gpt-5 (full models) → High reasoning depth and reliability. + * Reserved for heavier tasks: advanced quizzes, deep analogies, or critical reviews. + * + * ✅ Suggested Tiered Use Strategy: + * - Dictionary fallback (Search with jAI): gpt-4.1-mini - https://github.com/jargonsdev/roadmap/issues/8 + * - Word follow-up chat: gpt-4.1-mini - https://github.com/jargonsdev/roadmap/issues/6 + * - Contribution AI reviewer/copilot: gpt-4.1-mini - https://github.com/jargonsdev/roadmap/issues/11 + * - Extensions/fast lookups: gpt-5-mini - https://github.com/jargonsdev/roadmap/issues/2 + * - Advanced reasoning/quiz generation: gpt-4.1 or gpt-5 - https://github.com/jargonsdev/roadmap/issues/7 + * + * This structure ensures a balance between speed, cost, and reasoning quality, + * while allowing flexibility for scaling different features of jargons.dev. + */ + import { ChatOpenAI } from "@langchain/openai"; // Create the model diff --git a/public/jAI.svg b/public/jAI.svg new file mode 100644 index 00000000..53017ffc --- /dev/null +++ b/public/jAI.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/islands/search.jsx b/src/components/islands/search.jsx index ab69bc4d..806f5385 100644 --- a/src/components/islands/search.jsx +++ b/src/components/islands/search.jsx @@ -1,11 +1,12 @@ +import { buildWordPathname, buildWordSlug } from "../../lib/utils/index.js"; import Flexsearch from "flexsearch"; import { useEffect, useState } from "react"; import { useStore } from "@nanostores/react"; import useRouter from "../../lib/hooks/use-router.js"; -import { buildWordPathname, buildWordSlug } from "../../lib/utils/index.js"; import useIsMacOS from "../../lib/hooks/use-is-mac-os.js"; import useLockBody from "../../lib/hooks/use-lock-body.js"; import { $isSearchOpen } from "../../lib/stores/search.js"; +import { JAIWordSearchTrigger as SearchWithJAI } from "../../../apps/jai/components/word-search.jsx"; // Create Search Index const searchIndex = new Flexsearch.Document({ @@ -183,7 +184,9 @@ function SearchDialog() { // Arrow Up/Down & Enter - keybind function handleKeyboardCtrl(e) { - const resultsCount = searchResult?.length || 0; + // Results count - if no results, but search term exists, allow AskJAI search option cursor navigation + const resultsCount = + searchResult?.length || (searchTerm.length >= 1 ? 1 : 0); if (resultsCount && e.key === "ArrowUp") { e.preventDefault(); setCursor( @@ -205,6 +208,12 @@ function SearchDialog() { } } + // Update search term state on input change and reset cursor + function handleSearchTermChange(e) { + setSearchTerm(e.target.value); + setCursor(-1); + } + return (
{/* Blur */} @@ -241,7 +250,7 @@ function SearchDialog() { type="text" value={searchTerm} onKeyDown={handleKeyboardCtrl} - onChange={(e) => setSearchTerm(e.target.value)} + onChange={handleSearchTermChange} className="block w-full bg-transparent text-gray-600 focus:outline-none text-base md:text-lg" /> {result.length < 1 && searchTerm.length >= 1 ? ( - /** - * @todo add message suggesting adding/contributing the word to dictionary - */ -

No Result found

+ ) : ( result.map(({ doc }, i) => ( + + + + + + + + + + diff --git a/src/components/jargonsdev-logo.astro b/src/components/jargonsdev-logo.astro index fa45e5e2..40f3374f 100644 --- a/src/components/jargonsdev-logo.astro +++ b/src/components/jargonsdev-logo.astro @@ -1,28 +1,29 @@ --- -const { class:list } = Astro.props; +const { class: list } = Astro.props; --- - - - - - - - - - - + + + + + + + - \ No newline at end of file + + diff --git a/src/pages/api/jai/search.js b/src/pages/api/jai/search.js new file mode 100644 index 00000000..9d0373a8 --- /dev/null +++ b/src/pages/api/jai/search.js @@ -0,0 +1,65 @@ +import { LangChainAdapter } from "ai"; +import { RunnableSequence } from "@langchain/core/runnables"; +import { jAIPrompts, model } from "../../../../apps/jai/index.js"; +import { HttpResponseOutputParser } from "langchain/output_parsers"; + +export async function POST({ request }) { + const corsHeaders = { + "Access-Control-Allow-Origin": "same-origin", + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization", + }; + + try { + // Extract the `messages` from the body of the request + const { messages } = await request.json(); + + const currentMessageContent = messages[messages.length - 1].content; + + // Create the parser - parses the response from the model into http-friendly format + const parser = new HttpResponseOutputParser(); + + // Create a chain of runnables - this is the core of the LangChain API + const chain = RunnableSequence.from([ + { + question: (input) => input.question, + }, + jAIPrompts.SEARCH_WORD, + model, + parser, + ]); + + // Convert the response into a friendly text-stream + const stream = await chain.stream({ + question: currentMessageContent, + }); + + // Convert Uint8Array stream to string stream before returning + const textStream = new ReadableStream({ + async start(controller) { + const decoder = new TextDecoder(); + for await (const chunk of stream) { + controller.enqueue(decoder.decode(chunk)); + } + controller.close(); + }, + }); + + const response = LangChainAdapter.toDataStreamResponse(textStream); + + // Add CORS headers to the response + Object.entries(corsHeaders).forEach(([key, value]) => { + response.headers.set(key, value); + }); + + return response; + } catch (e) { + return Response.json( + { error: e.message }, + { + status: e.status ?? 500, + headers: corsHeaders, + }, + ); + } +} diff --git a/src/pages/browse/with-jai/index.astro b/src/pages/browse/with-jai/index.astro new file mode 100644 index 00000000..910fd469 --- /dev/null +++ b/src/pages/browse/with-jai/index.astro @@ -0,0 +1,55 @@ +--- +import { getCollection } from "astro:content"; +import BaseLayout from "../../../layouts/base.astro"; +import Navbar from "../../../components/navbar.astro"; +import JAILogo from "../../../components/jai-logo.astro"; +import Search from "../../../components/islands/search.jsx"; +import { capitalizeText } from "../../../lib/utils/index.js"; +import JAIWordSearch from "../../../../apps/jai/components/word-search.jsx"; + +const dictionary = await getCollection("dictionary"); + +const word = Astro.url.searchParams.get("word"); +if (!word) return Astro.redirect("/"); + +const title = capitalizeText(word); +--- + + + + + + +
+
+

+ {title} +

+ +
+
+ Generated by + +
+ + +
+
+ + +
+ +
+ + +
+
diff --git a/tailwind.config.js b/tailwind.config.js index 3eee8968..a874a0ff 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,6 +1,9 @@ /** @type {import('tailwindcss').Config} */ export default { - content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], + content: [ + "./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}", + "./apps/jai/components/**/*.{js,jsx}", + ], theme: { extend: {}, },