# Project export: artsee

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: You create. We connect.
- Devpost: https://devpost.com/software/artsee-ptek75
- GitHub: https://github.com/Ayush7970/Collab_AI_hack
- Video: https://www.youtube.com/embed/kdlt0cKnx4k?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Ayush Bhardwaj (2 commits)

## Devpost submission (written by the team)

### Inspiration

The art world is increasingly collaborative, but finding the right creative partners, negotiating terms, and managing logistics is a slow and often manual process. We were inspired by the promise of Agentic AI—systems that could act on behalf of users to find, coordinate, and follow through on creative collaborations. Fetch.ai's uAgents technology offered a perfect foundation to turn this into a reality. With artsee, we wanted to explore how artists could simply express their vision, and then let agents handle the rest.

### What it does

artsee is an agent-powered creative collaboration platform that allows artists to build a personal creative profile (name, art style, preferences) which is stored in their own autonomous Fetch.ai uAgent. Once a profile is saved: The uAgent stores this user data on-chain and in the agent's memory. The information is displayed back to the user on a web app to confirm successful creation. The agent can then—via future roadmap—begin scanning for collaborations, negotiate timelines and royalties, and set up shared creative workspaces. It’s a step toward fully autonomous creative collaboration.

### How we built it

We built artsee by combining: A React web application to collect user input (name and art style). The Fetch.ai uAgents SDK to instantiate a user-specific agent that holds and manages this data. The Agentverse for uAgent discovery and registration. Claude 4 as the voice of our LLMs, for A2A communication. Groq to speed up LLM Inference, allowing for speedy Agentic communication. Python back-end services to facilitate communication between the frontend and uAgent lifecycle methods. Agent message-handling and internal storage within the uAgent to persist user data.

### Challenges we ran into

The biggest challenge we ran into was unfamiliar technologies: none of us had used Fetch.ai, uAgents, or Agentverse before. While documentation was helpful, setting up agents that could store and reflect back user data required learning a completely new ecosystem, understanding async agent messaging, and deploying within the Agentverse structure. Integrating agent responses with a React frontend was another technical hurdle—but one we overcame through trial, error, and iteration.

### Accomplishments we're proud of

Successfully initialized a working uAgent capable of storing and returning custom user data. Integrated agent storage and logic with a modern web interface. Developed a minimal yet extensible prototype for future agentic collaboration systems. Took our first step into Agentic AI, building with real infrastructure instead of a simulation.

### What we learned

We dove deep into the emerging world of Agentic AI and came away with concrete knowledge: How uAgents work, how they're registered, and how they maintain internal state. The design patterns for agent negotiation, autonomy, and messaging using the Fetch.ai stack. How to structure a frontend–agent interface to enable truly autonomous user-driven workflows. The value of decentralized, peer-to-peer automation for artistic communities.

### What's next

Agent-to-agent collaboration matchmaking: Allowing uAgents to discover and propose matches for creative collaboration. Negotiation flows: Agents will handle creative agreements, deadlines, and compensation automatically. Smart contract integration: Automatically enforce royalties, ownership, and delivery timelines using Fetch.ai’s ledger layer. Workspace setup and nudges: Agents will create shared creative environments and prompt collaborators when needed. Creative AI support: Integrate Claude, Gemini, and generative models like MusicLM to support mood-based tagging, beat generation, and portfolio analysis. Get artsee deployed via Docker and GCP. Ultimately, artsee is just the beginning of a future where creators can focus on art while their agents handle the hustle.

## README (from the GitHub repository)

# ArtSee — Agentic Matchmaking for Creative Collaborations
> **You Create. We Connect.**  
ArtSee is an agent-based system that matches creators and negotiates collaboration terms using LLMs (Claude + Groq) on top of the [uAgents](https://github.com/fetchai/uAgents) framework.

<p align="center">
  <img src="ArtSee1.jpg" alt="ArtSee logo" width="520">
</p>

<p align="center">
  <a href="https://youtube.com/watch?v=kdlt0cKnx4k"><b>▶ Demo Video</b></a>
</p>

---

## Table of Contents
- [Why ArtSee](#why-artsee)
- [Features](#features)
- [Architecture](#architecture)
- [Tech Stack](#tech-stack)
- [Screenshots](#screenshots)
- [Quickstart](#quickstart)
- [Configuration](#configuration)
- [Data Models](#data-models)
- [Dummy Profiles Schema](#dummy-profiles-schema)
- [Repo Structure](#repo-structure)
- [Troubleshooting](#troubleshooting)
- [Security & Privacy](#security--privacy)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)

---

## Why ArtSee
Finding the *right* creative collaborator is hard. ArtSee turns the search and early negotiation into a fast, AI-assisted workflow:
1. You describe the project.
2. A **Matchmaker Agent** ranks top fits from a talent pool.
3. Two agents run a short **negotiation loop** to converge on scope, vibe, and timeline.
4. You get a clean transcript, ready to move to human chat.

---

## Features
- 🔎 **Smart matching** — Claude 4 ranks the top 3 collaborators for a query.
- 🤝 **Agentic negotiation** — Groq Llama-3 70B exchanges proposals/counter-proposals (up to 5 rounds).
- 🧠 **Profile graph** — creator profiles with tags, descriptions, and on-chain-friendly addresses.
- 📝 **Conversation logging** — transcripts saved to `logs/chat_log.txt` with a terminal `===END===` marker.
- 🧰 **Modular code** — shared Pydantic models across agents; clean .env-based configuration.
- 🧪 **CI-ready** — formatting/lint hooks and a repo layout meant for teams.

---

## Architecture
```
                   (Claude 4)
   user_requestor ─────────────► matchmaker
        │                           │
        │      best match address   │
        └───────────────◄───────────┘
                 negotiation (Groq Llama-3 70B, up to 5 rounds)
        ┌────────────────────────────────────────────────────┐
        │                                                    │
        ▼                                                    ▲
   video_director  ◄────────────── proposals ────────────────┘
```

- **Matchmaker**: ranks & returns the best collaborator address.
- **User Requestor**: initiates negotiation and logs the convo.
- **Video Director**: replies with creative counters and constraints.

---

## Tech Stack
**Agents & Messaging:** uAgents  
**Ranking LLM:** Anthropic Claude 4  
**Negotiation LLM:** Groq Llama-3-70B  
**Models:** Pydantic (uAgents `Model`)  
**Config:** `.env` via `python-dotenv`

---

## Screenshots
<p align="center">
  <img src="ArtSee2.jpg" alt="Create Profile" width="720"><br/>
  <em>Create Your Profile</em>
</p>

<p align="center">
  <img src="ArtSee3.jpg" alt="Agent Matchmaking Log" width="720"><br/>
  <em>Agent Matchmaking Log</em>
</p>

---

## Quickstart

### 1) Prerequisites
- Python 3.11+
- Anthropic & Groq API keys

### 2) Install
```bash
git clone <your-repo-url> && cd ArtSee
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -r requirements.txt
```

### 3) Configure
```bash
cp .env.example .env
# edit .env with:
#   ANTHROPIC_API_KEY=...
#   GROQ_API_KEY=...
#   MATCHMAKER_ADDRESS=...   # you’ll copy this after step 4
#   COLLAB_QUERY="Looking for a hip-hop visualizer collaborator"
```

### 4) Run Agents (3 terminals)
**Terminal 1 – Matchmaker**
```bash
python agents/matchmaker_agent.py
# copy the printed address → paste into MATCHMAKER_ADDRESS in .env
```

**Terminal 2 – Video Director**
```bash
python agents/video_director_agent.py
# copy its printed address and (optionally) add to dummy_profiles/agents.json
```

**Terminal 3 – User Requestor**
```bash
python agents/user_requestor.py
```

**Where’s the output?**  
Negotiation transcript is appended to **`logs/chat_log.txt`**. The line `===END===` marks completion for UI readers.

---

## Configuration
All configuration lives in `.env`:

| Variable | Purpose |
|---|---|
| `ANTHROPIC_API_KEY` | Claude 4 for ranking candidates |
| `GROQ_API_KEY` | Groq Llama-3 for proposals/counters |
| `MATCHMAKER_ADDRESS` | Address printed when matchmaker boots |
| `VIDEO_DIRECTOR_ADDRESS` | Optional: shortcut to a known collaborator |
| `COLLAB_QUERY` | Default text query sent by the user agent |

---

## Data Models
```python
class Request(Model):
    query: str

class Message(Model):
    body: str

class Proposal(Model):
    content: str
    round: int

class MatchResult(Model):
    name: str
    address: str
```
---

## Dummy Profiles Schema
`dummy_profiles/agents.json`
```json
[
  {
    "name": "Anya Sharma",
    "tags": ["photographer", "nature", "documentary"],
    "address": "agent1xxxxxxxx...",
    "description": "Nature & documentary photographer"
  },
  {
    "name": "Leo Martinez",
    "tags": ["videography", "music videos", "drone"],
    "address": "agent1yyyyyyyy...",
    "description": "Cinematic R&B video director"
  }
]
```

`dummy_profiles/video_director.json`
```json
{
  "name": "Leo Martinez",
  "specialty": "Music videos, drone videography, cinematic storytelling",
  "tools": ["DaVinci Resolve", "After Effects", "Sony FX3"],
  "rate": "$1200/day"
}
```

---

## Repo Structure
```
ArtSee/
├─ agents/
│  ├─ models.py
│  ├─ matchmaker_agent.py
│  ├─ user_requestor.py
│  └─ video_director_agent.py
├─ dummy_profiles/
│  ├─ agents.json
│  └─ video_director.json
├─ docs/
│  └─ images/
│     ├─ logo.png
│     ├─ profile-form.png
│     └─ agent-chat.png
├─ logs/               # negotiation transcripts
├─ .env.example
├─ requirements.txt
└─ README.md
```

---

## Troubleshooting
- **`AssertionError: Set MATCHMAKER_ADDRESS in .env`**  
  Run the matchmaker first and paste its printed address into `.env`.
- **No matches found**  
  Ensure names in `agents.json` are realistic and that tags relate to the query.
- **Empty/partial logs**  
  Check file permissions on `logs/` and verify each agent is running without errors.
- **Keys not loading**  
  Confirm `.env` exists and you started the shell with the virtualenv active.

---

## Security & Privacy
- Never commit `.env` or real API keys.
- Scrub `logs/chat_log.txt` before publishing transcripts.
- Treat profile data as PII—request consent before making profiles public.

---

## Roadmap
- [ ] Real-time UI that streams the negotiation
- [ ] Multi-agent marketplace (accept/decline top 3)
- [ ] Safety guardrails & cost tracking
- [ ] Docker Compose for one-command launch
- [ ] Persistent vector search over collaborator portfolio embeddings

---

## Contributing
1. Fork & create a feature branch.
2. Keep commits scoped and descriptive (e.g., `feat(matchmaker): add reranking prompt`).
3. Add/adjust tests (if applicable), run formatters/linters.
4. Open a PR with screenshots and a short demo clip.

---

## License
MIT — see `LICENSE`.


## Detected evidence (automated analysis)

Indexed codebase: 13 recognized source files, 76 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Anthropic (technology) — claimed on Devpost, not found in the code
- Flask (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (17 of 17)

```
.gitignore
artsee-app/package.json
artsee-app/README.md
artsee-app/src/App.js
artsee-app/src/index.js
backend/__init__.py
backend/frontend_app.py
backend/matchmaker_agent.py
backend/package.json
backend/user_input_agent.py
backend/user_send_request.py
backend/user.json
Frontend/index.html
Frontend/script.js
Frontend/style.css
README.md
Untitled-1.py
```

### Dependencies

- artsee-app/package.json: @testing-library/dom@^10.4.0, @testing-library/jest-dom@^6.6.3, @testing-library/react@^16.3.0, @testing-library/user-event@^13.5.0, react@^19.1.0, react-dom@^19.1.0, react-scripts@5.0.1, three@^0.177.0, web-vitals@^2.1.4
- backend/package.json: cors@^2.8.5, express@^5.1.0

### Recent commits (newest first)

- Update README.md
- Update README.md
- Add files via upload
- Update README.md
- jinu-1
- hey
- first commit

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/package.json

```
{
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^5.1.0"
  }
}

```

### artsee-app/package.json

```
{
  "name": "artsee-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.3.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "react-scripts": "5.0.1",
    "three": "^0.177.0",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

```

### artsee-app/src/index.js

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

```

### artsee-app/src/App.js

```javascript
import React, { useState, useEffect, useRef } from 'react';
import * as THREE from 'three';

// --- Reusable Style Objects ---
const styles = {
    app: { fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif", background: 'linear-gradient(135deg, #f3e7ff, #e3eeff)', color: '#333', width: '100vw', height: '100vh', overflow: 'hidden' },
    view: { width: '100%', height: '100%', position: 'absolute', top: 0, left: 0, display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center', opacity: 0, transition: 'opacity 0.5s ease-in-out', pointerEvents: 'none' },
    viewActive: { opacity: 1, pointerEvents: 'auto' },
    canvas: { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', zIndex: 1, cursor: 'pointer' },
    textContent: { position: 'relative', zIndex: 2, color: '#443c68', textShadow: '0px 1px 3px rgba(255, 255, 255, 0.5)', pointerEvents: 'none' },
    h1: { fontSize: 'clamp(2.5rem, 6vw, 4rem)', margin: '0 0 0.5rem 0' },
    tagline: { fontSize: 'clamp(1rem, 2.5vw, 1.25rem)', opacity: 0.8, maxWidth: '500px', margin: '0 auto' },
    modalOverlay: { position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', backdropFilter: 'blur(5px)', display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 1000 },
    modalBubble: { background: '#fff', padding: '25px 35px', borderRadius: '20px', boxShadow: '0 10px 30px rgba(0,0,0,0.2)', width: '90%', maxWidth: '500px', maxHeight: '90vh', overflowY: 'auto', position: 'relative', textAlign: 'left' },
    modalClose: { position: 'absolute', top: '15px', right: '15px', fontSize: '24px', color: '#aaa', cursor: 'pointer', border: 'none', background: 'none' },
    formGroup: { marginBottom: '15px' },
    formLabel: { display: 'block', marginBottom: '5px', fontWeight: 500 },
    formInput: { width: '100%', padding: '10px', border: '1px solid #ddd', borderRadius: '8px', boxSizing: 'border-box', fontFamily: 'inherit' },
    primaryButton: { background: 'linear-gradient(135deg, #6a11cb, #2575fc)', color: 'white', border: 'none', padding: '12px 20px', borderRadius: '8px', cursor: 'pointer', fontSize: '16px', width: '100%', marginTop: '10px', transition: 'transform 0.2s, box-shadow 0.2s' },
    contentContainer: { width: '100%', maxHeight: '70vh' },
    // --- PROFILE PAGE STYLES ---
    profilePageContainer: { width: '100%', height: '100%', display: 'flex', flexDirection: 'row', alignItems: 'flex-start', padding: '20px', boxSizing: 'border-box', overflowY: 'auto', background: 'linear-gradient(135deg, #f3e7ff, #e3eeff)' },
    sidePanel: { flex: '0 0 250px', marginRight: '40px', textAlign: 'left' },
    mainContent: { flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' },
    profileHeader: { display: 'flex', alignItems: 'center', width: '100%', maxWidth: '900px', marginBottom: '20px' },
    profilePic: { width: '150px', height: '150px', borderRadius: '50%', objectFit: 'cover', marginRight: '40px', border: '3px solid white', boxShadow: '0 4px 10px rgba(0,0,0,0.1)', flexShrink: 0 },
    profileInfo: { textAlign: 'left', flexGrow: 1 },
    profileName: { fontSize: '2rem', fontWeight: 'bold', margin: 0 },
    profileBio: { fontSize: '1rem', margin: '10px 0', whiteSpace: 'pre-wrap' },
    portfolioLink: { display: 'block', color: '#6a11cb', textDecoration: 'none', marginBottom: '5px' },
    statsContainer: { display: 'flex', gap: '30px', marginTop: '10px' },
    statItem: { textAlign: 'center' },
    statNumber: { fontSize: '1.2rem', fontWeight: 'bold' },
    statLabel: { fontSize: '0.9rem', color: '#555' },
    contentGrid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '15px', width: '100%', maxWidth: '900px', padding: '20px 0' },
    contentThumbnail: { width: '100%', height: '250px', objectFit: 'cover', borderRadius: '10px', cursor: 'pointer', transition: 'transform 0.2s', '&:hover': { transform: 'scale(1.05)' } },
    reUploadButton: { position: 'fixed', bottom: '30px', right: '30px', width: '60px', height: '60px', borderRadius: '50%', background: 'linear-gradient(135deg, #6a11cb, #2575fc)', color: 'white', fontSize: '30px', border: 'none', cursor: 'pointer', display: 'flex', justifyContent: 'center', alignItems: 'center', boxShadow: '0 4px 12px rgba(0,0,0,0.3)', zIndex: 1001 },
    collaboratorCard: { background: 'white', padding: '20px', borderRadius: '10px', boxShadow: '0 4px 10px rgba(0,0,0,0.1)', width: '300px' }
};

// --- Landing Page 3D Scene Component ---
const LandingScene = ({ onNodeClick }) => {
    const mountRef = useRef(null);
    const hoverLabelRef = useRef(null);

    useEffect(() => {
        const mountNode = mountRef.current;
        if (!mountNode) return;
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.z = 220;
        const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        mountNode.appendChild(renderer.domElement);

        const nodesData = [
            { label: "🎵 Music", creators: ["Mozart", "Bowie", "Björk"], position: new THREE.Vector3(-120, 70, -40) },
            { label: "🎨 Art", creators: ["Frida Kahlo", "da Vinci", "Warhol"], position: new THREE.Vector3(100, 120, 10) },
            { label: "✍️ Poetry", creators: ["Maya Angelou", "Shakespeare", "Rumi"], position: new THREE.Vector3(-140, -90, -60) },
            { label: "👗 Fashion", creators: ["Coco Chanel", "McQueen", "Miyake"], position: new THREE.Vector3(150, -60, 50) },
            { label: "💡 Design", creators: ["Dieter Rams", "Zaha Hadid", "Saul Bass"], position: new THREE.Vector3(10, -20, -100) },
            { label: "🎬 Film", creators: ["Kubrick", "DuVernay", "Miyazaki"], position: new THREE.Vector3(120, 30, 120) },
            { label: "💻 C
[truncated — 16319 more characters]
```

### backend/user_send_request.py

```python
from uagents import Agent, Context, Model
from uagents.protocol import Protocol

class Request(Model):
    query: str

# User Agent
user = Agent(name="user_requestor", seed="user_seed", endpoint=["http://127.0.0.1:5056/submit"], port=5056)

# Protocol to send match request
protocol = Protocol("send_request")

# Include the protocol
user.include(protocol)

# Send the request on agent startup (NOT protocol startup)
@user.on_event("startup")
async def send(ctx: Context):
    await ctx.send(
        "agent1qv3tkmaqp56zgx9gf6g6dgpxh0q5zw7e8x8wa6cqf675txayl2fczccvxqq",  # Replace with your actual matchmaker address
        Request(query="Video director for a hip-hop visualizer")
    )
    ctx.logger.info("📤 Sent request to matchmaker.")

# ✅ Run the agent
if __name__ == "__main__":
    user.run()
```

### backend/matchmaker_agent.py

```python
from uagents import Agent, Context, Model
from uagents.protocol import Protocol
import json
import os
import anthropic  # ✅ Claude 4 SDK

client = anthropic.Anthropic()  # Automatically picks up ANTHROPIC_API_KEY from environment

# Claude 4 API Setup

class Request(Model):
    query: str

class Message(Model):
    body: str

# Load dummy agents
with open("user.json", "r") as f:
    dummy_agents = json.load(f)

# Matchmaker Agent
matchmaker = Agent(
    name="matchmaker",
    seed="matchmaker_seed",
    endpoint=["http://127.0.0.1:5055/submit"],
    port=5055
)

protocol = Protocol(name="matchmaking")

@protocol.on_message(model=Request)
async def handle_request(ctx: Context, sender: str, request: Request):
    ctx.logger.info(f"🎯 Received request: {request.query}")
    
    # Build LLM prompt
    profiles_text = "\n".join(
        [f"- Name: {agent['name']}, Tags: {', '.join(agent['tags'])}" for agent in dummy_agents]
    )
    prompt = f"""Given the following creator profiles, recommend the best fit for:
Request: "{request.query}"
Profiles:
{profiles_text}
Respond with the name only."""

    # Claude 4 API call
    response = client.messages.create(
        model="claude-3-opus-20240229",
        max_tokens=100,
        messages=[
            {"role": "user", "content": prompt}
        ]
    )

    match_name = response.content[0].text.strip()
    ctx.logger.info(f"💡 Claude recommends: {match_name}")

    selected_agent = next((a for a in dummy_agents if a["name"] == match_name), None)

    if not selected_agent:
        await ctx.send(sender, Message(body="❌ No suitable match found."))
        return

    await ctx.send(
        selected_agent["address"],
        Message(body=f"User is requesting collaboration: '{request.query}'")
    )

    await ctx.send(sender, Message(body=f"✅ Sent your request to {match_name}!"))

matchmaker.include(protocol)

if __name__ == "__main__":
    matchmaker.run()
```

### backend/frontend_app.py

```python
from flask import Flask, request, jsonify
from flask_cors import CORS # Import CORS
import requests
import json
from datetime import datetime
import os

app = Flask(__name__)
CORS(app) # Enable CORS for your entire app

# Configuration
AGENT_URL = "http://127.0.0.1:8003"
USERS_FILE = "user.json"

def load_users():
    """Load user profiles from JSON file"""
    if os.path.exists(USERS_FILE):
        try:
            with open(USERS_FILE, 'r') as f:
                return json.load(f)
        except (json.JSONDecodeError, FileNotFoundError):
            return {"users": [], "metadata": {"total": 0, "last_updated": None}}
    return {"users": [], "metadata": {"total": 0, "last_updated": None}}

@app.route('/create_profile', methods=['POST'])
def create_profile():
    """Create a new user profile"""
    try:
        data = request.get_json()
        
        # Prepare request for agent - forward the entire payload
        response = requests.post(f"{AGENT_URL}/create_profile", json=data)
        
        if response.status_code == 200:
            return jsonify(response.json())
        else:
            return jsonify({
                'success': False,
                'message': f'Agent error: {response.status_code} - {response.text}'
            }), 500
            
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'Error creating profile: {str(e)}'
        }), 500

@app.route('/match_users', methods=['POST'])
def match_users():
    """Use Claude to find the best matches for a query"""
    try:
        data = request.get_json()
        query = data.get('query', '').strip()
        
        if not query:
            return jsonify({'success': False, 'message': 'Match query is required'}), 400
        
        # Prepare request for agent
        match_data = {
            'query': query,
            'limit': data.get('limit', 3)
        }
        
        response = requests.post(f"{AGENT_URL}/match_users", json=match_data)
        
        if response.status_code == 200:
            return jsonify(response.json())
        else:
            return jsonify({'success': False, 'message': f'Agent error: {response.status_code}'}), 500
            
    except Exception as e:
        return jsonify({'success': False, 'message': f'Error matching users: {str(e)}'}), 500

if __name__ == '__main__':
    print("Starting User Profile Frontend...")
    print(f"Agent URL: {AGENT_URL}")
    print("Make sure the user profile agent is running on port 8003")
    # Change the port number in the next line
    app.run(debug=True, host='0.0.0.0', port=5001) 
```

### Frontend/script.js

```javascript
const hoverLabel = document.getElementById("hover-label");
const container = document.getElementById("creative-dna-container");

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });

renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);

camera.position.z = 150;

// --- Data with Example Creators ---
const nodesData = [
  { 
    label: "🎵 Music", 
    x: -50, y: 30, z: 0,
    creators: ["Beethoven", "David Bowie", "Daft Punk"]
  },
  { 
    label: "🎨 Art", 
    x: 40, y: 60, z: 20,
    creators: ["Leonardo da Vinci", "Yayoi Kusama", "Jean-Michel Basquiat"]
  },
  { 
    label: "✍️ Poetry", 
    x: -70, y: -40, z: -10,
    creators: ["Maya Angelou", "William Shakespeare", "Edgar Allan Poe"]
  },
  { 
    label: "👗 Fashion", 
    x: 70, y: -20, z: 30,
    creators: ["Coco Chanel", "Alexander McQueen", "Vivienne Westwood"]
  },
  { 
    label: "💡 Design", 
    x: 0, y: 0, z: -40,
    creators: ["Dieter Rams", "Paula Scher", "Saul Bass"]
  }
];

// --- Lighting for a more dynamic look ---
const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);

const pointLight = new THREE.PointLight(0x00ffff, 1, 500);
pointLight.position.set(50, 50, 50);
scene.add(pointLight);

// --- Updated Materials and Geometry ---
const nodeGeometry = new THREE.SphereGeometry(8, 32, 32);
const nodeMaterial = new THREE.MeshStandardMaterial({ 
    color: 0x00ffff, // Bright cyan color
    emissive: 0x00ffff, // Emissive property makes it glow
    emissiveIntensity: 0.3,
    metalness: 0.1,
    roughness: 0.4
});

const nodes = [];
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

nodesData.forEach(data => {
  const mesh = new THREE.Mesh(nodeGeometry.clone(), nodeMaterial.clone());
  mesh.position.set(data.x, data.y, data.z);
  // Store all data, including creators, in the mesh's userData
  mesh.userData = data; 
  scene.add(mesh);
  nodes.push(mesh);
});

function connectNodes() {
  for (let i = 0; i < nodes.length; i++) {
    for (let j = i + 1; j < nodes.length; j++) {
      const material = new THREE.LineBasicMaterial({ 
          color: 0x00ffff,
          transparent: true,
          opacity: 0.2
      });
      const points = [];
      points.push(nodes[i].position);
      points.push(nodes[j].position);
      const geometry = new THREE.BufferGeometry().setFromPoints(points);
      const line = new THREE.Line(geometry, material);
      scene.add(line);
    }
  }
}

connectNodes();

function animate() {
  requestAnimationFrame(animate);
  scene.rotation.y += 0.002;
  scene.rotation.x += 0.0005;
  renderer.render(scene, camera);
}

function onMouseMove(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);

  const intersects = raycaster.intersectObjects(nodes);

  if (intersects.length > 0) {
    document.body.style.cursor = 'pointer';
    const obj = intersects[0].object;
    
    nodes.forEach(node => node.scale.set(1, 1, 1));
    obj.scale.set(1.5, 1.5, 1.5);
    
    const data = obj.userData;
    // --- Updated Hover Label Logic ---
    hoverLabel.innerHTML = `<strong>${data.label}</strong>${data.creators.join('<br>')}`;
    hoverLabel.style.left = `${event.clientX + 15}px`;
    hoverLabel.style.top = `${event.clientY + 15}px`;
    hoverLabel.style.display = "block";

  } else {
    document.body.style.cursor = 'default';
    hoverLabel.style.display = "none";
    nodes.forEach(node => node.scale.set(1, 1, 1));
  }
}

window.addEventListener("mousemove", onMouseMove);
animate();
```

### backend/user_input_agent.py

```python
from uagents import Agent, Context, Model
from uagents.setup import fund_agent_if_low
import json
import os
from datetime import datetime
from typing import Dict, Any, List, Optional
from dataclasses import asdict
import anthropic

# --- Environment and API Key Setup ---
# Ensures the Anthropic API key is available from your environment variables
try:
    client = anthropic.Anthropic()
except Exception as e:
    print(f"Error initializing Anthropic client: {e}")
    print("Please make sure your ANTHROPIC_API_KEY is set as an environment variable.")
    client = None

# --- Data Models ---
class UserProfileRequest(Model):
    user_id: str
    name: str
    bio: str
    age: Optional[str] = None
    gender: Optional[str] = None
    interests: Optional[List[str]] = []
    skills: Optional[List[str]] = []
    portfolioLinks: Optional[List[str]] = []
    uploads: Optional[Dict[str, Any]] = {}
    isCollaborating: Optional[bool] = True

class UserProfileResponse(Model):
    success: bool
    message: str
    user_id: Optional[str] = None
    timestamp: Optional[str] = None
    error: Optional[str] = None

class MatchRequest(Model):
    query: str
    limit: Optional[int] = 3

class MatchResponse(Model):
    success: bool
    matches: List[Dict[str, Any]]
    error: Optional[str] = None

# --- AGENT SETUP ---
user_input_agent = Agent(
    name="user_input_agent",
    port=8003,
    seed="user_input_secret_seed",
    endpoint=["http://127.0.0.1:8003/submit"]
)

fund_agent_if_low(user_input_agent.wallet.address())

# --- STORAGE CONFIGURATION ---
USERS_FILE = "user.json"

def load_user_list() -> List[Dict[str, Any]]:
    if not os.path.exists(USERS_FILE):
        return []
    try:
        with open(USERS_FILE, 'r') as f:
            content = f.read()
            if not content: return []
            return json.loads(content)
    except (json.JSONDecodeError, FileNotFoundError):
        return []

def save_user_list(users: List[Dict[str, Any]]):
    with open(USERS_FILE, 'w') as f:
        json.dump(users, f, indent=2)

@user_input_agent.on_event("startup")
async def startup_event(ctx: Context):
    ctx.logger.info(f"User Profile Agent {user_input_agent.name} started on port 8003!")
    if not os.path.exists(USERS_FILE):
        save_user_list([])
        ctx.logger.info(f"Initialized new empty '{USERS_FILE}'")

# --- API ENDPOINTS ---
@user_input_agent.on_rest_post("/create_profile", UserProfileRequest, UserProfileResponse)
async def create_user_profile(ctx: Context, req: UserProfileRequest) -> UserProfileResponse:
    try:
        users = load_user_list()
        user_profile_dict = req.dict()
        user_profile_dict["timestamp"] = datetime.now().isoformat()
        users.append(user_profile_dict)
        save_user_list(users)
        
        ctx.logger.info(f"Successfully created profile for user_id: {req.user_id}")
        return UserProfileResponse(success=True, message="Profile created successfully", user_id=req.user_id, timestamp=user_profile_dict["timestamp"])
        
    except Exception as e:
        ctx.logger.error(f"Error in create_user_profile: {str(e)}")
        return UserProfileResponse(success=False, message="Internal server error", error=str(e))

@user_input_agent.on_rest_post("/match_users", MatchRequest, MatchResponse)
async def match_users(ctx: Context, req: MatchRequest) -> MatchResponse:
    if not client:
        return MatchResponse(success=False, matches=[], error="Anthropic client not initialized. Check API Key.")

    try:
        ctx.logger.info(f"Received match request for query: '{req.query}'")
        users = load_user_list()
        if not users:
            return MatchResponse(success=False, matches=[], error="No users available to match.")

        # Build the text block of profiles for the AI prompt
        profiles_text = "\n\n".join([
            f"Profile:\n- User ID: {user.get('user_id')}\n- Name: {user.get('name')}\n- Bio: {user.get('bio')}\n- Skills: {', '.join(user.get('skills', []))}\n- Interests: {', '.join(user.get('interests', []))}"
            for user in users
        ])
        
        prompt = f"""From the following list of creator profiles, find the best {req.limit} matches for the request below.

Request: "{req.query}"

Available Profiles:
{profiles_text}

Based on the request and the profiles, respond with ONLY the user_id's of the top {req.limit} best matches, each on a new line. Do not include any other text, explanation, or formatting.
"""

        # Call Claude AI
        response = client.messages.create(
            model="claude-3-opus-20240229",
            max_tokens=100,
            messages=[{"role": "user", "content": prompt}]
        )
        
        # Parse the AI response to get a list of user IDs
        matched_ids = [line.strip() for line in response.content[0].text.strip().split('\n') if line.strip()]
        ctx.logger.info(f"Claude AI matched the following user IDs: {matched_ids}")

        # Find the full profile objects for the matched IDs
        user_map = {user['user_id']: user for user in users}
        matched_profiles = [user_map[user_id] for user_id in matched_ids if user_id in user_map]
        
        if not matched_profiles:
            return MatchResponse(success=False, matches=[], error="AI could not find any suitable matches.")

        return MatchResponse(success=True, matches=matched_profiles)
        
    except Exception as e:
        ctx.logger.error(f"Error in match_users: {str(e)}")
        return MatchResponse(success=False, matches=[], error=str(e))

if __name__ == "__main__":
    user_input_agent.run()

```

### Frontend/index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>artseé - Creative DNA Visualizer</title>
    <style>
        /* Basic page setup */
        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            background: linear-gradient(135deg, #f3e7ff, #e3eeff);
            color: #333;
        }

        /* --- VIEW MANAGEMENT --- */
        .view {
            display: none;
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
        }
        .view.active {
            display: flex; /* Use flex to help with centering */
            justify-content: center;
            align-items: center;
            text-align: center;
        }

        /* --- LANDING PAGE STYLES --- */
        #dna-canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 1;
        }
        #text-content {
            position: relative;
            z-index: 2;
            color: #443c68;
            text-shadow: 0px 1px 3px rgba(255, 255, 255, 0.5);
            pointer-events: none;
        }
        h1 {
            font-size: clamp(2.5rem, 6vw, 4rem);
            margin-bottom: 0.5rem;
        }
        .tagline {
            font-size: clamp(1rem, 2.5vw, 1.25rem);
            opacity: 0.8;
            max-width: 500px;
            margin: 0 auto;
        }
        #hover-label {
            display: none; position: absolute; background-color: rgba(255, 255, 255, 0.8);
            backdrop-filter: blur(10px); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 12px;
            padding: 12px 18px; text-align: left; pointer-events: none; z-index: 100;
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); line-height: 1.6; transition: opacity 0.2s;
        }
        #hover-label .label-title {
            font-weight: bold; font-size: 1.1rem; color: #635985; margin-bottom: 5px; display: block;
        }

        /* --- MODAL STYLES --- */
        .modal-overlay {
            display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background-color: rgba(0, 0, 0, 0.4); backdrop-filter: blur(5px);
            justify-content: center; align-items: center; z-index: 1000;
        }
        .modal-bubble {
            background: #fff; padding: 25px 35px; border-radius: 20px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2); width: 90%; max-width: 500px;
            max-height: 90vh; overflow-y: auto; position: relative; text-align: left;
        }
        .modal-bubble h2 { margin-top: 0; color: #635985; }
        .modal-close {
            position: absolute; top: 15px; right: 15px; font-size: 24px;
            color: #aaa; cursor: pointer; border: none; background: none;
        }
        .form-group { margin-bottom: 15px; }
        .form-group label { display: block; margin-bottom: 5px; font-weight: 500; }
        .form-group input, .form-group textarea {
            width: 100%; padding: 10px; border: 1px solid #ddd;
            border-radius: 8px; box-sizing: border-box; font-family: inherit;
        }
        .form-group textarea { resize: vertical; min-height: 80px; }
        .form-group input::placeholder, .form-group textarea::placeholder { color: #bbb; }
        .portfolio-links-container .form-group { margin-bottom: 8px; }
        .toggle-group { display: flex; align-items: center; justify-content: space-between; }
        .toggle-switch { position: relative; display: inline-block; width: 50px; height: 28px; }
        .toggle-switch input { opacity: 0; width: 0; height: 0; }
        .toggle-slider {
            position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
            background-color: #ccc; transition: .4s; border-radius: 28px;
        }
        .toggle-slider:before {
            position: absolute; content: ""; height: 20px; width: 20px;
            left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%;
        }
        input:checked + .toggle-slider { background: linear-gradient(135deg, #6a11cb, #2575fc); }
        input:checked + .toggle-slider:before { transform: translateX(22px); }
        .primary-button {
            background: linear-gradient(135deg, #6a11cb, #2575fc); color: white; border: none;
            padding: 12px 20px; border-radius: 8px; cursor: pointer; font-size: 16px;
            width: 100%; margin-top: 10px; transition: transform 0.2s, box-shadow 0.2s;
        }
        .primary-button:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0,0,0,0.2); }

        /* --- UPLOAD MODAL STYLES --- */
        #upload-modal .upload-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
        .upload-box {
            border: 2px dashed #ddd; border-radius: 10px; padding: 15px;
            text-align: center; cursor: pointer; transition: background-color 0.2s, border-color 0.2s;
        }
        .upload-box:hover { background-color: #f9f9f9; border-color: #c7c7c7; }
        .upload-box .icon { font-size: 24px; color: #aaa; }
        .upload-box p { margin: 5px 0 0; color: #777; font-size: 0.9rem; }
        .upload-box input[type="file"] { display: none; }
        .file-name { font-size: 0.75rem; color: #32cd32; margin-top: 5px; font-weight: bold; }
        .profile-pic-label { cursor: pointer; color: #6a11cb; text-decoration: underline; }

        /* --- PROFILE PAGE STYLES --- */
        #profile-page { background: linear-gradient(135deg, #e0c3fc, #8ec5fc); }
        #profile-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
        #bio-overlay {
            display: non
[truncated — 23901 more characters]
```