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