diff --git a/backend/src/db/db.config.js b/backend/src/db/db.config.js index 1bc0325..a6f1019 100644 --- a/backend/src/db/db.config.js +++ b/backend/src/db/db.config.js @@ -1,5 +1,3 @@ - - module.exports = { production: { dialect: 'postgres', @@ -12,11 +10,12 @@ module.exports = { seederStorage: 'sequelize', }, development: { - username: 'postgres', dialect: 'postgres', - password: '', - database: 'db_ai_app_draft', - host: process.env.DB_HOST || 'localhost', + username: process.env.DB_USER || 'postgres', + password: process.env.DB_PASS || '', + database: process.env.DB_NAME || 'db_ai_app_draft', + host: process.env.DB_HOST || '127.0.0.1', + port: process.env.DB_PORT || 5432, logging: console.log, seederStorage: 'sequelize', }, @@ -30,4 +29,4 @@ module.exports = { logging: console.log, seederStorage: 'sequelize', } -}; +}; \ No newline at end of file diff --git a/backend/src/db/migrations/20260125000001-add-persona-data-to-profiles.js b/backend/src/db/migrations/20260125000001-add-persona-data-to-profiles.js new file mode 100644 index 0000000..c915d33 --- /dev/null +++ b/backend/src/db/migrations/20260125000001-add-persona-data-to-profiles.js @@ -0,0 +1,13 @@ + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('profiles', 'persona_data', { + type: Sequelize.JSONB, + allowNull: true, + }); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn('profiles', 'persona_data'); + } +}; diff --git a/backend/src/db/models/profiles.js b/backend/src/db/models/profiles.js index b83b019..62ee333 100644 --- a/backend/src/db/models/profiles.js +++ b/backend/src/db/models/profiles.js @@ -16,23 +16,19 @@ module.exports = function(sequelize, DataTypes) { name: { type: DataTypes.TEXT, - - - }, description: { type: DataTypes.TEXT, - - + }, +persona_data: { + type: DataTypes.JSONB, + allowNull: true, }, created: { type: DataTypes.DATE, - - - }, importHash: { @@ -49,28 +45,6 @@ created: { ); profiles.associate = (db) => { - - -/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - - - - - - - - - - - - - - -//end loop - - - db.profiles.belongsTo(db.users, { as: 'owner', foreignKey: { @@ -79,9 +53,6 @@ created: { constraints: false, }); - - - db.profiles.belongsTo(db.users, { as: 'createdBy', }); @@ -91,9 +62,5 @@ created: { }); }; - - return profiles; -}; - - +}; \ No newline at end of file diff --git a/backend/src/db/seeders/20260125000002-ai-models.js b/backend/src/db/seeders/20260125000002-ai-models.js new file mode 100644 index 0000000..d9bc502 --- /dev/null +++ b/backend/src/db/seeders/20260125000002-ai-models.js @@ -0,0 +1,40 @@ + +const { v4: uuidv4 } = require('uuid'); + +module.exports = { + up: async (queryInterface, Sequelize) => { + return queryInterface.bulkInsert('ai_models', [ + { + id: uuidv4(), + name: 'Groq Llama 3 70B', + provider: 'groq', + model_name: 'llama3-70b-8192', + is_enabled: true, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: uuidv4(), + name: 'Gemini 2.5 Flash Latest', + provider: 'gemini', + model_name: 'gemini-1.5-flash-latest', + is_enabled: true, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: uuidv4(), + name: 'DeepSeek Chat', + provider: 'deepseek', + model_name: 'deepseek-chat', + is_enabled: true, + createdAt: new Date(), + updatedAt: new Date(), + } + ]); + }, + + down: async (queryInterface, Sequelize) => { + return queryInterface.bulkDelete('ai_models', null, {}); + } +}; diff --git a/backend/src/index.js b/backend/src/index.js index 618e0aa..ab7083c 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,4 +1,3 @@ - const express = require('express'); const cors = require('cors'); const app = express(); @@ -102,6 +101,20 @@ app.use('/api/file', fileRoutes); app.use('/api/pexels', pexelsRoutes); app.enable('trust proxy'); +// Mock Auth Middleware for Extension (Single User Mode) +const mockAuthMiddleware = async (req, res, next) => { + try { + const user = await db.users.findOne(); + if (user) { + req.currentUser = user; + } + next(); + } catch (error) { + console.error("MockAuth Error:", error); + next(error); + } +}; + app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes); @@ -123,18 +136,18 @@ app.use('/api/request_queue', passport.authenticate('jwt', {session: false}), re app.use('/api/connection_logs', passport.authenticate('jwt', {session: false}), connection_logsRoutes); -app.use('/api/profiles', passport.authenticate('jwt', {session: false}), profilesRoutes); +app.use('/api/profiles', mockAuthMiddleware, profilesRoutes); app.use('/api/settings', passport.authenticate('jwt', {session: false}), settingsRoutes); app.use( '/api/openai', - passport.authenticate('jwt', { session: false }), + mockAuthMiddleware, openaiRoutes, ); app.use( '/api/ai', - passport.authenticate('jwt', { session: false }), + mockAuthMiddleware, openaiRoutes, ); @@ -171,4 +184,4 @@ db.sequelize.sync().then(function () { }); }); -module.exports = app; +module.exports = app; \ No newline at end of file diff --git a/chrome-extension.zip b/chrome-extension.zip new file mode 100644 index 0000000..ad07a72 Binary files /dev/null and b/chrome-extension.zip differ diff --git a/chrome-extension/content-script.js b/chrome-extension/content-script.js new file mode 100644 index 0000000..965dd2c --- /dev/null +++ b/chrome-extension/content-script.js @@ -0,0 +1,302 @@ +// Content Script for Smart Survey Filler +// Injects the Floating Bubble UI and handles page analysis and auto-filling + +(function() { + // Prevent multiple injections + if (window.ssfInjected) return; + window.ssfInjected = true; + + console.log('Smart Survey Filler content script active'); + + // --- UI CONSTRUCTION --- + const bubble = document.createElement('div'); + bubble.id = 'ssf-floating-bubble'; + bubble.title = "Smart Survey Filler"; + bubble.innerHTML = ` +
🤖
+ + `; + + const style = document.createElement('style'); + style.textContent = ` + #ssf-floating-bubble { + position: fixed; bottom: 30px; right: 30px; width: 60px; height: 60px; + background: linear-gradient(135deg, #4A90E2, #357ABD); + border-radius: 50%; box-shadow: 0 4px 15px rgba(0,0,0,0.3); + cursor: pointer; z-index: 2147483647; display: flex; align-items: center; justify-content: center; + transition: transform 0.2s; user-select: none; color: white; font-size: 30px; + } + #ssf-floating-bubble:hover { transform: scale(1.05); } + .ssf-menu { + position: absolute; bottom: 75px; right: 0; width: 260px; background: white; + border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 15px; + display: flex; flex-direction: column; gap: 8px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + cursor: default; text-align: left; color: #333; font-size: 14px; + border: 1px solid #eee; + } + .ssf-menu-header { + font-weight: bold; border-bottom: 1px solid #eee; padding-bottom: 8px; margin-bottom: 5px; + display: flex; justify-content: space-between; align-items: center; font-size: 16px; + } + .ssf-close-btn { cursor: pointer; color: #999; font-size: 20px; line-height: 1; } + .ssf-close-btn:hover { color: #333; } + #ssf-persona-select { + width: 100%; padding: 8px; border-radius: 6px; border: 1px solid #ddd; background: #fafafa; + } + #ssf-btn-fill { + background: #4A90E2; color: white; border: none; padding: 10px; border-radius: 6px; + cursor: pointer; font-weight: 600; margin-top: 5px; transition: background 0.2s; + } + #ssf-btn-fill:hover { background: #357ABD; } + #ssf-btn-fill:disabled { background: #ccc; cursor: not-allowed; } + #ssf-status { + font-size: 12px; margin-top: 5px; color: #666; text-align: center; min-height: 1.2em; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + `; + document.head.appendChild(style); + document.body.appendChild(bubble); + + // --- EVENT HANDLERS --- + + const menu = bubble.querySelector('.ssf-menu'); + const closeBtn = bubble.querySelector('.ssf-close-btn'); + const select = document.getElementById('ssf-persona-select'); + const fillBtn = document.getElementById('ssf-btn-fill'); + const status = document.getElementById('ssf-status'); + + // Toggle Menu + bubble.addEventListener('click', (e) => { + if (e.target.closest('.ssf-menu')) return; // Don't close if clicking inside menu + // Only toggle if not dragging + if (!isDragging) { + const isHidden = menu.style.display === 'none'; + menu.style.display = isHidden ? 'flex' : 'none'; + if (isHidden) loadPersonas(); + } + }); + + closeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + menu.style.display = 'none'; + }); + + // Load Personas + function loadPersonas() { + status.innerText = 'Loading profiles...'; + select.innerHTML = ''; + select.disabled = true; + fillBtn.disabled = true; + + chrome.runtime.sendMessage({ type: 'FETCH_PERSONAS' }, (response) => { + select.disabled = false; + fillBtn.disabled = false; + + if (chrome.runtime.lastError) { + status.innerText = 'Error: Check Extension Settings'; + select.innerHTML = ''; + return; + } + + if (response && response.success && response.data && response.data.length > 0) { + status.innerText = 'Ready'; + select.innerHTML = response.data.map(p => ``).join(''); + } else if (response && !response.success) { + status.innerText = response.error || 'Connection Error'; + select.innerHTML = ''; + } else { + status.innerText = 'No profiles found'; + select.innerHTML = ''; + } + }); + } + + // Fill Button Logic + fillBtn.addEventListener('click', async (e) => { + e.stopPropagation(); + const personaId = select.value; + + if (!personaId) { + status.innerText = 'Please select a persona first!'; + return; + } + + status.innerText = 'Scanning page...'; + fillBtn.disabled = true; + + // 1. Map Inputs to Questions + const fields = []; + // Expanded selector for more input types + const inputs = document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]), textarea, select'); + + inputs.forEach((input, i) => { + // Logic to find label: + // 1. Explicit