├── .nvmrc ├── .gitignore ├── Dockerfile ├── compose.yaml ├── package.json ├── src ├── config.js ├── utils.js ├── processor │ ├── categorizer.js │ ├── responder.js │ ├── index.js │ ├── featureId.js │ └── clusterer.js └── generator.js ├── README.md ├── index.js ├── data ├── comments.json └── results.json └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.10.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .DS_Store -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-slim 2 | WORKDIR /usr/local/app 3 | COPY package* ./ 4 | RUN npm ci && npm cache clean --force 5 | COPY ./data ./data 6 | COPY index.js ./ 7 | COPY ./src ./src 8 | CMD ["node", "./index.js"] 9 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: ./ 4 | ports: 5 | - "8080:8080" 6 | models: 7 | gemma3: 8 | endpoint_var: OPENAI_BASE_URL 9 | model_var: LLM_MODEL 10 | embeddings: 11 | model_var: EMBEDDINGS_MODEL 12 | 13 | models: 14 | gemma3: 15 | model: ai/gemma3:4B-F16 16 | embeddings: 17 | model: ai/mxbai-embed-large 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-reviewer", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "description": "A Node.js application that demonstrates how to use an LLM to process user comments about a fictional AI assistant called Jarvis", 10 | "dependencies": { 11 | "openai": "^4.90.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration settings for the Comment Processing System 3 | */ 4 | 5 | function asBoolean(value, defaultValue) { 6 | if (!value) return defaultValue; 7 | if (typeof value === 'string') { 8 | return value.toLowerCase() === 'true'; 9 | } 10 | if (typeof value === 'boolean') { 11 | return value; 12 | } 13 | return defaultValue; 14 | } 15 | 16 | export default { 17 | // Model configuration 18 | openai: { 19 | baseURL: process.env.OPENAI_BASE_URL || "http://localhost:12434/engines/v1", // Base URL for Docker Model Runner 20 | apiKey: 'ignored', 21 | model: process.env.LLM_MODEL || "ai/gemma3", // Model to use for generation and processing 22 | commentGeneration: { 23 | temperature: 0.3, 24 | max_tokens: 250, 25 | n: 1, 26 | }, 27 | embedding: { 28 | model: process.env.EMBEDDINGS_MODEL || "ai/mxbai-embed-large", // Model for generating embeddings 29 | }, 30 | }, 31 | 32 | // Comment generation settings 33 | generator: { 34 | allowRegenerateComments: asBoolean(process.env.ALLOW_GENERATE_COMMENTS, true), 35 | numComments: 20, // Number of synthetic comments to generate 36 | commentTypes: ["positive", "negative", "neutral"], // Types of comments to generate 37 | topics: [ 38 | "user interface", 39 | "response quality", 40 | "response speed", 41 | "accuracy", 42 | "helpfulness", 43 | "feature requests", 44 | "bugs", 45 | "pricing", 46 | "comparison to competitors", 47 | "general experience" 48 | ], 49 | }, 50 | 51 | // Processing settings 52 | processor: { 53 | clustering: { 54 | similarityThreshold: 0.75, // Threshold for considering comments similar 55 | }, 56 | }, 57 | 58 | // Paths 59 | paths: { 60 | commentsFile: "./data/comments.json", 61 | resultsFile: "./data/results.json", 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility functions for the Comment Processing System 3 | */ 4 | 5 | import { promises as fs } from 'fs'; 6 | import path from 'path'; 7 | 8 | /** 9 | * Save data to a JSON file 10 | * @param {string} filePath - Path to the file 11 | * @param {object} data - Data to save 12 | * @returns {Promise} 13 | */ 14 | async function saveToJson(filePath, data) { 15 | try { 16 | const dirPath = path.dirname(filePath); 17 | await fs.mkdir(dirPath, { recursive: true }); 18 | await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'); 19 | console.log(`Data successfully saved to ${filePath}`); 20 | } catch (error) { 21 | console.error(`Error saving data to ${filePath}:`, error); 22 | throw error; 23 | } 24 | } 25 | 26 | /** 27 | * Load data from a JSON file 28 | * @param {string} filePath - Path to the file 29 | * @returns {Promise} - Parsed JSON data 30 | */ 31 | async function loadFromJson(filePath) { 32 | try { 33 | const data = await fs.readFile(filePath, 'utf8'); 34 | return JSON.parse(data); 35 | } catch (error) { 36 | console.error(`Error loading data from ${filePath}:`, error); 37 | throw error; 38 | } 39 | } 40 | 41 | /** 42 | * Calculate cosine similarity between two vectors 43 | * @param {number[]} vecA - First vector 44 | * @param {number[]} vecB - Second vector 45 | * @returns {number} - Similarity score between 0 and 1 46 | */ 47 | function cosineSimilarity(vecA, vecB) { 48 | if (vecA.length !== vecB.length) { 49 | throw new Error('Vectors must have the same length'); 50 | } 51 | 52 | let dotProduct = 0; 53 | let normA = 0; 54 | let normB = 0; 55 | 56 | for (let i = 0; i < vecA.length; i++) { 57 | dotProduct += vecA[i] * vecB[i]; 58 | normA += vecA[i] * vecA[i]; 59 | normB += vecB[i] * vecB[i]; 60 | } 61 | 62 | if (normA === 0 || normB === 0) { 63 | return 0; 64 | } 65 | 66 | return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); 67 | } 68 | 69 | /** 70 | * Format the current date and time 71 | * @returns {string} - Formatted date and time 72 | */ 73 | function getFormattedDateTime() { 74 | const now = new Date(); 75 | return now.toISOString(); 76 | } 77 | 78 | /** 79 | * Print a section header to the console 80 | * @param {string} title - Section title 81 | */ 82 | function printSectionHeader(title) { 83 | const line = '='.repeat(title.length + 4); 84 | console.log('\n' + line); 85 | console.log(`= ${title} =`); 86 | console.log(line + '\n'); 87 | } 88 | 89 | export { 90 | saveToJson, 91 | loadFromJson, 92 | cosineSimilarity, 93 | getFormattedDateTime, 94 | printSectionHeader 95 | }; 96 | -------------------------------------------------------------------------------- /src/processor/categorizer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Comment categorizer for the Comment Processing System 3 | * Categorizes comments as positive, negative, or neutral 4 | */ 5 | 6 | import OpenAI from 'openai'; 7 | import config from '../config.js'; 8 | 9 | // Initialize OpenAI client 10 | const client = new OpenAI({ 11 | baseURL: config.openai.baseURL, 12 | apiKey: config.openai.apiKey, 13 | }); 14 | 15 | /** 16 | * Categorize a comment as positive, negative, or neutral 17 | * @param {object} comment - Comment object 18 | * @returns {Promise} - Comment with category added 19 | */ 20 | async function categorizeComment(comment) { 21 | try { 22 | const category = await determineSentiment(comment.text); 23 | 24 | return { 25 | ...comment, 26 | category 27 | }; 28 | } catch (error) { 29 | console.error(`Error categorizing comment ${comment.id}:`, error); 30 | // Default to neutral if categorization fails 31 | return { 32 | ...comment, 33 | category: 'neutral', 34 | categoryError: error.message 35 | }; 36 | } 37 | } 38 | 39 | /** 40 | * Determine the sentiment of a comment 41 | * @param {string} text - Comment text 42 | * @returns {Promise} - Sentiment category (positive, negative, neutral) 43 | */ 44 | async function determineSentiment(text) { 45 | const response = await client.chat.completions.create({ 46 | model: config.openai.model, 47 | messages: [ 48 | { 49 | role: "system", 50 | content: `You are a sentiment analysis system. Analyze the sentiment of user comments about an AI assistant called Jarvis. 51 | Classify each comment as exactly one of: "positive", "negative", or "neutral". 52 | Respond with only the category word, nothing else.` 53 | }, 54 | { 55 | role: "user", 56 | content: text 57 | } 58 | ], 59 | temperature: 0.1, 60 | max_tokens: 10 61 | }); 62 | 63 | const result = response.choices[0].message.content.trim().toLowerCase(); 64 | 65 | // Ensure the result is one of the valid categories 66 | if (['positive', 'negative', 'neutral'].includes(result)) { 67 | return result; 68 | } else { 69 | // Default to neutral if the result is not a valid category 70 | console.warn(`Invalid category result: "${result}". Defaulting to "neutral".`); 71 | return 'neutral'; 72 | } 73 | } 74 | 75 | /** 76 | * Categorize multiple comments 77 | * @param {Array} comments - Array of comment objects 78 | * @returns {Promise} - Array of comments with categories added 79 | */ 80 | async function categorizeComments(comments) { 81 | console.log(`Categorizing ${comments.length} comments...`); 82 | 83 | const categorizedComments = []; 84 | 85 | for (let i = 0; i < comments.length; i++) { 86 | const comment = comments[i]; 87 | const categorizedComment = await categorizeComment(comment); 88 | categorizedComments.push(categorizedComment); 89 | 90 | console.log(`Categorized comment ${i + 1}/${comments.length} as "${categorizedComment.category}"`); 91 | } 92 | 93 | return categorizedComments; 94 | } 95 | 96 | export { 97 | categorizeComments 98 | }; 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Comment Processing System 2 | 3 | A Node.js application that demonstrates how to use Gemma3 to process user comments about a fictional AI assistant called Jarvis. 4 | 5 | ## Overview 6 | 7 | This system processes user comments through a workflow that: 8 | 9 | 1. **Generates** synthetic user comments 10 | 2. **Categorizes** comments as positive, negative, or neutral 11 | 3. **Clusters** similar comments together 12 | 4. **Identifies** potential product features based on comment content 13 | 5. **Generates** polite responses for each comment 14 | 15 | ## Project Structure 16 | 17 | ``` 18 | ai-reviewer/ 19 | ├── src/ 20 | │ ├── config.js # Configuration settings 21 | │ ├── generator.js # Comment generation logic 22 | │ ├── processor/ 23 | │ │ ├── index.js # Main processor 24 | │ │ ├── categorizer.js # Comment categorization 25 | │ │ ├── clusterer.js # Comment clustering 26 | │ │ ├── featureId.js # Feature identification 27 | │ │ └── responder.js # Response generation 28 | │ └── utils.js # Utility functions 29 | ├── data/ 30 | │ └── comments.json # Generated comments storage 31 | ├── index.js # Main entry point 32 | ├── package.json 33 | └── .nvmrc 34 | ``` 35 | 36 | ## Requirements 37 | 38 | - Node.js v20.10.0 or later (ES Modules support) 39 | - Docker Desktop v4.40.0 or later (includes Docker Model Runner) 40 | - Hardware Requirements: 41 | - Minimum 3.4GB VRAM for Gemma3 model 42 | - Minimum 2.5GB disk space for Gemma3 model 43 | 44 | ## Installation 45 | 46 | 1. Clone the repository 47 | 2. Install dependencies: 48 | ```bash 49 | npm install 50 | ``` 51 | 52 | 3. Enable Docker Model Runner and listen on port 12434: 53 | ```bash 54 | docker desktop enable model-runner --tcp 12434 55 | ``` 56 | 57 | 4. Pull the Gemma3 model: 58 | 59 | ```bash 60 | docker model pull ai/gemma3 61 | ``` 62 | 63 | 5. Pull the embeddings model: 64 | 65 | ```bash 66 | docker model pull ai/mxbai-embed-large 67 | ``` 68 | 69 | ## Usage 70 | 71 | Run the application: 72 | 73 | ```bash 74 | node index.js 75 | ``` 76 | 77 | The application will: 78 | 79 | 1. Generate synthetic user comments about Jarvis (or use existing ones if available) 80 | 2. Process these comments through the workflow 81 | 3. Display a summary of the results 82 | 4. Save detailed results to `data/results.json` 83 | 84 | ## Configuration 85 | 86 | You can customize the application behavior by modifying `src/config.js`: 87 | 88 | - Change the number of comments to generate 89 | - Adjust the Gemma3 model and parameters 90 | - Modify the clustering similarity threshold 91 | - Add or remove comment topics 92 | 93 | ## How It Works 94 | 95 | ### Comment Generation 96 | 97 | The system uses Gemma3 to generate synthetic user comments about Jarvis, focusing on various aspects like UI, performance, features, etc. These comments are stored in JSON format with metadata. 98 | 99 | ### Comment Processing 100 | 101 | 1. **Categorization**: Uses sentiment analysis with Gemma3 to classify comments as positive, negative, or neutral. 102 | 2. **Clustering**: Implements embedding-based clustering to group similar comments together, even if they belong to different categories. 103 | 3. **Feature Identification**: Extracts potential feature requests or improvements from comment clusters using Gemma3. 104 | 4. **Response Generation**: Creates contextually appropriate, polite responses for each comment. 105 | 106 | ## Example Output 107 | 108 | The system generates a summary that includes: 109 | 110 | - Comment category distribution 111 | - Number of clusters found 112 | - Identified features with priority levels 113 | - Sample comments and responses 114 | 115 | Detailed results are saved to `data/results.json` for further analysis. 116 | -------------------------------------------------------------------------------- /src/processor/responder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Responder module for the Comment Processing System 3 | * Generates polite responses for user comments 4 | */ 5 | 6 | import OpenAI from 'openai'; 7 | import config from '../config.js'; 8 | 9 | // Initialize OpenAI client 10 | const client = new OpenAI({ 11 | baseURL: config.openai.baseURL, 12 | apiKey: config.openai.apiKey, 13 | }); 14 | 15 | /** 16 | * Generate responses for comments 17 | * @param {Array} comments - Array of comment objects 18 | * @param {object} features - Identified features 19 | * @returns {Promise} - Array of comments with responses added 20 | */ 21 | async function generateResponses(comments, features) { 22 | console.log(`Generating responses for ${comments.length} comments...`); 23 | 24 | const commentsWithResponses = []; 25 | 26 | for (let i = 0; i < comments.length; i++) { 27 | const comment = comments[i]; 28 | 29 | try { 30 | // Get cluster features 31 | const clusterFeatures = features.clusterFeatures[comment.clusterId] || []; 32 | 33 | // Generate response 34 | const response = await generateSingleResponse(comment, clusterFeatures); 35 | 36 | commentsWithResponses.push({ 37 | ...comment, 38 | response 39 | }); 40 | 41 | console.log(`Generated response for comment ${i + 1}/${comments.length}`); 42 | } catch (error) { 43 | console.error(`Error generating response for comment ${comment.id}:`, error); 44 | // Add comment without response 45 | commentsWithResponses.push({ 46 | ...comment, 47 | response: "We appreciate your feedback and will take it into consideration.", 48 | responseError: error.message 49 | }); 50 | } 51 | } 52 | 53 | return commentsWithResponses; 54 | } 55 | 56 | /** 57 | * Generate a response for a single comment 58 | * @param {object} comment - Comment object 59 | * @param {Array} relatedFeatures - Features related to this comment's cluster 60 | * @returns {Promise} - Generated response 61 | */ 62 | async function generateSingleResponse(comment, relatedFeatures) { 63 | // Create a context with the comment and related features 64 | let featuresContext = ''; 65 | if (relatedFeatures && relatedFeatures.length > 0) { 66 | featuresContext = `Based on this feedback, we've identified these potential features or improvements:\n`; 67 | relatedFeatures.forEach(feature => { 68 | featuresContext += `- ${feature.name}: ${feature.description} (${feature.type}, ${feature.priority} priority)\n`; 69 | }); 70 | } 71 | 72 | const response = await client.chat.completions.create({ 73 | model: config.openai.model, 74 | messages: [ 75 | { 76 | role: "system", 77 | content: `You are a customer support representative for an AI assistant called Jarvis. Your task is to generate polite, helpful responses to user comments. 78 | 79 | Guidelines for responses: 80 | 1. Be empathetic and acknowledge the user's feedback 81 | 2. Thank the user for their input 82 | 3. If the comment is positive, express appreciation 83 | 4. If the comment is negative, apologize for the inconvenience and assure them you're working on improvements 84 | 5. If the comment is neutral, acknowledge their observation 85 | 6. If relevant, mention that their feedback will be considered for future updates 86 | 7. Keep responses concise (2-4 sentences) and professional 87 | 8. Do not make specific promises about feature implementation or timelines 88 | 9. Sign the response as "The Jarvis Team"` 89 | }, 90 | { 91 | role: "user", 92 | content: `User comment: "${comment.text}" 93 | Comment category: ${comment.category || 'unknown'} 94 | 95 | ${featuresContext} 96 | 97 | Generate a polite, helpful response to this user comment.` 98 | } 99 | ], 100 | temperature: 0.7, 101 | max_tokens: 200 102 | }); 103 | 104 | return response.choices[0].message.content.trim(); 105 | } 106 | 107 | export { 108 | generateResponses 109 | }; 110 | -------------------------------------------------------------------------------- /src/processor/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main processor module for the Comment Processing System 3 | * Orchestrates the entire comment processing workflow 4 | */ 5 | 6 | import { printSectionHeader, saveToJson } from '../utils.js'; 7 | import config from '../config.js'; 8 | import { categorizeComments } from './categorizer.js'; 9 | import { clusterComments } from './clusterer.js'; 10 | import { identifyFeatures } from './featureId.js'; 11 | import { generateResponses } from './responder.js'; 12 | 13 | /** 14 | * Process comments through the entire workflow 15 | * @param {Array} comments - Array of comment objects 16 | * @returns {Promise} - Processing results 17 | */ 18 | async function processComments(comments) { 19 | printSectionHeader('Processing Comments'); 20 | console.log(`Starting processing workflow for ${comments.length} comments...`); 21 | 22 | // Step 1: Categorize comments 23 | printSectionHeader('Step 1: Categorization'); 24 | const categorizedComments = await categorizeComments(comments); 25 | 26 | // Step 2: Cluster comments 27 | printSectionHeader('Step 2: Clustering'); 28 | const clusteringResult = await clusterComments(categorizedComments); 29 | const { comments: clusteredComments, clusterNames } = clusteringResult; 30 | 31 | // Step 3: Identify features 32 | printSectionHeader('Step 3: Feature Identification'); 33 | const features = await identifyFeatures(clusteredComments); 34 | 35 | // Step 4: Generate responses 36 | printSectionHeader('Step 4: Response Generation'); 37 | const processedComments = await generateResponses(clusteredComments, features); 38 | 39 | // Remove embeddings from comments to clean up the JSON output 40 | const cleanedComments = removeEmbeddings(processedComments); 41 | 42 | // Prepare results 43 | const results = { 44 | metadata: { 45 | totalComments: comments.length, 46 | processedAt: new Date().toISOString(), 47 | categories: countByCategory(cleanedComments), 48 | clusters: countByClusters(cleanedComments), 49 | clusterNames: clusterNames 50 | }, 51 | comments: cleanedComments, 52 | features: features.allFeatures 53 | }; 54 | 55 | // Save results 56 | await saveToJson(config.paths.resultsFile, results); 57 | console.log(`Processing complete. Results saved to ${config.paths.resultsFile}`); 58 | 59 | return results; 60 | } 61 | 62 | /** 63 | * Count comments by category 64 | * @param {Array} comments - Array of comment objects 65 | * @returns {object} - Count by category 66 | */ 67 | function countByCategory(comments) { 68 | const counts = { 69 | positive: 0, 70 | negative: 0, 71 | neutral: 0, 72 | unknown: 0 73 | }; 74 | 75 | comments.forEach(comment => { 76 | const category = comment.category || 'unknown'; 77 | counts[category] = (counts[category] || 0) + 1; 78 | }); 79 | 80 | return counts; 81 | } 82 | 83 | /** 84 | * Count comments by cluster 85 | * @param {Array} comments - Array of comment objects 86 | * @returns {object} - Count by cluster 87 | */ 88 | function countByClusters(comments) { 89 | const counts = {}; 90 | 91 | comments.forEach(comment => { 92 | const clusterId = comment.clusterId; 93 | if (clusterId) { 94 | counts[clusterId] = (counts[clusterId] || 0) + 1; 95 | } 96 | }); 97 | 98 | return counts; 99 | } 100 | 101 | /** 102 | * Remove embeddings from comments to clean up the JSON output 103 | * @param {Array} comments - Array of comment objects with embeddings 104 | * @returns {Array} - Array of comment objects without embeddings 105 | */ 106 | function removeEmbeddings(comments) { 107 | return comments.map(comment => { 108 | // Create a shallow copy of the comment 109 | const cleanComment = { ...comment }; 110 | 111 | // Remove the embedding property if it exists 112 | if ('embedding' in cleanComment) { 113 | delete cleanComment.embedding; 114 | } 115 | 116 | return cleanComment; 117 | }); 118 | } 119 | 120 | export { 121 | processComments 122 | }; 123 | -------------------------------------------------------------------------------- /src/generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Comment generator for the Comment Processing System 3 | * Generates synthetic user comments 4 | */ 5 | 6 | import OpenAI from 'openai'; 7 | import config from './config.js'; 8 | import { saveToJson, getFormattedDateTime, printSectionHeader } from './utils.js'; 9 | 10 | // Initialize OpenAI client 11 | const client = new OpenAI({ 12 | baseURL: config.openai.baseURL, 13 | apiKey: config.openai.apiKey, 14 | }); 15 | 16 | console.log("OpenAI client initialized with base URL:", config.openai.baseURL); 17 | 18 | /** 19 | * Generate synthetic user comments about Jarvis 20 | * @returns {Promise} - Array of generated comments 21 | */ 22 | async function generateComments() { 23 | printSectionHeader('Generating Synthetic Comments'); 24 | 25 | const { numComments, commentTypes, topics } = config.generator; 26 | const comments = []; 27 | 28 | console.log(`Generating ${numComments} synthetic comments about Jarvis...`); 29 | 30 | for (let i = 0; i < numComments; i++) { 31 | // Randomly select comment type and topic 32 | const type = commentTypes[Math.floor(Math.random() * commentTypes.length)]; 33 | const topic = topics[Math.floor(Math.random() * topics.length)]; 34 | 35 | try { 36 | const comment = await generateSingleComment(type, topic); 37 | comments.push({ 38 | id: `comment-${i + 1}`, 39 | text: comment, 40 | timestamp: getFormattedDateTime(), 41 | metadata: { 42 | generatedType: type, 43 | generatedTopic: topic 44 | } 45 | }); 46 | 47 | console.log(`Generated comment ${i + 1}/${numComments} (${type} about ${topic})`); 48 | } catch (error) { 49 | console.error(`Error generating comment ${i + 1}:`, error); 50 | } 51 | } 52 | 53 | // Save generated comments to file 54 | await saveToJson(config.paths.commentsFile, { comments }); 55 | console.log(`All comments generated and saved to ${config.paths.commentsFile}`); 56 | 57 | return comments; 58 | } 59 | 60 | /** 61 | * Generate a single comment 62 | * @param {string} type - Type of comment (positive, negative, neutral) 63 | * @param {string} topic - Topic of the comment 64 | * @returns {Promise} - Generated comment text 65 | */ 66 | async function generateSingleComment(type, topic) { 67 | const prompt = createPromptForCommentGeneration(type, topic); 68 | 69 | const response = await client.chat.completions.create({ 70 | model: config.openai.model, 71 | messages: [ 72 | { 73 | role: "system", 74 | content: "You are a helpful assistant that generates realistic user comments about an AI assistant called Jarvis." 75 | }, 76 | { 77 | role: "user", 78 | content: prompt 79 | } 80 | ], 81 | ...config.openai.commentGeneration 82 | }); 83 | 84 | return response.choices[0].message.content.trim(); 85 | } 86 | 87 | /** 88 | * Create a prompt for comment generation 89 | * @param {string} type - Type of comment (positive, negative, neutral) 90 | * @param {string} topic - Topic of the comment 91 | * @returns {string} - Prompt for OpenAI 92 | */ 93 | function createPromptForCommentGeneration(type, topic) { 94 | let sentiment = ''; 95 | 96 | switch (type) { 97 | case 'positive': 98 | sentiment = 'positive and appreciative'; 99 | break; 100 | case 'negative': 101 | sentiment = 'negative and critical'; 102 | break; 103 | case 'neutral': 104 | sentiment = 'neutral and balanced'; 105 | break; 106 | default: 107 | sentiment = 'general'; 108 | } 109 | 110 | return `Generate a realistic ${sentiment} user comment about an AI assistant called Jarvis, focusing on its ${topic}. 111 | 112 | The comment should sound natural, as if written by a real user who has been using Jarvis. 113 | Keep the comment concise (1-3 sentences) and focused on the specific topic. 114 | Do not include ratings (like "5/5 stars") or formatting. 115 | Just return the comment text without any additional context or explanation.`; 116 | } 117 | 118 | export { 119 | generateComments 120 | }; 121 | -------------------------------------------------------------------------------- /src/processor/featureId.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Feature identification module for the Comment Processing System 3 | * Identifies potential product features based on comment content 4 | */ 5 | 6 | import OpenAI from 'openai'; 7 | import config from '../config.js'; 8 | 9 | // Initialize OpenAI client 10 | const client = new OpenAI({ 11 | baseURL: config.openai.baseURL, 12 | apiKey: config.openai.apiKey, 13 | }); 14 | 15 | /** 16 | * Identify features from a set of comments 17 | * @param {Array} comments - Array of comment objects 18 | * @returns {Promise} - Object with identified features 19 | */ 20 | async function identifyFeatures(comments) { 21 | console.log('Identifying potential product features from comments...'); 22 | 23 | // Group comments by cluster 24 | const commentsByCluster = {}; 25 | comments.forEach(comment => { 26 | const clusterId = comment.clusterId; 27 | if (!commentsByCluster[clusterId]) { 28 | commentsByCluster[clusterId] = []; 29 | } 30 | commentsByCluster[clusterId].push(comment); 31 | }); 32 | 33 | // Identify features for each cluster 34 | const clusterFeatures = {}; 35 | const allFeatures = []; 36 | 37 | for (const [clusterId, clusterComments] of Object.entries(commentsByCluster)) { 38 | console.log(`Identifying features for cluster ${clusterId} (${clusterComments.length} comments)...`); 39 | 40 | try { 41 | // Extract comments text 42 | const commentsText = clusterComments.map(comment => comment.text).join('\n\n'); 43 | 44 | // Identify features 45 | const features = await extractFeaturesFromComments(commentsText); 46 | 47 | // Store features for this cluster 48 | clusterFeatures[clusterId] = features; 49 | 50 | // Add to all features 51 | features.forEach(feature => { 52 | if (!allFeatures.some(f => f.name === feature.name)) { 53 | allFeatures.push({ 54 | ...feature, 55 | clusters: [clusterId] 56 | }); 57 | } else { 58 | // Update existing feature 59 | const existingFeature = allFeatures.find(f => f.name === feature.name); 60 | if (!existingFeature.clusters.includes(clusterId)) { 61 | existingFeature.clusters.push(clusterId); 62 | } 63 | } 64 | }); 65 | 66 | console.log(`Identified ${features.length} features for cluster ${clusterId}`); 67 | } catch (error) { 68 | console.error(`Error identifying features for cluster ${clusterId}:`, error); 69 | } 70 | } 71 | 72 | console.log(`Feature identification complete. Found ${allFeatures.length} unique features.`); 73 | 74 | return { 75 | clusterFeatures, 76 | allFeatures 77 | }; 78 | } 79 | 80 | /** 81 | * Extract features from comments 82 | * @param {string} commentsText - Text of comments 83 | * @returns {Promise} - Array of identified features 84 | */ 85 | async function extractFeaturesFromComments(commentsText) { 86 | const response = await client.chat.completions.create({ 87 | model: config.openai.model, 88 | messages: [ 89 | { 90 | role: "system", 91 | content: `You are a product analyst for an AI assistant called Jarvis. Your task is to identify potential product features or improvements based on user comments. 92 | 93 | For each set of comments, identify up to 3 potential features or improvements that could address the user feedback. 94 | 95 | For each feature, provide: 96 | 1. A short name (2-5 words) 97 | 2. A brief description (1-2 sentences) 98 | 3. The type of feature (New Feature, Improvement, Bug Fix) 99 | 4. Priority (High, Medium, Low) 100 | 101 | Format your response as a JSON array of features, with each feature having the fields: name, description, type, and priority.` 102 | }, 103 | { 104 | role: "user", 105 | content: `Here are some user comments about Jarvis. Identify potential features or improvements based on these comments: 106 | 107 | ${commentsText}` 108 | } 109 | ], 110 | response_format: { type: "json_object" }, 111 | temperature: 0.5 112 | }); 113 | 114 | try { 115 | const result = JSON.parse(response.choices[0].message.content); 116 | return result.features || []; 117 | } catch (error) { 118 | console.error('Error parsing feature identification response:', error); 119 | return []; 120 | } 121 | } 122 | 123 | export { 124 | identifyFeatures 125 | }; 126 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main entry point for the Comment Processing System 3 | */ 4 | 5 | import { generateComments } from './src/generator.js'; 6 | import { processComments } from './src/processor/index.js'; 7 | import { loadFromJson, printSectionHeader } from './src/utils.js'; 8 | import config from './src/config.js'; 9 | import { promises as fs } from 'fs'; 10 | import path from 'path'; 11 | import readline from 'readline'; 12 | 13 | /** 14 | * Main function to run the entire workflow 15 | */ 16 | async function main() { 17 | printSectionHeader('Comment Processing System'); 18 | console.log('Starting the comment processing workflow...'); 19 | 20 | try { 21 | // Create data directory if it doesn't exist 22 | const dataDir = path.dirname(config.paths.commentsFile); 23 | await fs.mkdir(dataDir, { recursive: true }); 24 | 25 | // Step 1: Generate synthetic comments or use existing ones 26 | let comments; 27 | try { 28 | // Try to load existing comments 29 | const data = await loadFromJson(config.paths.commentsFile); 30 | comments = data.comments; 31 | console.log(`Loaded ${comments.length} existing comments from ${config.paths.commentsFile}`); 32 | 33 | // Ask if user wants to regenerate comments 34 | if (config.generator.allowRegenerateComments) { 35 | const rl = readline.createInterface({ 36 | input: process.stdin, 37 | output: process.stdout 38 | }); 39 | 40 | const answer = await new Promise(resolve => { 41 | rl.question('Do you want to regenerate comments? (y/n): ', resolve); 42 | }); 43 | 44 | rl.close(); 45 | 46 | if (answer.toLowerCase() === 'y') { 47 | comments = await generateComments(); 48 | } 49 | } 50 | } catch (error) { 51 | // Generate new comments if file doesn't exist or is invalid 52 | console.log('No existing comments found or error loading comments. Generating new comments...'); 53 | comments = await generateComments(); 54 | } 55 | 56 | // Step 2: Process the comments 57 | const results = await processComments(comments); 58 | 59 | // Step 3: Display summary 60 | displaySummary(results); 61 | 62 | console.log('\nComment processing workflow completed successfully!'); 63 | console.log(`Full results saved to ${config.paths.resultsFile}`); 64 | 65 | } catch (error) { 66 | console.error('Error in main workflow:', error); 67 | process.exit(1); 68 | } 69 | } 70 | 71 | /** 72 | * Display a summary of the processing results 73 | * @param {object} results - Processing results 74 | */ 75 | function displaySummary(results) { 76 | printSectionHeader('Processing Summary'); 77 | 78 | // Display category counts 79 | console.log('Comment Categories:'); 80 | const { categories } = results.metadata; 81 | Object.entries(categories).forEach(([category, count]) => { 82 | if (count > 0) { 83 | console.log(`- ${category}: ${count} comments`); 84 | } 85 | }); 86 | 87 | // Display cluster counts and names 88 | console.log('\nComment Clusters:'); 89 | const { clusters, clusterNames } = results.metadata; 90 | console.log(`- Total clusters: ${Object.keys(clusters).length}`); 91 | 92 | // Display cluster names if available 93 | if (clusterNames && Object.keys(clusterNames).length > 0) { 94 | console.log('\nCluster Names:'); 95 | Object.entries(clusterNames).forEach(([clusterId, name]) => { 96 | const count = clusters[clusterId] || 0; 97 | console.log(`- Cluster ${clusterId} (${count} comments): "${name}"`); 98 | }); 99 | } 100 | 101 | // Display features 102 | console.log('\nIdentified Features:'); 103 | results.features.forEach((feature, index) => { 104 | console.log(`- ${feature.name} (${feature.type}, ${feature.priority} priority)`); 105 | console.log(` ${feature.description}`); 106 | if (index < results.features.length - 1) { 107 | console.log(''); 108 | } 109 | }); 110 | 111 | // Display sample comments and responses 112 | console.log('\nSample Comments and Responses:'); 113 | const sampleSize = Math.min(3, results.comments.length); 114 | for (let i = 0; i < sampleSize; i++) { 115 | const comment = results.comments[i]; 116 | console.log(`\nComment ${i + 1} (${comment.category}):`); 117 | console.log(`"${comment.text}"`); 118 | console.log('\nResponse:'); 119 | console.log(`"${comment.response}"`); 120 | console.log('---'); 121 | } 122 | } 123 | 124 | // Run the main function 125 | main().catch(console.error); 126 | 127 | export { 128 | main 129 | }; 130 | -------------------------------------------------------------------------------- /data/comments.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "id": "comment-1", 5 | "text": "Jarvis’s interface is pretty clean and intuitive, which is a nice change from some of the more cluttered AI assistants I’ve tried. It’s easy to find what I’m looking for, though I do wish there were a few more visual options for customizing the display.", 6 | "timestamp": "2025-03-31T11:32:59.823Z", 7 | "metadata": { 8 | "generatedType": "neutral", 9 | "generatedTopic": "user interface" 10 | } 11 | }, 12 | { 13 | "id": "comment-2", 14 | "text": "“Jarvis is seriously impressive – the speed at which it responds is incredible! I’ve never used an AI assistant that’s so quick and efficient.”", 15 | "timestamp": "2025-03-31T11:33:00.844Z", 16 | "metadata": { 17 | "generatedType": "positive", 18 | "generatedTopic": "response speed" 19 | } 20 | }, 21 | { 22 | "id": "comment-3", 23 | "text": "Jarvis is seriously impressive – the information it provides is consistently accurate and incredibly detailed. I’ve been amazed at how well it understands my complex queries and delivers the right answers every time. It’s a total game-changer for my research!", 24 | "timestamp": "2025-03-31T11:33:02.296Z", 25 | "metadata": { 26 | "generatedType": "positive", 27 | "generatedTopic": "accuracy" 28 | } 29 | }, 30 | { 31 | "id": "comment-4", 32 | "text": "Jarvis is pretty helpful when it works, but I’ve noticed a few glitches lately – it sometimes misunderstands simple requests and occasionally just freezes up. It’s a promising tool, but needs a bit more refinement.", 33 | "timestamp": "2025-03-31T11:33:03.647Z", 34 | "metadata": { 35 | "generatedType": "neutral", 36 | "generatedTopic": "bugs" 37 | } 38 | }, 39 | { 40 | "id": "comment-5", 41 | "text": "Jarvis is pretty handy for quickly summarizing long documents, but I’d really like to see a feature to directly export those summaries to a note-taking app like Evernote. It would be great if Jarvis could also integrate with my calendar to remind me about key points from those summaries.", 42 | "timestamp": "2025-03-31T11:33:05.264Z", 43 | "metadata": { 44 | "generatedType": "neutral", 45 | "generatedTopic": "feature requests" 46 | } 47 | }, 48 | { 49 | "id": "comment-6", 50 | "text": "Jarvis is pretty handy for quick information retrieval, but the subscription cost feels a little high compared to some other AI assistants on the market. I'm still evaluating if the features justify the ongoing expense.", 51 | "timestamp": "2025-03-31T11:33:06.478Z", 52 | "metadata": { 53 | "generatedType": "neutral", 54 | "generatedTopic": "pricing" 55 | } 56 | }, 57 | { 58 | "id": "comment-7", 59 | "text": "Honestly, Jarvis is just a lot of empty promises. It keeps suggesting irrelevant articles and failing to actually understand my requests for help with my work – it’s not helpful at all.", 60 | "timestamp": "2025-03-31T11:33:07.615Z", 61 | "metadata": { 62 | "generatedType": "negative", 63 | "generatedTopic": "helpfulness" 64 | } 65 | }, 66 | { 67 | "id": "comment-8", 68 | "text": "Honestly, Jarvis is a game-changer. It’s so much more intuitive than those other AI assistants I’ve tried – the responses are actually helpful and it just *gets* what I’m asking for.", 69 | "timestamp": "2025-03-31T11:33:08.904Z", 70 | "metadata": { 71 | "generatedType": "positive", 72 | "generatedTopic": "comparison to competitors" 73 | } 74 | }, 75 | { 76 | "id": "comment-9", 77 | "text": "Jarvis is seriously impressive – it’s incredibly accurate with my research requests. I’ve been amazed at how quickly it nails the specific details I need, saving me so much time. Definitely a game-changer!", 78 | "timestamp": "2025-03-31T11:33:10.195Z", 79 | "metadata": { 80 | "generatedType": "positive", 81 | "generatedTopic": "accuracy" 82 | } 83 | }, 84 | { 85 | "id": "comment-10", 86 | "text": "Jarvis’s interface is pretty clean and straightforward, which is nice. I appreciate how easily I can navigate between the different functions, though sometimes the icons could be a little clearer. Overall, it’s a functional UI that gets the job done.", 87 | "timestamp": "2025-03-31T11:33:11.696Z", 88 | "metadata": { 89 | "generatedType": "neutral", 90 | "generatedTopic": "user interface" 91 | } 92 | }, 93 | { 94 | "id": "comment-11", 95 | "text": "Jarvis is pretty handy for quickly getting information and setting reminders, but I’ve noticed it occasionally misunderstands complex requests. Overall, it’s a useful tool, though I’m still getting used to its quirks.", 96 | "timestamp": "2025-03-31T11:33:13.041Z", 97 | "metadata": { 98 | "generatedType": "neutral", 99 | "generatedTopic": "general experience" 100 | } 101 | }, 102 | { 103 | "id": "comment-12", 104 | "text": "“Jarvis is seriously impressive – the speed at which it responds is incredible! I’ve never used an AI assistant that’s so quick and efficient.”", 105 | "timestamp": "2025-03-31T11:33:14.077Z", 106 | "metadata": { 107 | "generatedType": "positive", 108 | "generatedTopic": "response speed" 109 | } 110 | }, 111 | { 112 | "id": "comment-13", 113 | "text": "Honestly, Jarvis feels incredibly frustrating to use. It misunderstands my requests constantly, and the responses are often just generic and unhelpful – I’m wasting more time trying to get it to do simple things than I would just doing them myself.", 114 | "timestamp": "2025-03-31T11:33:15.500Z", 115 | "metadata": { 116 | "generatedType": "negative", 117 | "generatedTopic": "general experience" 118 | } 119 | }, 120 | { 121 | "id": "comment-14", 122 | "text": "Jarvis has been surprisingly helpful with scheduling meetings and quickly finding information. It’s definitely streamlined my workflow and I appreciate its responsiveness.", 123 | "timestamp": "2025-03-31T11:33:16.376Z", 124 | "metadata": { 125 | "generatedType": "neutral", 126 | "generatedTopic": "helpfulness" 127 | } 128 | }, 129 | { 130 | "id": "comment-15", 131 | "text": "“Jarvis is seriously impressive – the speed at which it responds is incredible! I’ve never used an AI assistant that’s so quick and efficient, it’s a game changer.”", 132 | "timestamp": "2025-03-31T11:33:17.544Z", 133 | "metadata": { 134 | "generatedType": "positive", 135 | "generatedTopic": "response speed" 136 | } 137 | }, 138 | { 139 | "id": "comment-16", 140 | "text": "Honestly, Jarvis is amazing! I was a little worried about the occasional glitches, but it’s actually gotten *better* at handling them – it just recovers really quickly and keeps going. I really appreciate how quickly the developers are addressing those little bugs.", 141 | "timestamp": "2025-03-31T11:33:19.009Z", 142 | "metadata": { 143 | "generatedType": "positive", 144 | "generatedTopic": "bugs" 145 | } 146 | }, 147 | { 148 | "id": "comment-17", 149 | "text": "Honestly, Jarvis is incredibly slow. It takes forever for it to process even simple requests, and I've wasted so much time waiting for a response. I'm starting to think it's not worth the effort.", 150 | "timestamp": "2025-03-31T11:33:20.331Z", 151 | "metadata": { 152 | "generatedType": "negative", 153 | "generatedTopic": "response speed" 154 | } 155 | }, 156 | { 157 | "id": "comment-18", 158 | "text": "Jarvis is pretty useful for scheduling and reminders, but the premium subscription feels a little pricey compared to some other AI assistants on the market. I’m still evaluating if the extra features are worth the ongoing cost.", 159 | "timestamp": "2025-03-31T11:33:21.606Z", 160 | "metadata": { 161 | "generatedType": "neutral", 162 | "generatedTopic": "pricing" 163 | } 164 | }, 165 | { 166 | "id": "comment-19", 167 | "text": "Jarvis is seriously impressive – it’s actually gotten my research questions right every time! I’m amazed at how accurate it is and it’s already saving me so much time.", 168 | "timestamp": "2025-03-31T11:33:22.778Z", 169 | "metadata": { 170 | "generatedType": "positive", 171 | "generatedTopic": "accuracy" 172 | } 173 | }, 174 | { 175 | "id": "comment-20", 176 | "text": "Honestly, Jarvis is amazing! I was really worried about the occasional glitches, but it’s actually gotten better at handling those little hiccups – it’s still learning, but I appreciate how quickly it recovers now.", 177 | "timestamp": "2025-03-31T11:33:24.045Z", 178 | "metadata": { 179 | "generatedType": "positive", 180 | "generatedTopic": "bugs" 181 | } 182 | } 183 | ] 184 | } -------------------------------------------------------------------------------- /src/processor/clusterer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Comment clusterer for the Comment Processing System 3 | * Clusters similar comments together based on their content 4 | */ 5 | 6 | import OpenAI from 'openai'; 7 | import config from '../config.js'; 8 | import { cosineSimilarity } from '../utils.js'; 9 | 10 | // Initialize OpenAI client 11 | const client = new OpenAI({ 12 | baseURL: config.openai.baseURL, 13 | apiKey: config.openai.apiKey, 14 | }); 15 | 16 | /** 17 | * Cluster comments based on their content similarity 18 | * @param {Array} comments - Array of comment objects 19 | * @returns {Promise} - Object with clustered comments and cluster names 20 | */ 21 | async function clusterComments(comments) { 22 | console.log(`Clustering ${comments.length} comments...`); 23 | 24 | // Generate embeddings for all comments 25 | const commentsWithEmbeddings = await generateEmbeddings(comments); 26 | 27 | // Perform clustering 28 | const clusteredComments = performClustering(commentsWithEmbeddings); 29 | 30 | // Count comments per cluster and group comments by cluster 31 | const clusterCounts = {}; 32 | const commentsByCluster = {}; 33 | 34 | clusteredComments.forEach(comment => { 35 | const clusterId = comment.clusterId; 36 | // Update counts 37 | clusterCounts[clusterId] = (clusterCounts[clusterId] || 0) + 1; 38 | 39 | // Group comments by cluster 40 | if (!commentsByCluster[clusterId]) { 41 | commentsByCluster[clusterId] = []; 42 | } 43 | commentsByCluster[clusterId].push(comment); 44 | }); 45 | 46 | // Log clustering results 47 | console.log(`Clustering complete. Found ${Object.keys(clusterCounts).length} clusters:`); 48 | Object.entries(clusterCounts).forEach(([clusterId, count]) => { 49 | console.log(`- Cluster ${clusterId}: ${count} comments`); 50 | }); 51 | 52 | // Generate names for each cluster 53 | const clusterNames = await generateClusterNames(commentsByCluster); 54 | 55 | // Log cluster names 56 | console.log('Generated cluster names:'); 57 | Object.entries(clusterNames).forEach(([clusterId, name]) => { 58 | console.log(`- Cluster ${clusterId}: "${name}"`); 59 | }); 60 | 61 | return { 62 | comments: clusteredComments, 63 | clusterNames 64 | }; 65 | } 66 | 67 | /** 68 | * Generate embeddings for comments 69 | * @param {Array} comments - Array of comment objects 70 | * @returns {Promise} - Array of comments with embeddings added 71 | */ 72 | async function generateEmbeddings(comments) { 73 | console.log(`Generating embeddings for ${comments.length} comments...`); 74 | 75 | const commentsWithEmbeddings = []; 76 | 77 | for (let i = 0; i < comments.length; i++) { 78 | const comment = comments[i]; 79 | 80 | try { 81 | const embedding = await getEmbedding(comment.text); 82 | commentsWithEmbeddings.push({ 83 | ...comment, 84 | embedding 85 | }); 86 | 87 | console.log(`Generated embedding for comment ${i + 1}/${comments.length}`); 88 | } catch (error) { 89 | console.error(`Error generating embedding for comment ${comment.id}:`, error); 90 | // Add comment without embedding 91 | commentsWithEmbeddings.push(comment); 92 | } 93 | } 94 | 95 | return commentsWithEmbeddings; 96 | } 97 | 98 | /** 99 | * Get embedding for a text 100 | * @param {string} text - Text to embed 101 | * @returns {Promise} - Embedding vector 102 | */ 103 | async function getEmbedding(text) { 104 | const response = await client.embeddings.create({ 105 | model: config.openai.embedding.model, 106 | input: text, 107 | encoding_format: "float" 108 | }); 109 | 110 | return response.data[0].embedding; 111 | } 112 | 113 | /** 114 | * Perform clustering on comments with embeddings 115 | * @param {Array} commentsWithEmbeddings - Array of comment objects with embeddings 116 | * @returns {Array} - Array of comments with cluster IDs added 117 | */ 118 | function performClustering(commentsWithEmbeddings) { 119 | const { similarityThreshold } = config.processor.clustering; 120 | const clusters = []; 121 | const clusteredComments = []; 122 | 123 | // Filter out comments without embeddings 124 | const validComments = commentsWithEmbeddings.filter(comment => comment.embedding); 125 | 126 | // For each comment 127 | for (const comment of validComments) { 128 | let assignedToExistingCluster = false; 129 | 130 | // Check if it belongs to any existing cluster 131 | for (let i = 0; i < clusters.length; i++) { 132 | const clusterId = i + 1; 133 | const clusterComments = clusteredComments.filter(c => c.clusterId === clusterId); 134 | 135 | // Check similarity with each comment in the cluster 136 | for (const clusterComment of clusterComments) { 137 | const similarity = cosineSimilarity(comment.embedding, clusterComment.embedding); 138 | 139 | if (similarity >= similarityThreshold) { 140 | // Add to existing cluster 141 | clusteredComments.push({ 142 | ...comment, 143 | clusterId, 144 | similarityScore: similarity 145 | }); 146 | assignedToExistingCluster = true; 147 | break; 148 | } 149 | } 150 | 151 | if (assignedToExistingCluster) { 152 | break; 153 | } 154 | } 155 | 156 | if (!assignedToExistingCluster) { 157 | // Create a new cluster 158 | const newClusterId = clusters.length + 1; 159 | clusters.push(newClusterId); 160 | clusteredComments.push({ 161 | ...comment, 162 | clusterId: newClusterId, 163 | similarityScore: 1.0 // Perfect similarity with itself 164 | }); 165 | } 166 | } 167 | 168 | // Add comments without embeddings to their own clusters 169 | const commentsWithoutEmbeddings = commentsWithEmbeddings.filter(comment => !comment.embedding); 170 | for (const comment of commentsWithoutEmbeddings) { 171 | const newClusterId = clusters.length + 1; 172 | clusters.push(newClusterId); 173 | clusteredComments.push({ 174 | ...comment, 175 | clusterId: newClusterId, 176 | clusteringError: "No embedding available" 177 | }); 178 | } 179 | 180 | return clusteredComments; 181 | } 182 | 183 | /** 184 | * Generate descriptive names for each cluster based on the comments 185 | * @param {object} commentsByCluster - Object with comments grouped by cluster ID 186 | * @returns {Promise} - Object with cluster IDs as keys and names as values 187 | */ 188 | async function generateClusterNames(commentsByCluster) { 189 | console.log('Generating descriptive names for clusters...'); 190 | 191 | const clusterNames = {}; 192 | 193 | for (const [clusterId, comments] of Object.entries(commentsByCluster)) { 194 | try { 195 | // Extract a sample of comments for naming (to avoid token limits) 196 | const sampleSize = Math.min(5, comments.length); 197 | const sampleComments = comments 198 | .slice(0, sampleSize) 199 | .map(comment => comment.text) 200 | .join('\n\n'); 201 | 202 | // Generate a name for this cluster 203 | const name = await generateNameForCluster(sampleComments); 204 | clusterNames[clusterId] = name; 205 | 206 | console.log(`Generated name for cluster ${clusterId}: "${name}"`); 207 | } catch (error) { 208 | console.error(`Error generating name for cluster ${clusterId}:`, error); 209 | clusterNames[clusterId] = `Cluster ${clusterId}`; 210 | } 211 | } 212 | 213 | return clusterNames; 214 | } 215 | 216 | /** 217 | * Generate a descriptive name for a cluster based on sample comments 218 | * @param {string} commentsText - Text of sample comments from the cluster 219 | * @returns {Promise} - Descriptive name for the cluster 220 | */ 221 | async function generateNameForCluster(commentsText) { 222 | const response = await client.chat.completions.create({ 223 | model: config.openai.model, 224 | messages: [ 225 | { 226 | role: "system", 227 | content: `You are an expert at identifying common themes in text. Your task is to analyze a set of user comments about an AI assistant called Jarvis and identify the main topic or theme they discuss. 228 | 229 | Generate a short, descriptive name (3-5 words) that captures the common theme or topic in these comments. Focus on the subject matter rather than sentiment. 230 | 231 | For example: 232 | - "Response Speed and Performance" 233 | - "User Interface Design" 234 | - "Accuracy of Information" 235 | - "Feature Request: Data Visualization" 236 | - "Pricing and Value Concerns" 237 | 238 | Respond with ONLY the theme name, nothing else.` 239 | }, 240 | { 241 | role: "user", 242 | content: `Here are some user comments about Jarvis. Identify the common theme and provide a short, descriptive name: 243 | 244 | ${commentsText}` 245 | } 246 | ], 247 | temperature: 0.3, 248 | max_tokens: 20 249 | }); 250 | 251 | const name = response.choices[0].message.content.trim(); 252 | 253 | // Remove any quotes that might be in the response 254 | return name.replace(/["']/g, ''); 255 | } 256 | 257 | export { 258 | clusterComments 259 | }; 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2025 Docker, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /data/results.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "totalComments": 20, 4 | "processedAt": "2025-03-31T11:34:55.535Z", 5 | "categories": { 6 | "positive": 14, 7 | "negative": 4, 8 | "neutral": 2, 9 | "unknown": 0 10 | }, 11 | "clusters": { 12 | "1": 16, 13 | "2": 2, 14 | "3": 1, 15 | "4": 1 16 | }, 17 | "clusterNames": { 18 | "1": "Ease of Use and Accuracy", 19 | "2": "Speed and Accuracy of Responses", 20 | "3": "Integration with Note-Taking Apps", 21 | "4": "Jarvis Lack of Practical Utility" 22 | } 23 | }, 24 | "comments": [ 25 | { 26 | "id": "comment-1", 27 | "text": "Jarvis’s interface is pretty clean and intuitive, which is a nice change from some of the more cluttered AI assistants I’ve tried. It’s easy to find what I’m looking for, though I do wish there were a few more visual options for customizing the display.", 28 | "timestamp": "2025-03-31T11:32:59.823Z", 29 | "metadata": { 30 | "generatedType": "neutral", 31 | "generatedTopic": "user interface" 32 | }, 33 | "category": "positive", 34 | "clusterId": 1, 35 | "similarityScore": 1, 36 | "response": "Thank you so much for your positive feedback regarding Jarvis’s interface! We’re thrilled to hear you find it clean and intuitive – that’s exactly what we’re aiming for. We appreciate you pointing out your desire for more visual customization options, and your feedback will definitely be considered as we continue to develop Jarvis.\n\nThe Jarvis Team" 37 | }, 38 | { 39 | "id": "comment-2", 40 | "text": "“Jarvis is seriously impressive – the speed at which it responds is incredible! I’ve never used an AI assistant that’s so quick and efficient.”", 41 | "timestamp": "2025-03-31T11:33:00.844Z", 42 | "metadata": { 43 | "generatedType": "positive", 44 | "generatedTopic": "response speed" 45 | }, 46 | "category": "positive", 47 | "clusterId": 2, 48 | "similarityScore": 1, 49 | "response": "Thank you so much for your positive feedback – we’re thrilled to hear you’re impressed with Jarvis’s speed and efficiency! It’s fantastic to know you’re enjoying the experience. We truly appreciate you bringing this to our attention, and your feedback will be invaluable as we continue to refine Jarvis’s performance.\n\nThe Jarvis Team" 50 | }, 51 | { 52 | "id": "comment-3", 53 | "text": "Jarvis is seriously impressive – the information it provides is consistently accurate and incredibly detailed. I’ve been amazed at how well it understands my complex queries and delivers the right answers every time. It’s a total game-changer for my research!", 54 | "timestamp": "2025-03-31T11:33:02.296Z", 55 | "metadata": { 56 | "generatedType": "positive", 57 | "generatedTopic": "accuracy" 58 | }, 59 | "category": "positive", 60 | "clusterId": 2, 61 | "similarityScore": 0.7648832958019861, 62 | "response": "Thank you so much for your incredibly positive feedback! We’re thrilled to hear you’re finding Jarvis so impressive and that it’s significantly enhancing your research. We truly appreciate you highlighting its accuracy and detail – this helps us immensely as we continue to refine Jarvis and explore improvements like rapid response optimization and enhanced query understanding.\n\nThe Jarvis Team" 63 | }, 64 | { 65 | "id": "comment-4", 66 | "text": "Jarvis is pretty helpful when it works, but I’ve noticed a few glitches lately – it sometimes misunderstands simple requests and occasionally just freezes up. It’s a promising tool, but needs a bit more refinement.", 67 | "timestamp": "2025-03-31T11:33:03.647Z", 68 | "metadata": { 69 | "generatedType": "neutral", 70 | "generatedTopic": "bugs" 71 | }, 72 | "category": "negative", 73 | "clusterId": 1, 74 | "similarityScore": 0.7789716622292208, 75 | "response": "Thank you for your feedback – we appreciate you taking the time to share your observations about Jarvis. We sincerely apologize for the glitches and freezes you’ve experienced; we understand how frustrating that can be. Your input is valuable, and we’re actively working on improvements to enhance Jarvis’s reliability and accuracy. The Jarvis Team" 76 | }, 77 | { 78 | "id": "comment-5", 79 | "text": "Jarvis is pretty handy for quickly summarizing long documents, but I’d really like to see a feature to directly export those summaries to a note-taking app like Evernote. It would be great if Jarvis could also integrate with my calendar to remind me about key points from those summaries.", 80 | "timestamp": "2025-03-31T11:33:05.264Z", 81 | "metadata": { 82 | "generatedType": "neutral", 83 | "generatedTopic": "feature requests" 84 | }, 85 | "category": "positive", 86 | "clusterId": 3, 87 | "similarityScore": 1, 88 | "response": "The Jarvis Team\n\nThank you so much for your valuable feedback! We’re delighted to hear you find Jarvis handy for summarizing documents – we appreciate you letting us know. We’ve noted your suggestion for direct export to Evernote and calendar reminders, and we’ll definitely be considering these features as we continue to develop Jarvis and improve its capabilities. \n\nSincerely,\nThe Jarvis Team" 89 | }, 90 | { 91 | "id": "comment-6", 92 | "text": "Jarvis is pretty handy for quick information retrieval, but the subscription cost feels a little high compared to some other AI assistants on the market. I'm still evaluating if the features justify the ongoing expense.", 93 | "timestamp": "2025-03-31T11:33:06.478Z", 94 | "metadata": { 95 | "generatedType": "neutral", 96 | "generatedTopic": "pricing" 97 | }, 98 | "category": "neutral", 99 | "clusterId": 1, 100 | "similarityScore": 0.8381369557592502, 101 | "response": "Thank you for your valuable feedback regarding the subscription cost of Jarvis! We appreciate you sharing your thoughts and observations about its value in relation to other AI assistants. We acknowledge your point and are continually exploring ways to enhance the overall user experience, including considering different subscription options. Your input will be valuable as we work on ongoing improvements to Jarvis.\n\nThe Jarvis Team" 102 | }, 103 | { 104 | "id": "comment-7", 105 | "text": "Honestly, Jarvis is just a lot of empty promises. It keeps suggesting irrelevant articles and failing to actually understand my requests for help with my work – it’s not helpful at all.", 106 | "timestamp": "2025-03-31T11:33:07.615Z", 107 | "metadata": { 108 | "generatedType": "negative", 109 | "generatedTopic": "helpfulness" 110 | }, 111 | "category": "negative", 112 | "clusterId": 4, 113 | "similarityScore": 1, 114 | "response": "The Jarvis Team\n\nWe sincerely appreciate you sharing your honest feedback about your experience with Jarvis. We’re truly sorry to hear that you’ve found the suggestions irrelevant and that it hasn’t been helpful with your work requests – we understand your frustration. We’re actively working on improving Jarvis’s intent recognition and contextual understanding, and your feedback will be invaluable as we develop these enhancements. \n\nThank you again for bringing this to our attention. \n\nThe Jarvis Team" 115 | }, 116 | { 117 | "id": "comment-8", 118 | "text": "Honestly, Jarvis is a game-changer. It’s so much more intuitive than those other AI assistants I’ve tried – the responses are actually helpful and it just *gets* what I’m asking for.", 119 | "timestamp": "2025-03-31T11:33:08.904Z", 120 | "metadata": { 121 | "generatedType": "positive", 122 | "generatedTopic": "comparison to competitors" 123 | }, 124 | "category": "positive", 125 | "clusterId": 1, 126 | "similarityScore": 0.8854228617130362, 127 | "response": "The Jarvis Team\n\nThank you so much for your kind words – we’re thrilled to hear that you’re finding Jarvis so intuitive and helpful! We truly appreciate you taking the time to share your positive experience. Your feedback regarding the helpfulness of the responses is invaluable to us as we continue to refine Jarvis’s capabilities. \n\nWe’ll definitely keep your comments in mind as we work on ongoing improvements!" 128 | }, 129 | { 130 | "id": "comment-9", 131 | "text": "Jarvis is seriously impressive – it’s incredibly accurate with my research requests. I’ve been amazed at how quickly it nails the specific details I need, saving me so much time. Definitely a game-changer!", 132 | "timestamp": "2025-03-31T11:33:10.195Z", 133 | "metadata": { 134 | "generatedType": "positive", 135 | "generatedTopic": "accuracy" 136 | }, 137 | "category": "positive", 138 | "clusterId": 1, 139 | "similarityScore": 0.7552924258231567, 140 | "response": "The Jarvis Team\n\nThank you so much for your wonderful feedback! We’re thrilled to hear that you’re finding Jarvis so impressive and accurate with your research requests – it’s truly rewarding to know we’re helping you save valuable time. We appreciate you recognizing the positive impact Jarvis is having, and your comments will definitely be considered as we continue to refine and improve the assistant." 141 | }, 142 | { 143 | "id": "comment-10", 144 | "text": "Jarvis’s interface is pretty clean and straightforward, which is nice. I appreciate how easily I can navigate between the different functions, though sometimes the icons could be a little clearer. Overall, it’s a functional UI that gets the job done.", 145 | "timestamp": "2025-03-31T11:33:11.696Z", 146 | "metadata": { 147 | "generatedType": "neutral", 148 | "generatedTopic": "user interface" 149 | }, 150 | "category": "positive", 151 | "clusterId": 1, 152 | "similarityScore": 0.9049940326471817, 153 | "response": "Thank you for your valuable feedback regarding the Jarvis interface! We’re delighted to hear you find it clean and straightforward, and appreciate you highlighting the ease of navigation. We’re always working to improve, and your observation about the icons will certainly be considered as we explore potential visual enhancements for future updates.\n\nThe Jarvis Team" 154 | }, 155 | { 156 | "id": "comment-11", 157 | "text": "Jarvis is pretty handy for quickly getting information and setting reminders, but I’ve noticed it occasionally misunderstands complex requests. Overall, it’s a useful tool, though I’m still getting used to its quirks.", 158 | "timestamp": "2025-03-31T11:33:13.041Z", 159 | "metadata": { 160 | "generatedType": "neutral", 161 | "generatedTopic": "general experience" 162 | }, 163 | "category": "positive", 164 | "clusterId": 1, 165 | "similarityScore": 0.7976244367456051, 166 | "response": "Thank you for your feedback on Jarvis! We appreciate you highlighting its usefulness for quick information and reminders. We’re glad to hear you find it a useful tool and are working hard to continuously improve its accuracy and understanding of complex requests. Your observations will certainly be considered as we develop future updates for Jarvis.\n\nThe Jarvis Team" 167 | }, 168 | { 169 | "id": "comment-12", 170 | "text": "“Jarvis is seriously impressive – the speed at which it responds is incredible! I’ve never used an AI assistant that’s so quick and efficient.”", 171 | "timestamp": "2025-03-31T11:33:14.077Z", 172 | "metadata": { 173 | "generatedType": "positive", 174 | "generatedTopic": "response speed" 175 | }, 176 | "category": "positive", 177 | "clusterId": 1, 178 | "similarityScore": 0.8514215813239125, 179 | "response": "The Jarvis Team\n\nThank you so much for your positive feedback! We’re thrilled to hear you’re impressed with Jarvis’s speed and efficiency – it’s fantastic to know it’s meeting your expectations. We truly appreciate you taking the time to share your thoughts, and they’ll certainly be considered as we continue to refine and improve Jarvis." 180 | }, 181 | { 182 | "id": "comment-13", 183 | "text": "Honestly, Jarvis feels incredibly frustrating to use. It misunderstands my requests constantly, and the responses are often just generic and unhelpful – I’m wasting more time trying to get it to do simple things than I would just doing them myself.", 184 | "timestamp": "2025-03-31T11:33:15.500Z", 185 | "metadata": { 186 | "generatedType": "negative", 187 | "generatedTopic": "general experience" 188 | }, 189 | "category": "negative", 190 | "clusterId": 1, 191 | "similarityScore": 0.762450691533025, 192 | "response": "The Jarvis Team\n\nWe sincerely appreciate you bringing this to our attention. We understand your frustration with Jarvis’s performance and are truly sorry to hear that you’ve been experiencing misunderstandings and unhelpful responses. Thank you for sharing your feedback – it’s incredibly valuable as we continuously work to improve Jarvis’s accuracy and usefulness. We’ll be carefully considering your comments, particularly regarding request understanding, for future updates." 193 | }, 194 | { 195 | "id": "comment-14", 196 | "text": "Jarvis has been surprisingly helpful with scheduling meetings and quickly finding information. It’s definitely streamlined my workflow and I appreciate its responsiveness.", 197 | "timestamp": "2025-03-31T11:33:16.376Z", 198 | "metadata": { 199 | "generatedType": "neutral", 200 | "generatedTopic": "helpfulness" 201 | }, 202 | "category": "positive", 203 | "clusterId": 1, 204 | "similarityScore": 0.7591284715583156, 205 | "response": "The Jarvis Team\n\nThank you so much for your positive feedback – we’re thrilled to hear that Jarvis is streamlining your workflow and proving helpful with scheduling and information retrieval! We truly appreciate you noticing its responsiveness and value. Your input will definitely be considered as we continue to refine Jarvis and explore new ways to enhance your experience." 206 | }, 207 | { 208 | "id": "comment-15", 209 | "text": "“Jarvis is seriously impressive – the speed at which it responds is incredible! I’ve never used an AI assistant that’s so quick and efficient, it’s a game changer.”", 210 | "timestamp": "2025-03-31T11:33:17.544Z", 211 | "metadata": { 212 | "generatedType": "positive", 213 | "generatedTopic": "response speed" 214 | }, 215 | "category": "positive", 216 | "clusterId": 1, 217 | "similarityScore": 0.7512107449828891, 218 | "response": "The Jarvis Team\n\nThank you so much for your wonderful feedback! We’re thrilled to hear you’re finding Jarvis’s speed and efficiency so impressive – it’s exactly what we’re aiming for. We truly appreciate you highlighting this and will certainly be considering your comments as we continue to refine Jarvis’s performance. \n\nSincerely,\nThe Jarvis Team" 219 | }, 220 | { 221 | "id": "comment-16", 222 | "text": "Honestly, Jarvis is amazing! I was a little worried about the occasional glitches, but it’s actually gotten *better* at handling them – it just recovers really quickly and keeps going. I really appreciate how quickly the developers are addressing those little bugs.", 223 | "timestamp": "2025-03-31T11:33:19.009Z", 224 | "metadata": { 225 | "generatedType": "positive", 226 | "generatedTopic": "bugs" 227 | }, 228 | "category": "positive", 229 | "clusterId": 1, 230 | "similarityScore": 0.7835948390771207, 231 | "response": "The Jarvis Team\n\nThank you so much for your wonderful feedback! We’re thrilled to hear you’re finding Jarvis amazing and that you’ve noticed the improvements in its handling of glitches – it’s exactly what we’re aiming for. We truly appreciate you letting us know you’re seeing these positive changes, and your input will definitely be considered as we continue to refine Jarvis’s performance." 232 | }, 233 | { 234 | "id": "comment-17", 235 | "text": "Honestly, Jarvis is incredibly slow. It takes forever for it to process even simple requests, and I've wasted so much time waiting for a response. I'm starting to think it's not worth the effort.", 236 | "timestamp": "2025-03-31T11:33:20.331Z", 237 | "metadata": { 238 | "generatedType": "negative", 239 | "generatedTopic": "response speed" 240 | }, 241 | "category": "negative", 242 | "clusterId": 1, 243 | "similarityScore": 0.8579730643169199, 244 | "response": "We sincerely apologize that you’ve experienced slow processing times with Jarvis and that it’s impacted your workflow. Thank you for bringing this to our attention – we truly appreciate you letting us know. We understand your frustration and are actively working on optimizing Jarvis’s speed and efficiency through several improvements, including a focused effort on response time reduction. Your feedback will be carefully considered as we continue to enhance Jarvis’s performance.\n\nThe Jarvis Team" 245 | }, 246 | { 247 | "id": "comment-18", 248 | "text": "Jarvis is pretty useful for scheduling and reminders, but the premium subscription feels a little pricey compared to some other AI assistants on the market. I’m still evaluating if the extra features are worth the ongoing cost.", 249 | "timestamp": "2025-03-31T11:33:21.606Z", 250 | "metadata": { 251 | "generatedType": "neutral", 252 | "generatedTopic": "pricing" 253 | }, 254 | "category": "neutral", 255 | "clusterId": 1, 256 | "similarityScore": 0.8631383961558244, 257 | "response": "Thank you for sharing your feedback on Jarvis! We appreciate you highlighting its usefulness for scheduling and reminders. We understand your observation regarding the premium subscription cost and are actively exploring options like tiered subscription models to better meet diverse user needs. Your input will certainly be considered as we continue to refine Jarvis and improve its overall experience.\n\nThe Jarvis Team" 258 | }, 259 | { 260 | "id": "comment-19", 261 | "text": "Jarvis is seriously impressive – it’s actually gotten my research questions right every time! I’m amazed at how accurate it is and it’s already saving me so much time.", 262 | "timestamp": "2025-03-31T11:33:22.778Z", 263 | "metadata": { 264 | "generatedType": "positive", 265 | "generatedTopic": "accuracy" 266 | }, 267 | "category": "positive", 268 | "clusterId": 1, 269 | "similarityScore": 0.7604153028026742, 270 | "response": "The Jarvis Team\n\nThank you so much for your wonderful feedback! We’re thrilled to hear that Jarvis is accurately answering your research questions and saving you valuable time – that’s fantastic to know. We truly appreciate you highlighting the positive impact Jarvis is having on your workflow. Your input will certainly be considered as we continue to refine and improve the assistant." 271 | }, 272 | { 273 | "id": "comment-20", 274 | "text": "Honestly, Jarvis is amazing! I was really worried about the occasional glitches, but it’s actually gotten better at handling those little hiccups – it’s still learning, but I appreciate how quickly it recovers now.", 275 | "timestamp": "2025-03-31T11:33:24.045Z", 276 | "metadata": { 277 | "generatedType": "positive", 278 | "generatedTopic": "bugs" 279 | }, 280 | "category": "positive", 281 | "clusterId": 1, 282 | "similarityScore": 0.7628356619617648, 283 | "response": "The Jarvis Team\n\nThank you so much for your positive feedback! We’re thrilled to hear that you’ve found Jarvis to be amazing and appreciate the improvements in handling glitches – it’s fantastic to know it’s learning and recovering quickly. We truly value your input and will certainly consider it as we continue to refine Jarvis’s performance." 284 | } 285 | ], 286 | "features": [ 287 | { 288 | "name": "Enhanced Visual Customization", 289 | "description": "Allows users to personalize the Jarvis interface with more themes, icon styles, and display options to improve visual appeal and user preference.", 290 | "type": "Improvement", 291 | "priority": "Medium", 292 | "clusters": [ 293 | "1" 294 | ] 295 | }, 296 | { 297 | "name": "Improved Request Understanding", 298 | "description": "Refines Jarvis’s natural language processing capabilities to better interpret complex and nuanced user requests, reducing misunderstandings.", 299 | "type": "Bug Fix", 300 | "priority": "High", 301 | "clusters": [ 302 | "1" 303 | ] 304 | }, 305 | { 306 | "name": "Subscription Tier Options", 307 | "description": "Introduce a tiered subscription model with varying feature sets and pricing to cater to different user needs and budgets, addressing the cost concern.", 308 | "type": "New Feature", 309 | "priority": "Medium", 310 | "clusters": [ 311 | "1" 312 | ] 313 | }, 314 | { 315 | "name": "Speed Optimization", 316 | "description": "Invest in infrastructure and algorithm improvements to significantly reduce response times across all requests, addressing the slowness issue.", 317 | "type": "Bug Fix", 318 | "priority": "High", 319 | "clusters": [ 320 | "1" 321 | ] 322 | }, 323 | { 324 | "name": "Icon Clarity Enhancement", 325 | "description": "Redesign the icons within the Jarvis interface to be more intuitive and easily recognizable, simplifying navigation.", 326 | "type": "Improvement", 327 | "priority": "Low", 328 | "clusters": [ 329 | "1" 330 | ] 331 | }, 332 | { 333 | "name": "Rapid Recovery Module", 334 | "description": "Implement a system that allows Jarvis to quickly recover from errors and freezes, minimizing disruption and maintaining user flow.", 335 | "type": "New Feature", 336 | "priority": "Medium", 337 | "clusters": [ 338 | "1" 339 | ] 340 | }, 341 | { 342 | "name": "Accuracy Boost – Research", 343 | "description": "Fine-tune the research capabilities specifically to improve accuracy and detail retrieval for complex research requests.", 344 | "type": "Improvement", 345 | "priority": "High", 346 | "clusters": [ 347 | "1" 348 | ] 349 | }, 350 | { 351 | "name": "Rapid Response Optimization", 352 | "description": "This feature would focus on further refining Jarvis’s processing speed, building on its current impressive response times to ensure an even smoother and more immediate user experience. It’s directly addressing the user’s praise for Jarvis’s speed.", 353 | "type": "Improvement", 354 | "priority": "High", 355 | "clusters": [ 356 | "2" 357 | ] 358 | }, 359 | { 360 | "name": "Enhanced Query Understanding", 361 | "description": "This improvement would deepen Jarvis’s ability to interpret complex and nuanced queries, allowing it to more accurately grasp the user’s intent and deliver even more relevant and detailed responses. This directly addresses the user's amazement at its understanding.", 362 | "type": "Improvement", 363 | "priority": "High", 364 | "clusters": [ 365 | "2" 366 | ] 367 | }, 368 | { 369 | "name": "Contextual Accuracy Boost", 370 | "description": "This feature aims to strengthen Jarvis's ability to maintain context across multiple turns in a conversation and consistently provide accurate information based on that context. It builds upon the existing accuracy and detailed information, ensuring sustained performance.", 371 | "type": "Improvement", 372 | "priority": "Medium", 373 | "clusters": [ 374 | "2" 375 | ] 376 | }, 377 | { 378 | "name": "Evernote Export Integration", 379 | "description": "Allows users to directly export generated summaries to popular note-taking apps like Evernote, streamlining the workflow and reducing manual copying.", 380 | "type": "New Feature", 381 | "priority": "High", 382 | "clusters": [ 383 | "3" 384 | ] 385 | }, 386 | { 387 | "name": "Calendar Reminders", 388 | "description": "Automatically generates reminders within Jarvis based on key points identified in document summaries, helping users stay on track with important information.", 389 | "type": "New Feature", 390 | "priority": "Medium", 391 | "clusters": [ 392 | "3" 393 | ] 394 | }, 395 | { 396 | "name": "Summary Length Control", 397 | "description": "Provides users with more granular control over the length of the generated summaries, allowing them to tailor the output to their specific needs.", 398 | "type": "Improvement", 399 | "priority": "Low", 400 | "clusters": [ 401 | "3" 402 | ] 403 | }, 404 | { 405 | "name": "Improved Intent Recognition", 406 | "description": "Jarvis needs a better understanding of user intent, particularly within professional contexts. This involves enhanced natural language processing to accurately interpret complex requests and prioritize relevant information.", 407 | "type": "New Feature", 408 | "priority": "High", 409 | "clusters": [ 410 | "4" 411 | ] 412 | }, 413 | { 414 | "name": "Contextual Relevance Engine", 415 | "description": "The system should be able to learn and maintain context from ongoing conversations and user work habits to deliver more relevant suggestions and assistance. This will move beyond simply suggesting articles and focus on truly helpful support.", 416 | "type": "New Feature", 417 | "priority": "High", 418 | "clusters": [ 419 | "4" 420 | ] 421 | }, 422 | { 423 | "name": "Feedback Loop Enhancement", 424 | "description": "Implement a more robust feedback mechanism where users can directly indicate whether a suggestion was helpful or not. This data can be used to train the AI and improve its accuracy over time.", 425 | "type": "Improvement", 426 | "priority": "Medium", 427 | "clusters": [ 428 | "4" 429 | ] 430 | } 431 | ] 432 | } --------------------------------------------------------------------------------