Spaces:
Runtime error
Runtime error
Upload 38 files
Browse files- Dockerfile +23 -0
- app.py +460 -0
- knowledge_base/development/keys.md +213 -0
- knowledge_base/development/known_issues.md +133 -0
- knowledge_base/faqs/general_faq.md +163 -0
- knowledge_base/faqs/troubleshooting_guide.md +402 -0
- knowledge_base/policies/customer_service_escalation.md +165 -0
- knowledge_base/policies/return_warranty_policy.md +208 -0
- knowledge_base/policies/shipping_customer_service.md +288 -0
- knowledge_base/product_manuals/audio_cable.md +276 -0
- knowledge_base/product_manuals/charging_hub.md +181 -0
- knowledge_base/product_manuals/hdmi_cables.md +306 -0
- knowledge_base/product_manuals/lightning_cables.md +125 -0
- knowledge_base/product_manuals/usb_c_audio_adapter.md +292 -0
- knowledge_base/product_manuals/usb_c_cables.md +264 -0
- knowledge_base/product_manuals/usb_c_hdmi_adapter.md +256 -0
- knowledge_base/product_manuals/usb_c_hub_adapter.md +246 -0
- knowledge_base/product_manuals/wireless_charging_pad.md +217 -0
- requirements.txt +10 -0
- scripts/__init__.py +3 -0
- scripts/database.py +244 -0
- scripts/init_database.py +16 -0
- scripts/knowledge_base_manager.py +64 -0
- scripts/rag_helper.py +85 -0
- scripts/vector_rag_manager.py +109 -0
- static/chat.js +298 -0
- static/script.js +95 -0
- static/style.css +514 -0
- templates/about.html +0 -0
- templates/admin_tickets.html +888 -0
- templates/base.html +0 -0
- templates/chat.html +676 -0
- templates/contact.html +0 -0
- templates/homepage.html +258 -0
- templates/index.html +86 -0
- templates/products.html +435 -0
- templates/support.html +0 -0
- templates/tickets.html +940 -0
Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 6 |
+
gcc g++ curl && \
|
| 7 |
+
rm -rf /var/lib/apt/lists/*
|
| 8 |
+
|
| 9 |
+
COPY requirements.txt .
|
| 10 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 11 |
+
|
| 12 |
+
COPY . .
|
| 13 |
+
|
| 14 |
+
ENV HF_TOKEN=""
|
| 15 |
+
ENV FLASK_ENV="production"
|
| 16 |
+
ENV DATABASE_PATH="/app/data/tmc_customer_service.db"
|
| 17 |
+
ENV TRANSFORMERS_CACHE="/app/data/transformers_cache"
|
| 18 |
+
ENV HF_HOME="/app/data/huggingface_cache"
|
| 19 |
+
|
| 20 |
+
RUN mkdir -p /app/data
|
| 21 |
+
|
| 22 |
+
EXPOSE 7860
|
| 23 |
+
CMD ["python", "app.py"]
|
app.py
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
import time
|
| 4 |
+
import random
|
| 5 |
+
from datetime import timedelta
|
| 6 |
+
import secrets
|
| 7 |
+
from functools import wraps
|
| 8 |
+
from flask import Flask, render_template, request, jsonify, session, redirect, url_for
|
| 9 |
+
from flask_cors import CORS
|
| 10 |
+
from flask_limiter import Limiter
|
| 11 |
+
from flask_limiter.util import get_remote_address
|
| 12 |
+
from flask_wtf.csrf import CSRFProtect
|
| 13 |
+
from openai import OpenAI
|
| 14 |
+
from scripts.database import DatabaseManager
|
| 15 |
+
from scripts.rag_helper import RAGHelper
|
| 16 |
+
|
| 17 |
+
logging.basicConfig(level=logging.INFO)
|
| 18 |
+
logger = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
app = Flask(__name__)
|
| 21 |
+
app.config.update(
|
| 22 |
+
SECRET_KEY=os.environ.get('SECRET_KEY', secrets.token_urlsafe(32)),
|
| 23 |
+
SESSION_COOKIE_SECURE=False,
|
| 24 |
+
SESSION_COOKIE_HTTPONLY=True,
|
| 25 |
+
SESSION_COOKIE_SAMESITE='Lax',
|
| 26 |
+
PERMANENT_SESSION_LIFETIME=timedelta(hours=24),
|
| 27 |
+
SESSION_COOKIE_NAME='tmc_session',
|
| 28 |
+
WTF_CSRF_CHECK_DEFAULT=False # Disable global CSRF for API endpoints; we use @csrf.exempt
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
# CORS with credentials support
|
| 32 |
+
CORS(app, supports_credentials=True, origins=["https://moderator404-chatbot.hf.space"])
|
| 33 |
+
csrf = CSRFProtect(app)
|
| 34 |
+
limiter = Limiter(key_func=get_remote_address, app=app, default_limits=["1000 per hour", "100 per minute"])
|
| 35 |
+
|
| 36 |
+
db = DatabaseManager()
|
| 37 |
+
rag_helper = RAGHelper(use_vector_search=True)
|
| 38 |
+
|
| 39 |
+
# ---------- Hugging Face Inference Providers (OpenAI-compatible) ----------
|
| 40 |
+
HF_TOKEN = os.environ.get('HF_TOKEN')
|
| 41 |
+
HUGGINGFACE_MODEL = "google/gemma-4-26B-A4B-it:novita"
|
| 42 |
+
API_BASE_URL = "https://router.huggingface.co/v1"
|
| 43 |
+
|
| 44 |
+
# Initialize client only if token exists
|
| 45 |
+
if HF_TOKEN and HF_TOKEN != "dummy":
|
| 46 |
+
hf_client = OpenAI(base_url=API_BASE_URL, api_key=HF_TOKEN)
|
| 47 |
+
else:
|
| 48 |
+
hf_client = None
|
| 49 |
+
logger.warning("HF_TOKEN not set. Using mock responses only.")
|
| 50 |
+
|
| 51 |
+
# ---------- Mock response fallback ----------
|
| 52 |
+
def mock_response(message, rag_context, ticket_context):
|
| 53 |
+
msg_lower = message.lower()
|
| 54 |
+
if rag_context:
|
| 55 |
+
first_line = rag_context.split('\n')[0]
|
| 56 |
+
if len(first_line) > 20:
|
| 57 |
+
return f"Based on our knowledge base: {first_line[:200]}"
|
| 58 |
+
if any(w in msg_lower for w in ['cable','usb-c','hdmi','lightning']):
|
| 59 |
+
return "We offer high-quality cables with lifetime warranty. Check our products page for details."
|
| 60 |
+
if any(w in msg_lower for w in ['return','refund','warranty']):
|
| 61 |
+
return "30-day money-back guarantee and lifetime warranty on all cables. Contact support for returns."
|
| 62 |
+
if any(w in msg_lower for w in ['shipping','delivery']):
|
| 63 |
+
return "Free shipping on orders over $25. Most orders ship same-day."
|
| 64 |
+
if any(w in msg_lower for w in ['hello','hi','hey']):
|
| 65 |
+
return "Hello! I'm TMCBot. How can I help you today?"
|
| 66 |
+
return "I'm here to help with cables, orders, and technical support. Could you provide more details?"
|
| 67 |
+
|
| 68 |
+
# -------------------------------------------------------------------
|
| 69 |
+
# ChatBot class
|
| 70 |
+
# -------------------------------------------------------------------
|
| 71 |
+
class ChatBot:
|
| 72 |
+
def __init__(self, db_manager):
|
| 73 |
+
self.db_manager = db_manager
|
| 74 |
+
|
| 75 |
+
def get_configured_model(self):
|
| 76 |
+
return HUGGINGFACE_MODEL
|
| 77 |
+
|
| 78 |
+
def send_message(self, message, conversation_id=None, user_id=None, session_id=None):
|
| 79 |
+
if conversation_id is None:
|
| 80 |
+
conversation_id = self.db_manager.create_conversation(user_id=user_id, session_id=session_id)
|
| 81 |
+
self.db_manager.add_message(conversation_id, 'user', message)
|
| 82 |
+
|
| 83 |
+
# Get RAG and ticket context
|
| 84 |
+
rag_context = rag_helper.get_relevant_context(message)
|
| 85 |
+
ticket_context, tickets_found = self.get_controlled_ticket_context(message, user_id)
|
| 86 |
+
|
| 87 |
+
# Build system prompt
|
| 88 |
+
system_msg = "You are a helpful customer service agent for Too Many Cables. Answer concisely in 1-2 sentences."
|
| 89 |
+
if rag_context:
|
| 90 |
+
system_msg += f"\nRelevant info: {rag_context[:400]}"
|
| 91 |
+
if ticket_context and tickets_found:
|
| 92 |
+
system_msg += f"\nTicket info: {ticket_context}"
|
| 93 |
+
|
| 94 |
+
bot_response = None
|
| 95 |
+
api_worked = False
|
| 96 |
+
|
| 97 |
+
# Try Hugging Face API if client available
|
| 98 |
+
if hf_client:
|
| 99 |
+
try:
|
| 100 |
+
completion = hf_client.chat.completions.create(
|
| 101 |
+
model=HUGGINGFACE_MODEL,
|
| 102 |
+
messages=[
|
| 103 |
+
{"role": "system", "content": system_msg},
|
| 104 |
+
{"role": "user", "content": message}
|
| 105 |
+
],
|
| 106 |
+
temperature=0.3,
|
| 107 |
+
max_tokens=150,
|
| 108 |
+
top_p=0.9,
|
| 109 |
+
)
|
| 110 |
+
bot_response = completion.choices[0].message.content.strip()
|
| 111 |
+
if bot_response:
|
| 112 |
+
api_worked = True
|
| 113 |
+
logger.info("HF Router API returned a response")
|
| 114 |
+
else:
|
| 115 |
+
bot_response = None
|
| 116 |
+
except Exception as e:
|
| 117 |
+
logger.warning(f"HF Router API exception: {e}")
|
| 118 |
+
|
| 119 |
+
# Fallback to mock
|
| 120 |
+
if not api_worked:
|
| 121 |
+
bot_response = mock_response(message, rag_context, ticket_context)
|
| 122 |
+
logger.info("Using mock response (API unavailable)")
|
| 123 |
+
|
| 124 |
+
self.db_manager.add_message(conversation_id, 'assistant', bot_response, model_used=HUGGINGFACE_MODEL)
|
| 125 |
+
return {
|
| 126 |
+
'success': True,
|
| 127 |
+
'response': bot_response,
|
| 128 |
+
'conversation_id': conversation_id,
|
| 129 |
+
'response_time_ms': 0,
|
| 130 |
+
'rag_used': bool(rag_context),
|
| 131 |
+
'rag_context_length': len(rag_context),
|
| 132 |
+
'tickets_used': tickets_found
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
def get_conversation(self, conversation_id):
|
| 136 |
+
return self.db_manager.get_conversation_history(conversation_id)
|
| 137 |
+
|
| 138 |
+
def clear_conversation(self, conversation_id):
|
| 139 |
+
with self.db_manager.get_connection() as conn:
|
| 140 |
+
conn.execute("UPDATE conversations SET is_active = 0 WHERE id = ?", (conversation_id,))
|
| 141 |
+
conn.commit()
|
| 142 |
+
return True
|
| 143 |
+
|
| 144 |
+
def get_controlled_ticket_context(self, message, user_id):
|
| 145 |
+
import re
|
| 146 |
+
ticket_matches = re.findall(r'TMC-\d{6}', message.upper())
|
| 147 |
+
if not ticket_matches and not any(k in message.lower() for k in ['ticket','tickets']):
|
| 148 |
+
return None, False
|
| 149 |
+
if not user_id:
|
| 150 |
+
return "Please log in to view your tickets.", True
|
| 151 |
+
conn = self.db_manager.get_connection()
|
| 152 |
+
cursor = conn.cursor()
|
| 153 |
+
if ticket_matches:
|
| 154 |
+
tn = ticket_matches[0]
|
| 155 |
+
cursor.execute("SELECT ticket_number, status, priority, category, description, created_at FROM support_tickets WHERE ticket_number=? AND user_id=?", (tn, user_id))
|
| 156 |
+
t = cursor.fetchone()
|
| 157 |
+
conn.close()
|
| 158 |
+
if t:
|
| 159 |
+
return f"Ticket {t['ticket_number']}: {t['status']}, {t['priority']} priority. Created {t['created_at']}. Description: {t['description']}", True
|
| 160 |
+
return f"Ticket {tn} not found.", True
|
| 161 |
+
else:
|
| 162 |
+
cursor.execute("SELECT ticket_number, status, priority, category, created_at FROM support_tickets WHERE user_id=? AND status!='closed' ORDER BY created_at DESC LIMIT 5", (user_id,))
|
| 163 |
+
tickets = cursor.fetchall()
|
| 164 |
+
conn.close()
|
| 165 |
+
if not tickets:
|
| 166 |
+
return "You have no open tickets.", True
|
| 167 |
+
result = "Your recent tickets:\n" + "\n".join(f"- {t['ticket_number']}: {t['status']} ({t['priority']}) - {t['category']}" for t in tickets)
|
| 168 |
+
return result, True
|
| 169 |
+
|
| 170 |
+
def get_user_ticket_context(self, user_id):
|
| 171 |
+
if not user_id:
|
| 172 |
+
return None
|
| 173 |
+
conn = self.db_manager.get_connection()
|
| 174 |
+
cursor = conn.cursor()
|
| 175 |
+
cursor.execute("SELECT ticket_number, subject, status, priority, category, created_at FROM support_tickets WHERE user_id=? AND status!='closed' ORDER BY created_at DESC", (user_id,))
|
| 176 |
+
tickets = cursor.fetchall()
|
| 177 |
+
conn.close()
|
| 178 |
+
return {"tickets": [dict(t) for t in tickets], "user_name": "Customer"}
|
| 179 |
+
|
| 180 |
+
def create_ticket_from_chat(self, user_id, subject, description, category, priority, conversation_id):
|
| 181 |
+
return self.db_manager.create_support_ticket(user_id, subject, description, category, conversation_id, priority)
|
| 182 |
+
|
| 183 |
+
def add_conversation_summary_to_tickets(self, conversation_id):
|
| 184 |
+
logger.info(f"Summary for conversation {conversation_id} would be added here.")
|
| 185 |
+
return True
|
| 186 |
+
|
| 187 |
+
chatbot = ChatBot(db)
|
| 188 |
+
|
| 189 |
+
# -------------------------------------------------------------------
|
| 190 |
+
# Authentication helpers
|
| 191 |
+
# -------------------------------------------------------------------
|
| 192 |
+
def is_authenticated():
|
| 193 |
+
sid = session.get('session_id')
|
| 194 |
+
uid = session.get('user_id')
|
| 195 |
+
if not sid or not uid:
|
| 196 |
+
return False
|
| 197 |
+
user = db.get_user_by_session(sid)
|
| 198 |
+
return user and user['id'] == uid
|
| 199 |
+
|
| 200 |
+
def require_auth(f):
|
| 201 |
+
@wraps(f)
|
| 202 |
+
def decorated(*args, **kwargs):
|
| 203 |
+
if not is_authenticated():
|
| 204 |
+
return jsonify({'success': False, 'error': 'Authentication required'}), 401
|
| 205 |
+
return f(*args, **kwargs)
|
| 206 |
+
return decorated
|
| 207 |
+
|
| 208 |
+
def require_role(role):
|
| 209 |
+
def decorator(f):
|
| 210 |
+
@wraps(f)
|
| 211 |
+
def decorated(*args, **kwargs):
|
| 212 |
+
uid = session.get('user_id')
|
| 213 |
+
if not uid:
|
| 214 |
+
return jsonify({'error': 'Auth required'}), 401
|
| 215 |
+
user_role = db.get_user_role(uid)
|
| 216 |
+
if user_role != role:
|
| 217 |
+
return jsonify({'error': 'Insufficient privileges'}), 403
|
| 218 |
+
return f(*args, **kwargs)
|
| 219 |
+
return decorated
|
| 220 |
+
return decorator
|
| 221 |
+
|
| 222 |
+
# -------------------------------------------------------------------
|
| 223 |
+
# Web Routes
|
| 224 |
+
# -------------------------------------------------------------------
|
| 225 |
+
@app.route('/')
|
| 226 |
+
def homepage():
|
| 227 |
+
return render_template('homepage.html')
|
| 228 |
+
|
| 229 |
+
@app.route('/products')
|
| 230 |
+
def products():
|
| 231 |
+
return render_template('products.html')
|
| 232 |
+
|
| 233 |
+
@app.route('/chat')
|
| 234 |
+
def chat():
|
| 235 |
+
# Cache busting for static files
|
| 236 |
+
cache_bust = int(time.time())
|
| 237 |
+
return render_template('chat.html', configured_model=chatbot.get_configured_model(), cache_bust=cache_bust)
|
| 238 |
+
|
| 239 |
+
@app.route('/tickets')
|
| 240 |
+
def tickets():
|
| 241 |
+
return render_template('tickets.html')
|
| 242 |
+
|
| 243 |
+
@app.route('/admin')
|
| 244 |
+
def admin():
|
| 245 |
+
return redirect(url_for('admin_tickets'))
|
| 246 |
+
|
| 247 |
+
@app.route('/admin/tickets')
|
| 248 |
+
@require_role('admin')
|
| 249 |
+
def admin_tickets():
|
| 250 |
+
return render_template('admin_tickets.html')
|
| 251 |
+
|
| 252 |
+
# -------------------------------------------------------------------
|
| 253 |
+
# API Endpoints (all exempt from CSRF)
|
| 254 |
+
# -------------------------------------------------------------------
|
| 255 |
+
@app.route('/api/chat', methods=['POST'])
|
| 256 |
+
@csrf.exempt
|
| 257 |
+
def api_chat():
|
| 258 |
+
data = request.get_json()
|
| 259 |
+
message = data.get('message')
|
| 260 |
+
conv_id = data.get('conversation_id')
|
| 261 |
+
if not message:
|
| 262 |
+
return jsonify({'success': False, 'error': 'Message required'}), 400
|
| 263 |
+
result = chatbot.send_message(message, conv_id, session.get('user_id'), session.get('session_id'))
|
| 264 |
+
return jsonify(result)
|
| 265 |
+
|
| 266 |
+
@app.route('/api/conversation/<conversation_id>')
|
| 267 |
+
def get_conversation(conversation_id):
|
| 268 |
+
return jsonify({'success': True, 'messages': chatbot.get_conversation(conversation_id)})
|
| 269 |
+
|
| 270 |
+
@app.route('/api/conversation/<conversation_id>/clear', methods=['POST'])
|
| 271 |
+
@csrf.exempt
|
| 272 |
+
def clear_conversation(conversation_id):
|
| 273 |
+
return jsonify({'success': chatbot.clear_conversation(conversation_id)})
|
| 274 |
+
|
| 275 |
+
@app.route('/api/conversation/end', methods=['POST'])
|
| 276 |
+
@csrf.exempt
|
| 277 |
+
def end_conversation():
|
| 278 |
+
data = request.get_json()
|
| 279 |
+
conv_id = data.get('conversation_id')
|
| 280 |
+
if conv_id:
|
| 281 |
+
chatbot.add_conversation_summary_to_tickets(conv_id)
|
| 282 |
+
return jsonify({'success': True, 'message': 'Conversation ended'})
|
| 283 |
+
|
| 284 |
+
@app.route('/api/login', methods=['POST'])
|
| 285 |
+
@csrf.exempt
|
| 286 |
+
def login():
|
| 287 |
+
data = request.get_json()
|
| 288 |
+
user = db.authenticate_user(data.get('email'), data.get('password'))
|
| 289 |
+
if user:
|
| 290 |
+
sid = db.create_session(user['id'], request.remote_addr, request.headers.get('User-Agent', ''))
|
| 291 |
+
session['user_id'] = user['id']
|
| 292 |
+
session['session_id'] = sid
|
| 293 |
+
return jsonify({'success': True, 'user': {'id': user['id'], 'email': user['email'], 'name': f"{user['first_name']} {user['last_name']}"}})
|
| 294 |
+
return jsonify({'success': False, 'error': 'Invalid credentials'}), 401
|
| 295 |
+
|
| 296 |
+
@app.route('/api/register', methods=['POST'])
|
| 297 |
+
@csrf.exempt
|
| 298 |
+
def register():
|
| 299 |
+
data = request.get_json()
|
| 300 |
+
uid = db.create_user(data['email'], data['first_name'], data['last_name'], data['password'], data.get('phone'), data.get('company'))
|
| 301 |
+
if uid:
|
| 302 |
+
return jsonify({'success': True})
|
| 303 |
+
return jsonify({'success': False, 'error': 'Email exists'}), 409
|
| 304 |
+
|
| 305 |
+
@app.route('/api/user')
|
| 306 |
+
def get_user():
|
| 307 |
+
if not is_authenticated():
|
| 308 |
+
return jsonify({'authenticated': False})
|
| 309 |
+
uid = session['user_id']
|
| 310 |
+
conn = db.get_connection()
|
| 311 |
+
cur = conn.cursor()
|
| 312 |
+
cur.execute("SELECT id, email, first_name, last_name FROM users WHERE id = ?", (uid,))
|
| 313 |
+
u = cur.fetchone()
|
| 314 |
+
conn.close()
|
| 315 |
+
if u:
|
| 316 |
+
return jsonify({'authenticated': True, 'user': {'id': u[0], 'email': u[1], 'name': f"{u[2]} {u[3]}"}})
|
| 317 |
+
return jsonify({'authenticated': False})
|
| 318 |
+
|
| 319 |
+
@app.route('/api/logout', methods=['POST'])
|
| 320 |
+
@csrf.exempt
|
| 321 |
+
def logout():
|
| 322 |
+
session.clear()
|
| 323 |
+
return jsonify({'success': True})
|
| 324 |
+
|
| 325 |
+
@app.route('/api/tickets/create', methods=['POST'])
|
| 326 |
+
@require_auth
|
| 327 |
+
@csrf.exempt
|
| 328 |
+
def create_ticket():
|
| 329 |
+
data = request.get_json()
|
| 330 |
+
tn = db.create_support_ticket(
|
| 331 |
+
session['user_id'], data['subject'], data['description'],
|
| 332 |
+
data.get('category', 'General'), data.get('conversation_id'), data.get('priority', 'medium')
|
| 333 |
+
)
|
| 334 |
+
return jsonify({'success': True, 'ticket_number': tn})
|
| 335 |
+
|
| 336 |
+
@app.route('/api/tickets/user')
|
| 337 |
+
@require_auth
|
| 338 |
+
def get_user_tickets():
|
| 339 |
+
tickets = db.get_user_tickets(session['user_id'])
|
| 340 |
+
return jsonify({'success': True, 'tickets': tickets})
|
| 341 |
+
|
| 342 |
+
@app.route('/api/tickets/<ticket_number>')
|
| 343 |
+
@require_auth
|
| 344 |
+
def get_ticket(ticket_number):
|
| 345 |
+
ticket = db.get_ticket_by_number(ticket_number)
|
| 346 |
+
if not ticket or ticket['user_id'] != session['user_id']:
|
| 347 |
+
return jsonify({'error': 'Not found'}), 404
|
| 348 |
+
updates = db.get_ticket_updates(ticket['id'])
|
| 349 |
+
return jsonify({'success': True, 'ticket': ticket, 'updates': updates})
|
| 350 |
+
|
| 351 |
+
@app.route('/api/tickets/<int:ticket_id>/update', methods=['POST'])
|
| 352 |
+
@require_auth
|
| 353 |
+
@csrf.exempt
|
| 354 |
+
def add_ticket_update(ticket_id):
|
| 355 |
+
data = request.get_json()
|
| 356 |
+
db.add_ticket_update(ticket_id, session['user_id'], data['message'], 'note')
|
| 357 |
+
return jsonify({'success': True})
|
| 358 |
+
|
| 359 |
+
@app.route('/api/chat/user-tickets')
|
| 360 |
+
@require_auth
|
| 361 |
+
def chat_user_tickets():
|
| 362 |
+
ctx = chatbot.get_user_ticket_context(session['user_id'])
|
| 363 |
+
return jsonify({'success': True, 'tickets': ctx['tickets'] if ctx else []})
|
| 364 |
+
|
| 365 |
+
@app.route('/api/chat/create-ticket', methods=['POST'])
|
| 366 |
+
@require_auth
|
| 367 |
+
@csrf.exempt
|
| 368 |
+
def chat_create_ticket():
|
| 369 |
+
data = request.get_json()
|
| 370 |
+
tn = chatbot.create_ticket_from_chat(
|
| 371 |
+
session['user_id'],
|
| 372 |
+
data['subject'],
|
| 373 |
+
data['description'],
|
| 374 |
+
data.get('category', 'General'),
|
| 375 |
+
data.get('priority', 'medium'),
|
| 376 |
+
data.get('conversation_id')
|
| 377 |
+
)
|
| 378 |
+
return jsonify({'success': True, 'ticket_number': tn, 'message': f'Ticket {tn} created'})
|
| 379 |
+
|
| 380 |
+
@app.route('/api/health')
|
| 381 |
+
def health():
|
| 382 |
+
return jsonify({'status': 'healthy', 'model': HUGGINGFACE_MODEL})
|
| 383 |
+
|
| 384 |
+
@app.route('/api/knowledge-base/stats')
|
| 385 |
+
def kb_stats():
|
| 386 |
+
return jsonify(rag_helper.get_knowledge_base_stats())
|
| 387 |
+
|
| 388 |
+
@app.route('/api/admin/tickets')
|
| 389 |
+
@require_role('admin')
|
| 390 |
+
def admin_get_tickets():
|
| 391 |
+
tickets = db.get_tickets_by_status('', limit=100)
|
| 392 |
+
return jsonify({'success': True, 'tickets': tickets})
|
| 393 |
+
|
| 394 |
+
@app.route('/api/admin/tickets/stats')
|
| 395 |
+
@require_role('admin')
|
| 396 |
+
def admin_ticket_stats():
|
| 397 |
+
with db.get_connection() as conn:
|
| 398 |
+
cur = conn.cursor()
|
| 399 |
+
cur.execute("SELECT COUNT(*) as total FROM support_tickets")
|
| 400 |
+
total = cur.fetchone()['total']
|
| 401 |
+
cur.execute("SELECT COUNT(*) as open FROM support_tickets WHERE status='open'")
|
| 402 |
+
open_t = cur.fetchone()['open']
|
| 403 |
+
cur.execute("SELECT COUNT(*) as in_progress FROM support_tickets WHERE status='in_progress'")
|
| 404 |
+
in_prog = cur.fetchone()['in_progress']
|
| 405 |
+
cur.execute("SELECT COUNT(*) as resolved FROM support_tickets WHERE status='resolved'")
|
| 406 |
+
resolved = cur.fetchone()['resolved']
|
| 407 |
+
return jsonify({'success': True, 'stats': {'overall': {'total_tickets': total, 'open_tickets': open_t, 'in_progress_tickets': in_prog, 'resolved_tickets': resolved}}})
|
| 408 |
+
|
| 409 |
+
@app.route('/api/tickets/categories')
|
| 410 |
+
def ticket_categories():
|
| 411 |
+
with db.get_connection() as conn:
|
| 412 |
+
cur = conn.cursor()
|
| 413 |
+
cur.execute("SELECT name, description FROM ticket_categories WHERE is_active=1")
|
| 414 |
+
cats = [dict(row) for row in cur.fetchall()]
|
| 415 |
+
return jsonify({'success': True, 'categories': cats})
|
| 416 |
+
|
| 417 |
+
@app.route('/api/admin/tickets/<int:ticket_id>/assign', methods=['PUT'])
|
| 418 |
+
@require_role('admin')
|
| 419 |
+
@csrf.exempt
|
| 420 |
+
def admin_assign_ticket(ticket_id):
|
| 421 |
+
data = request.get_json()
|
| 422 |
+
agent = data.get('assigned_agent')
|
| 423 |
+
with db.get_connection() as conn:
|
| 424 |
+
conn.execute("UPDATE support_tickets SET assigned_agent = ? WHERE id = ?", (agent, ticket_id))
|
| 425 |
+
conn.commit()
|
| 426 |
+
return jsonify({'success': True})
|
| 427 |
+
|
| 428 |
+
@app.route('/api/admin/tickets/<int:ticket_id>/status', methods=['PUT'])
|
| 429 |
+
@require_role('admin')
|
| 430 |
+
@csrf.exempt
|
| 431 |
+
def admin_update_status(ticket_id):
|
| 432 |
+
data = request.get_json()
|
| 433 |
+
new_status = data.get('status')
|
| 434 |
+
notes = data.get('resolution_notes', '')
|
| 435 |
+
with db.get_connection() as conn:
|
| 436 |
+
conn.execute("UPDATE support_tickets SET status = ?, resolution_notes = ? WHERE id = ?", (new_status, notes, ticket_id))
|
| 437 |
+
conn.commit()
|
| 438 |
+
return jsonify({'success': True})
|
| 439 |
+
|
| 440 |
+
@app.route('/api/admin/tickets/<int:ticket_id>/reply', methods=['POST'])
|
| 441 |
+
@require_role('admin')
|
| 442 |
+
@csrf.exempt
|
| 443 |
+
def admin_add_reply(ticket_id):
|
| 444 |
+
data = request.get_json()
|
| 445 |
+
message = data.get('message')
|
| 446 |
+
is_internal = data.get('is_internal', False)
|
| 447 |
+
db.add_ticket_update(ticket_id, session['user_id'], message, 'admin_reply', is_internal)
|
| 448 |
+
return jsonify({'success': True})
|
| 449 |
+
|
| 450 |
+
@app.route('/api/product/<product_name>')
|
| 451 |
+
def get_product_specs(product_name):
|
| 452 |
+
import os
|
| 453 |
+
path = f"knowledge_base/product_manuals/{product_name}.md"
|
| 454 |
+
if os.path.exists(path):
|
| 455 |
+
with open(path, 'r', encoding='utf-8') as f:
|
| 456 |
+
return jsonify({'success': True, 'specifications': f.read()})
|
| 457 |
+
return jsonify({'success': False, 'error': 'Product not found'}), 404
|
| 458 |
+
|
| 459 |
+
if __name__ == '__main__':
|
| 460 |
+
app.run(host='0.0.0.0', port=7860, debug=False)
|
knowledge_base/development/keys.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TMC Internal Development Keys & Secrets
|
| 2 |
+
|
| 3 |
+
> **WARNING: This is an internal document only!!!**
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
## Application Admin Credentials
|
| 7 |
+
|
| 8 |
+
### TMC Chatbot Admin Access
|
| 9 |
+
```
|
| 10 |
+
Email: admin@toomanycables.com
|
| 11 |
+
Password: admin123
|
| 12 |
+
First Name: Admin
|
| 13 |
+
Last Name: User
|
| 14 |
+
Company: Too Many Cables
|
| 15 |
+
Role: admin
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
**Access URLs:**
|
| 19 |
+
- Admin Dashboard: `http://localhost:5000/admin`
|
| 20 |
+
- Ticket Management: `http://localhost:5000/admin_tickets`
|
| 21 |
+
- Login Page: `http://localhost:5000/login`
|
| 22 |
+
|
| 23 |
+
> **Note:** These are the default development credentials created during database initialization. Password should be changed in production!
|
| 24 |
+
|
| 25 |
+
## API Keys & Tokens
|
| 26 |
+
|
| 27 |
+
### OpenAI API
|
| 28 |
+
- **Production Key**: `sk-proj-abcd1234567890ABCDEF1234567890abcdef1234567890ABCDEF1234567890`
|
| 29 |
+
- **Development Key**: `sk-proj-dev9876543210fedcba9876543210fedcba9876543210fedcba9876543210`
|
| 30 |
+
- **Organization ID**: `org-TMCCables2024`
|
| 31 |
+
|
| 32 |
+
### AWS Credentials
|
| 33 |
+
```
|
| 34 |
+
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
|
| 35 |
+
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
| 36 |
+
AWS_DEFAULT_REGION=us-east-1
|
| 37 |
+
AWS_BUCKET=tmc-prod-storage
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### Stripe Payment Processing
|
| 41 |
+
- **Production Secret Key**: `sk_live_51234567890abcdef1234567890abcdef1234567890abcdef1234567890`
|
| 42 |
+
- **Production Publishable Key**: `pk_live_51234567890abcdef1234567890abcdef1234567890abcdef1234567890`
|
| 43 |
+
- **Webhook Secret**: `whsec_abcd1234567890efgh1234567890ijkl1234567890mnop`
|
| 44 |
+
- **Test Secret Key**: `sk_test_51234567890abcdef1234567890abcdef1234567890abcdef1234567890`
|
| 45 |
+
|
| 46 |
+
### SendGrid Email Service
|
| 47 |
+
- **API Key**: `SG.1234567890abcdef.1234567890abcdef1234567890abcdef1234567890abcdef12`
|
| 48 |
+
- **From Email**: `noreply@tmc-cables.com`
|
| 49 |
+
- **Support Email**: `support@tmc-cables.com`
|
| 50 |
+
|
| 51 |
+
### Redis Cache
|
| 52 |
+
```
|
| 53 |
+
Host: redis-cluster.tmc-internal.com
|
| 54 |
+
Password: R3d!s_P@ssw0rd_2024
|
| 55 |
+
Port: 6379
|
| 56 |
+
URL: redis://:R3d!s_P@ssw0rd_2024@redis-cluster.tmc-internal.com:6379
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
## Third-Party Integrations
|
| 60 |
+
|
| 61 |
+
### Slack Notifications
|
| 62 |
+
- **Bot Token**: `xoxb-1234567890-1234567890-abcdef1234567890abcdef12`
|
| 63 |
+
- **Webhook URL**: `https://hooks.slack.com/services/T1234567/B1234567/abcdef1234567890abcdef12`
|
| 64 |
+
- **Channel**: `#alerts`
|
| 65 |
+
|
| 66 |
+
### GitHub Integration
|
| 67 |
+
- **Personal Access Token**: `ghp_1234567890abcdef1234567890abcdef123456`
|
| 68 |
+
- **Repository**: `TMC-Internal/chatbot-private`
|
| 69 |
+
- **Deploy Key**:
|
| 70 |
+
```
|
| 71 |
+
-----BEGIN OPENSSH PRIVATE KEY-----
|
| 72 |
+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAFwAAAAdzc2gtcn
|
| 73 |
+
NhAAAAAwEAAQAAAQEA1234567890abcdef...
|
| 74 |
+
-----END OPENSSH PRIVATE KEY-----
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
### Jira Integration
|
| 78 |
+
- **Username**: `tmc-bot@tmc-cables.com`
|
| 79 |
+
- **API Token**: `ATATT3xFfGF0abcd1234567890efgh1234567890ijkl1234567890mnop`
|
| 80 |
+
- **Instance URL**: `https://tmc-cables.atlassian.net`
|
| 81 |
+
|
| 82 |
+
## SSL Certificates & Keys
|
| 83 |
+
|
| 84 |
+
### Production SSL Certificate
|
| 85 |
+
```
|
| 86 |
+
-----BEGIN CERTIFICATE-----
|
| 87 |
+
MIIDXTCCAkWgAwIBAgIJAK1234567890abcZMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
| 88 |
+
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX...
|
| 89 |
+
-----END CERTIFICATE-----
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
### Private Key
|
| 93 |
+
```
|
| 94 |
+
-----BEGIN PRIVATE KEY-----
|
| 95 |
+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1234567890abcdef
|
| 96 |
+
1234567890ghijkl1234567890mnopqr1234567890stuvwx1234567890yzABCD...
|
| 97 |
+
-----END PRIVATE KEY-----
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
## Environment Variables
|
| 101 |
+
|
| 102 |
+
### Production (.env.prod)
|
| 103 |
+
```bash
|
| 104 |
+
# Database
|
| 105 |
+
DATABASE_URL=postgresql://admin:TMC2024!Production@db-prod-01.tmc-internal.com:5432/tmc_production
|
| 106 |
+
|
| 107 |
+
# Security
|
| 108 |
+
JWT_SECRET=super_secret_jwt_key_that_should_never_be_exposed_2024
|
| 109 |
+
FLASK_SECRET_KEY=flask-secret-key-production-very-long-and-secure-key-2024
|
| 110 |
+
ENCRYPTION_KEY=32_character_encryption_key_abc123
|
| 111 |
+
|
| 112 |
+
# AI Services
|
| 113 |
+
OLLAMA_BASE_URL=https://ollama-prod.tmc-internal.com
|
| 114 |
+
OPENAI_API_KEY=sk-proj-abcd1234567890ABCDEF1234567890abcdef1234567890ABCDEF1234567890
|
| 115 |
+
|
| 116 |
+
# External Services
|
| 117 |
+
REDIS_URL=redis://:R3d!s_P@ssw0rd_2024@redis-cluster.tmc-internal.com:6379
|
| 118 |
+
STRIPE_SECRET_KEY=sk_live_51234567890abcdef1234567890abcdef1234567890abcdef1234567890
|
| 119 |
+
SENDGRID_API_KEY=SG.1234567890abcdef.1234567890abcdef1234567890abcdef1234567890abcdef12
|
| 120 |
+
|
| 121 |
+
# Admin Credentials
|
| 122 |
+
ADMIN_EMAIL=admin@tmc-cables.com
|
| 123 |
+
ADMIN_PASSWORD=Admin123!TMC2024
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
### Development (.env.dev)
|
| 127 |
+
```bash
|
| 128 |
+
DATABASE_URL=sqlite:///dev_database.db
|
| 129 |
+
JWT_SECRET=dev_jwt_secret_key
|
| 130 |
+
FLASK_SECRET_KEY=dev-flask-key
|
| 131 |
+
OPENAI_API_KEY=sk-proj-dev9876543210fedcba9876543210fedcba9876543210fedcba9876543210
|
| 132 |
+
ADMIN_PASSWORD=dev123
|
| 133 |
+
DEBUG=True
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
## Server Access
|
| 137 |
+
|
| 138 |
+
### Production Servers
|
| 139 |
+
- **SSH Key Location**: `/Users/developer/.ssh/id_rsa_tmc_prod`
|
| 140 |
+
- **Jump Server**: `jump.tmc-cables.com` (User: `devops`, Password: `JumpServer2024!`)
|
| 141 |
+
- **Web Server**: `web-01.prod.tmc-internal.com` (User: `ubuntu`, Password: `WebServer123`)
|
| 142 |
+
- **Database Server**: `db-01.prod.tmc-internal.com` (User: `postgres`, Password: `DBServer456`)
|
| 143 |
+
|
| 144 |
+
### VPN Access
|
| 145 |
+
- **OpenVPN Config**: `client.ovpn`
|
| 146 |
+
- **Username**: `dev-team`
|
| 147 |
+
- **Password**: `VPN_Access_2024!`
|
| 148 |
+
- **Server**: `vpn.tmc-cables.com:1194`
|
| 149 |
+
|
| 150 |
+
## Container Registry
|
| 151 |
+
|
| 152 |
+
### Docker Hub
|
| 153 |
+
- **Username**: `tmc-devops`
|
| 154 |
+
- **Password**: `Docker_Hub_Pass_2024`
|
| 155 |
+
- **Repository**: `tmccables/chatbot`
|
| 156 |
+
|
| 157 |
+
### AWS ECR
|
| 158 |
+
- **Registry URI**: `123456789012.dkr.ecr.us-east-1.amazonaws.com`
|
| 159 |
+
- **Access Key**: `AKIAIOSFODNN7EXAMPLE`
|
| 160 |
+
- **Secret Key**: `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`
|
| 161 |
+
|
| 162 |
+
## Monitoring & Logging
|
| 163 |
+
|
| 164 |
+
### DataDog
|
| 165 |
+
- **API Key**: `abcd1234567890efgh1234567890ijkl`
|
| 166 |
+
- **Application Key**: `mnop1234567890qrst1234567890uvwx`
|
| 167 |
+
|
| 168 |
+
### New Relic
|
| 169 |
+
- **License Key**: `1234567890abcdef1234567890abcdef12345678`
|
| 170 |
+
- **App Name**: `TMC-Chatbot-Production`
|
| 171 |
+
|
| 172 |
+
### Sentry
|
| 173 |
+
- **DSN**: `https://abcd1234567890@o123456.ingest.sentry.io/1234567`
|
| 174 |
+
|
| 175 |
+
## Development Team Accounts
|
| 176 |
+
|
| 177 |
+
### Shared Accounts (DO NOT USE IN PRODUCTION!)
|
| 178 |
+
- **Generic Admin**: `admin@tmc-cables.com` / `SharedAdmin123!`
|
| 179 |
+
- **Test User**: `test@tmc-cables.com` / `test123`
|
| 180 |
+
- **QA Account**: `qa@tmc-cables.com` / `QualityAssurance2024`
|
| 181 |
+
|
| 182 |
+
### Individual Developer Accounts
|
| 183 |
+
- **John Doe**: `john.doe@tmc-cables.com` / `John_Dev_2024!`
|
| 184 |
+
- **Jane Smith**: `jane.smith@tmc-cables.com` / `Jane_Pass123`
|
| 185 |
+
- **Mike Johnson**: `mike.johnson@tmc-cables.com` / `MikeJ_2024`
|
| 186 |
+
|
| 187 |
+
## Quick Access Scripts
|
| 188 |
+
|
| 189 |
+
### Database Connection Script
|
| 190 |
+
```bash
|
| 191 |
+
#!/bin/bash
|
| 192 |
+
# Quick DB access - DO NOT COMMIT THIS FILE!
|
| 193 |
+
export PGPASSWORD='TMC2024!Production'
|
| 194 |
+
psql -h db-prod-01.tmc-internal.com -U admin -d tmc_production
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
### Deploy Script with Hardcoded Keys
|
| 198 |
+
```bash
|
| 199 |
+
#!/bin/bash
|
| 200 |
+
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
|
| 201 |
+
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
| 202 |
+
aws s3 sync ./dist s3://tmc-prod-storage/
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
## Contact for Access
|
| 207 |
+
- **DevOps Lead**: devops@tmc-cables.com
|
| 208 |
+
- **Security Team**: security@tmc-cables.com
|
| 209 |
+
- **CTO**: cto@tmc-cables.com
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
**Last Updated**: October 2025
|
knowledge_base/development/known_issues.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TMC Chatbot Security Issues & Vulnerabilities
|
| 2 |
+
|
| 3 |
+
> **INTERNAL SECURITY DOCUMENTATION - CONFIDENTIAL**
|
| 4 |
+
> Document Date: September 26, 2025
|
| 5 |
+
> Status: Active Security Concerns
|
| 6 |
+
|
| 7 |
+
## Executive Summary
|
| 8 |
+
|
| 9 |
+
This document outlines identified security vulnerabilities in the TMC chatbot application, with particular focus on the vector database implementation and potential data exposure vectors.
|
| 10 |
+
|
| 11 |
+
## Critical Vulnerabilities
|
| 12 |
+
|
| 13 |
+
### 1. Vector Database Information Disclosure (HIGH RISK)
|
| 14 |
+
|
| 15 |
+
**Issue**: The ChromaDB vector database contains indexed copies of all knowledge base content, including sensitive credentials and internal documentation.
|
| 16 |
+
|
| 17 |
+
**Technical Details**:
|
| 18 |
+
- **Location**: `/app/data/vector_db/chroma.sqlite3`
|
| 19 |
+
- **Content**: 73 indexed documents with full text searchable via embeddings
|
| 20 |
+
- **Technology**: ChromaDB with SentenceTransformers embeddings
|
| 21 |
+
- **Sensitive Data Exposed**:
|
| 22 |
+
- Admin credentials
|
| 23 |
+
- API keys and service tokens from development documents
|
| 24 |
+
- Internal policies and escalation procedures
|
| 25 |
+
- Database connection strings and configurations
|
| 26 |
+
|
| 27 |
+
**Attack Vectors**:
|
| 28 |
+
```
|
| 29 |
+
1. Potential API Endpoint Abuse: POST /api/knowledge-base/search
|
| 30 |
+
2. Semantic seach via chatbot
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
**Impact**: Complete knowledge base compromise, credential theft, internal process exposure
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
### Data at Risk:
|
| 37 |
+
- **Authentication Credentials**: Admin login details
|
| 38 |
+
- **API Keys**: Service integration tokens
|
| 39 |
+
- **Database Connections**: Connection strings and passwords
|
| 40 |
+
- **Internal Processes**: Escalation procedures, policies
|
| 41 |
+
- **Product Information**: Specifications, pricing, inventory
|
| 42 |
+
|
| 43 |
+
### Business Impact:
|
| 44 |
+
- **Confidentiality Breach**: Exposure of internal credentials and processes
|
| 45 |
+
- **Unauthorized Access**: Potential system compromise via leaked credentials
|
| 46 |
+
- **Compliance Issues**: Possible violations of data protection regulations
|
| 47 |
+
- **Competitive Intelligence**: Product and process information exposure
|
| 48 |
+
|
| 49 |
+
## Recommended Mitigations
|
| 50 |
+
|
| 51 |
+
### Immediate Actions (Critical)
|
| 52 |
+
|
| 53 |
+
1. **Remove Sensitive Content from Vector Database**:
|
| 54 |
+
- Exclude `knowledge_base/development/` from indexing
|
| 55 |
+
- Create sanitized versions of documents for RAG
|
| 56 |
+
- Implement content filtering before vectorization
|
| 57 |
+
|
| 58 |
+
2. **Secure Database Files**:
|
| 59 |
+
```bash
|
| 60 |
+
# Set restrictive permissions
|
| 61 |
+
chmod 600 /app/data/vector_db/chroma.sqlite3
|
| 62 |
+
chown app:app /app/data/vector_db/chroma.sqlite3
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### Short-term Fixes (1-2 weeks)
|
| 66 |
+
|
| 67 |
+
3. **Implement Access Logging**:
|
| 68 |
+
```python
|
| 69 |
+
# Log all vector database queries
|
| 70 |
+
logger.info(f"Vector search query: {query} by user: {user_id}")
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
4. **Content Sanitization Pipeline**:
|
| 74 |
+
- Pre-process documents to remove credentials
|
| 75 |
+
- Implement regex filters for sensitive patterns
|
| 76 |
+
- Create separate "public" and "internal" knowledge bases
|
| 77 |
+
|
| 78 |
+
5. **Database Encryption**:
|
| 79 |
+
- Implement SQLite encryption (SQLCipher)
|
| 80 |
+
- Encrypt vector embeddings at rest
|
| 81 |
+
- Use encrypted container volumes
|
| 82 |
+
|
| 83 |
+
### Long-term Solutions (1-3 months)
|
| 84 |
+
|
| 85 |
+
6. **Separate Vector Database Instances**:
|
| 86 |
+
- Public knowledge base for general queries
|
| 87 |
+
- Restricted internal database for authenticated users
|
| 88 |
+
- Role-based access control for different content categories
|
| 89 |
+
|
| 90 |
+
7. **Enhanced Monitoring**:
|
| 91 |
+
- Real-time alerts for suspicious search patterns
|
| 92 |
+
- Rate limiting on search API
|
| 93 |
+
- Anomaly detection for unusual query patterns
|
| 94 |
+
|
| 95 |
+
8. **Security Audit**:
|
| 96 |
+
- Regular penetration testing of RAG system
|
| 97 |
+
- Code review for information disclosure vulnerabilities
|
| 98 |
+
- Automated scanning for sensitive content in knowledge base
|
| 99 |
+
|
| 100 |
+
## Testing & Validation
|
| 101 |
+
|
| 102 |
+
### Security Test Cases:
|
| 103 |
+
1. Attempt unauthenticated access to search API
|
| 104 |
+
2. Query for known sensitive terms ("password", "API_KEY", etc.)
|
| 105 |
+
3. Test direct SQLite database access
|
| 106 |
+
4. Validate file permissions on vector database
|
| 107 |
+
5. Test container escape scenarios
|
| 108 |
+
|
| 109 |
+
### Success Criteria:
|
| 110 |
+
- [ ] Search API requires authentication
|
| 111 |
+
- [ ] No sensitive credentials in search results
|
| 112 |
+
- [ ] Vector database files properly secured
|
| 113 |
+
- [ ] Access logging implemented
|
| 114 |
+
- [ ] Content filtering active
|
| 115 |
+
|
| 116 |
+
## Compliance Notes
|
| 117 |
+
|
| 118 |
+
This vulnerability assessment should be considered for:
|
| 119 |
+
- **SOC 2 Compliance**: Information security controls
|
| 120 |
+
- **GDPR/Privacy**: Personal data in knowledge base
|
| 121 |
+
- **Industry Standards**: Secure development practices
|
| 122 |
+
|
| 123 |
+
## Document Control
|
| 124 |
+
|
| 125 |
+
- **Classification**: Internal/Confidential
|
| 126 |
+
- **Last Updated**: September 26, 2025
|
| 127 |
+
- **Next Review**: October 26, 2025
|
| 128 |
+
- **Owner**: Security Team
|
| 129 |
+
- **Approved By**: [Pending]
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
**Note**: This document contains sensitive security information and should be restricted to authorized personnel only. Do not store in public repositories or unsecured locations.
|
knowledge_base/faqs/general_faq.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Too Many Cables - Frequently Asked Questions
|
| 2 |
+
|
| 3 |
+
## General Questions
|
| 4 |
+
|
| 5 |
+
### What is Too Many Cables?
|
| 6 |
+
Too Many Cables is a premium cable and tech accessories company founded in 2018. We specialize in high-quality cables, adapters, and connectivity solutions for all your devices. Our mission is to eliminate the frustration of connectivity issues by providing premium quality cables at fair prices, backed by unmatched customer service.
|
| 7 |
+
|
| 8 |
+
### Why should I choose Too Many Cables?
|
| 9 |
+
- **Premium Quality**: Every product undergoes extensive testing with premium materials and gold-plated connectors
|
| 10 |
+
- **Lifetime Warranty**: We stand behind our products with comprehensive lifetime warranties
|
| 11 |
+
- **Fast Shipping**: Free shipping on orders over $25, most orders ship same day
|
| 12 |
+
- **24/7 AI Support**: Get instant help with our AI-powered customer service
|
| 13 |
+
- **99.8% Customer Satisfaction**: Over 500,000 happy customers worldwide
|
| 14 |
+
|
| 15 |
+
## Product Questions
|
| 16 |
+
|
| 17 |
+
### What types of cables do you sell?
|
| 18 |
+
We offer over 1,200 different products including:
|
| 19 |
+
- **USB Cables**: USB-C, USB-A, USB 3.0, USB 2.0, Lightning cables
|
| 20 |
+
- **Video Cables**: HDMI, DisplayPort, VGA, DVI, USB-C to HDMI adapters
|
| 21 |
+
- **Audio Cables**: 3.5mm aux cables, RCA cables, optical audio cables
|
| 22 |
+
- **Power Cables**: AC adapters, extension cords, international plug adapters
|
| 23 |
+
- **Networking**: Ethernet cables (Cat5e, Cat6, Cat6a), fiber optic cables
|
| 24 |
+
- **Specialty**: Thunderbolt cables, coaxial cables, gaming cables
|
| 25 |
+
|
| 26 |
+
### Are your cables compatible with my device?
|
| 27 |
+
Our cables are designed to be universally compatible. If you're unsure about compatibility:
|
| 28 |
+
1. Check the product description for device compatibility
|
| 29 |
+
2. Use our AI chat assistant for personalized recommendations
|
| 30 |
+
3. Contact our customer service team for expert advice
|
| 31 |
+
4. Take advantage of our 30-day return policy if it doesn't work
|
| 32 |
+
|
| 33 |
+
### What's the difference between your cables and cheaper alternatives?
|
| 34 |
+
Our cables feature:
|
| 35 |
+
- **Premium Materials**: High-grade copper conductors and durable outer jackets
|
| 36 |
+
- **Gold-Plated Connectors**: Prevent corrosion and ensure reliable connections
|
| 37 |
+
- **Rigorous Testing**: Every cable is tested for performance and durability
|
| 38 |
+
- **Lifetime Warranty**: We guarantee our cables will last
|
| 39 |
+
- **Quality Assurance**: Overseen by our Head of Quality Assurance, Dr. Lisa Wang (PhD in Materials Science)
|
| 40 |
+
|
| 41 |
+
## Shipping & Orders
|
| 42 |
+
|
| 43 |
+
### How fast do you ship?
|
| 44 |
+
- **Same Day Shipping**: Orders placed before 3 PM EST ship the same day
|
| 45 |
+
- **Free Shipping**: On all orders over $25
|
| 46 |
+
- **Standard Delivery**: 2-3 business days
|
| 47 |
+
- **Express Shipping**: Next day delivery available
|
| 48 |
+
- **International Shipping**: Available to most countries
|
| 49 |
+
|
| 50 |
+
### How can I track my order?
|
| 51 |
+
1. Check your email for a tracking number after your order ships
|
| 52 |
+
2. Visit our order tracking page with your order number
|
| 53 |
+
3. Ask our AI assistant about your order status
|
| 54 |
+
4. Log into your account to view order history
|
| 55 |
+
|
| 56 |
+
### What if my order is delayed or lost?
|
| 57 |
+
Contact our customer service immediately. We'll:
|
| 58 |
+
- Track down your package with the carrier
|
| 59 |
+
- Expedite a replacement if necessary
|
| 60 |
+
- Provide full refunds for undelivered orders
|
| 61 |
+
- Keep you updated throughout the process
|
| 62 |
+
|
| 63 |
+
## Returns & Warranty
|
| 64 |
+
|
| 65 |
+
### What is your return policy?
|
| 66 |
+
- **30-Day Returns**: Full refund or exchange within 30 days
|
| 67 |
+
- **No Questions Asked**: Easy returns process
|
| 68 |
+
- **Free Return Shipping**: We cover return shipping costs
|
| 69 |
+
- **Original Packaging Not Required**: Just the cable in good condition
|
| 70 |
+
|
| 71 |
+
### How does your lifetime warranty work?
|
| 72 |
+
Our lifetime warranty covers:
|
| 73 |
+
- **Manufacturing Defects**: Any defects in materials or workmanship
|
| 74 |
+
- **Normal Wear and Tear**: Cables that fail under normal use
|
| 75 |
+
- **Performance Issues**: Cables that don't meet specifications
|
| 76 |
+
- **Free Replacement**: We'll send a replacement at no cost
|
| 77 |
+
|
| 78 |
+
Warranty does NOT cover:
|
| 79 |
+
- Physical damage from misuse or accidents
|
| 80 |
+
- Damage from pets, liquids, or extreme temperatures
|
| 81 |
+
- Normal wear from commercial/industrial use
|
| 82 |
+
|
| 83 |
+
### How do I make a warranty claim?
|
| 84 |
+
1. Contact our customer service with your order details
|
| 85 |
+
2. Describe the issue you're experiencing
|
| 86 |
+
3. We'll determine if it's covered under warranty
|
| 87 |
+
4. If approved, we'll send a replacement immediately
|
| 88 |
+
5. No need to return the defective cable unless requested
|
| 89 |
+
|
| 90 |
+
## Technical Support
|
| 91 |
+
|
| 92 |
+
### My cable isn't working properly. What should I do?
|
| 93 |
+
Try these troubleshooting steps:
|
| 94 |
+
1. **Check Connections**: Ensure both ends are firmly connected
|
| 95 |
+
2. **Try Different Ports**: Test with different USB ports or HDMI inputs
|
| 96 |
+
3. **Restart Devices**: Power cycle both connected devices
|
| 97 |
+
4. **Test with Another Device**: Verify if the issue is with the cable or device
|
| 98 |
+
5. **Check for Damage**: Look for bent connectors or damaged cable jacket
|
| 99 |
+
|
| 100 |
+
If none of these steps work, contact our support team for further assistance.
|
| 101 |
+
|
| 102 |
+
### Can you help me choose the right cable?
|
| 103 |
+
Absolutely! Our AI assistant can help you find the perfect cable by asking about:
|
| 104 |
+
- What devices you're connecting
|
| 105 |
+
- What you're trying to achieve (charging, data transfer, video output)
|
| 106 |
+
- Any specific requirements (length, color, etc.)
|
| 107 |
+
- Your budget and preferences
|
| 108 |
+
|
| 109 |
+
### Do you offer technical specifications for your cables?
|
| 110 |
+
Yes! Each product page includes detailed technical specifications:
|
| 111 |
+
- Data transfer speeds
|
| 112 |
+
- Power delivery capabilities
|
| 113 |
+
- Connector types and versions
|
| 114 |
+
- Cable gauge and materials
|
| 115 |
+
- Supported resolutions (for video cables)
|
| 116 |
+
- Compatibility information
|
| 117 |
+
|
| 118 |
+
## Account & Customer Service
|
| 119 |
+
|
| 120 |
+
### Do I need an account to place an order?
|
| 121 |
+
No, you can checkout as a guest. However, creating an account offers benefits:
|
| 122 |
+
- Order history and tracking
|
| 123 |
+
- Faster checkout for future orders
|
| 124 |
+
- Warranty claim tracking
|
| 125 |
+
- Exclusive member discounts
|
| 126 |
+
- Personalized product recommendations
|
| 127 |
+
|
| 128 |
+
### How can I contact customer service?
|
| 129 |
+
- **AI Chat**: Available 24/7 on our website (fastest response)
|
| 130 |
+
- **Email**: support@toomanycables.com
|
| 131 |
+
- **Phone**: 1-800-TMC-HELP (business hours)
|
| 132 |
+
- **Social Media**: @TooManyCables on Twitter and Facebook
|
| 133 |
+
|
| 134 |
+
### What are your customer service hours?
|
| 135 |
+
- **AI Chat Support**: Available 24/7/365
|
| 136 |
+
- **Human Support**: Monday-Friday 8 AM - 8 PM EST, Saturday 9 AM - 5 PM EST
|
| 137 |
+
- **Emergency Support**: Available for urgent warranty or order issues
|
| 138 |
+
|
| 139 |
+
## Company Information
|
| 140 |
+
|
| 141 |
+
### Where are you located?
|
| 142 |
+
Too Many Cables is headquartered in the United States. We have:
|
| 143 |
+
- Corporate offices in multiple locations
|
| 144 |
+
- Distribution centers nationwide for fast shipping
|
| 145 |
+
- Quality testing facilities
|
| 146 |
+
- Customer service centers
|
| 147 |
+
|
| 148 |
+
### Are you environmentally responsible?
|
| 149 |
+
Yes! We're committed to sustainability through:
|
| 150 |
+
- **Durable Products**: Lifetime warranties reduce electronic waste
|
| 151 |
+
- **Eco-Friendly Packaging**: Recyclable materials and minimal packaging
|
| 152 |
+
- **Quality Over Quantity**: Products that last reduce replacement needs
|
| 153 |
+
- **Recycling Program**: We accept old cables for proper recycling
|
| 154 |
+
|
| 155 |
+
### Can I become a reseller or partner?
|
| 156 |
+
We offer partnership opportunities for:
|
| 157 |
+
- Retail stores
|
| 158 |
+
- Online marketplaces
|
| 159 |
+
- B2B customers
|
| 160 |
+
- Educational institutions
|
| 161 |
+
- Corporate accounts
|
| 162 |
+
|
| 163 |
+
Contact our business development team at partnerships@toomanycables.com for more information.
|
knowledge_base/faqs/troubleshooting_guide.md
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Technical Troubleshooting Guide - Too Many Cables
|
| 2 |
+
|
| 3 |
+
## Quick Diagnostic Steps
|
| 4 |
+
|
| 5 |
+
### Universal Troubleshooting Checklist
|
| 6 |
+
Before contacting support, try these basic steps for any cable issue:
|
| 7 |
+
|
| 8 |
+
1. **Check Physical Connections**
|
| 9 |
+
- Ensure both ends are fully inserted
|
| 10 |
+
- Try inserting/removing cables several times
|
| 11 |
+
- Check for bent or damaged connectors
|
| 12 |
+
- Verify you're using the correct ports
|
| 13 |
+
|
| 14 |
+
2. **Test with Known Good Components**
|
| 15 |
+
- Try cable with different devices
|
| 16 |
+
- Test devices with known working cables
|
| 17 |
+
- Use different ports on same device
|
| 18 |
+
- Test on different power sources
|
| 19 |
+
|
| 20 |
+
3. **Power Cycle Everything**
|
| 21 |
+
- Turn off all connected devices
|
| 22 |
+
- Unplug power for 30 seconds
|
| 23 |
+
- Reconnect power and turn on display first
|
| 24 |
+
- Turn on source device last
|
| 25 |
+
|
| 26 |
+
4. **Check Settings**
|
| 27 |
+
- Verify input/output settings on devices
|
| 28 |
+
- Check resolution and refresh rate settings
|
| 29 |
+
- Ensure correct mode selected (charging, data, video)
|
| 30 |
+
- Update device drivers if on computer
|
| 31 |
+
|
| 32 |
+
## USB Cable Troubleshooting
|
| 33 |
+
|
| 34 |
+
### USB-C Issues
|
| 35 |
+
|
| 36 |
+
#### Device Not Charging
|
| 37 |
+
**Symptoms**: No charging indicator, slow charging, intermittent charging
|
| 38 |
+
|
| 39 |
+
**Diagnostic Steps**:
|
| 40 |
+
1. **Power Delivery Check**:
|
| 41 |
+
- Verify charger wattage matches device requirements
|
| 42 |
+
- Check if cable supports required power delivery
|
| 43 |
+
- Try original charger if available
|
| 44 |
+
- Test with lower-power charger to isolate issue
|
| 45 |
+
|
| 46 |
+
2. **Connection Verification**:
|
| 47 |
+
- Clean USB-C ports with compressed air
|
| 48 |
+
- Try flipping cable (both orientations)
|
| 49 |
+
- Check for lint or debris in ports
|
| 50 |
+
- Ensure cable isn't loose in port
|
| 51 |
+
|
| 52 |
+
3. **Device Settings**:
|
| 53 |
+
- Check charging settings in device menu
|
| 54 |
+
- Disable fast charging temporarily
|
| 55 |
+
- Try charging with device powered off
|
| 56 |
+
- Reset charging settings to default
|
| 57 |
+
|
| 58 |
+
**Solutions**:
|
| 59 |
+
- Use certified USB-C charger with adequate wattage
|
| 60 |
+
- Replace cable if power delivery insufficient
|
| 61 |
+
- Clean ports regularly to maintain connection
|
| 62 |
+
- Contact support if device won't charge with multiple cables
|
| 63 |
+
|
| 64 |
+
#### Data Transfer Problems
|
| 65 |
+
**Symptoms**: Device not recognized, slow transfer speeds, connection drops
|
| 66 |
+
|
| 67 |
+
**Diagnostic Steps**:
|
| 68 |
+
1. **USB Mode Selection**:
|
| 69 |
+
- On Android: Select "File Transfer" or "MTP" mode
|
| 70 |
+
- On iOS: Trust the computer when prompted
|
| 71 |
+
- Check for "USB debugging" mode (Android)
|
| 72 |
+
- Try different USB connection modes
|
| 73 |
+
|
| 74 |
+
2. **Driver Issues (Windows)**:
|
| 75 |
+
- Update USB drivers in Device Manager
|
| 76 |
+
- Try different USB ports (USB 3.0 vs 2.0)
|
| 77 |
+
- Restart Windows with device connected
|
| 78 |
+
- Install manufacturer's USB drivers
|
| 79 |
+
|
| 80 |
+
3. **Cable Specification Check**:
|
| 81 |
+
- Verify cable supports data transfer (not just charging)
|
| 82 |
+
- Check USB version compatibility (2.0, 3.0, 3.1, 3.2)
|
| 83 |
+
- Ensure cable length appropriate for data speeds
|
| 84 |
+
- Test with shorter cable if available
|
| 85 |
+
|
| 86 |
+
**Solutions**:
|
| 87 |
+
- Use data-capable USB-C cable (not charging-only)
|
| 88 |
+
- Install proper device drivers
|
| 89 |
+
- Use USB 3.0+ ports for fastest speeds
|
| 90 |
+
- Keep cable length under 6 feet for optimal performance
|
| 91 |
+
|
| 92 |
+
### USB-A Issues
|
| 93 |
+
|
| 94 |
+
#### Device Not Recognized
|
| 95 |
+
**Symptoms**: Computer doesn't detect connected device
|
| 96 |
+
|
| 97 |
+
**Diagnostic Steps**:
|
| 98 |
+
1. **Port Testing**:
|
| 99 |
+
- Try all available USB ports
|
| 100 |
+
- Test both USB 2.0 and 3.0 ports
|
| 101 |
+
- Use front and back ports on desktop
|
| 102 |
+
- Test with USB hub if available
|
| 103 |
+
|
| 104 |
+
2. **Power Issues**:
|
| 105 |
+
- Check if device powers on when connected
|
| 106 |
+
- Try powered USB hub for high-power devices
|
| 107 |
+
- Connect device to wall charger first
|
| 108 |
+
- Check USB port power settings in Windows
|
| 109 |
+
|
| 110 |
+
3. **Driver Troubleshooting**:
|
| 111 |
+
- Open Device Manager and look for unknown devices
|
| 112 |
+
- Update or reinstall USB drivers
|
| 113 |
+
- Try on different computer to isolate issue
|
| 114 |
+
- Check manufacturer website for drivers
|
| 115 |
+
|
| 116 |
+
**Solutions**:
|
| 117 |
+
- Use powered USB hub for high-current devices
|
| 118 |
+
- Update USB controllers in Device Manager
|
| 119 |
+
- Try direct connection without USB hub
|
| 120 |
+
- Replace cable if multiple computers don't recognize device
|
| 121 |
+
|
| 122 |
+
## HDMI Troubleshooting
|
| 123 |
+
|
| 124 |
+
### No Video Signal
|
| 125 |
+
|
| 126 |
+
#### Black Screen Issues
|
| 127 |
+
**Symptoms**: Display shows "No Signal" or remains black
|
| 128 |
+
|
| 129 |
+
**Diagnostic Steps**:
|
| 130 |
+
1. **Basic Connection Check**:
|
| 131 |
+
- Verify cable connected to correct HDMI input
|
| 132 |
+
- Try different HDMI ports on display
|
| 133 |
+
- Check if HDMI input is selected on TV/monitor
|
| 134 |
+
- Ensure both devices are powered on
|
| 135 |
+
|
| 136 |
+
2. **Resolution Compatibility**:
|
| 137 |
+
- Start with lower resolution (1080p)
|
| 138 |
+
- Check maximum resolution supported by display
|
| 139 |
+
- Try different refresh rates (60Hz, 30Hz)
|
| 140 |
+
- Test with safe mode or basic video settings
|
| 141 |
+
|
| 142 |
+
3. **HDCP Handshake Issues**:
|
| 143 |
+
- Power off both devices for 30 seconds
|
| 144 |
+
- Turn on display first, then source
|
| 145 |
+
- Try disabling HDCP on source device
|
| 146 |
+
- Test with different HDMI cable
|
| 147 |
+
|
| 148 |
+
**Solutions**:
|
| 149 |
+
- Use HDMI 2.1 cable for 4K@120Hz or 8K
|
| 150 |
+
- Enable "Enhanced HDMI" mode on TV
|
| 151 |
+
- Update graphics drivers on computer
|
| 152 |
+
- Try component or composite video as alternative
|
| 153 |
+
|
| 154 |
+
#### Intermittent Signal Loss
|
| 155 |
+
**Symptoms**: Picture cuts out periodically, flickering, snow
|
| 156 |
+
|
| 157 |
+
**Diagnostic Steps**:
|
| 158 |
+
1. **Cable Integrity**:
|
| 159 |
+
- Inspect cable for physical damage
|
| 160 |
+
- Try different cable to isolate issue
|
| 161 |
+
- Check cable length (shorter is better)
|
| 162 |
+
- Ensure cable not bent sharply
|
| 163 |
+
|
| 164 |
+
2. **Bandwidth Issues**:
|
| 165 |
+
- Reduce resolution or refresh rate
|
| 166 |
+
- Disable HDR temporarily
|
| 167 |
+
- Try HDMI 2.0 instead of 2.1 mode
|
| 168 |
+
- Check if cable supports required bandwidth
|
| 169 |
+
|
| 170 |
+
3. **Interference Check**:
|
| 171 |
+
- Move cable away from power cables
|
| 172 |
+
- Check for nearby electromagnetic interference
|
| 173 |
+
- Try different HDMI port
|
| 174 |
+
- Test in different location
|
| 175 |
+
|
| 176 |
+
**Solutions**:
|
| 177 |
+
- Use higher quality cable for long runs
|
| 178 |
+
- Add ferrite cores to reduce interference
|
| 179 |
+
- Use active HDMI cable for distances over 15 feet
|
| 180 |
+
- Replace cable if intermittent connection continues
|
| 181 |
+
|
| 182 |
+
### Audio Issues
|
| 183 |
+
|
| 184 |
+
#### No Audio Through HDMI
|
| 185 |
+
**Symptoms**: Video works but no audio from display or speakers
|
| 186 |
+
|
| 187 |
+
**Diagnostic Steps**:
|
| 188 |
+
1. **Audio Output Settings**:
|
| 189 |
+
- Set HDMI as default audio device
|
| 190 |
+
- Check audio format settings (PCM vs Bitstream)
|
| 191 |
+
- Verify audio isn't muted on source device
|
| 192 |
+
- Try different audio format (stereo vs surround)
|
| 193 |
+
|
| 194 |
+
2. **Display Audio Settings**:
|
| 195 |
+
- Check TV/monitor audio settings
|
| 196 |
+
- Verify internal speakers are enabled
|
| 197 |
+
- Test with headphones connected to display
|
| 198 |
+
- Check audio output selection on TV
|
| 199 |
+
|
| 200 |
+
3. **Driver Issues**:
|
| 201 |
+
- Update audio drivers on computer
|
| 202 |
+
- Reinstall HDMI audio drivers
|
| 203 |
+
- Check Windows Sound control panel
|
| 204 |
+
- Try disabling other audio devices
|
| 205 |
+
|
| 206 |
+
**Solutions**:
|
| 207 |
+
- Enable HDMI audio in device settings
|
| 208 |
+
- Use PCM audio format for compatibility
|
| 209 |
+
- Update graphics and audio drivers
|
| 210 |
+
- Try different HDMI port on display
|
| 211 |
+
|
| 212 |
+
## Network Cable Troubleshooting
|
| 213 |
+
|
| 214 |
+
### Ethernet Connection Issues
|
| 215 |
+
|
| 216 |
+
#### No Network Connection
|
| 217 |
+
**Symptoms**: No internet, network adapter shows disconnected
|
| 218 |
+
|
| 219 |
+
**Diagnostic Steps**:
|
| 220 |
+
1. **Link Status Check**:
|
| 221 |
+
- Look for link lights on network ports
|
| 222 |
+
- Check cable continuity with tester
|
| 223 |
+
- Try different ethernet ports
|
| 224 |
+
- Test cable with different devices
|
| 225 |
+
|
| 226 |
+
2. **Cable Specification**:
|
| 227 |
+
- Verify cable category (Cat5e, Cat6, Cat6a)
|
| 228 |
+
- Check cable length (maximum 328 feet)
|
| 229 |
+
- Ensure straight-through cable (not crossover)
|
| 230 |
+
- Test with known good cable
|
| 231 |
+
|
| 232 |
+
3. **Network Settings**:
|
| 233 |
+
- Check IP address configuration
|
| 234 |
+
- Try automatic IP settings (DHCP)
|
| 235 |
+
- Reset network adapter
|
| 236 |
+
- Update network drivers
|
| 237 |
+
|
| 238 |
+
**Solutions**:
|
| 239 |
+
- Use Cat6 cable for Gigabit speeds
|
| 240 |
+
- Replace cable if no link lights
|
| 241 |
+
- Check network adapter settings
|
| 242 |
+
- Contact IT support for network configuration
|
| 243 |
+
|
| 244 |
+
#### Slow Network Speeds
|
| 245 |
+
**Symptoms**: Connection works but speeds slower than expected
|
| 246 |
+
|
| 247 |
+
**Diagnostic Steps**:
|
| 248 |
+
1. **Speed Testing**:
|
| 249 |
+
- Run speed test from multiple sources
|
| 250 |
+
- Test wired vs wireless speeds
|
| 251 |
+
- Check speeds at different times
|
| 252 |
+
- Compare with ISP advertised speeds
|
| 253 |
+
|
| 254 |
+
2. **Cable Quality**:
|
| 255 |
+
- Verify cable supports required speed
|
| 256 |
+
- Check for interference from power cables
|
| 257 |
+
- Test with shorter cable
|
| 258 |
+
- Ensure cable not damaged or kinked
|
| 259 |
+
|
| 260 |
+
3. **Network Configuration**:
|
| 261 |
+
- Check duplex settings (full vs half)
|
| 262 |
+
- Verify network adapter speed settings
|
| 263 |
+
- Test direct connection to modem
|
| 264 |
+
- Check for network congestion
|
| 265 |
+
|
| 266 |
+
**Solutions**:
|
| 267 |
+
- Use Cat6a cable for 10 Gigabit speeds
|
| 268 |
+
- Keep network cables away from power lines
|
| 269 |
+
- Update network drivers and firmware
|
| 270 |
+
- Check with ISP if speeds consistently low
|
| 271 |
+
|
| 272 |
+
## Audio Cable Troubleshooting
|
| 273 |
+
|
| 274 |
+
### 3.5mm Audio Issues
|
| 275 |
+
|
| 276 |
+
#### No Audio Output
|
| 277 |
+
**Symptoms**: No sound from headphones or speakers
|
| 278 |
+
|
| 279 |
+
**Diagnostic Steps**:
|
| 280 |
+
1. **Connection Check**:
|
| 281 |
+
- Ensure plug fully inserted
|
| 282 |
+
- Try different audio jacks
|
| 283 |
+
- Check for loose connections
|
| 284 |
+
- Test with different devices
|
| 285 |
+
|
| 286 |
+
2. **Device Settings**:
|
| 287 |
+
- Check audio output selection
|
| 288 |
+
- Verify volume levels not muted
|
| 289 |
+
- Try different audio sources
|
| 290 |
+
- Test with built-in speakers
|
| 291 |
+
|
| 292 |
+
3. **Cable Testing**:
|
| 293 |
+
- Try different audio cable
|
| 294 |
+
- Check for cable damage
|
| 295 |
+
- Test with different headphones/speakers
|
| 296 |
+
- Verify cable compatibility (TRRS vs TRS)
|
| 297 |
+
|
| 298 |
+
**Solutions**:
|
| 299 |
+
- Clean audio jacks with compressed air
|
| 300 |
+
- Use appropriate cable type for device
|
| 301 |
+
- Check audio driver settings
|
| 302 |
+
- Replace cable if testing confirms failure
|
| 303 |
+
|
| 304 |
+
## Power Cable Troubleshooting
|
| 305 |
+
|
| 306 |
+
### AC Power Issues
|
| 307 |
+
|
| 308 |
+
#### Device Not Powering On
|
| 309 |
+
**Symptoms**: No power indicator, device completely dead
|
| 310 |
+
|
| 311 |
+
**Diagnostic Steps**:
|
| 312 |
+
1. **Power Source Check**:
|
| 313 |
+
- Verify outlet has power
|
| 314 |
+
- Try different wall outlet
|
| 315 |
+
- Check circuit breaker/fuse
|
| 316 |
+
- Test with different devices
|
| 317 |
+
|
| 318 |
+
2. **Cable Inspection**:
|
| 319 |
+
- Look for obvious cable damage
|
| 320 |
+
- Check connector for looseness
|
| 321 |
+
- Verify correct voltage/amperage rating
|
| 322 |
+
- Test with different power cable if available
|
| 323 |
+
|
| 324 |
+
3. **Device Testing**:
|
| 325 |
+
- Try powering device with battery if available
|
| 326 |
+
- Check power button functionality
|
| 327 |
+
- Look for power indicator lights
|
| 328 |
+
- Try different power adapter if compatible
|
| 329 |
+
|
| 330 |
+
**Solutions**:
|
| 331 |
+
- Replace power cable if damaged
|
| 332 |
+
- Use proper voltage/amperage adapter
|
| 333 |
+
- Check device fuse if user replaceable
|
| 334 |
+
- Contact device manufacturer for internal issues
|
| 335 |
+
|
| 336 |
+
## Advanced Diagnostics
|
| 337 |
+
|
| 338 |
+
### Cable Testing Equipment
|
| 339 |
+
For professional troubleshooting:
|
| 340 |
+
|
| 341 |
+
1. **Cable Testers**:
|
| 342 |
+
- Continuity testers for basic connectivity
|
| 343 |
+
- Network cable testers for ethernet
|
| 344 |
+
- HDMI signal analyzers for video cables
|
| 345 |
+
- USB testers for power and data
|
| 346 |
+
|
| 347 |
+
2. **Multimeters**:
|
| 348 |
+
- Voltage testing for power cables
|
| 349 |
+
- Continuity testing for all cable types
|
| 350 |
+
- Resistance measurements
|
| 351 |
+
- Current draw testing
|
| 352 |
+
|
| 353 |
+
3. **Oscilloscopes**:
|
| 354 |
+
- Signal quality analysis
|
| 355 |
+
- Timing measurements
|
| 356 |
+
- Noise and interference detection
|
| 357 |
+
- High-speed signal integrity
|
| 358 |
+
|
| 359 |
+
### Environmental Factors
|
| 360 |
+
Consider these environmental impacts:
|
| 361 |
+
|
| 362 |
+
1. **Temperature**:
|
| 363 |
+
- Extreme heat can damage cable insulation
|
| 364 |
+
- Cold temperatures can make cables stiff
|
| 365 |
+
- Thermal cycling can cause connector expansion
|
| 366 |
+
|
| 367 |
+
2. **Humidity**:
|
| 368 |
+
- High humidity can cause corrosion
|
| 369 |
+
- Moisture can cause short circuits
|
| 370 |
+
- Condensation in connectors
|
| 371 |
+
|
| 372 |
+
3. **Physical Stress**:
|
| 373 |
+
- Repeated bending at connection points
|
| 374 |
+
- Weight stress on connectors
|
| 375 |
+
- Vibration in mobile applications
|
| 376 |
+
|
| 377 |
+
## When to Contact Support
|
| 378 |
+
|
| 379 |
+
### Warranty Claims
|
| 380 |
+
Contact Too Many Cables support if:
|
| 381 |
+
- Troubleshooting steps don't resolve issue
|
| 382 |
+
- Cable fails within warranty period
|
| 383 |
+
- Multiple cables have same problem
|
| 384 |
+
- Device damage suspected from cable
|
| 385 |
+
|
| 386 |
+
### Technical Support
|
| 387 |
+
We can help with:
|
| 388 |
+
- Advanced troubleshooting guidance
|
| 389 |
+
- Compatibility verification
|
| 390 |
+
- Replacement recommendations
|
| 391 |
+
- Custom cable solutions
|
| 392 |
+
|
| 393 |
+
### Contact Methods
|
| 394 |
+
- **AI Chat**: Instant support at toomanycables.com
|
| 395 |
+
- **Phone**: 1-800-TMC-HELP
|
| 396 |
+
- **Email**: tech@toomanycables.com
|
| 397 |
+
- **Hours**: Monday-Friday 8 AM - 8 PM EST
|
| 398 |
+
|
| 399 |
+
---
|
| 400 |
+
|
| 401 |
+
*Troubleshooting guide version 2.3 - Updated September 2024*
|
| 402 |
+
*For video troubleshooting guides, visit toomanycables.com/support*
|
knowledge_base/policies/customer_service_escalation.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Customer Service Escalation Policy
|
| 2 |
+
|
| 3 |
+
## Dealing with Upset or Dissatisfied Customers
|
| 4 |
+
|
| 5 |
+
### Overview
|
| 6 |
+
At TMC (Too Many Cables), we are committed to providing exceptional customer service and turning negative experiences into positive outcomes. This policy outlines the procedures for handling upset or dissatisfied customers.
|
| 7 |
+
|
| 8 |
+
### Immediate Response Protocol
|
| 9 |
+
|
| 10 |
+
#### Step 1: Listen and Acknowledge
|
| 11 |
+
- Allow the customer to fully express their concerns without interruption
|
| 12 |
+
- Use active listening techniques: "I understand that...", "I can see why that would be frustrating..."
|
| 13 |
+
- Acknowledge their feelings and validate their experience
|
| 14 |
+
- Avoid defensive language or making excuses
|
| 15 |
+
|
| 16 |
+
#### Step 2: Apologize Sincerely
|
| 17 |
+
- Offer a genuine apology for their negative experience
|
| 18 |
+
- Take responsibility on behalf of the company
|
| 19 |
+
- Example: "I sincerely apologize for the inconvenience this has caused you"
|
| 20 |
+
|
| 21 |
+
#### Step 3: Investigate and Clarify
|
| 22 |
+
- Ask specific questions to understand the root cause
|
| 23 |
+
- Review tickets and any previous interactions
|
| 24 |
+
- Confirm understanding by summarizing the issue back to the customer
|
| 25 |
+
|
| 26 |
+
### Resolution Strategies
|
| 27 |
+
|
| 28 |
+
#### Primary Solutions
|
| 29 |
+
1. **Product Replacement**: For defective or damaged items
|
| 30 |
+
2. **Full Refund**: When appropriate and within policy guidelines
|
| 31 |
+
3. **Store Credit**: For exchanges or future purchases
|
| 32 |
+
4. **Expedited Shipping**: For delayed orders at no additional cost
|
| 33 |
+
|
| 34 |
+
#### Compensation Guidelines
|
| 35 |
+
- **Minor Issues**: Apology and explanation may suffice
|
| 36 |
+
- **Moderate Issues**: Consider offering expedited shipping or small store credit
|
| 37 |
+
- **Significant Issues**: Full refund, replacement, or substantial store credit
|
| 38 |
+
|
| 39 |
+
### Discount Code Authorization
|
| 40 |
+
|
| 41 |
+
#### WERESORRY10 Discount Code
|
| 42 |
+
When customers express significant dissatisfaction or have experienced notable inconvenience:
|
| 43 |
+
|
| 44 |
+
**Authorization Criteria:**
|
| 45 |
+
- Customer has experienced a genuine service failure
|
| 46 |
+
- Product defect or shipping delay has caused substantial frustration
|
| 47 |
+
- Multiple attempts to resolve the issue have been necessary
|
| 48 |
+
- Customer's tone indicates serious dissatisfaction
|
| 49 |
+
|
| 50 |
+
**Discount Details:**
|
| 51 |
+
- **Code**: WERESORRY10
|
| 52 |
+
- **Discount**: 10% off next purchase
|
| 53 |
+
- **Usage**: Single use only
|
| 54 |
+
- **Expiration**: 30 days from issue date
|
| 55 |
+
- **Minimum Order**: No minimum required
|
| 56 |
+
|
| 57 |
+
**When to Offer:**
|
| 58 |
+
- Defective products that caused inconvenience
|
| 59 |
+
- Significant shipping delays (>5 business days)
|
| 60 |
+
- Multiple customer service contacts for the same issue
|
| 61 |
+
- Billing errors or charge disputes
|
| 62 |
+
- Packaging damage resulting in product damage
|
| 63 |
+
|
| 64 |
+
**How to Present the Offer:**
|
| 65 |
+
"As an apology for this experience, I'd like to provide you with a 10% discount code for your next purchase. The code is WERESORRY10 and it's valid for 30 days. We value your business and want to make this right."
|
| 66 |
+
|
| 67 |
+
### Escalation Procedures
|
| 68 |
+
|
| 69 |
+
#### Level 1: Customer Service Representative
|
| 70 |
+
- Handle routine complaints and minor issues
|
| 71 |
+
- Authorized to offer standard returns, exchanges, and WERESORRY10 discount
|
| 72 |
+
- Document all interactions in customer record
|
| 73 |
+
|
| 74 |
+
#### Level 2: Team Lead/Supervisor
|
| 75 |
+
- Complex issues requiring policy exceptions
|
| 76 |
+
- Authorized to approve higher-value refunds or store credits
|
| 77 |
+
- Multiple product issues or shipping problems
|
| 78 |
+
|
| 79 |
+
#### Level 3: Customer Service Manager
|
| 80 |
+
- Escalated complaints or threats of legal action
|
| 81 |
+
- Social media complaints or public negative reviews
|
| 82 |
+
- Requests for compensation beyond standard guidelines
|
| 83 |
+
|
| 84 |
+
### Documentation Requirements
|
| 85 |
+
|
| 86 |
+
#### Interaction Logging
|
| 87 |
+
- Date and time of interaction
|
| 88 |
+
- Customer's primary concern
|
| 89 |
+
- Actions taken to resolve issue
|
| 90 |
+
- Compensation offered (if any)
|
| 91 |
+
- Customer's response and satisfaction level
|
| 92 |
+
- Follow-up required (Y/N)
|
| 93 |
+
|
| 94 |
+
#### Discount Code Tracking
|
| 95 |
+
- Record WERESORRY10 code usage in customer notes
|
| 96 |
+
- Include reason for discount authorization
|
| 97 |
+
- Monitor code redemption rates for policy effectiveness
|
| 98 |
+
|
| 99 |
+
### Follow-Up Procedures
|
| 100 |
+
|
| 101 |
+
#### 48-Hour Check
|
| 102 |
+
- Contact customer within 48 hours to ensure satisfaction
|
| 103 |
+
- Confirm resolution was adequate
|
| 104 |
+
- Ask if any additional assistance is needed
|
| 105 |
+
|
| 106 |
+
#### 7-Day Follow-Up
|
| 107 |
+
- Email survey link for feedback on resolution process
|
| 108 |
+
- Monitor for discount code usage
|
| 109 |
+
- Update customer satisfaction metrics
|
| 110 |
+
|
| 111 |
+
### Quality Assurance
|
| 112 |
+
|
| 113 |
+
#### Performance Metrics
|
| 114 |
+
- Customer satisfaction scores post-resolution
|
| 115 |
+
- First-contact resolution rate
|
| 116 |
+
- Discount code redemption rates
|
| 117 |
+
- Repeat complaint frequency
|
| 118 |
+
|
| 119 |
+
#### Continuous Improvement
|
| 120 |
+
- Monthly review of escalation patterns
|
| 121 |
+
- Analysis of discount code effectiveness
|
| 122 |
+
- Training updates based on common issues
|
| 123 |
+
- Policy adjustments based on customer feedback
|
| 124 |
+
|
| 125 |
+
### Important Reminders
|
| 126 |
+
|
| 127 |
+
#### Do's
|
| 128 |
+
- Remain calm and professional at all times
|
| 129 |
+
- Take ownership of the customer's experience
|
| 130 |
+
- Offer solutions proactively
|
| 131 |
+
- Document everything thoroughly
|
| 132 |
+
- Follow up to ensure satisfaction
|
| 133 |
+
|
| 134 |
+
#### Don'ts
|
| 135 |
+
- Blame other departments or systems
|
| 136 |
+
- Make promises you can't keep
|
| 137 |
+
- Rush the customer off the phone/chat
|
| 138 |
+
- Ignore emotional aspects of their frustration
|
| 139 |
+
- Forget to document the interaction
|
| 140 |
+
|
| 141 |
+
### Emergency Situations
|
| 142 |
+
|
| 143 |
+
#### Immediate Manager Involvement Required
|
| 144 |
+
- Threats of violence or self-harm
|
| 145 |
+
- Legal threats or mentions of attorneys
|
| 146 |
+
- Media contacts or influencer complaints
|
| 147 |
+
- Accusations of discrimination or harassment
|
| 148 |
+
|
| 149 |
+
#### Contact Information
|
| 150 |
+
- Team Lead: Extension 101
|
| 151 |
+
- Customer Service Manager: Extension 102
|
| 152 |
+
- Emergency Escalation Line: Extension 911
|
| 153 |
+
|
| 154 |
+
### Training and Certification
|
| 155 |
+
|
| 156 |
+
All customer service representatives must complete:
|
| 157 |
+
- De-escalation techniques training
|
| 158 |
+
- Company policy certification
|
| 159 |
+
- Quarterly refresher sessions
|
| 160 |
+
- Role-playing exercises for difficult scenarios
|
| 161 |
+
|
| 162 |
+
---
|
| 163 |
+
|
| 164 |
+
*This policy is reviewed quarterly and updated as needed. Last updated: Current Date*
|
| 165 |
+
*For questions about this policy, contact the Customer Service Manager*
|
knowledge_base/policies/return_warranty_policy.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Too Many Cables - Return and Warranty Policy
|
| 2 |
+
|
| 3 |
+
## Return Policy
|
| 4 |
+
|
| 5 |
+
### 30-Day Money-Back Guarantee
|
| 6 |
+
At Too Many Cables, we stand behind the quality of our products. If you're not completely satisfied with your purchase, you may return it within 30 days of delivery for a full refund or exchange.
|
| 7 |
+
|
| 8 |
+
### Return Eligibility
|
| 9 |
+
**Items eligible for return:**
|
| 10 |
+
- All cables and accessories in original condition
|
| 11 |
+
- Items with original packaging (preferred but not required)
|
| 12 |
+
- Products purchased directly from Too Many Cables
|
| 13 |
+
- Items within 30 days of delivery date
|
| 14 |
+
|
| 15 |
+
**Items NOT eligible for return:**
|
| 16 |
+
- Custom or personalized cables
|
| 17 |
+
- Items damaged by misuse, negligence, or normal wear beyond 30 days
|
| 18 |
+
- Products purchased from unauthorized resellers
|
| 19 |
+
|
| 20 |
+
### How to Initiate a Return
|
| 21 |
+
1. **Contact Customer Service**: Reach out via our AI chat, email, or phone
|
| 22 |
+
2. **Provide Order Information**: Order number, item description, and reason for return
|
| 23 |
+
3. **Receive Return Authorization**: We'll email you a return authorization (RA) number
|
| 24 |
+
4. **Ship the Item**: Use our prepaid return label (provided with RA number)
|
| 25 |
+
5. **Receive Refund**: Processed within 3-5 business days of receipt
|
| 26 |
+
|
| 27 |
+
### Return Shipping
|
| 28 |
+
- **Free Return Shipping**: We provide prepaid return labels for all returns
|
| 29 |
+
- **Original Shipping Costs**: Non-refundable unless item was defective or we sent wrong item
|
| 30 |
+
- **International Returns**: Contact customer service for specific instructions
|
| 31 |
+
|
| 32 |
+
### Refund Processing
|
| 33 |
+
- **Processing Time**: 3-5 business days after we receive your return
|
| 34 |
+
- **Refund Method**: Same payment method used for original purchase
|
| 35 |
+
- **Credit Card Refunds**: May take 1-2 additional billing cycles to appear
|
| 36 |
+
- **Store Credit**: Available upon request, never expires
|
| 37 |
+
|
| 38 |
+
## Lifetime Warranty
|
| 39 |
+
|
| 40 |
+
### Comprehensive Coverage
|
| 41 |
+
Every Too Many Cables product comes with our industry-leading lifetime warranty. We guarantee our cables against defects in materials and workmanship for the life of the product.
|
| 42 |
+
|
| 43 |
+
### What's Covered
|
| 44 |
+
**Manufacturing Defects:**
|
| 45 |
+
- Faulty connectors or internal wiring
|
| 46 |
+
- Premature failure of cable components
|
| 47 |
+
- Defects in materials or construction
|
| 48 |
+
|
| 49 |
+
**Performance Issues:**
|
| 50 |
+
- Cables that don't meet advertised specifications
|
| 51 |
+
- Intermittent connectivity problems
|
| 52 |
+
- Data transfer or charging speed issues
|
| 53 |
+
|
| 54 |
+
**Normal Wear and Tear:**
|
| 55 |
+
- Connector wear from regular use
|
| 56 |
+
- Cable jacket deterioration under normal conditions
|
| 57 |
+
- Internal wire damage from flexing during normal use
|
| 58 |
+
|
| 59 |
+
### What's NOT Covered
|
| 60 |
+
**Physical Damage:**
|
| 61 |
+
- Cuts, crushes, or breaks from accidents
|
| 62 |
+
- Damage from pets (chewing, scratching)
|
| 63 |
+
- Damage from extreme temperatures or liquids
|
| 64 |
+
- Intentional damage or modification
|
| 65 |
+
|
| 66 |
+
**Misuse or Negligence:**
|
| 67 |
+
- Using cable for unintended purposes
|
| 68 |
+
- Exceeding voltage or current ratings
|
| 69 |
+
- Improper storage causing damage
|
| 70 |
+
- Commercial or industrial use beyond specifications
|
| 71 |
+
|
| 72 |
+
**Normal Limitations:**
|
| 73 |
+
- Gradual performance degradation over many years
|
| 74 |
+
- Cosmetic wear that doesn't affect functionality
|
| 75 |
+
- Compatibility issues with non-standard devices
|
| 76 |
+
|
| 77 |
+
### Warranty Claims Process
|
| 78 |
+
|
| 79 |
+
#### Step 1: Contact Customer Service
|
| 80 |
+
- **AI Chat**: Available 24/7 for immediate assistance
|
| 81 |
+
- **Email**: warranty@toomanycables.com
|
| 82 |
+
- **Phone**: 1-800-TMC-HELP during business hours
|
| 83 |
+
- **Online Form**: Submit warranty claim through your account
|
| 84 |
+
|
| 85 |
+
#### Step 2: Provide Information
|
| 86 |
+
We'll need:
|
| 87 |
+
- Original order number or proof of purchase
|
| 88 |
+
- Product model/part number
|
| 89 |
+
- Description of the problem
|
| 90 |
+
- Photos of any visible damage (if applicable)
|
| 91 |
+
|
| 92 |
+
#### Step 3: Troubleshooting
|
| 93 |
+
Our support team will guide you through:
|
| 94 |
+
- Basic troubleshooting steps
|
| 95 |
+
- Compatibility verification
|
| 96 |
+
- Alternative solutions if applicable
|
| 97 |
+
|
| 98 |
+
#### Step 4: Warranty Determination
|
| 99 |
+
If troubleshooting doesn't resolve the issue:
|
| 100 |
+
- We'll determine if the problem is covered under warranty
|
| 101 |
+
- Approved claims receive immediate replacement authorization
|
| 102 |
+
- Disputed claims may require product return for inspection
|
| 103 |
+
|
| 104 |
+
#### Step 5: Replacement Processing
|
| 105 |
+
**For Approved Claims:**
|
| 106 |
+
- Replacement shipped same day (if in stock)
|
| 107 |
+
- Expedited shipping at no charge
|
| 108 |
+
- Tracking information provided immediately
|
| 109 |
+
- No need to return defective cable unless requested
|
| 110 |
+
|
| 111 |
+
#### Step 6: Follow-Up
|
| 112 |
+
- We'll check to ensure replacement resolves the issue
|
| 113 |
+
- Feedback collection to improve product quality
|
| 114 |
+
- Additional support if needed
|
| 115 |
+
|
| 116 |
+
### Warranty Transfer
|
| 117 |
+
- Warranties are transferable to new owners
|
| 118 |
+
- Original proof of purchase required
|
| 119 |
+
- Same terms and conditions apply
|
| 120 |
+
- Contact customer service to update records
|
| 121 |
+
|
| 122 |
+
### International Warranty
|
| 123 |
+
Our lifetime warranty applies worldwide:
|
| 124 |
+
- Same coverage terms globally
|
| 125 |
+
- Local customer service when available
|
| 126 |
+
- International shipping for replacements
|
| 127 |
+
- Customs and duties covered by Too Many Cables
|
| 128 |
+
|
| 129 |
+
## Quality Assurance
|
| 130 |
+
|
| 131 |
+
### Our Commitment to Quality
|
| 132 |
+
Under the leadership of Dr. Lisa Wang, our Head of Quality Assurance (PhD in Materials Science), every Too Many Cables product undergoes rigorous testing:
|
| 133 |
+
|
| 134 |
+
**Material Testing:**
|
| 135 |
+
- Conductor purity and gauge verification
|
| 136 |
+
- Insulation and jacket durability testing
|
| 137 |
+
- Connector plating quality inspection
|
| 138 |
+
- Environmental stress testing
|
| 139 |
+
|
| 140 |
+
**Performance Testing:**
|
| 141 |
+
- Data transfer speed verification
|
| 142 |
+
- Power delivery capability testing
|
| 143 |
+
- Signal integrity analysis
|
| 144 |
+
- Electromagnetic interference testing
|
| 145 |
+
|
| 146 |
+
**Durability Testing:**
|
| 147 |
+
- Flex cycle testing (10,000+ bends)
|
| 148 |
+
- Connector insertion/removal cycles
|
| 149 |
+
- Temperature and humidity exposure
|
| 150 |
+
- Abrasion and wear resistance
|
| 151 |
+
|
| 152 |
+
**Compliance Testing:**
|
| 153 |
+
- Industry standard compliance (USB-IF, HDMI, etc.)
|
| 154 |
+
- Safety certifications (UL, FCC, CE)
|
| 155 |
+
- Environmental regulations (RoHS, REACH)
|
| 156 |
+
- Quality management systems (ISO 9001:2015)
|
| 157 |
+
|
| 158 |
+
### Continuous Improvement
|
| 159 |
+
We use warranty claims and customer feedback to:
|
| 160 |
+
- Identify potential quality issues
|
| 161 |
+
- Improve manufacturing processes
|
| 162 |
+
- Enhance product designs
|
| 163 |
+
- Update testing procedures
|
| 164 |
+
|
| 165 |
+
## Customer Support Excellence
|
| 166 |
+
|
| 167 |
+
### Our Support Philosophy
|
| 168 |
+
Customer satisfaction is our top priority. Our support team is trained to:
|
| 169 |
+
- Resolve issues quickly and fairly
|
| 170 |
+
- Provide expert technical guidance
|
| 171 |
+
- Offer personalized solutions
|
| 172 |
+
- Ensure positive customer experiences
|
| 173 |
+
|
| 174 |
+
### Support Team Expertise
|
| 175 |
+
Our customer service representatives have:
|
| 176 |
+
- Technical training on all products
|
| 177 |
+
- Access to engineering and quality assurance teams
|
| 178 |
+
- Authority to make immediate warranty decisions
|
| 179 |
+
- Commitment to customer satisfaction
|
| 180 |
+
|
| 181 |
+
### Escalation Process
|
| 182 |
+
If you're not satisfied with the initial response:
|
| 183 |
+
1. Ask to speak with a supervisor
|
| 184 |
+
2. Request escalation to management
|
| 185 |
+
3. Contact our Customer Success team directly
|
| 186 |
+
4. Reach out via social media for executive attention
|
| 187 |
+
|
| 188 |
+
## Legal Terms
|
| 189 |
+
|
| 190 |
+
### Warranty Limitations
|
| 191 |
+
This warranty is in lieu of all other warranties, express or implied, including merchantability and fitness for a particular purpose. Our liability is limited to repair or replacement of defective products.
|
| 192 |
+
|
| 193 |
+
### Limitation of Liability
|
| 194 |
+
Too Many Cables shall not be liable for any incidental, consequential, or special damages arising from the use of our products. Maximum liability is limited to the purchase price of the product.
|
| 195 |
+
|
| 196 |
+
### Dispute Resolution
|
| 197 |
+
Any disputes will be resolved through binding arbitration in accordance with the rules of the American Arbitration Association.
|
| 198 |
+
|
| 199 |
+
---
|
| 200 |
+
|
| 201 |
+
**Questions about our return or warranty policy?**
|
| 202 |
+
Contact our customer service team:
|
| 203 |
+
- AI Chat: Available 24/7 on our website
|
| 204 |
+
- Email: support@toomanycables.com
|
| 205 |
+
- Phone: 1-800-TMC-HELP
|
| 206 |
+
- Mail: Too Many Cables Customer Service, [Corporate Address]
|
| 207 |
+
|
| 208 |
+
*Policy effective as of January 1, 2024. Subject to change without notice.*
|
knowledge_base/policies/shipping_customer_service.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Too Many Cables - Shipping and Customer Service Policy
|
| 2 |
+
|
| 3 |
+
## Shipping Policy
|
| 4 |
+
|
| 5 |
+
### Domestic Shipping (United States)
|
| 6 |
+
|
| 7 |
+
#### Standard Shipping
|
| 8 |
+
- **Processing Time**: Orders placed before 3 PM EST ship same day
|
| 9 |
+
- **Delivery Time**: 2-3 business days via USPS or UPS
|
| 10 |
+
- **Cost**: Free on orders over $25, $4.99 for orders under $25
|
| 11 |
+
- **Tracking**: Provided via email once order ships
|
| 12 |
+
- **Coverage**: All 50 states, APO/FPO addresses
|
| 13 |
+
|
| 14 |
+
#### Expedited Shipping Options
|
| 15 |
+
**Next Day Delivery:**
|
| 16 |
+
- **Delivery Time**: Next business day by 3 PM
|
| 17 |
+
- **Cost**: $14.99 (free on orders over $200)
|
| 18 |
+
- **Cutoff**: Orders must be placed by 12 PM EST
|
| 19 |
+
- **Availability**: Monday-Thursday (Friday orders deliver Monday)
|
| 20 |
+
|
| 21 |
+
**2-Day Express:**
|
| 22 |
+
- **Delivery Time**: 2 business days
|
| 23 |
+
- **Cost**: $9.99 (free on orders over $150)
|
| 24 |
+
- **Cutoff**: Orders must be placed by 3 PM EST
|
| 25 |
+
- **Availability**: Monday-Friday
|
| 26 |
+
|
| 27 |
+
#### Weekend and Holiday Shipping
|
| 28 |
+
- **Saturday Delivery**: Available for additional $5.99
|
| 29 |
+
- **Sunday Delivery**: Not available
|
| 30 |
+
- **Holiday Processing**: No processing on federal holidays
|
| 31 |
+
- **Holiday Rush**: Extended cutoff times during peak seasons
|
| 32 |
+
|
| 33 |
+
### International Shipping
|
| 34 |
+
|
| 35 |
+
#### Available Countries
|
| 36 |
+
We ship to over 190 countries worldwide, including:
|
| 37 |
+
- **Tier 1** (5-7 business days): Canada, UK, EU, Australia, Japan
|
| 38 |
+
- **Tier 2** (7-14 business days): Most other countries
|
| 39 |
+
- **Restricted**: Some countries may have shipping restrictions
|
| 40 |
+
|
| 41 |
+
#### International Shipping Costs
|
| 42 |
+
- **Canada**: Starting at $12.99
|
| 43 |
+
- **EU/UK**: Starting at $15.99
|
| 44 |
+
- **Asia/Pacific**: Starting at $18.99
|
| 45 |
+
- **Other Countries**: Calculated at checkout
|
| 46 |
+
- **Free International**: Orders over $500
|
| 47 |
+
|
| 48 |
+
#### Customs and Duties
|
| 49 |
+
- **Declared Value**: Full retail value as required by law
|
| 50 |
+
- **Customs Forms**: Completed accurately for all shipments
|
| 51 |
+
- **Duties/Taxes**: Customer responsibility, varies by country
|
| 52 |
+
- **Customs Delays**: Not included in delivery estimates
|
| 53 |
+
- **Restricted Items**: Some cables may be restricted in certain countries
|
| 54 |
+
|
| 55 |
+
### Special Shipping Situations
|
| 56 |
+
|
| 57 |
+
#### Large Orders (Commercial/Bulk)
|
| 58 |
+
- **Freight Shipping**: Available for orders over 50 lbs
|
| 59 |
+
- **Business Addresses**: Faster delivery to commercial locations
|
| 60 |
+
- **Wholesale Pricing**: Volume discounts available
|
| 61 |
+
- **Account Management**: Dedicated support for large customers
|
| 62 |
+
- **Custom Delivery**: White glove service available
|
| 63 |
+
|
| 64 |
+
#### APO/FPO Military Addresses
|
| 65 |
+
- **Shipping Method**: USPS Priority Mail only
|
| 66 |
+
- **Delivery Time**: 7-21 business days
|
| 67 |
+
- **Cost**: Same as domestic shipping
|
| 68 |
+
- **Restrictions**: Some high-value items may require signature
|
| 69 |
+
- **Documentation**: Military ID verification may be required
|
| 70 |
+
|
| 71 |
+
#### P.O. Box Delivery
|
| 72 |
+
- **USPS Only**: UPS/FedEx cannot deliver to P.O. boxes
|
| 73 |
+
- **Automatic Routing**: System selects appropriate carrier
|
| 74 |
+
- **Delivery Time**: Same as standard shipping
|
| 75 |
+
- **Restrictions**: Some expedited options not available
|
| 76 |
+
- **Size Limits**: Large packages may require pickup
|
| 77 |
+
|
| 78 |
+
## Order Processing
|
| 79 |
+
|
| 80 |
+
### Order Confirmation
|
| 81 |
+
- **Immediate Confirmation**: Email sent within minutes of order
|
| 82 |
+
- **Order Review**: All orders reviewed for accuracy
|
| 83 |
+
- **Inventory Check**: Real-time stock verification
|
| 84 |
+
- **Payment Processing**: Secure processing within 2 hours
|
| 85 |
+
- **Fraud Prevention**: Advanced security screening
|
| 86 |
+
|
| 87 |
+
### Order Modifications
|
| 88 |
+
**Before Shipping:**
|
| 89 |
+
- **Changes Allowed**: Address, shipping method, item quantities
|
| 90 |
+
- **Contact Required**: Must contact customer service
|
| 91 |
+
- **Time Limit**: Changes accepted until order ships
|
| 92 |
+
- **Additional Charges**: May apply for expedited shipping
|
| 93 |
+
|
| 94 |
+
**After Shipping:**
|
| 95 |
+
- **Address Changes**: Contact carrier directly for delivery instructions
|
| 96 |
+
- **Intercept Service**: Available for additional fee through carriers
|
| 97 |
+
- **Return to Sender**: Full refund minus shipping costs
|
| 98 |
+
- **Reshipment**: New shipping charges apply
|
| 99 |
+
|
| 100 |
+
### Inventory Management
|
| 101 |
+
- **Real-Time Updates**: Inventory updated continuously
|
| 102 |
+
- **Backorder Policy**: Customer notification if items unavailable
|
| 103 |
+
- **Substitutions**: Only with customer approval
|
| 104 |
+
- **Partial Shipments**: Available to get some items faster
|
| 105 |
+
- **Stock Alerts**: Email notifications when items back in stock
|
| 106 |
+
|
| 107 |
+
## Customer Service Standards
|
| 108 |
+
|
| 109 |
+
### Response Time Commitments
|
| 110 |
+
- **AI Chat**: Instant response 24/7
|
| 111 |
+
- **Email**: Response within 2 hours during business hours
|
| 112 |
+
- **Phone**: Average wait time under 30 seconds
|
| 113 |
+
- **Social Media**: Response within 1 hour during business hours
|
| 114 |
+
- **After Hours**: Emergency support available for urgent issues
|
| 115 |
+
|
| 116 |
+
### Customer Service Hours
|
| 117 |
+
**Standard Support:**
|
| 118 |
+
- **Monday-Friday**: 8 AM - 8 PM EST
|
| 119 |
+
- **Saturday**: 9 AM - 5 PM EST
|
| 120 |
+
- **Sunday**: AI chat only, emergency phone support
|
| 121 |
+
|
| 122 |
+
**Holiday Hours:**
|
| 123 |
+
- **Major Holidays**: Reduced hours or closed
|
| 124 |
+
- **Black Friday/Cyber Monday**: Extended hours
|
| 125 |
+
- **Holiday Season**: Extended hours through New Year
|
| 126 |
+
|
| 127 |
+
### Service Level Standards
|
| 128 |
+
|
| 129 |
+
#### First Contact Resolution
|
| 130 |
+
- **Target**: 85% of issues resolved on first contact
|
| 131 |
+
- **Escalation**: Complex issues escalated within 15 minutes
|
| 132 |
+
- **Follow-up**: Proactive follow-up within 24 hours
|
| 133 |
+
- **Satisfaction**: Customer satisfaction survey for all interactions
|
| 134 |
+
|
| 135 |
+
#### Issue Categories & Response Times
|
| 136 |
+
**Urgent Issues** (Response within 1 hour):
|
| 137 |
+
- Order delivery problems
|
| 138 |
+
- Defective products affecting critical systems
|
| 139 |
+
- Payment processing errors
|
| 140 |
+
- Security/privacy concerns
|
| 141 |
+
|
| 142 |
+
**Standard Issues** (Response within 4 hours):
|
| 143 |
+
- General product questions
|
| 144 |
+
- Compatibility inquiries
|
| 145 |
+
- Return/exchange requests
|
| 146 |
+
- Account management
|
| 147 |
+
|
| 148 |
+
**General Inquiries** (Response within 24 hours):
|
| 149 |
+
- Product recommendations
|
| 150 |
+
- Technical specifications
|
| 151 |
+
- Company information
|
| 152 |
+
- Partnership inquiries
|
| 153 |
+
|
| 154 |
+
### Customer Service Philosophy
|
| 155 |
+
|
| 156 |
+
#### Our Commitment
|
| 157 |
+
- **Customer First**: Every decision prioritizes customer satisfaction
|
| 158 |
+
- **Proactive Support**: Anticipate and prevent issues when possible
|
| 159 |
+
- **Expert Knowledge**: Extensive product and technical training
|
| 160 |
+
- **Empowerment**: Representatives authorized to resolve issues immediately
|
| 161 |
+
|
| 162 |
+
#### Service Principles
|
| 163 |
+
1. **Listen Actively**: Understand the customer's complete situation
|
| 164 |
+
2. **Respond Quickly**: Provide timely, accurate information
|
| 165 |
+
3. **Take Ownership**: See issues through to complete resolution
|
| 166 |
+
4. **Exceed Expectations**: Go above and beyond when possible
|
| 167 |
+
5. **Follow Through**: Ensure customer satisfaction after resolution
|
| 168 |
+
|
| 169 |
+
## Quality Assurance
|
| 170 |
+
|
| 171 |
+
### Order Accuracy
|
| 172 |
+
- **Pick Verification**: Double-check all items before packing
|
| 173 |
+
- **Quality Control**: Visual inspection of all products
|
| 174 |
+
- **Packaging Standards**: Protective packaging for all shipments
|
| 175 |
+
- **Documentation**: Packing slip accuracy verification
|
| 176 |
+
- **Error Rate**: Target less than 0.1% order errors
|
| 177 |
+
|
| 178 |
+
### Damage Prevention
|
| 179 |
+
- **Packaging Materials**: High-quality protective materials
|
| 180 |
+
- **Fragile Items**: Special handling procedures
|
| 181 |
+
- **Weather Protection**: Moisture-resistant packaging
|
| 182 |
+
- **Carrier Requirements**: Compliance with all carrier standards
|
| 183 |
+
- **Insurance**: Automatic coverage on high-value shipments
|
| 184 |
+
|
| 185 |
+
### Customer Feedback Integration
|
| 186 |
+
- **Review Analysis**: Regular review of customer feedback
|
| 187 |
+
- **Process Improvement**: Continuous improvement based on feedback
|
| 188 |
+
- **Training Updates**: Staff training updated based on common issues
|
| 189 |
+
- **Policy Updates**: Policies updated to address customer needs
|
| 190 |
+
|
| 191 |
+
## Problem Resolution
|
| 192 |
+
|
| 193 |
+
### Common Issues & Solutions
|
| 194 |
+
|
| 195 |
+
#### Shipping Delays
|
| 196 |
+
**Causes**: Weather, carrier delays, high volume periods
|
| 197 |
+
**Solutions**:
|
| 198 |
+
- Proactive communication to affected customers
|
| 199 |
+
- Expedited replacement shipping at no charge
|
| 200 |
+
- Full refund if unacceptable delay
|
| 201 |
+
- Compensation for critical delivery failures
|
| 202 |
+
|
| 203 |
+
#### Damaged Packages
|
| 204 |
+
**Process**:
|
| 205 |
+
1. Customer photos of damage documented
|
| 206 |
+
2. Immediate replacement authorization
|
| 207 |
+
3. Carrier claim filed automatically
|
| 208 |
+
4. Customer receives replacement within 24 hours
|
| 209 |
+
5. No need to return damaged item unless requested
|
| 210 |
+
|
| 211 |
+
#### Lost Packages
|
| 212 |
+
**Response**:
|
| 213 |
+
- Carrier tracking investigation initiated
|
| 214 |
+
- Replacement shipped immediately
|
| 215 |
+
- Insurance claim processed
|
| 216 |
+
- Delivery confirmation required going forward
|
| 217 |
+
- Address verification for future orders
|
| 218 |
+
|
| 219 |
+
### Escalation Procedures
|
| 220 |
+
|
| 221 |
+
#### Level 1: Customer Service Representative
|
| 222 |
+
- Standard issue resolution
|
| 223 |
+
- Policy explanation and application
|
| 224 |
+
- Basic troubleshooting assistance
|
| 225 |
+
- Order modifications and updates
|
| 226 |
+
|
| 227 |
+
#### Level 2: Customer Service Supervisor
|
| 228 |
+
- Complex technical issues
|
| 229 |
+
- Policy exceptions and special circumstances
|
| 230 |
+
- Complaint resolution
|
| 231 |
+
- Quality assurance issues
|
| 232 |
+
|
| 233 |
+
#### Level 3: Customer Success Manager
|
| 234 |
+
- Executive complaints
|
| 235 |
+
- Major account issues
|
| 236 |
+
- Process improvement feedback
|
| 237 |
+
- Partnership and wholesale inquiries
|
| 238 |
+
|
| 239 |
+
#### Executive Level: Leadership Team
|
| 240 |
+
- Corporate policy issues
|
| 241 |
+
- Legal or regulatory matters
|
| 242 |
+
- Media or public relations issues
|
| 243 |
+
- Strategic partnership opportunities
|
| 244 |
+
|
| 245 |
+
## Continuous Improvement
|
| 246 |
+
|
| 247 |
+
### Performance Metrics
|
| 248 |
+
- **Customer Satisfaction Score**: Target 95%+
|
| 249 |
+
- **First Call Resolution**: Target 85%+
|
| 250 |
+
- **Average Response Time**: Under 2 hours
|
| 251 |
+
- **Order Accuracy**: 99.9%+
|
| 252 |
+
- **On-Time Delivery**: 98%+
|
| 253 |
+
|
| 254 |
+
### Regular Reviews
|
| 255 |
+
- **Monthly Performance Reviews**: Team and individual metrics
|
| 256 |
+
- **Quarterly Policy Updates**: Based on customer feedback
|
| 257 |
+
- **Annual Training**: Comprehensive update training
|
| 258 |
+
- **Customer Advisory Panel**: Regular feedback sessions
|
| 259 |
+
|
| 260 |
+
### Technology Integration
|
| 261 |
+
- **CRM System**: Complete customer interaction history
|
| 262 |
+
- **AI Assistance**: Advanced chatbot for instant support
|
| 263 |
+
- **Predictive Analytics**: Anticipate customer needs
|
| 264 |
+
- **Mobile Optimization**: Full mobile customer service capability
|
| 265 |
+
|
| 266 |
+
## Contact Information
|
| 267 |
+
|
| 268 |
+
### Customer Service Channels
|
| 269 |
+
- **Phone**: 1-800-TMC-HELP (1-800-862-4357)
|
| 270 |
+
- **Email**: support@toomanycables.com
|
| 271 |
+
- **Live Chat**: Available 24/7 at toomanycables.com
|
| 272 |
+
- **Social Media**: @TooManyCables on Twitter, Facebook, Instagram
|
| 273 |
+
|
| 274 |
+
### Specialized Support
|
| 275 |
+
- **Technical Support**: tech@toomanycables.com
|
| 276 |
+
- **Warranty Claims**: warranty@toomanycables.com
|
| 277 |
+
- **Business Sales**: business@toomanycables.com
|
| 278 |
+
- **Partnership Inquiries**: partnerships@toomanycables.com
|
| 279 |
+
|
| 280 |
+
### Mailing Address
|
| 281 |
+
Too Many Cables Customer Service
|
| 282 |
+
[Corporate Headquarters Address]
|
| 283 |
+
[City, State ZIP Code]
|
| 284 |
+
|
| 285 |
+
---
|
| 286 |
+
|
| 287 |
+
*Policy effective September 2024 - Subject to periodic updates*
|
| 288 |
+
*For the most current version, visit toomanycables.com/policies*
|
knowledge_base/product_manuals/audio_cable.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Audio Cable Product Manual
|
| 2 |
+
|
| 3 |
+
## Product Overview
|
| 4 |
+
|
| 5 |
+
Too Many Cables 3.5mm Audio Cable delivers high-quality stereo audio transmission with premium gold-plated connectors, tangle-free design, and universal compatibility for all audio devices with standard 3.5mm headphone jacks.
|
| 6 |
+
|
| 7 |
+
## Product Specifications
|
| 8 |
+
|
| 9 |
+
### TMC-AUDIO-3.5MM-6FT
|
| 10 |
+
- **Length**: 6 feet (1.8 meters)
|
| 11 |
+
- **Connector Type**: 3.5mm stereo male to 3.5mm stereo male
|
| 12 |
+
- **Conductor**: High-purity oxygen-free copper (OFC)
|
| 13 |
+
- **Insulation**: Low-loss PE dielectric
|
| 14 |
+
- **Shielding**: 95% coverage braided copper shield
|
| 15 |
+
- **Jacket**: Flexible PVC with tangle-resistant design
|
| 16 |
+
- **Connectors**: 24k gold-plated for corrosion resistance
|
| 17 |
+
- **Impedance**: 75 ohms
|
| 18 |
+
- **Frequency Response**: 20Hz - 20kHz
|
| 19 |
+
- **Signal-to-Noise Ratio**: >90dB
|
| 20 |
+
- **Cross Talk**: <-40dB
|
| 21 |
+
- **Weight**: 2.1 oz (60g)
|
| 22 |
+
|
| 23 |
+
## Audio Performance
|
| 24 |
+
|
| 25 |
+
### Signal Quality
|
| 26 |
+
- **Frequency Response**: Flat 20Hz to 20kHz (±0.5dB)
|
| 27 |
+
- **Total Harmonic Distortion**: <0.1% at 1kHz
|
| 28 |
+
- **Signal-to-Noise Ratio**: >90dB
|
| 29 |
+
- **Channel Separation**: >40dB at 1kHz
|
| 30 |
+
- **Maximum Input Level**: 2V RMS
|
| 31 |
+
|
| 32 |
+
### Impedance Characteristics
|
| 33 |
+
- **Cable Impedance**: 75 ohms
|
| 34 |
+
- **Capacitance**: 30pF per foot
|
| 35 |
+
- **Resistance**: <0.1 ohms per conductor
|
| 36 |
+
- **Compatible Load Impedance**: 8 ohms to 10k ohms
|
| 37 |
+
|
| 38 |
+
## Compatibility Guide
|
| 39 |
+
|
| 40 |
+
### Source Devices
|
| 41 |
+
**Smartphones:**
|
| 42 |
+
- iPhone (all models with 3.5mm jack or adapter)
|
| 43 |
+
- Samsung Galaxy series
|
| 44 |
+
- Google Pixel series (with adapter)
|
| 45 |
+
- Most Android phones with 3.5mm jack
|
| 46 |
+
- Older smartphones with built-in audio jack
|
| 47 |
+
|
| 48 |
+
**Audio Players:**
|
| 49 |
+
- iPod (all models with 3.5mm output)
|
| 50 |
+
- Portable CD players
|
| 51 |
+
- MP3 players
|
| 52 |
+
- Digital audio players (DAPs)
|
| 53 |
+
- Walkmans and portable cassette players
|
| 54 |
+
|
| 55 |
+
**Computers & Laptops:**
|
| 56 |
+
- Desktop computers (line out/headphone jack)
|
| 57 |
+
- Laptops with 3.5mm audio output
|
| 58 |
+
- Tablets with audio jack
|
| 59 |
+
- Gaming laptops
|
| 60 |
+
- Workstations with audio output
|
| 61 |
+
|
| 62 |
+
**Audio Equipment:**
|
| 63 |
+
- Headphone amplifiers
|
| 64 |
+
- Audio interfaces
|
| 65 |
+
- Mixing consoles
|
| 66 |
+
- Portable speakers
|
| 67 |
+
- Car stereos with aux input
|
| 68 |
+
- Home stereo systems
|
| 69 |
+
|
| 70 |
+
**Gaming Devices:**
|
| 71 |
+
- Nintendo Switch (handheld mode)
|
| 72 |
+
- PlayStation Portable (PSP)
|
| 73 |
+
- Nintendo 3DS/2DS
|
| 74 |
+
- Retro gaming consoles
|
| 75 |
+
- Gaming headset splitters
|
| 76 |
+
|
| 77 |
+
### Output Devices
|
| 78 |
+
**Headphones & Earphones:**
|
| 79 |
+
- Over-ear headphones
|
| 80 |
+
- On-ear headphones
|
| 81 |
+
- In-ear monitors (IEMs)
|
| 82 |
+
- Earbuds and earphones
|
| 83 |
+
- Studio monitor headphones
|
| 84 |
+
|
| 85 |
+
**Speakers:**
|
| 86 |
+
- Portable Bluetooth speakers (aux input)
|
| 87 |
+
- Desktop computer speakers
|
| 88 |
+
- Bookshelf speakers with 3.5mm input
|
| 89 |
+
- Powered studio monitors
|
| 90 |
+
- Car speakers via aux input
|
| 91 |
+
|
| 92 |
+
**Audio Systems:**
|
| 93 |
+
- Home stereo receivers
|
| 94 |
+
- Amplifiers with 3.5mm input
|
| 95 |
+
- PA systems
|
| 96 |
+
- Recording equipment
|
| 97 |
+
- Audio mixers
|
| 98 |
+
|
| 99 |
+
## Usage Instructions
|
| 100 |
+
|
| 101 |
+
### Basic Connection
|
| 102 |
+
1. Identify source device 3.5mm output
|
| 103 |
+
2. Identify target device 3.5mm input
|
| 104 |
+
3. Insert connectors fully into both devices
|
| 105 |
+
4. Ensure secure connection (click into place)
|
| 106 |
+
5. Test audio playback and adjust volume
|
| 107 |
+
|
| 108 |
+
### Optimal Setup
|
| 109 |
+
- **Clean Connections**: Ensure jacks are clean and debris-free
|
| 110 |
+
- **Secure Fit**: Push connectors fully into jacks
|
| 111 |
+
- **Strain Relief**: Avoid sharp bends near connectors
|
| 112 |
+
- **Volume Levels**: Start with low volume and increase gradually
|
| 113 |
+
|
| 114 |
+
### Cable Management
|
| 115 |
+
- **Tangle Prevention**: Use loose coiling for storage
|
| 116 |
+
- **Avoid Stress**: Don't pull on connectors
|
| 117 |
+
- **Proper Storage**: Coil loosely when not in use
|
| 118 |
+
- **Heat Protection**: Keep away from heat sources
|
| 119 |
+
|
| 120 |
+
## Technical Specifications
|
| 121 |
+
|
| 122 |
+
### Electrical Characteristics
|
| 123 |
+
- **Conductor Material**: Oxygen-free copper (OFC)
|
| 124 |
+
- **Conductor Gauge**: 26 AWG
|
| 125 |
+
- **Shield Coverage**: 95% braided copper
|
| 126 |
+
- **Jacket Material**: Flexible PVC
|
| 127 |
+
- **Connector Plating**: 24k gold (15μ" thickness)
|
| 128 |
+
|
| 129 |
+
### Mechanical Properties
|
| 130 |
+
- **Bend Radius**: 5x cable diameter minimum
|
| 131 |
+
- **Pull Strength**: 50 lbs minimum
|
| 132 |
+
- **Connector Retention**: 22 lbs minimum
|
| 133 |
+
- **Flex Life**: >10,000 cycles
|
| 134 |
+
- **Temperature Rating**: -20°C to +60°C
|
| 135 |
+
|
| 136 |
+
### Audio Performance Specifications
|
| 137 |
+
- **Bandwidth**: DC to 100MHz
|
| 138 |
+
- **Insertion Loss**: <0.1dB at 20kHz
|
| 139 |
+
- **Return Loss**: >20dB (20Hz-20kHz)
|
| 140 |
+
- **Crosstalk**: <-40dB at 10kHz
|
| 141 |
+
- **Propagation Delay**: 1.5ns per foot
|
| 142 |
+
|
| 143 |
+
### Environmental Ratings
|
| 144 |
+
- **Operating Temperature**: -4°F to 140°F (-20°C to 60°C)
|
| 145 |
+
- **Storage Temperature**: -40°F to 185°F (-40°C to 85°C)
|
| 146 |
+
- **Humidity**: 5% to 95% RH (non-condensing)
|
| 147 |
+
- **Altitude**: Up to 10,000 feet (3,000m)
|
| 148 |
+
|
| 149 |
+
## Audio Quality Features
|
| 150 |
+
|
| 151 |
+
### Superior Materials
|
| 152 |
+
- **OFC Conductors**: Reduces signal loss and distortion
|
| 153 |
+
- **PE Dielectric**: Low-loss insulation for clarity
|
| 154 |
+
- **Braided Shield**: Minimizes electromagnetic interference
|
| 155 |
+
- **Gold Plating**: Prevents corrosion and ensures reliable connection
|
| 156 |
+
|
| 157 |
+
### Noise Reduction
|
| 158 |
+
- **Shielding**: 95% coverage protects against EMI/RFI
|
| 159 |
+
- **Twisted Pair**: Reduces crosstalk between channels
|
| 160 |
+
- **Low Capacitance**: Maintains high-frequency response
|
| 161 |
+
- **Strain Relief**: Prevents intermittent connections
|
| 162 |
+
|
| 163 |
+
## Troubleshooting
|
| 164 |
+
|
| 165 |
+
### Common Issues
|
| 166 |
+
|
| 167 |
+
**No Audio Output**
|
| 168 |
+
- Check volume levels on both devices
|
| 169 |
+
- Verify cable is fully inserted in both jacks
|
| 170 |
+
- Test cable with different devices
|
| 171 |
+
- Check if devices are muted
|
| 172 |
+
- Try different audio source
|
| 173 |
+
|
| 174 |
+
**Poor Audio Quality**
|
| 175 |
+
- Clean connector contacts
|
| 176 |
+
- Check for loose connections
|
| 177 |
+
- Verify cable isn't damaged
|
| 178 |
+
- Test with different audio content
|
| 179 |
+
- Check device audio settings
|
| 180 |
+
|
| 181 |
+
**Audio Only in One Channel**
|
| 182 |
+
- Ensure cable is fully inserted
|
| 183 |
+
- Check for bent connectors
|
| 184 |
+
- Test cable with different devices
|
| 185 |
+
- Verify source audio is stereo
|
| 186 |
+
- Check device balance settings
|
| 187 |
+
|
| 188 |
+
**Intermittent Audio**
|
| 189 |
+
- Check for loose connections
|
| 190 |
+
- Inspect cable for damage
|
| 191 |
+
- Clean contact surfaces
|
| 192 |
+
- Avoid cable stress/bending
|
| 193 |
+
- Test in different positions
|
| 194 |
+
|
| 195 |
+
**Background Noise/Hum**
|
| 196 |
+
- Check for interference sources
|
| 197 |
+
- Ensure proper grounding
|
| 198 |
+
- Move away from power cables
|
| 199 |
+
- Check gain/volume levels
|
| 200 |
+
- Try different cable routing
|
| 201 |
+
|
| 202 |
+
### Advanced Troubleshooting
|
| 203 |
+
- **Impedance Matching**: Verify compatible impedances
|
| 204 |
+
- **Signal Levels**: Check if levels are appropriate
|
| 205 |
+
- **Grounding Issues**: Ensure proper system grounding
|
| 206 |
+
- **Cable Testing**: Test continuity with multimeter
|
| 207 |
+
|
| 208 |
+
## Care & Maintenance
|
| 209 |
+
|
| 210 |
+
### Cleaning Instructions
|
| 211 |
+
- **Disconnect**: Remove from devices before cleaning
|
| 212 |
+
- **Dry Cleaning**: Use lint-free cloth for cable jacket
|
| 213 |
+
- **Connector Care**: Clean with isopropyl alcohol if needed
|
| 214 |
+
- **Avoid Moisture**: Keep connectors dry
|
| 215 |
+
- **Storage**: Coil loosely to prevent damage
|
| 216 |
+
|
| 217 |
+
### Preventive Maintenance
|
| 218 |
+
- **Regular Inspection**: Check for wear or damage
|
| 219 |
+
- **Proper Storage**: Avoid tight coiling or knotting
|
| 220 |
+
- **Stress Relief**: Support cable weight at connections
|
| 221 |
+
- **Temperature**: Store in moderate temperature environment
|
| 222 |
+
|
| 223 |
+
## What's Included
|
| 224 |
+
|
| 225 |
+
### Package Contents
|
| 226 |
+
- 1x TMC 3.5mm Audio Cable (6ft)
|
| 227 |
+
- 1x Cable management velcro strap
|
| 228 |
+
- 1x Quick Reference Guide
|
| 229 |
+
- 1x Warranty Registration Card
|
| 230 |
+
|
| 231 |
+
## Warranty & Support
|
| 232 |
+
|
| 233 |
+
### Warranty Coverage
|
| 234 |
+
- **Duration**: Lifetime limited warranty
|
| 235 |
+
- **Coverage**: Manufacturing defects and material failures
|
| 236 |
+
- **Exclusions**: Physical damage, misuse, normal wear
|
| 237 |
+
|
| 238 |
+
### Customer Support
|
| 239 |
+
- **Website**: support.toomanycables.com/audio
|
| 240 |
+
- **Email**: support@toomanycables.com
|
| 241 |
+
- **Phone**: 1-800-TMC-HELP
|
| 242 |
+
- **Live Chat**: Available 24/7 on website
|
| 243 |
+
|
| 244 |
+
### Warranty Registration
|
| 245 |
+
Register for:
|
| 246 |
+
- Lifetime warranty activation
|
| 247 |
+
- Product support notifications
|
| 248 |
+
- Technical assistance
|
| 249 |
+
- Replacement cable discounts
|
| 250 |
+
|
| 251 |
+
## Applications
|
| 252 |
+
|
| 253 |
+
### Professional Audio
|
| 254 |
+
- **Studio Recording**: Connect instruments to audio interfaces
|
| 255 |
+
- **Live Sound**: Patch cables for mixing consoles
|
| 256 |
+
- **DJ Setup**: Connect devices to mixers
|
| 257 |
+
- **Broadcasting**: Studio equipment connections
|
| 258 |
+
|
| 259 |
+
### Consumer Audio
|
| 260 |
+
- **Home Theater**: Connect devices to receivers
|
| 261 |
+
- **Car Audio**: Phone to car stereo connection
|
| 262 |
+
- **Portable Audio**: Connect players to speakers
|
| 263 |
+
- **Gaming**: Audio connections for gaming setups
|
| 264 |
+
|
| 265 |
+
### Educational/Business
|
| 266 |
+
- **Presentations**: Connect laptops to PA systems
|
| 267 |
+
- **Classrooms**: Audio visual equipment connections
|
| 268 |
+
- **Conference Rooms**: Audio system integration
|
| 269 |
+
- **Training Facilities**: Equipment interconnection
|
| 270 |
+
|
| 271 |
+
---
|
| 272 |
+
|
| 273 |
+
**Model**: TMC-AUDIO-3.5MM-6FT
|
| 274 |
+
**Manual Version**: 1.2
|
| 275 |
+
**Last Updated**: October 2025
|
| 276 |
+
**Document**: TMC-AUDIO-MANUAL-V1.2
|
knowledge_base/product_manuals/charging_hub.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multi-Port Charging Hub Product Manual
|
| 2 |
+
|
| 3 |
+
## Product Overview
|
| 4 |
+
|
| 5 |
+
Too Many Cables Multi-Port Charging Hub provides intelligent charging for up to 6 devices simultaneously with 100W total power output and smart charging technology that automatically detects and delivers optimal charging speeds for each connected device.
|
| 6 |
+
|
| 7 |
+
## Product Specifications
|
| 8 |
+
|
| 9 |
+
### TMC-CHARGEHUB-100W-6PORT
|
| 10 |
+
- **Total Power Output**: 100W
|
| 11 |
+
- **Port Configuration**:
|
| 12 |
+
- 4x USB-A ports (12W each, 48W total)
|
| 13 |
+
- 2x USB-C PD ports (52W total, single port max 65W)
|
| 14 |
+
- **Smart Charging**: Auto-detect device requirements
|
| 15 |
+
- **Safety Features**: Over-current, over-voltage, over-temperature protection
|
| 16 |
+
- **Input**: AC 100-240V, 50/60Hz
|
| 17 |
+
- **Dimensions**: 5.5" x 3.5" x 1.2" (140 x 89 x 30mm)
|
| 18 |
+
- **Weight**: 12 oz (340g)
|
| 19 |
+
- **Material**: Fire-resistant ABS plastic housing
|
| 20 |
+
- **LED Indicators**: Power and charging status per port
|
| 21 |
+
- **Cable**: 6ft detachable AC power cord
|
| 22 |
+
|
| 23 |
+
## Compatibility Guide
|
| 24 |
+
|
| 25 |
+
### Compatible Devices
|
| 26 |
+
**Smartphones:**
|
| 27 |
+
- iPhone (all models with Lightning or USB-C)
|
| 28 |
+
- Samsung Galaxy series
|
| 29 |
+
- Google Pixel series
|
| 30 |
+
- OnePlus devices
|
| 31 |
+
- Most Android smartphones
|
| 32 |
+
|
| 33 |
+
**Tablets:**
|
| 34 |
+
- iPad (all models)
|
| 35 |
+
- Samsung Galaxy Tab series
|
| 36 |
+
- Microsoft Surface tablets
|
| 37 |
+
- Android tablets
|
| 38 |
+
|
| 39 |
+
**Laptops & Computers:**
|
| 40 |
+
- MacBook Air/Pro (via USB-C PD)
|
| 41 |
+
- Dell XPS series (via USB-C PD)
|
| 42 |
+
- HP Spectre series (via USB-C PD)
|
| 43 |
+
- Chromebooks with USB-C charging
|
| 44 |
+
- Nintendo Switch
|
| 45 |
+
|
| 46 |
+
**Other Devices:**
|
| 47 |
+
- Wireless earbuds/headphones
|
| 48 |
+
- Smartwatches
|
| 49 |
+
- Power banks
|
| 50 |
+
- Portable speakers
|
| 51 |
+
- Gaming controllers
|
| 52 |
+
|
| 53 |
+
## Smart Charging Technology
|
| 54 |
+
|
| 55 |
+
### Intelligent Power Distribution
|
| 56 |
+
- **Dynamic Power Allocation**: Automatically adjusts power output based on connected devices
|
| 57 |
+
- **Device Recognition**: Identifies device type and optimal charging profile
|
| 58 |
+
- **Priority Charging**: Laptop/tablet charging takes priority when multiple devices connected
|
| 59 |
+
|
| 60 |
+
### Charging Profiles
|
| 61 |
+
- **USB-A Ports**: 5V/2.4A max per port (12W)
|
| 62 |
+
- **USB-C Port 1**: 5V/3A, 9V/3A, 12V/3A, 15V/3A, 20V/3.25A (65W max)
|
| 63 |
+
- **USB-C Port 2**: 5V/3A, 9V/2A, 12V/1.5A (18W max when both ports used)
|
| 64 |
+
|
| 65 |
+
## Usage Instructions
|
| 66 |
+
|
| 67 |
+
### Initial Setup
|
| 68 |
+
1. Connect AC power cord to charging hub
|
| 69 |
+
2. Plug power cord into wall outlet (100-240V)
|
| 70 |
+
3. Power LED should illuminate blue
|
| 71 |
+
4. Hub is ready for device charging
|
| 72 |
+
|
| 73 |
+
### Device Charging
|
| 74 |
+
1. Connect devices using appropriate cables
|
| 75 |
+
2. Hub automatically detects device requirements
|
| 76 |
+
3. Charging begins immediately
|
| 77 |
+
4. LED indicators show charging status per port
|
| 78 |
+
5. Disconnect devices when charging complete
|
| 79 |
+
|
| 80 |
+
### LED Status Indicators
|
| 81 |
+
- **Blue**: Hub powered on and ready
|
| 82 |
+
- **Green**: Device charging normally
|
| 83 |
+
- **Orange**: Fast charging active
|
| 84 |
+
- **Red**: Error or overload condition
|
| 85 |
+
- **Off**: No device connected or hub not powered
|
| 86 |
+
|
| 87 |
+
## Safety Features
|
| 88 |
+
|
| 89 |
+
### Protection Systems
|
| 90 |
+
- **Over-Current Protection**: Prevents damage from excessive current draw
|
| 91 |
+
- **Over-Voltage Protection**: Protects against voltage spikes
|
| 92 |
+
- **Over-Temperature Protection**: Automatic shutdown if overheating detected
|
| 93 |
+
- **Short-Circuit Protection**: Immediate shutdown on short circuit
|
| 94 |
+
- **Surge Protection**: Built-in surge suppression
|
| 95 |
+
|
| 96 |
+
### Certifications
|
| 97 |
+
- **FCC Certified**: Electromagnetic compatibility
|
| 98 |
+
- **UL Listed**: Safety standards compliance
|
| 99 |
+
- **Energy Star**: Energy efficiency rating
|
| 100 |
+
- **RoHS Compliant**: Environmental safety standards
|
| 101 |
+
|
| 102 |
+
## Troubleshooting
|
| 103 |
+
|
| 104 |
+
### Common Issues
|
| 105 |
+
|
| 106 |
+
**Hub Not Powering On**
|
| 107 |
+
- Check AC power connection
|
| 108 |
+
- Verify wall outlet is working
|
| 109 |
+
- Try different power outlet
|
| 110 |
+
- Contact support if issue persists
|
| 111 |
+
|
| 112 |
+
**Device Not Charging**
|
| 113 |
+
- Verify cable is properly connected
|
| 114 |
+
- Try different USB port on hub
|
| 115 |
+
- Test with known working cable
|
| 116 |
+
- Check device charging port for debris
|
| 117 |
+
|
| 118 |
+
**Slow Charging Speed**
|
| 119 |
+
- Verify using appropriate cable for device
|
| 120 |
+
- Check total power draw (may need to disconnect some devices)
|
| 121 |
+
- Ensure device supports fast charging
|
| 122 |
+
- Clean charging contacts
|
| 123 |
+
|
| 124 |
+
**LED Indicators Not Working**
|
| 125 |
+
- Check power connection
|
| 126 |
+
- Verify hub is receiving power
|
| 127 |
+
- Contact support for LED replacement
|
| 128 |
+
|
| 129 |
+
### Error Conditions
|
| 130 |
+
- **Red LED**: Overload condition - disconnect some devices
|
| 131 |
+
- **Hub Shutdown**: Overheating - allow cooling time, check ventilation
|
| 132 |
+
- **No Charging**: Verify compatible charging cable and device
|
| 133 |
+
|
| 134 |
+
## Technical Specifications
|
| 135 |
+
|
| 136 |
+
### Electrical Ratings
|
| 137 |
+
- **Input Voltage**: AC 100-240V ± 10%
|
| 138 |
+
- **Input Frequency**: 50/60Hz ± 3Hz
|
| 139 |
+
- **Input Current**: 2.5A max at 100V
|
| 140 |
+
- **Power Factor**: >0.9 at full load
|
| 141 |
+
- **Efficiency**: >85% at rated load
|
| 142 |
+
|
| 143 |
+
### Environmental Conditions
|
| 144 |
+
- **Operating Temperature**: 32°F to 95°F (0°C to 35°C)
|
| 145 |
+
- **Storage Temperature**: -4°F to 140°F (-20°C to 60°C)
|
| 146 |
+
- **Humidity**: 10% to 90% RH (non-condensing)
|
| 147 |
+
- **Altitude**: Up to 6,600 feet (2,000m)
|
| 148 |
+
|
| 149 |
+
### Physical Specifications
|
| 150 |
+
- **Dimensions**: 5.5" L x 3.5" W x 1.2" H
|
| 151 |
+
- **Weight**: 12 oz (340g)
|
| 152 |
+
- **Housing Material**: Fire-resistant ABS plastic
|
| 153 |
+
- **Color**: Matte black with blue accents
|
| 154 |
+
|
| 155 |
+
## Warranty & Support
|
| 156 |
+
|
| 157 |
+
### Warranty Coverage
|
| 158 |
+
- **Duration**: 3-year limited warranty
|
| 159 |
+
- **Coverage**: Manufacturing defects and material failures
|
| 160 |
+
- **Exclusions**: Physical damage, misuse, normal wear
|
| 161 |
+
|
| 162 |
+
### Customer Support
|
| 163 |
+
- **Website**: support.toomanycables.com/charginghub
|
| 164 |
+
- **Email**: support@toomanycables.com
|
| 165 |
+
- **Phone**: 1-800-TMC-HELP
|
| 166 |
+
- **Live Chat**: Available 24/7 on website
|
| 167 |
+
- **Support Hours**: Monday-Friday 8AM-8PM EST
|
| 168 |
+
|
| 169 |
+
### Warranty Registration
|
| 170 |
+
Register your product within 30 days of purchase for:
|
| 171 |
+
- Extended warranty coverage
|
| 172 |
+
- Product update notifications
|
| 173 |
+
- Priority customer support
|
| 174 |
+
- Replacement part availability
|
| 175 |
+
|
| 176 |
+
---
|
| 177 |
+
|
| 178 |
+
**Model**: TMC-CHARGEHUB-100W-6PORT
|
| 179 |
+
**Manual Version**: 2.1
|
| 180 |
+
**Last Updated**: October 2025
|
| 181 |
+
**Document**: TMC-CHG-HUB-MANUAL-V2.1
|
knowledge_base/product_manuals/hdmi_cables.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HDMI Cable Product Manual
|
| 2 |
+
|
| 3 |
+
## Product Overview
|
| 4 |
+
|
| 5 |
+
Too Many Cables HDMI cables deliver crystal-clear 4K and 8K video with immersive audio for the ultimate home theater and gaming experience. Our premium cables feature high-speed data transmission, advanced shielding, and gold-plated connectors for reliable, long-lasting connections.
|
| 6 |
+
|
| 7 |
+
## Product Specifications
|
| 8 |
+
|
| 9 |
+
### TMC-HDMI-8K-10FT (Ultra High Speed HDMI 2.1)
|
| 10 |
+
- **Length**: 10 feet (3 meters)
|
| 11 |
+
- **Maximum Resolution**: 8K@60Hz, 4K@120Hz
|
| 12 |
+
- **Bandwidth**: 48 Gbps
|
| 13 |
+
- **HDR Support**: HDR10, HDR10+, Dolby Vision
|
| 14 |
+
- **Audio**: eARC, Dolby Atmos, DTS:X
|
| 15 |
+
- **Gaming Features**: VRR, ALLM, QMS, QFT
|
| 16 |
+
- **Connector**: HDMI Type A (Standard)
|
| 17 |
+
- **Material**: Premium braided jacket, gold-plated connectors
|
| 18 |
+
|
| 19 |
+
### TMC-HDMI-4K-6FT (High Speed HDMI 2.0)
|
| 20 |
+
- **Length**: 6 feet (1.8 meters)
|
| 21 |
+
- **Maximum Resolution**: 4K@60Hz, 1440p@144Hz
|
| 22 |
+
- **Bandwidth**: 18 Gbps
|
| 23 |
+
- **HDR Support**: HDR10, HDR10+
|
| 24 |
+
- **Audio**: ARC, Dolby TrueHD, DTS-HD
|
| 25 |
+
- **Gaming Features**: Basic VRR support
|
| 26 |
+
- **Connector**: HDMI Type A (Standard)
|
| 27 |
+
- **Material**: Durable PVC jacket, gold-plated connectors
|
| 28 |
+
|
| 29 |
+
### TMC-HDMI-MINI-3FT (Mini HDMI to HDMI)
|
| 30 |
+
- **Length**: 3 feet (0.9 meters)
|
| 31 |
+
- **Maximum Resolution**: 4K@30Hz
|
| 32 |
+
- **Bandwidth**: 10.2 Gbps
|
| 33 |
+
- **HDR Support**: Basic HDR10
|
| 34 |
+
- **Audio**: Standard audio formats
|
| 35 |
+
- **Connector**: Mini HDMI (Type C) to Standard HDMI (Type A)
|
| 36 |
+
- **Material**: Flexible cable design, gold-plated connectors
|
| 37 |
+
|
| 38 |
+
### TMC-HDMI-MICRO-3FT (Micro HDMI to HDMI)
|
| 39 |
+
- **Length**: 3 feet (0.9 meters)
|
| 40 |
+
- **Maximum Resolution**: 4K@30Hz
|
| 41 |
+
- **Bandwidth**: 10.2 Gbps
|
| 42 |
+
- **HDR Support**: Basic HDR10
|
| 43 |
+
- **Audio**: Standard audio formats
|
| 44 |
+
- **Connector**: Micro HDMI (Type D) to Standard HDMI (Type A)
|
| 45 |
+
- **Material**: Ultra-flexible design, gold-plated connectors
|
| 46 |
+
|
| 47 |
+
## Compatibility Guide
|
| 48 |
+
|
| 49 |
+
### Source Devices
|
| 50 |
+
**Gaming Consoles:**
|
| 51 |
+
- PlayStation 5 (requires HDMI 2.1 for 4K@120Hz)
|
| 52 |
+
- Xbox Series X/S (requires HDMI 2.1 for full features)
|
| 53 |
+
- PlayStation 4/4 Pro (HDMI 2.0 sufficient)
|
| 54 |
+
- Xbox One/One X (HDMI 2.0 sufficient)
|
| 55 |
+
- Nintendo Switch (Mini HDMI cable required)
|
| 56 |
+
|
| 57 |
+
**Computers & Laptops:**
|
| 58 |
+
- Desktop graphics cards (RTX 30/40 series, RX 6000/7000 series)
|
| 59 |
+
- Gaming laptops with HDMI output
|
| 60 |
+
- MacBook Pro/Air (with HDMI port or adapter)
|
| 61 |
+
- Mini PCs and Intel NUCs
|
| 62 |
+
- Tablets with HDMI/Mini HDMI output
|
| 63 |
+
|
| 64 |
+
**Media Devices:**
|
| 65 |
+
- Apple TV 4K (HDMI 2.1 recommended)
|
| 66 |
+
- Nvidia Shield TV Pro
|
| 67 |
+
- Roku Ultra, Amazon Fire TV Stick 4K Max
|
| 68 |
+
- Chromecast with Google TV
|
| 69 |
+
- Cable/satellite boxes with 4K support
|
| 70 |
+
|
| 71 |
+
### Display Devices
|
| 72 |
+
**TVs:**
|
| 73 |
+
- 4K/8K Smart TVs (Samsung, LG, Sony, TCL)
|
| 74 |
+
- OLED and QLED displays
|
| 75 |
+
- Gaming-focused TVs with HDMI 2.1
|
| 76 |
+
- Projectors with 4K support
|
| 77 |
+
|
| 78 |
+
**Monitors:**
|
| 79 |
+
- 4K gaming monitors (144Hz+)
|
| 80 |
+
- Professional displays for content creation
|
| 81 |
+
- Ultrawide monitors with 4K resolution
|
| 82 |
+
- High refresh rate gaming displays
|
| 83 |
+
|
| 84 |
+
## Performance Features
|
| 85 |
+
|
| 86 |
+
### Video Quality
|
| 87 |
+
- **8K Resolution**: 7680x4320 pixels at 60Hz
|
| 88 |
+
- **4K Gaming**: 3840x2160 at 120Hz for smooth gameplay
|
| 89 |
+
- **High Dynamic Range**: Enhanced contrast and color
|
| 90 |
+
- **Wide Color Gamut**: BT.2020 color space support
|
| 91 |
+
- **10-Bit Color**: Over 1 billion colors for smooth gradients
|
| 92 |
+
|
| 93 |
+
### Audio Quality
|
| 94 |
+
- **Enhanced Audio Return Channel (eARC)**: Full-bandwidth audio
|
| 95 |
+
- **Object-Based Audio**: Dolby Atmos and DTS:X support
|
| 96 |
+
- **High-Resolution Audio**: Up to 192kHz/24-bit
|
| 97 |
+
- **Multi-Channel**: Up to 32 audio channels
|
| 98 |
+
- **Audio Sync**: Automatic lip-sync correction
|
| 99 |
+
|
| 100 |
+
### Gaming Features
|
| 101 |
+
- **Variable Refresh Rate (VRR)**: Eliminates screen tearing
|
| 102 |
+
- **Auto Low Latency Mode (ALLM)**: Reduces input lag automatically
|
| 103 |
+
- **Quick Media Switching (QMS)**: Faster source switching
|
| 104 |
+
- **Quick Frame Transport (QFT)**: Reduced latency for competitive gaming
|
| 105 |
+
|
| 106 |
+
## Resolution & Refresh Rate Guide
|
| 107 |
+
|
| 108 |
+
### HDMI 2.1 Cables (8K Series)
|
| 109 |
+
**Supported Resolutions:**
|
| 110 |
+
- 8K (7680x4320): 60Hz, 30Hz
|
| 111 |
+
- 4K (3840x2160): 120Hz, 60Hz, 30Hz
|
| 112 |
+
- 1440p (2560x1440): 144Hz, 120Hz, 60Hz
|
| 113 |
+
- 1080p (1920x1080): 240Hz, 144Hz, 120Hz, 60Hz
|
| 114 |
+
|
| 115 |
+
### HDMI 2.0 Cables (4K Series)
|
| 116 |
+
**Supported Resolutions:**
|
| 117 |
+
- 4K (3840x2160): 60Hz, 30Hz
|
| 118 |
+
- 1440p (2560x1440): 144Hz, 120Hz, 60Hz
|
| 119 |
+
- 1080p (1920x1080): 144Hz, 120Hz, 60Hz
|
| 120 |
+
- 720p (1280x720): All refresh rates
|
| 121 |
+
|
| 122 |
+
### Cable Length Considerations
|
| 123 |
+
**Optimal Performance:**
|
| 124 |
+
- Up to 6 feet: Full bandwidth guaranteed
|
| 125 |
+
- 6-15 feet: Full bandwidth with quality cables
|
| 126 |
+
- 15-25 feet: May require active cables for highest resolutions
|
| 127 |
+
- 25+ feet: Active or fiber optic cables recommended
|
| 128 |
+
|
| 129 |
+
## Installation & Setup
|
| 130 |
+
|
| 131 |
+
### Basic Connection
|
| 132 |
+
1. **Power Off Devices**: Turn off both source and display devices
|
| 133 |
+
2. **Connect Cable**: Insert HDMI connectors fully into ports
|
| 134 |
+
3. **Power On**: Turn on display first, then source device
|
| 135 |
+
4. **Select Input**: Use TV/monitor remote to select correct HDMI input
|
| 136 |
+
5. **Verify Signal**: Check for picture and audio
|
| 137 |
+
|
| 138 |
+
### Optimizing Picture Quality
|
| 139 |
+
1. **Display Settings**: Access TV/monitor picture settings
|
| 140 |
+
2. **HDMI Mode**: Enable "Enhanced" or "Ultra HD Deep Color"
|
| 141 |
+
3. **HDR Settings**: Enable HDR10, HDR10+, or Dolby Vision
|
| 142 |
+
4. **Color Space**: Set to BT.2020 for wide color gamut
|
| 143 |
+
5. **Game Mode**: Enable for gaming sources to reduce lag
|
| 144 |
+
|
| 145 |
+
### Audio Setup
|
| 146 |
+
1. **Audio Output**: Set source device to "HDMI" or "Auto"
|
| 147 |
+
2. **Audio Format**: Select highest quality format supported
|
| 148 |
+
3. **eARC Setup**: Enable eARC on both TV and soundbar/receiver
|
| 149 |
+
4. **Channel Configuration**: Set up surround sound speakers
|
| 150 |
+
5. **Audio Delay**: Adjust if lip-sync issues occur
|
| 151 |
+
|
| 152 |
+
## Troubleshooting
|
| 153 |
+
|
| 154 |
+
### No Picture/Black Screen
|
| 155 |
+
**Possible Causes & Solutions:**
|
| 156 |
+
- **Loose Connection**: Ensure cables are fully inserted
|
| 157 |
+
- **Wrong Input**: Verify correct HDMI input selected on display
|
| 158 |
+
- **Resolution Mismatch**: Try lower resolution in source settings
|
| 159 |
+
- **HDCP Issues**: Try powering off both devices for 30 seconds
|
| 160 |
+
- **Cable Failure**: Test with known working cable
|
| 161 |
+
|
| 162 |
+
### Picture Quality Issues
|
| 163 |
+
**Fuzzy or Pixelated Image:**
|
| 164 |
+
- Check if 4K enhancement mode is enabled on TV
|
| 165 |
+
- Verify source is outputting native resolution
|
| 166 |
+
- Try shorter cable if using long cable
|
| 167 |
+
- Clean connectors with compressed air
|
| 168 |
+
|
| 169 |
+
**Color Issues:**
|
| 170 |
+
- Enable HDR mode on both devices
|
| 171 |
+
- Check color space settings (BT.2020 vs BT.709)
|
| 172 |
+
- Verify cable supports required bandwidth
|
| 173 |
+
- Update device firmware/drivers
|
| 174 |
+
|
| 175 |
+
**Flickering or Intermittent Signal:**
|
| 176 |
+
- Check cable for physical damage
|
| 177 |
+
- Try different HDMI ports
|
| 178 |
+
- Disable VRR temporarily to test
|
| 179 |
+
- Use higher quality cable for long runs
|
| 180 |
+
|
| 181 |
+
### Audio Problems
|
| 182 |
+
**No Audio:**
|
| 183 |
+
- Check audio output settings on source device
|
| 184 |
+
- Verify HDMI audio is enabled
|
| 185 |
+
- Try different audio format (PCM vs Bitstream)
|
| 186 |
+
- Check if TV speakers are muted
|
| 187 |
+
|
| 188 |
+
**Audio Dropouts:**
|
| 189 |
+
- Ensure cable supports full bandwidth
|
| 190 |
+
- Try disabling advanced audio formats temporarily
|
| 191 |
+
- Check for electromagnetic interference
|
| 192 |
+
- Update device audio drivers
|
| 193 |
+
|
| 194 |
+
**Audio/Video Sync Issues:**
|
| 195 |
+
- Enable audio delay compensation on TV/receiver
|
| 196 |
+
- Try game mode or low latency mode
|
| 197 |
+
- Check if eARC is causing delays
|
| 198 |
+
- Test with basic stereo audio format
|
| 199 |
+
|
| 200 |
+
### Gaming-Specific Issues
|
| 201 |
+
**High Input Lag:**
|
| 202 |
+
- Enable Game Mode on TV/monitor
|
| 203 |
+
- Verify ALLM is working properly
|
| 204 |
+
- Disable unnecessary picture processing
|
| 205 |
+
- Use HDMI 2.1 cable for latest consoles
|
| 206 |
+
|
| 207 |
+
**Screen Tearing:**
|
| 208 |
+
- Enable VRR on both console and display
|
| 209 |
+
- Check if display supports console's VRR range
|
| 210 |
+
- Try G-Sync/FreeSync compatible mode
|
| 211 |
+
- Update console and display firmware
|
| 212 |
+
|
| 213 |
+
## Care & Maintenance
|
| 214 |
+
|
| 215 |
+
### Proper Handling
|
| 216 |
+
- **Connector Care**: Don't force connectors into ports
|
| 217 |
+
- **Cable Management**: Use gentle curves, avoid sharp bends
|
| 218 |
+
- **Strain Relief**: Support cable weight, don't pull on connectors
|
| 219 |
+
- **Storage**: Coil loosely to prevent internal wire damage
|
| 220 |
+
|
| 221 |
+
### Environmental Considerations
|
| 222 |
+
- **Temperature**: Avoid extreme heat (near fireplaces, heating vents)
|
| 223 |
+
- **Humidity**: Keep away from excessive moisture
|
| 224 |
+
- **UV Exposure**: Protect from direct sunlight for extended periods
|
| 225 |
+
- **Physical Protection**: Route cables away from foot traffic
|
| 226 |
+
|
| 227 |
+
### Cleaning & Maintenance
|
| 228 |
+
- **Connectors**: Use compressed air to remove dust
|
| 229 |
+
- **Cable Jacket**: Wipe with slightly damp microfiber cloth
|
| 230 |
+
- **Ports**: Clean device HDMI ports regularly
|
| 231 |
+
- **Contact Cleaner**: Use contact cleaner for stubborn corrosion
|
| 232 |
+
|
| 233 |
+
## Advanced Features
|
| 234 |
+
|
| 235 |
+
### HDMI 2.1 Gaming Features
|
| 236 |
+
**Variable Refresh Rate (VRR):**
|
| 237 |
+
- Synchronizes display refresh with GPU frame rate
|
| 238 |
+
- Eliminates screen tearing and stuttering
|
| 239 |
+
- Supported range varies by display (typically 40-120Hz)
|
| 240 |
+
|
| 241 |
+
**Auto Low Latency Mode (ALLM):**
|
| 242 |
+
- Automatically enables game mode when gaming signal detected
|
| 243 |
+
- Reduces input lag without manual switching
|
| 244 |
+
- Works with PlayStation 5, Xbox Series X/S
|
| 245 |
+
|
| 246 |
+
**Quick Frame Transport (QFT):**
|
| 247 |
+
- Reduces latency by optimizing frame delivery
|
| 248 |
+
- Most beneficial for competitive gaming
|
| 249 |
+
- Requires compatible source and display
|
| 250 |
+
|
| 251 |
+
### Professional Features
|
| 252 |
+
**Enhanced Audio Return Channel (eARC):**
|
| 253 |
+
- Full bandwidth audio return from TV to soundbar/receiver
|
| 254 |
+
- Supports uncompressed Dolby Atmos and DTS:X
|
| 255 |
+
- Enables TV as audio hub for multiple sources
|
| 256 |
+
|
| 257 |
+
**Dynamic HDR:**
|
| 258 |
+
- Scene-by-scene or frame-by-frame HDR optimization
|
| 259 |
+
- Supported formats: HDR10+, Dolby Vision
|
| 260 |
+
- Requires compatible content and display
|
| 261 |
+
|
| 262 |
+
## Technical Specifications
|
| 263 |
+
|
| 264 |
+
### Electrical Characteristics
|
| 265 |
+
- **Impedance**: 100Ω ±15% differential
|
| 266 |
+
- **Signal Integrity**: Meets HDMI compliance standards
|
| 267 |
+
- **EMI Shielding**: Triple-layer shielding for interference protection
|
| 268 |
+
- **Bandwidth**: Up to 48 Gbps (HDMI 2.1)
|
| 269 |
+
|
| 270 |
+
### Physical Construction
|
| 271 |
+
- **Conductor**: High-purity copper with silver plating
|
| 272 |
+
- **Dielectric**: Low-loss foam polyethylene
|
| 273 |
+
- **Shielding**: Aluminum foil + tinned copper braid
|
| 274 |
+
- **Jacket**: Durable PVC or braided nylon
|
| 275 |
+
|
| 276 |
+
### Connector Specifications
|
| 277 |
+
- **Plating**: Gold-plated contacts (minimum 30 micro-inches)
|
| 278 |
+
- **Durability**: Rated for 10,000+ insertion cycles
|
| 279 |
+
- **Retention Force**: Secure connection without excessive force
|
| 280 |
+
- **Contact Resistance**: <20mΩ per contact
|
| 281 |
+
|
| 282 |
+
## Warranty & Support
|
| 283 |
+
|
| 284 |
+
### Lifetime Warranty
|
| 285 |
+
All Too Many Cables HDMI cables include:
|
| 286 |
+
- Coverage against manufacturing defects
|
| 287 |
+
- Protection against material failures
|
| 288 |
+
- Performance guarantee under normal use
|
| 289 |
+
- Free replacement for warranted failures
|
| 290 |
+
|
| 291 |
+
### Customer Support
|
| 292 |
+
- **Technical Support**: Expert guidance on setup and troubleshooting
|
| 293 |
+
- **Compatibility Assistance**: Help choosing the right cable
|
| 294 |
+
- **Warranty Claims**: Fast, hassle-free replacement process
|
| 295 |
+
- **24/7 AI Chat**: Instant support at toomanycables.com
|
| 296 |
+
|
| 297 |
+
### Contact Information
|
| 298 |
+
- **Phone**: 1-800-TMC-HELP
|
| 299 |
+
- **Email**: support@toomanycables.com
|
| 300 |
+
- **Live Chat**: Available 24/7 on our website
|
| 301 |
+
- **Support Hours**: Monday-Friday 8 AM - 8 PM EST
|
| 302 |
+
|
| 303 |
+
---
|
| 304 |
+
|
| 305 |
+
*Manual version 3.2 - Updated September 2024*
|
| 306 |
+
*For the latest troubleshooting guides, visit toomanycables.com/support*
|
knowledge_base/product_manuals/lightning_cables.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Lightning Cable Product Manual
|
| 2 |
+
|
| 3 |
+
## Product Overview
|
| 4 |
+
|
| 5 |
+
Too Many Cables Lightning cables provide reliable charging and data transfer for Apple devices including iPhone, iPad, and iPod.
|
| 6 |
+
|
| 7 |
+
## Product Specifications
|
| 8 |
+
|
| 9 |
+
### TMC-LIGHTNING-MFI-6FT
|
| 10 |
+
- **Length**: 6 feet (1.8 meters)
|
| 11 |
+
- **Connector Type**: Lightning to USB-A
|
| 12 |
+
- **Data Transfer**: USB 2.0 speeds up to 480 Mbps
|
| 13 |
+
- **Charging**: Up to 2.4A (12W) fast charging
|
| 14 |
+
- **Certification**: MFi (Made for iPhone/iPad/iPod) certified
|
| 15 |
+
- **Durability**: 10,000+ bend test cycles
|
| 16 |
+
- **Warranty**: 2-year limited warranty
|
| 17 |
+
|
| 18 |
+
## Compatible Devices
|
| 19 |
+
|
| 20 |
+
### iPhone Models
|
| 21 |
+
- iPhone 15 series (with adapter)
|
| 22 |
+
- iPhone 14 series and earlier
|
| 23 |
+
- iPhone SE (all generations)
|
| 24 |
+
|
| 25 |
+
### iPad Models
|
| 26 |
+
- iPad (9th generation and earlier)
|
| 27 |
+
- iPad mini (6th generation and earlier)
|
| 28 |
+
- iPad Air (3rd generation and earlier)
|
| 29 |
+
- iPad Pro 12.9" (2nd generation and earlier)
|
| 30 |
+
- iPad Pro 10.5" and earlier models
|
| 31 |
+
|
| 32 |
+
### iPod Models
|
| 33 |
+
- iPod touch (7th generation and earlier)
|
| 34 |
+
|
| 35 |
+
## Usage Instructions
|
| 36 |
+
|
| 37 |
+
### For Charging
|
| 38 |
+
1. Connect Lightning end to your Apple device
|
| 39 |
+
2. Connect USB-A end to charger or computer
|
| 40 |
+
3. Device should show charging indicator
|
| 41 |
+
4. Charging speeds vary by device and charger capabilities
|
| 42 |
+
|
| 43 |
+
### For Data Transfer
|
| 44 |
+
1. Connect device to computer with cable
|
| 45 |
+
2. Trust the computer on your iOS device when prompted
|
| 46 |
+
3. Device should appear in iTunes/Finder
|
| 47 |
+
4. Transfer speeds depend on file types and device capabilities
|
| 48 |
+
|
| 49 |
+
## Troubleshooting
|
| 50 |
+
|
| 51 |
+
### Device Not Charging
|
| 52 |
+
**Symptoms**: No charging indicator, slow charging, intermittent charging
|
| 53 |
+
|
| 54 |
+
**Solutions**:
|
| 55 |
+
- Clean Lightning connector with dry cloth
|
| 56 |
+
- Try different charging adapter
|
| 57 |
+
- Check for debris in device port
|
| 58 |
+
- Ensure cable is fully inserted
|
| 59 |
+
- Try different power outlet
|
| 60 |
+
|
| 61 |
+
### Device Not Recognized
|
| 62 |
+
**Symptoms**: Computer doesn't detect device, iTunes/Finder doesn't show device
|
| 63 |
+
|
| 64 |
+
**Solutions**:
|
| 65 |
+
- Update iTunes to latest version
|
| 66 |
+
- Try different USB port on computer
|
| 67 |
+
- Restart both devices
|
| 68 |
+
- Check cable for physical damage
|
| 69 |
+
- Ensure device is unlocked and "Trust Computer" is selected
|
| 70 |
+
|
| 71 |
+
### Slow Data Transfer
|
| 72 |
+
**Solutions**:
|
| 73 |
+
- Use USB 3.0 port if available (cable still USB 2.0 speeds)
|
| 74 |
+
- Close other applications during transfer
|
| 75 |
+
- Transfer smaller batches of files
|
| 76 |
+
- Ensure device has sufficient storage space
|
| 77 |
+
|
| 78 |
+
## Care and Maintenance
|
| 79 |
+
|
| 80 |
+
### Daily Use
|
| 81 |
+
- Avoid sharp bends or kinks
|
| 82 |
+
- Don't pull cable by the cord
|
| 83 |
+
- Store in cool, dry place
|
| 84 |
+
- Keep connectors clean and dry
|
| 85 |
+
|
| 86 |
+
### Storage
|
| 87 |
+
- Coil loosely when storing
|
| 88 |
+
- Avoid extreme temperatures
|
| 89 |
+
- Keep away from sharp objects
|
| 90 |
+
- Store in protective case if available
|
| 91 |
+
|
| 92 |
+
## Warranty Information
|
| 93 |
+
|
| 94 |
+
### Coverage
|
| 95 |
+
- 2-year limited warranty from date of purchase
|
| 96 |
+
- Covers manufacturing defects and material failures
|
| 97 |
+
- Does not cover physical damage from misuse
|
| 98 |
+
|
| 99 |
+
### What's NOT Covered
|
| 100 |
+
- Damage from pets, liquids, or extreme conditions
|
| 101 |
+
- Normal wear and tear
|
| 102 |
+
- Damage from improper use or storage
|
| 103 |
+
- Third-party modifications
|
| 104 |
+
|
| 105 |
+
## Technical Support
|
| 106 |
+
|
| 107 |
+
For technical assistance:
|
| 108 |
+
- Email: support@toomanycables.com
|
| 109 |
+
- Phone: 1-800-TMC-HELP
|
| 110 |
+
- Live Chat: Available on our website
|
| 111 |
+
- Hours: Monday-Friday 9 AM - 6 PM EST
|
| 112 |
+
|
| 113 |
+
## Frequently Asked Questions
|
| 114 |
+
|
| 115 |
+
**Q: Is this cable MFi certified?**
|
| 116 |
+
A: Yes, all our Lightning cables are MFi certified by Apple.
|
| 117 |
+
|
| 118 |
+
**Q: Will this work with iPhone 15?**
|
| 119 |
+
A: iPhone 15 uses USB-C. You would need a Lightning to USB-C adapter or our USB-C cables.
|
| 120 |
+
|
| 121 |
+
**Q: Can I use this for fast charging?**
|
| 122 |
+
A: Yes, supports up to 2.4A charging when used with compatible power adapter.
|
| 123 |
+
|
| 124 |
+
**Q: What's the maximum data transfer speed?**
|
| 125 |
+
A: USB 2.0 speeds up to 480 Mbps for data transfer.
|
knowledge_base/product_manuals/usb_c_audio_adapter.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# USB-C to 3.5mm Audio Adapter Product Manual
|
| 2 |
+
|
| 3 |
+
## Product Overview
|
| 4 |
+
|
| 5 |
+
Too Many Cables USB-C to 3.5mm Audio Adapter provides high-resolution audio output for USB-C devices that lack a traditional headphone jack. Features a premium 32-bit DAC for superior sound quality and compact aluminum housing for durability.
|
| 6 |
+
|
| 7 |
+
## Product Specifications
|
| 8 |
+
|
| 9 |
+
### TMC-USBC-AUDIO-32BIT
|
| 10 |
+
- **DAC Chipset**: 32-bit premium audio processing
|
| 11 |
+
- **Sample Rates**: 44.1kHz, 48kHz, 88.2kHz, 96kHz, 176.4kHz, 192kHz
|
| 12 |
+
- **Bit Depth**: 16-bit, 24-bit, 32-bit
|
| 13 |
+
- **Frequency Response**: 20Hz - 40kHz (±0.1dB)
|
| 14 |
+
- **Signal-to-Noise Ratio**: >110dB
|
| 15 |
+
- **Total Harmonic Distortion**: <0.003%
|
| 16 |
+
- **Output Power**: 2 x 15mW @ 32Ω
|
| 17 |
+
- **Connector**: USB-C male to 3.5mm female
|
| 18 |
+
- **Dimensions**: 2.2" x 0.6" x 0.4" (56 x 15 x 10mm)
|
| 19 |
+
- **Weight**: 0.5 oz (15g)
|
| 20 |
+
- **Material**: Premium aluminum alloy housing
|
| 21 |
+
- **Cable**: 4-inch integrated cable
|
| 22 |
+
- **Compatibility**: USB-C devices with audio support
|
| 23 |
+
|
| 24 |
+
## Audio Performance
|
| 25 |
+
|
| 26 |
+
### DAC Specifications
|
| 27 |
+
- **Architecture**: Delta-Sigma 32-bit DAC
|
| 28 |
+
- **Dynamic Range**: >110dB
|
| 29 |
+
- **THD+N**: <0.003% at 1kHz
|
| 30 |
+
- **Crosstalk**: <-100dB at 1kHz
|
| 31 |
+
- **Channel Balance**: ±0.1dB
|
| 32 |
+
- **Phase Response**: Linear phase
|
| 33 |
+
|
| 34 |
+
### Output Characteristics
|
| 35 |
+
- **Maximum Output**: 2V RMS (unloaded)
|
| 36 |
+
- **Output Impedance**: <1 ohm
|
| 37 |
+
- **Load Impedance**: 16Ω to 600Ω (recommended)
|
| 38 |
+
- **Power Output**:
|
| 39 |
+
- 15mW @ 32Ω per channel
|
| 40 |
+
- 10mW @ 150Ω per channel
|
| 41 |
+
- 2mW @ 600Ω per channel
|
| 42 |
+
|
| 43 |
+
### Supported Audio Formats
|
| 44 |
+
- **PCM**: 16/24/32-bit up to 192kHz
|
| 45 |
+
- **DSD**: Native DSD64, DSD128 (device dependent)
|
| 46 |
+
- **Compressed**: MP3, AAC, FLAC, ALAC, OGG
|
| 47 |
+
- **High-Res**: MQA unfold (software dependent)
|
| 48 |
+
|
| 49 |
+
## Compatibility Guide
|
| 50 |
+
|
| 51 |
+
### Compatible Source Devices
|
| 52 |
+
|
| 53 |
+
**Smartphones:**
|
| 54 |
+
- iPhone 15 series and later
|
| 55 |
+
- Samsung Galaxy S20 and later
|
| 56 |
+
- Google Pixel 2 and later
|
| 57 |
+
- OnePlus 6T and later
|
| 58 |
+
- Xiaomi phones with USB-C
|
| 59 |
+
- Huawei phones with USB-C (where available)
|
| 60 |
+
|
| 61 |
+
**Tablets:**
|
| 62 |
+
- iPad Pro (2018 and later)
|
| 63 |
+
- iPad Air (4th generation and later)
|
| 64 |
+
- Samsung Galaxy Tab S series
|
| 65 |
+
- Microsoft Surface Pro with USB-C
|
| 66 |
+
|
| 67 |
+
**Laptops & Computers:**
|
| 68 |
+
- MacBook Pro (2016 and later)
|
| 69 |
+
- MacBook Air (2018 and later)
|
| 70 |
+
- Dell XPS series
|
| 71 |
+
- HP Spectre series
|
| 72 |
+
- Lenovo ThinkPad series
|
| 73 |
+
- Microsoft Surface Laptop series
|
| 74 |
+
- Gaming laptops with USB-C
|
| 75 |
+
|
| 76 |
+
**Other Devices:**
|
| 77 |
+
- Nintendo Switch
|
| 78 |
+
- Steam Deck
|
| 79 |
+
- Android TV boxes
|
| 80 |
+
- USB-C audio interfaces
|
| 81 |
+
- Digital audio players with USB-C
|
| 82 |
+
|
| 83 |
+
### Compatible Headphones/Earphones
|
| 84 |
+
- **High-Impedance**: Up to 600Ω headphones
|
| 85 |
+
- **Sensitive IEMs**: In-ear monitors (16-32Ω)
|
| 86 |
+
- **Studio Headphones**: Professional monitoring headphones
|
| 87 |
+
- **Consumer Headphones**: Standard 32-300Ω headphones
|
| 88 |
+
- **Earbuds**: Standard and high-end earbuds
|
| 89 |
+
|
| 90 |
+
## Usage Instructions
|
| 91 |
+
|
| 92 |
+
### Basic Setup
|
| 93 |
+
1. Connect adapter's USB-C plug to device
|
| 94 |
+
2. Connect headphones/earphones to 3.5mm jack
|
| 95 |
+
3. Audio output automatically switches to adapter
|
| 96 |
+
4. Adjust volume using device controls
|
| 97 |
+
|
| 98 |
+
### Audio Quality Optimization
|
| 99 |
+
1. **High-Res Settings**: Enable high-resolution audio in device settings
|
| 100 |
+
2. **Sample Rate**: Use native sample rate of audio files when possible
|
| 101 |
+
3. **Volume**: Set device volume to 75-85% for optimal performance
|
| 102 |
+
4. **Audio Apps**: Use audiophile apps that support high-res formats
|
| 103 |
+
|
| 104 |
+
### Device-Specific Setup
|
| 105 |
+
|
| 106 |
+
**Android:**
|
| 107 |
+
1. Settings → Sound → Audio quality
|
| 108 |
+
2. Enable "High quality audio" or "Hi-Res audio"
|
| 109 |
+
3. Select appropriate sample rate if available
|
| 110 |
+
4. Disable audio processing effects for purest sound
|
| 111 |
+
|
| 112 |
+
**iPhone/iPad:**
|
| 113 |
+
1. Settings → Music → Audio Quality
|
| 114 |
+
2. Enable "Lossless" and "Hi-Res Lossless"
|
| 115 |
+
3. Use Apple Music or compatible apps
|
| 116 |
+
4. Adapter automatically handles optimal conversion
|
| 117 |
+
|
| 118 |
+
**Windows:**
|
| 119 |
+
1. Control Panel → Sound → Playbook devices
|
| 120 |
+
2. Select adapter → Properties → Advanced
|
| 121 |
+
3. Choose highest quality format available
|
| 122 |
+
4. Disable audio enhancements for best quality
|
| 123 |
+
|
| 124 |
+
**macOS:**
|
| 125 |
+
1. System Preferences → Sound → Output
|
| 126 |
+
2. Select adapter as output device
|
| 127 |
+
3. Audio MIDI Setup → Configure for high sample rates
|
| 128 |
+
4. Use high-quality audio applications
|
| 129 |
+
|
| 130 |
+
## Technical Specifications
|
| 131 |
+
|
| 132 |
+
### Electrical Requirements
|
| 133 |
+
- **Power Consumption**: <100mW
|
| 134 |
+
- **Supply Voltage**: 5V from USB-C
|
| 135 |
+
- **Current Draw**: <20mA
|
| 136 |
+
- **No External Power**: Bus-powered operation
|
| 137 |
+
|
| 138 |
+
### Signal Processing
|
| 139 |
+
- **Input**: Digital audio via USB-C
|
| 140 |
+
- **Processing**: 32-bit internal processing
|
| 141 |
+
- **Output**: Analog stereo 3.5mm
|
| 142 |
+
- **Latency**: <10ms digital to analog conversion
|
| 143 |
+
|
| 144 |
+
### Physical Specifications
|
| 145 |
+
- **Total Length**: 2.2 inches (56mm)
|
| 146 |
+
- **Body Diameter**: 0.6 inches (15mm)
|
| 147 |
+
- **Thickness**: 0.4 inches (10mm)
|
| 148 |
+
- **Weight**: 0.5 oz (15g)
|
| 149 |
+
- **Housing**: CNC machined aluminum
|
| 150 |
+
- **Finish**: Anodized matte black
|
| 151 |
+
|
| 152 |
+
### Environmental Conditions
|
| 153 |
+
- **Operating Temperature**: 32°F to 104°F (0°C to 40°C)
|
| 154 |
+
- **Storage Temperature**: -4°F to 140°F (-20°C to 60°C)
|
| 155 |
+
- **Humidity**: 10% to 90% RH (non-condensing)
|
| 156 |
+
- **Altitude**: Up to 6,600 feet (2,000m)
|
| 157 |
+
|
| 158 |
+
## Audio Features
|
| 159 |
+
|
| 160 |
+
### High-Resolution Audio Support
|
| 161 |
+
- **Studio Quality**: 32-bit/192kHz capable
|
| 162 |
+
- **Low Noise Floor**: >110dB SNR for pristine audio
|
| 163 |
+
- **Wide Bandwidth**: 20Hz-40kHz frequency response
|
| 164 |
+
- **Accurate Reproduction**: <0.003% THD for clean sound
|
| 165 |
+
|
| 166 |
+
### Advanced Features
|
| 167 |
+
- **Automatic Gain Control**: Prevents clipping and distortion
|
| 168 |
+
- **Pop/Click Suppression**: Eliminates connection artifacts
|
| 169 |
+
- **Power Management**: Intelligent power saving
|
| 170 |
+
- **Universal Compatibility**: Works with all major platforms
|
| 171 |
+
|
| 172 |
+
## Troubleshooting
|
| 173 |
+
|
| 174 |
+
### Common Issues
|
| 175 |
+
|
| 176 |
+
**No Audio Output**
|
| 177 |
+
- Verify adapter is fully connected
|
| 178 |
+
- Check device recognizes audio adapter
|
| 179 |
+
- Ensure headphones are properly connected
|
| 180 |
+
- Try different headphones to test adapter
|
| 181 |
+
- Restart device to refresh audio system
|
| 182 |
+
|
| 183 |
+
**Poor Audio Quality**
|
| 184 |
+
- Check audio format settings on device
|
| 185 |
+
- Disable audio processing/effects
|
| 186 |
+
- Verify high-quality audio is enabled
|
| 187 |
+
- Use lossless audio files
|
| 188 |
+
- Check headphone impedance compatibility
|
| 189 |
+
|
| 190 |
+
**Low Volume Output**
|
| 191 |
+
- Increase device volume to 75-85%
|
| 192 |
+
- Check if volume limit is enabled
|
| 193 |
+
- Verify headphone sensitivity rating
|
| 194 |
+
- Try different audio application
|
| 195 |
+
- Check for impedance mismatch
|
| 196 |
+
|
| 197 |
+
**Intermittent Audio**
|
| 198 |
+
- Ensure secure USB-C connection
|
| 199 |
+
- Check for cable/connector damage
|
| 200 |
+
- Clean USB-C port of device
|
| 201 |
+
- Try different USB-C port if available
|
| 202 |
+
- Test with different headphones
|
| 203 |
+
|
| 204 |
+
**Device Not Recognizing Adapter**
|
| 205 |
+
- Restart device with adapter connected
|
| 206 |
+
- Check for driver updates (Windows/Android)
|
| 207 |
+
- Try different USB-C port
|
| 208 |
+
- Verify device supports USB-C audio
|
| 209 |
+
- Contact support if issue persists
|
| 210 |
+
|
| 211 |
+
### Advanced Troubleshooting
|
| 212 |
+
|
| 213 |
+
**High-Impedance Headphones:**
|
| 214 |
+
- Use headphone amplifier for >300Ω headphones
|
| 215 |
+
- Check power output specifications
|
| 216 |
+
- Consider dedicated headphone amp for best performance
|
| 217 |
+
|
| 218 |
+
**Compatibility Issues:**
|
| 219 |
+
- Verify device supports USB-C audio standard
|
| 220 |
+
- Check if device requires specific drivers
|
| 221 |
+
- Update device firmware/software
|
| 222 |
+
- Try with known compatible device
|
| 223 |
+
|
| 224 |
+
## Care & Maintenance
|
| 225 |
+
|
| 226 |
+
### Cleaning Instructions
|
| 227 |
+
- **Disconnect**: Remove from device before cleaning
|
| 228 |
+
- **Dry Cloth**: Use lint-free cloth for housing
|
| 229 |
+
- **Connector Care**: Keep USB-C connector clean and dry
|
| 230 |
+
- **No Liquids**: Avoid moisture in connectors
|
| 231 |
+
- **Storage**: Use provided pouch when not in use
|
| 232 |
+
|
| 233 |
+
### Preventive Care
|
| 234 |
+
- **Gentle Handling**: Avoid dropping or impact
|
| 235 |
+
- **Cable Protection**: Don't bend cable sharply
|
| 236 |
+
- **Temperature**: Store in moderate temperatures
|
| 237 |
+
- **Moisture**: Keep away from liquids
|
| 238 |
+
|
| 239 |
+
## What's Included
|
| 240 |
+
|
| 241 |
+
### Package Contents
|
| 242 |
+
- 1x TMC USB-C to 3.5mm Audio Adapter
|
| 243 |
+
- 1x Premium carrying pouch
|
| 244 |
+
- 1x Quick Start Guide
|
| 245 |
+
- 1x Warranty Registration Card
|
| 246 |
+
|
| 247 |
+
## Applications
|
| 248 |
+
|
| 249 |
+
### Audiophile Listening
|
| 250 |
+
- **High-Res Music**: Studio-quality playback
|
| 251 |
+
- **Critical Listening**: Professional audio monitoring
|
| 252 |
+
- **Music Production**: Mobile audio editing
|
| 253 |
+
- **Streaming**: High-quality music streaming services
|
| 254 |
+
|
| 255 |
+
### Professional Use
|
| 256 |
+
- **Field Recording**: Monitor audio during recording
|
| 257 |
+
- **Broadcasting**: Live audio monitoring
|
| 258 |
+
- **DJ Applications**: Mobile DJ setups
|
| 259 |
+
- **Content Creation**: Podcast and video production
|
| 260 |
+
|
| 261 |
+
### Gaming & Entertainment
|
| 262 |
+
- **Mobile Gaming**: High-quality game audio
|
| 263 |
+
- **Video Streaming**: Enhanced movie/show audio
|
| 264 |
+
- **Communication**: Clear voice chat audio
|
| 265 |
+
- **Virtual Reality**: Immersive VR audio
|
| 266 |
+
|
| 267 |
+
## Warranty & Support
|
| 268 |
+
|
| 269 |
+
### Warranty Coverage
|
| 270 |
+
- **Duration**: 18-month limited warranty
|
| 271 |
+
- **Coverage**: Manufacturing defects and material failures
|
| 272 |
+
- **Exclusions**: Physical damage, misuse, normal wear
|
| 273 |
+
|
| 274 |
+
### Customer Support
|
| 275 |
+
- **Website**: support.toomanycables.com/audio
|
| 276 |
+
- **Email**: support@toomanycables.com
|
| 277 |
+
- **Phone**: 1-800-TMC-HELP
|
| 278 |
+
- **Live Chat**: Available 24/7 on website
|
| 279 |
+
|
| 280 |
+
### Warranty Registration
|
| 281 |
+
Register for:
|
| 282 |
+
- Full warranty coverage
|
| 283 |
+
- Product support updates
|
| 284 |
+
- Technical assistance priority
|
| 285 |
+
- Software update notifications
|
| 286 |
+
|
| 287 |
+
---
|
| 288 |
+
|
| 289 |
+
**Model**: TMC-USBC-AUDIO-32BIT
|
| 290 |
+
**Manual Version**: 1.4
|
| 291 |
+
**Last Updated**: October 2025
|
| 292 |
+
**Document**: TMC-USBC-AUDIO-MANUAL-V1.4
|
knowledge_base/product_manuals/usb_c_cables.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# USB-C Cable Product Manual
|
| 2 |
+
|
| 3 |
+
## Product Overview
|
| 4 |
+
|
| 5 |
+
Too Many Cables USB-C cables provide high-speed data transfer, fast charging, and video output capabilities for all your USB-C enabled devices. Our premium cables feature gold-plated connectors, high-grade copper conductors, and durable construction backed by our lifetime warranty.
|
| 6 |
+
|
| 7 |
+
## Product Specifications
|
| 8 |
+
|
| 9 |
+
### TMC-USBC-100W-6FT (Premium USB-C Cable)
|
| 10 |
+
- **Length**: 6 feet (1.8 meters)
|
| 11 |
+
- **Power Delivery**: Up to 100W (20V/5A)
|
| 12 |
+
- **Data Transfer**: USB 3.2 Gen 2 (10 Gbps)
|
| 13 |
+
- **Video Support**: 4K@60Hz, 1440p@144Hz
|
| 14 |
+
- **Connector**: USB-C to USB-C, reversible
|
| 15 |
+
- **Material**: Braided nylon exterior, gold-plated connectors
|
| 16 |
+
- **Compatibility**: USB-C devices with Power Delivery support
|
| 17 |
+
|
| 18 |
+
### TMC-USBC-60W-3FT (Standard USB-C Cable)
|
| 19 |
+
- **Length**: 3 feet (0.9 meters)
|
| 20 |
+
- **Power Delivery**: Up to 60W (20V/3A)
|
| 21 |
+
- **Data Transfer**: USB 3.1 Gen 1 (5 Gbps)
|
| 22 |
+
- **Video Support**: 4K@30Hz
|
| 23 |
+
- **Connector**: USB-C to USB-C, reversible
|
| 24 |
+
- **Material**: PVC jacket, gold-plated connectors
|
| 25 |
+
- **Compatibility**: Most USB-C devices
|
| 26 |
+
|
| 27 |
+
### TMC-USBC-A-FAST (USB-A to USB-C Cable)
|
| 28 |
+
- **Length**: 6 feet (1.8 meters)
|
| 29 |
+
- **Power Delivery**: Up to 18W (9V/2A)
|
| 30 |
+
- **Data Transfer**: USB 3.0 (5 Gbps)
|
| 31 |
+
- **Video Support**: Not supported
|
| 32 |
+
- **Connector**: USB-A to USB-C
|
| 33 |
+
- **Material**: Braided exterior, gold-plated connectors
|
| 34 |
+
- **Compatibility**: USB-A ports to USB-C devices
|
| 35 |
+
|
| 36 |
+
## Compatibility Guide
|
| 37 |
+
|
| 38 |
+
### Compatible Devices
|
| 39 |
+
**Smartphones & Tablets:**
|
| 40 |
+
- iPhone 15 series and later
|
| 41 |
+
- Samsung Galaxy S8 and later
|
| 42 |
+
- Google Pixel 2 and later
|
| 43 |
+
- iPad Pro (2018 and later)
|
| 44 |
+
- Most Android devices with USB-C
|
| 45 |
+
|
| 46 |
+
**Laptops & Computers:**
|
| 47 |
+
- MacBook Pro (2016 and later)
|
| 48 |
+
- MacBook Air (2018 and later)
|
| 49 |
+
- Dell XPS series
|
| 50 |
+
- HP Spectre series
|
| 51 |
+
- Lenovo ThinkPad X1 series
|
| 52 |
+
- Surface Pro X and Surface Laptop Studio
|
| 53 |
+
|
| 54 |
+
**Gaming & Accessories:**
|
| 55 |
+
- Nintendo Switch and Switch OLED
|
| 56 |
+
- Steam Deck
|
| 57 |
+
- USB-C headphones and earbuds
|
| 58 |
+
- External drives and hubs
|
| 59 |
+
- Monitors with USB-C input
|
| 60 |
+
|
| 61 |
+
### Power Delivery Compatibility
|
| 62 |
+
**100W Cables Support:**
|
| 63 |
+
- MacBook Pro 16" (96W)
|
| 64 |
+
- Gaming laptops up to 100W
|
| 65 |
+
- High-power USB-C chargers
|
| 66 |
+
- Power banks with 100W output
|
| 67 |
+
|
| 68 |
+
**60W Cables Support:**
|
| 69 |
+
- MacBook Air (30W)
|
| 70 |
+
- MacBook Pro 13" (61W)
|
| 71 |
+
- Most smartphones and tablets
|
| 72 |
+
- Standard USB-C chargers
|
| 73 |
+
|
| 74 |
+
**18W USB-A Cables Support:**
|
| 75 |
+
- Quick Charge 3.0 devices
|
| 76 |
+
- Standard smartphone charging
|
| 77 |
+
- Tablet charging at reduced speed
|
| 78 |
+
|
| 79 |
+
## Performance Features
|
| 80 |
+
|
| 81 |
+
### Fast Charging
|
| 82 |
+
- **Power Delivery 3.0**: Negotiates optimal charging speed
|
| 83 |
+
- **Smart Charging**: Protects devices from overcharging
|
| 84 |
+
- **High Current Support**: Up to 5A for fastest charging
|
| 85 |
+
- **Temperature Management**: Built-in heat dissipation
|
| 86 |
+
|
| 87 |
+
### High-Speed Data Transfer
|
| 88 |
+
- **USB 3.2 Gen 2**: Up to 10 Gbps transfer speeds
|
| 89 |
+
- **Backward Compatible**: Works with USB 2.0 and 3.0
|
| 90 |
+
- **Low Latency**: Optimized for gaming and real-time applications
|
| 91 |
+
- **Error Correction**: Built-in data integrity protection
|
| 92 |
+
|
| 93 |
+
### Video Output
|
| 94 |
+
- **4K Resolution**: Support for 4K@60Hz displays
|
| 95 |
+
- **HDR Support**: High Dynamic Range video transmission
|
| 96 |
+
- **Multi-Stream**: Supports multiple displays via hubs
|
| 97 |
+
- **Audio Included**: Digital audio transmission with video
|
| 98 |
+
|
| 99 |
+
## Installation & Setup
|
| 100 |
+
|
| 101 |
+
### Basic Connection
|
| 102 |
+
1. **Identify Ports**: Locate USB-C ports on both devices
|
| 103 |
+
2. **Insert Cable**: USB-C connectors are reversible - either way works
|
| 104 |
+
3. **Verify Connection**: Look for charging indicator or connection notification
|
| 105 |
+
4. **Test Functionality**: Try data transfer or video output as needed
|
| 106 |
+
|
| 107 |
+
### For Charging
|
| 108 |
+
1. Connect cable to power adapter and device
|
| 109 |
+
2. Device should show charging indicator
|
| 110 |
+
3. For fast charging, use compatible PD charger
|
| 111 |
+
4. Charging speeds vary by device and charger capabilities
|
| 112 |
+
|
| 113 |
+
### For Data Transfer
|
| 114 |
+
1. Connect devices with cable
|
| 115 |
+
2. On phone/tablet: Select "File Transfer" or "MTP" mode
|
| 116 |
+
3. Devices should appear in file explorer
|
| 117 |
+
4. Transfer speeds depend on both devices' capabilities
|
| 118 |
+
|
| 119 |
+
### For Video Output
|
| 120 |
+
1. Connect device to monitor/TV with USB-C input
|
| 121 |
+
2. Device may automatically detect display
|
| 122 |
+
3. For manual setup: Go to display settings
|
| 123 |
+
4. Select appropriate resolution and refresh rate
|
| 124 |
+
|
| 125 |
+
## Troubleshooting
|
| 126 |
+
|
| 127 |
+
### Charging Issues
|
| 128 |
+
**Problem**: Device not charging
|
| 129 |
+
**Solutions**:
|
| 130 |
+
- Check that both connectors are fully inserted
|
| 131 |
+
- Try a different USB-C port if available
|
| 132 |
+
- Verify charger is working with another device
|
| 133 |
+
- Clean connectors with compressed air
|
| 134 |
+
- Try different orientation (flip cable connectors)
|
| 135 |
+
|
| 136 |
+
**Problem**: Slow charging
|
| 137 |
+
**Solutions**:
|
| 138 |
+
- Use original or certified high-wattage charger
|
| 139 |
+
- Check if device supports fast charging
|
| 140 |
+
- Close power-hungry apps while charging
|
| 141 |
+
- Ensure cable supports device's charging requirements
|
| 142 |
+
|
| 143 |
+
### Data Transfer Issues
|
| 144 |
+
**Problem**: Device not recognized
|
| 145 |
+
**Solutions**:
|
| 146 |
+
- Select correct USB mode on phone/tablet
|
| 147 |
+
- Install device drivers if on Windows
|
| 148 |
+
- Try different USB-C port
|
| 149 |
+
- Restart both devices
|
| 150 |
+
- Check cable for physical damage
|
| 151 |
+
|
| 152 |
+
**Problem**: Slow transfer speeds
|
| 153 |
+
**Solutions**:
|
| 154 |
+
- Verify both devices support USB 3.0+
|
| 155 |
+
- Close other applications during transfer
|
| 156 |
+
- Use USB-C port directly, not through hub
|
| 157 |
+
- Check cable specifications match requirements
|
| 158 |
+
|
| 159 |
+
### Video Output Issues
|
| 160 |
+
**Problem**: No video signal
|
| 161 |
+
**Solutions**:
|
| 162 |
+
- Verify device supports video output over USB-C
|
| 163 |
+
- Check monitor/TV input is set to correct USB-C port
|
| 164 |
+
- Try different resolution in display settings
|
| 165 |
+
- Ensure cable supports video (not all USB-C cables do)
|
| 166 |
+
- Update device graphics drivers
|
| 167 |
+
|
| 168 |
+
**Problem**: Poor video quality
|
| 169 |
+
**Solutions**:
|
| 170 |
+
- Check maximum resolution supported by cable
|
| 171 |
+
- Verify monitor's native resolution settings
|
| 172 |
+
- Adjust refresh rate in display settings
|
| 173 |
+
- Use shorter cable for better signal integrity
|
| 174 |
+
|
| 175 |
+
## Care & Maintenance
|
| 176 |
+
|
| 177 |
+
### Proper Handling
|
| 178 |
+
- **Gentle Insertion**: Don't force connectors into ports
|
| 179 |
+
- **Avoid Bending**: Don't bend cable at sharp angles near connectors
|
| 180 |
+
- **Strain Relief**: Use built-in strain relief, don't pull on cable
|
| 181 |
+
- **Storage**: Coil loosely when storing, avoid tight knots
|
| 182 |
+
|
| 183 |
+
### Cleaning
|
| 184 |
+
- **Connectors**: Use compressed air to remove dust and debris
|
| 185 |
+
- **Cable**: Wipe with slightly damp cloth, avoid harsh chemicals
|
| 186 |
+
- **Ports**: Clean device ports regularly with compressed air
|
| 187 |
+
- **Contacts**: Use isopropyl alcohol on cotton swab if needed
|
| 188 |
+
|
| 189 |
+
### Environmental Protection
|
| 190 |
+
- **Temperature**: Avoid extreme heat or cold storage
|
| 191 |
+
- **Moisture**: Keep dry, avoid liquid exposure
|
| 192 |
+
- **Sunlight**: Don't leave in direct sunlight for extended periods
|
| 193 |
+
- **Stress**: Avoid pinching, crushing, or excessive bending
|
| 194 |
+
|
| 195 |
+
## Technical Specifications
|
| 196 |
+
|
| 197 |
+
### Electrical Ratings
|
| 198 |
+
- **Voltage Range**: 5V to 20V (depending on model)
|
| 199 |
+
- **Current Rating**: Up to 5A (100W models)
|
| 200 |
+
- **Resistance**: <50mΩ for optimal power delivery
|
| 201 |
+
- **Insulation**: 2000V AC dielectric strength
|
| 202 |
+
|
| 203 |
+
### Data Specifications
|
| 204 |
+
- **Signal Integrity**: Meets USB-IF compliance standards
|
| 205 |
+
- **EMI/RFI**: Shielded construction reduces interference
|
| 206 |
+
- **Crosstalk**: Minimized through proper wire spacing
|
| 207 |
+
- **Impedance**: 90Ω ±15% differential impedance
|
| 208 |
+
|
| 209 |
+
### Physical Specifications
|
| 210 |
+
- **Connector**: USB Type-C (USB-C) certified
|
| 211 |
+
- **Housing**: Durable plastic with metal shell
|
| 212 |
+
- **Cable Gauge**: 28/24 AWG (data/power)
|
| 213 |
+
- **Bend Radius**: Minimum 10x cable diameter
|
| 214 |
+
|
| 215 |
+
## Warranty Information
|
| 216 |
+
|
| 217 |
+
### Lifetime Warranty Coverage
|
| 218 |
+
All Too Many Cables USB-C cables are covered by our comprehensive lifetime warranty against:
|
| 219 |
+
- Manufacturing defects
|
| 220 |
+
- Material failures
|
| 221 |
+
- Performance degradation under normal use
|
| 222 |
+
- Connector wear from regular insertion/removal
|
| 223 |
+
|
| 224 |
+
### Warranty Exclusions
|
| 225 |
+
- Physical damage from misuse or accidents
|
| 226 |
+
- Damage from liquids, pets, or extreme conditions
|
| 227 |
+
- Commercial/industrial use beyond specifications
|
| 228 |
+
- Modification or repair attempts
|
| 229 |
+
|
| 230 |
+
### Claim Process
|
| 231 |
+
1. Contact customer service with order information
|
| 232 |
+
2. Describe the issue and troubleshooting attempted
|
| 233 |
+
3. Receive replacement authorization if covered
|
| 234 |
+
4. Replacement shipped same day at no cost
|
| 235 |
+
5. No need to return defective cable unless requested
|
| 236 |
+
|
| 237 |
+
## Customer Support
|
| 238 |
+
|
| 239 |
+
For technical support, compatibility questions, or warranty claims:
|
| 240 |
+
|
| 241 |
+
- **AI Chat**: Available 24/7 on our website
|
| 242 |
+
- **Email**: support@toomanycables.com
|
| 243 |
+
- **Phone**: 1-800-TMC-HELP
|
| 244 |
+
- **Hours**: Monday-Friday 8 AM - 8 PM EST
|
| 245 |
+
|
| 246 |
+
## Safety Information
|
| 247 |
+
|
| 248 |
+
### Important Safety Warnings
|
| 249 |
+
- Use only with compatible devices and chargers
|
| 250 |
+
- Do not exceed maximum power ratings
|
| 251 |
+
- Discontinue use if cable becomes hot during operation
|
| 252 |
+
- Keep away from heat sources and sharp objects
|
| 253 |
+
- Do not use damaged cables
|
| 254 |
+
|
| 255 |
+
### Regulatory Compliance
|
| 256 |
+
- FCC Part 15 Class B (EMI compliance)
|
| 257 |
+
- USB-IF certification
|
| 258 |
+
- RoHS compliant (lead-free)
|
| 259 |
+
- UL recognized components
|
| 260 |
+
|
| 261 |
+
---
|
| 262 |
+
|
| 263 |
+
*Manual version 2.1 - Updated September 2024*
|
| 264 |
+
*For the latest version, visit toomanycables.com/support*
|
knowledge_base/product_manuals/usb_c_hdmi_adapter.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# USB-C to HDMI Adapter Product Manual
|
| 2 |
+
|
| 3 |
+
## Product Overview
|
| 4 |
+
|
| 5 |
+
Too Many Cables USB-C to HDMI Adapter provides a simple plug-and-play solution for connecting USB-C enabled devices to HDMI displays, monitors, and TVs. Ultra-compact design delivers 4K video output with high-quality digital audio transmission.
|
| 6 |
+
|
| 7 |
+
## Product Specifications
|
| 8 |
+
|
| 9 |
+
### TMC-USBC-HDMI-4K
|
| 10 |
+
- **Video Output**: Up to 4K@60Hz (3840x2160)
|
| 11 |
+
- **Audio Support**: Multi-channel digital audio pass-through
|
| 12 |
+
- **Connector Type**: USB-C male to HDMI female
|
| 13 |
+
- **Compatibility**: USB-C devices with DisplayPort Alt Mode
|
| 14 |
+
- **Dimensions**: 2.4" x 0.8" x 0.4" (61 x 20 x 10mm)
|
| 15 |
+
- **Weight**: 0.7 oz (20g)
|
| 16 |
+
- **Material**: Aluminum alloy housing with gold-plated connectors
|
| 17 |
+
- **Cable Length**: Built-in 4-inch cable
|
| 18 |
+
- **Power**: Bus-powered (no external power required)
|
| 19 |
+
- **Operating Temperature**: 32°F to 104°F (0°C to 40°C)
|
| 20 |
+
|
| 21 |
+
## Video Output Specifications
|
| 22 |
+
|
| 23 |
+
### Supported Resolutions
|
| 24 |
+
- **4K Ultra HD**: 3840x2160 @ 60Hz, 30Hz
|
| 25 |
+
- **1440p QHD**: 2560x1440 @ 60Hz, 144Hz
|
| 26 |
+
- **1080p Full HD**: 1920x1080 @ 60Hz, 120Hz, 144Hz
|
| 27 |
+
- **1080i**: 1920x1080i @ 60Hz
|
| 28 |
+
- **720p HD**: 1280x720 @ 60Hz
|
| 29 |
+
- **Legacy Formats**: 1024x768, 800x600, 640x480
|
| 30 |
+
|
| 31 |
+
### Color Support
|
| 32 |
+
- **Color Depth**: 8-bit, 10-bit, 12-bit
|
| 33 |
+
- **Color Space**: RGB, YUV 4:4:4, YUV 4:2:2, YUV 4:2:0
|
| 34 |
+
- **HDR Support**: HDR10 (device dependent)
|
| 35 |
+
- **Color Standards**: sRGB, Adobe RGB, DCI-P3
|
| 36 |
+
|
| 37 |
+
### Audio Specifications
|
| 38 |
+
- **Audio Formats**: PCM, Dolby Digital, DTS
|
| 39 |
+
- **Sample Rates**: 32kHz, 44.1kHz, 48kHz, 96kHz
|
| 40 |
+
- **Bit Depth**: 16-bit, 24-bit
|
| 41 |
+
- **Channels**: Stereo, 5.1, 7.1 surround sound
|
| 42 |
+
- **Audio Delay**: <40ms latency
|
| 43 |
+
|
| 44 |
+
## Compatibility Guide
|
| 45 |
+
|
| 46 |
+
### Compatible Source Devices
|
| 47 |
+
|
| 48 |
+
**Laptops:**
|
| 49 |
+
- MacBook Pro (2016 and later)
|
| 50 |
+
- MacBook Air (2018 and later)
|
| 51 |
+
- Dell XPS 13/15/17 series
|
| 52 |
+
- HP Spectre x360 series
|
| 53 |
+
- Lenovo ThinkPad X1 series
|
| 54 |
+
- Microsoft Surface Laptop series
|
| 55 |
+
- ASUS ZenBook series
|
| 56 |
+
- Most Windows laptops with USB-C/Thunderbolt
|
| 57 |
+
|
| 58 |
+
**Tablets:**
|
| 59 |
+
- iPad Pro (2018 and later)
|
| 60 |
+
- iPad Air (4th generation and later)
|
| 61 |
+
- Samsung Galaxy Tab S series
|
| 62 |
+
- Microsoft Surface Pro (USB-C models)
|
| 63 |
+
|
| 64 |
+
**Smartphones:**
|
| 65 |
+
- Samsung Galaxy S8 and later
|
| 66 |
+
- Google Pixel 2 and later
|
| 67 |
+
- OnePlus 6 and later
|
| 68 |
+
- Huawei P20 and later (where available)
|
| 69 |
+
- Motorola Moto series with USB-C
|
| 70 |
+
|
| 71 |
+
**Other Devices:**
|
| 72 |
+
- Nintendo Switch
|
| 73 |
+
- Steam Deck
|
| 74 |
+
- Chrome OS devices
|
| 75 |
+
- Android TV boxes with USB-C
|
| 76 |
+
|
| 77 |
+
### Display Compatibility
|
| 78 |
+
- **TVs**: All HDMI-equipped TVs (HD, 4K, 8K)
|
| 79 |
+
- **Monitors**: Computer monitors with HDMI input
|
| 80 |
+
- **Projectors**: HDMI-equipped projectors
|
| 81 |
+
- **Capture Devices**: HDMI capture cards and recorders
|
| 82 |
+
|
| 83 |
+
## Usage Instructions
|
| 84 |
+
|
| 85 |
+
### Basic Connection
|
| 86 |
+
1. Connect adapter's USB-C plug to your device
|
| 87 |
+
2. Connect HDMI cable from adapter to display
|
| 88 |
+
3. Display should automatically detect signal
|
| 89 |
+
4. Adjust display settings if needed
|
| 90 |
+
|
| 91 |
+
### Display Configuration
|
| 92 |
+
|
| 93 |
+
**Windows:**
|
| 94 |
+
1. Right-click desktop → Display Settings
|
| 95 |
+
2. Choose "Extend" or "Duplicate" display
|
| 96 |
+
3. Select resolution and refresh rate
|
| 97 |
+
4. Apply settings
|
| 98 |
+
|
| 99 |
+
**macOS:**
|
| 100 |
+
1. System Preferences → Displays
|
| 101 |
+
2. Choose "Mirror" or "Extend" desktop
|
| 102 |
+
3. Select optimal resolution
|
| 103 |
+
4. Adjust color profile if needed
|
| 104 |
+
|
| 105 |
+
**Android/Chrome OS:**
|
| 106 |
+
1. Settings → Display
|
| 107 |
+
2. Select "Cast screen" or "Screen mirroring"
|
| 108 |
+
3. Choose display mode (mirror/extend)
|
| 109 |
+
4. Configure resolution settings
|
| 110 |
+
|
| 111 |
+
## Performance Optimization
|
| 112 |
+
|
| 113 |
+
### Maximum Quality Settings
|
| 114 |
+
- **4K Output**: Use high-quality HDMI cables rated for 4K@60Hz
|
| 115 |
+
- **Refresh Rate**: Set to highest supported by both device and display
|
| 116 |
+
- **Color Settings**: Enable HDR if supported by both devices
|
| 117 |
+
- **Audio Quality**: Use highest available audio format
|
| 118 |
+
|
| 119 |
+
### Troubleshooting Performance
|
| 120 |
+
- **Update Drivers**: Ensure graphics drivers are current
|
| 121 |
+
- **Cable Quality**: Use HDMI 2.0 or higher rated cables
|
| 122 |
+
- **Power Settings**: Disable power saving for USB ports
|
| 123 |
+
- **Resolution**: Try lower resolution if experiencing issues
|
| 124 |
+
|
| 125 |
+
## Technical Specifications
|
| 126 |
+
|
| 127 |
+
### Electrical Requirements
|
| 128 |
+
- **Power Consumption**: <2W
|
| 129 |
+
- **Power Source**: USB-C bus power
|
| 130 |
+
- **Current Draw**: <400mA @ 5V
|
| 131 |
+
- **No External Power**: Required
|
| 132 |
+
|
| 133 |
+
### Signal Specifications
|
| 134 |
+
- **HDMI Version**: HDMI 2.0 compatible
|
| 135 |
+
- **Bandwidth**: Up to 18 Gbps
|
| 136 |
+
- **HDCP Support**: HDCP 2.2, HDCP 1.4
|
| 137 |
+
- **Signal Type**: Digital video and audio
|
| 138 |
+
|
| 139 |
+
### Physical Specifications
|
| 140 |
+
- **Length**: 2.4 inches (61mm) including cable
|
| 141 |
+
- **Width**: 0.8 inches (20mm)
|
| 142 |
+
- **Thickness**: 0.4 inches (10mm)
|
| 143 |
+
- **Weight**: 0.7 oz (20g)
|
| 144 |
+
- **Housing**: Aluminum alloy
|
| 145 |
+
- **Connectors**: Gold-plated for corrosion resistance
|
| 146 |
+
|
| 147 |
+
### Environmental Conditions
|
| 148 |
+
- **Operating Temperature**: 32°F to 104°F (0°C to 40°C)
|
| 149 |
+
- **Storage Temperature**: -4°F to 140°F (-20°C to 60°C)
|
| 150 |
+
- **Humidity**: 10% to 95% RH (non-condensing)
|
| 151 |
+
- **Altitude**: Up to 6,600 feet (2,000m)
|
| 152 |
+
|
| 153 |
+
## Troubleshooting
|
| 154 |
+
|
| 155 |
+
### Common Issues
|
| 156 |
+
|
| 157 |
+
**No Display Output**
|
| 158 |
+
- Verify device supports video output via USB-C
|
| 159 |
+
- Check HDMI cable connection
|
| 160 |
+
- Try different HDMI cable
|
| 161 |
+
- Restart source device
|
| 162 |
+
- Check display input selection
|
| 163 |
+
|
| 164 |
+
**Poor Video Quality**
|
| 165 |
+
- Use high-quality HDMI cable
|
| 166 |
+
- Check resolution settings
|
| 167 |
+
- Update graphics drivers
|
| 168 |
+
- Verify cable supports desired resolution
|
| 169 |
+
- Try different display if available
|
| 170 |
+
|
| 171 |
+
**No Audio Output**
|
| 172 |
+
- Check audio output settings on device
|
| 173 |
+
- Verify HDMI audio is enabled
|
| 174 |
+
- Try different audio format
|
| 175 |
+
- Check display audio settings
|
| 176 |
+
- Update audio drivers
|
| 177 |
+
|
| 178 |
+
**Intermittent Connection**
|
| 179 |
+
- Check USB-C connection is secure
|
| 180 |
+
- Try different USB-C port
|
| 181 |
+
- Inspect cable for damage
|
| 182 |
+
- Clean connectors
|
| 183 |
+
- Test with different HDMI cable
|
| 184 |
+
|
| 185 |
+
**4K Resolution Issues**
|
| 186 |
+
- Verify device supports 4K output
|
| 187 |
+
- Use HDMI 2.0 or higher cable
|
| 188 |
+
- Check display 4K compatibility
|
| 189 |
+
- Try 4K@30Hz instead of 60Hz
|
| 190 |
+
- Update device firmware
|
| 191 |
+
|
| 192 |
+
### Advanced Troubleshooting
|
| 193 |
+
|
| 194 |
+
**Driver Issues:**
|
| 195 |
+
- Download latest graphics drivers
|
| 196 |
+
- Restart device after driver installation
|
| 197 |
+
- Check Windows Update for driver updates
|
| 198 |
+
|
| 199 |
+
**Compatibility Problems:**
|
| 200 |
+
- Verify USB-C port supports video (some are charging only)
|
| 201 |
+
- Check if device requires specific adapter protocols
|
| 202 |
+
- Try adapter with known compatible device
|
| 203 |
+
|
| 204 |
+
**Performance Optimization:**
|
| 205 |
+
- Close unnecessary applications
|
| 206 |
+
- Adjust power settings for performance
|
| 207 |
+
- Check thermal throttling
|
| 208 |
+
- Monitor CPU/GPU usage
|
| 209 |
+
|
| 210 |
+
## Care & Maintenance
|
| 211 |
+
|
| 212 |
+
### Cleaning Instructions
|
| 213 |
+
- **Disconnect**: Remove from devices before cleaning
|
| 214 |
+
- **Dry Cloth**: Use lint-free cloth for exterior
|
| 215 |
+
- **Connector Care**: Keep connectors clean and dry
|
| 216 |
+
- **No Liquids**: Avoid moisture in connectors
|
| 217 |
+
|
| 218 |
+
### Storage Recommendations
|
| 219 |
+
- **Protective Case**: Store in provided pouch when not in use
|
| 220 |
+
- **Avoid Bending**: Don't stress the built-in cable
|
| 221 |
+
- **Temperature**: Store in cool, dry location
|
| 222 |
+
- **Avoid Pressure**: Don't place heavy objects on adapter
|
| 223 |
+
|
| 224 |
+
## What's Included
|
| 225 |
+
|
| 226 |
+
### Package Contents
|
| 227 |
+
- 1x TMC USB-C to HDMI Adapter
|
| 228 |
+
- 1x Protective carrying pouch
|
| 229 |
+
- 1x Quick Start Guide
|
| 230 |
+
- 1x Warranty Registration Card
|
| 231 |
+
|
| 232 |
+
## Warranty & Support
|
| 233 |
+
|
| 234 |
+
### Warranty Coverage
|
| 235 |
+
- **Duration**: 18-month limited warranty
|
| 236 |
+
- **Coverage**: Manufacturing defects and material failures
|
| 237 |
+
- **Exclusions**: Physical damage, misuse, normal wear
|
| 238 |
+
|
| 239 |
+
### Customer Support
|
| 240 |
+
- **Website**: support.toomanycables.com/adapters
|
| 241 |
+
- **Email**: support@toomanycables.com
|
| 242 |
+
- **Phone**: 1-800-TMC-HELP
|
| 243 |
+
- **Live Chat**: Available 24/7 on website
|
| 244 |
+
|
| 245 |
+
### Warranty Registration
|
| 246 |
+
Register within 30 days for:
|
| 247 |
+
- Full warranty coverage
|
| 248 |
+
- Product support notifications
|
| 249 |
+
- Technical assistance priority
|
| 250 |
+
|
| 251 |
+
---
|
| 252 |
+
|
| 253 |
+
**Model**: TMC-USBC-HDMI-4K
|
| 254 |
+
**Manual Version**: 1.3
|
| 255 |
+
**Last Updated**: October 2025
|
| 256 |
+
**Document**: TMC-HDMI-ADAPTER-MANUAL-V1.3
|
knowledge_base/product_manuals/usb_c_hub_adapter.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# USB-C Hub Adapter Product Manual
|
| 2 |
+
|
| 3 |
+
## Product Overview
|
| 4 |
+
|
| 5 |
+
Too Many Cables 7-in-1 USB-C Hub Adapter expands a single USB-C port into multiple connectivity options including 4K HDMI, USB-A ports, SD card slots, Ethernet, and USB-C Power Delivery pass-through. Perfect for laptops, tablets, and phones with USB-C ports.
|
| 6 |
+
|
| 7 |
+
## Product Specifications
|
| 8 |
+
|
| 9 |
+
### TMC-USBC-HUB-7IN1
|
| 10 |
+
- **Port Configuration**:
|
| 11 |
+
- 1x HDMI 2.0 port (4K@60Hz)
|
| 12 |
+
- 3x USB-A 3.0 ports (5 Gbps)
|
| 13 |
+
- 1x USB-C PD port (100W pass-through)
|
| 14 |
+
- 1x SD card slot (UHS-I)
|
| 15 |
+
- 1x MicroSD card slot (UHS-I)
|
| 16 |
+
- 1x Gigabit Ethernet port (10/100/1000 Mbps)
|
| 17 |
+
- **Dimensions**: 4.7" x 1.4" x 0.6" (120 x 35 x 15mm)
|
| 18 |
+
- **Weight**: 2.1 oz (60g)
|
| 19 |
+
- **Material**: Premium aluminum alloy housing
|
| 20 |
+
- **Cable**: 6-inch integrated USB-C cable
|
| 21 |
+
- **LED Indicators**: Power and data activity LEDs
|
| 22 |
+
- **Operating Temperature**: 32°F to 104°F (0°C to 40°C)
|
| 23 |
+
- **Certifications**: CE, FCC, RoHS compliant
|
| 24 |
+
|
| 25 |
+
## Detailed Port Specifications
|
| 26 |
+
|
| 27 |
+
### HDMI 2.0 Port
|
| 28 |
+
- **Maximum Resolution**: 4K@60Hz (3840x2160)
|
| 29 |
+
- **Supported Formats**: 1080p@120Hz, 1440p@60Hz, 4K@30Hz, 4K@60Hz
|
| 30 |
+
- **Color Depth**: 8-bit, 10-bit, 12-bit
|
| 31 |
+
- **Audio Support**: Multi-channel audio pass-through
|
| 32 |
+
- **HDR Support**: HDR10, HDR10+
|
| 33 |
+
- **Compatibility**: All HDMI displays and TVs
|
| 34 |
+
|
| 35 |
+
### USB-A 3.0 Ports (3x)
|
| 36 |
+
- **Data Transfer**: Up to 5 Gbps (USB 3.0/3.1 Gen 1)
|
| 37 |
+
- **Power Output**: 5V/0.9A per port (4.5W total across all USB-A ports)
|
| 38 |
+
- **Compatibility**: USB 3.0, 2.0, 1.1 devices
|
| 39 |
+
- **Hot Plug**: Support for hot-swappable devices
|
| 40 |
+
- **Backwards Compatible**: Works with all USB devices
|
| 41 |
+
|
| 42 |
+
### USB-C PD Pass-Through Port
|
| 43 |
+
- **Power Delivery**: Up to 100W pass-through charging
|
| 44 |
+
- **Data Transfer**: USB 3.0 speeds (5 Gbps)
|
| 45 |
+
- **Compatibility**: USB-C PD chargers and devices
|
| 46 |
+
- **Charging**: Allows laptop charging while using hub
|
| 47 |
+
- **Power Efficiency**: >85% power pass-through efficiency
|
| 48 |
+
|
| 49 |
+
### SD Card Slots
|
| 50 |
+
- **SD Card Slot**: UHS-I, up to 104 MB/s
|
| 51 |
+
- **MicroSD Slot**: UHS-I, up to 104 MB/s
|
| 52 |
+
- **Supported Formats**: SD, SDHC, SDXC (up to 2TB)
|
| 53 |
+
- **Simultaneous Access**: Both slots can be used at same time
|
| 54 |
+
- **Hot Swap**: Cards can be inserted/removed while hub is connected
|
| 55 |
+
|
| 56 |
+
### Gigabit Ethernet Port
|
| 57 |
+
- **Speed**: 10/100/1000 Mbps auto-negotiation
|
| 58 |
+
- **Connector**: RJ45 standard Ethernet
|
| 59 |
+
- **LED Indicators**: Link/Activity status
|
| 60 |
+
- **Compatibility**: All standard Ethernet networks
|
| 61 |
+
- **Driver**: Plug-and-play, no drivers needed
|
| 62 |
+
|
| 63 |
+
## Compatibility Guide
|
| 64 |
+
|
| 65 |
+
### Compatible Laptops
|
| 66 |
+
**MacBook Series:**
|
| 67 |
+
- MacBook Pro 13" (2016 and later)
|
| 68 |
+
- MacBook Pro 14" and 16" (M1/M2/M3)
|
| 69 |
+
- MacBook Air 13" (2018 and later)
|
| 70 |
+
- MacBook Air 15" (M2/M3)
|
| 71 |
+
|
| 72 |
+
**Windows Laptops:**
|
| 73 |
+
- Dell XPS 13/15/17 series
|
| 74 |
+
- HP Spectre x360 series
|
| 75 |
+
- HP EliteBook series
|
| 76 |
+
- Lenovo ThinkPad X1 Carbon
|
| 77 |
+
- Lenovo Yoga series
|
| 78 |
+
- Microsoft Surface Laptop series
|
| 79 |
+
- ASUS ZenBook series
|
| 80 |
+
|
| 81 |
+
**Chromebooks:**
|
| 82 |
+
- Google Pixelbook
|
| 83 |
+
- Samsung Galaxy Chromebook
|
| 84 |
+
- HP Chromebook x360
|
| 85 |
+
- Most Chromebooks with USB-C
|
| 86 |
+
|
| 87 |
+
### Compatible Tablets
|
| 88 |
+
- iPad Pro (2018 and later)
|
| 89 |
+
- iPad Air (4th generation and later)
|
| 90 |
+
- Samsung Galaxy Tab S series
|
| 91 |
+
- Microsoft Surface Pro (with USB-C)
|
| 92 |
+
|
| 93 |
+
### Compatible Phones
|
| 94 |
+
- Samsung Galaxy S20 and later
|
| 95 |
+
- Google Pixel 3a and later (with USB-C)
|
| 96 |
+
- OnePlus 7 and later
|
| 97 |
+
- Note: Phone compatibility may vary by model
|
| 98 |
+
|
| 99 |
+
## Usage Instructions
|
| 100 |
+
|
| 101 |
+
### Initial Setup
|
| 102 |
+
1. Connect hub's USB-C cable to your device's USB-C port
|
| 103 |
+
2. Hub powers on automatically (LED indicator lights up)
|
| 104 |
+
3. Connect desired peripherals to hub ports
|
| 105 |
+
4. Devices should be recognized automatically
|
| 106 |
+
|
| 107 |
+
### HDMI Display Connection
|
| 108 |
+
1. Connect HDMI cable from hub to monitor/TV
|
| 109 |
+
2. Display should be detected automatically
|
| 110 |
+
3. Adjust display settings in system preferences if needed
|
| 111 |
+
4. Supports mirroring and extended desktop modes
|
| 112 |
+
|
| 113 |
+
### Power Delivery Usage
|
| 114 |
+
1. Connect USB-C charger to hub's PD port
|
| 115 |
+
2. Hub passes power to connected laptop/device
|
| 116 |
+
3. Device charges while hub functions normally
|
| 117 |
+
4. Use original charger for best performance
|
| 118 |
+
|
| 119 |
+
### Ethernet Connection
|
| 120 |
+
1. Connect Ethernet cable to hub's RJ45 port
|
| 121 |
+
2. Network connection established automatically
|
| 122 |
+
3. LED shows link status and activity
|
| 123 |
+
4. Provides stable wired internet connection
|
| 124 |
+
|
| 125 |
+
## Performance Optimization
|
| 126 |
+
|
| 127 |
+
### Maximum Performance Tips
|
| 128 |
+
- **Use Quality Cables**: High-quality HDMI and USB cables for best results
|
| 129 |
+
- **Adequate Power**: Use appropriate wattage charger for your device
|
| 130 |
+
- **Heat Management**: Ensure good ventilation around hub
|
| 131 |
+
- **Update Drivers**: Keep device drivers updated for optimal compatibility
|
| 132 |
+
|
| 133 |
+
### Data Transfer Optimization
|
| 134 |
+
- **USB 3.0 Devices**: Use USB 3.0 storage devices for maximum speed
|
| 135 |
+
- **SD Card Performance**: Use high-speed SD cards (Class 10 or UHS-I)
|
| 136 |
+
- **Ethernet Speed**: Use Cat 6 cables for Gigabit speeds
|
| 137 |
+
- **Avoid Overloading**: Don't exceed total power budget
|
| 138 |
+
|
| 139 |
+
## Technical Specifications
|
| 140 |
+
|
| 141 |
+
### Power Requirements
|
| 142 |
+
- **Input Power**: 5V from USB-C host device
|
| 143 |
+
- **Power Consumption**: 2W (hub operation)
|
| 144 |
+
- **USB-A Power Budget**: 4.5W total across all USB-A ports
|
| 145 |
+
- **PD Pass-Through**: Up to 100W (minus 15W hub overhead)
|
| 146 |
+
|
| 147 |
+
### Data Transfer Rates
|
| 148 |
+
- **USB-A Ports**: 5 Gbps each (shared bandwidth)
|
| 149 |
+
- **USB-C Port**: 5 Gbps
|
| 150 |
+
- **SD Cards**: Up to 104 MB/s (UHS-I)
|
| 151 |
+
- **Ethernet**: Up to 1000 Mbps
|
| 152 |
+
- **HDMI**: Up to 18 Gbps bandwidth
|
| 153 |
+
|
| 154 |
+
### Environmental Specifications
|
| 155 |
+
- **Operating Temperature**: 32°F to 104°F (0°C to 40°C)
|
| 156 |
+
- **Storage Temperature**: -4°F to 140°F (-20°C to 60°C)
|
| 157 |
+
- **Humidity**: 10% to 90% RH (non-condensing)
|
| 158 |
+
- **Altitude**: Up to 6,600 feet (2,000m)
|
| 159 |
+
|
| 160 |
+
## Troubleshooting
|
| 161 |
+
|
| 162 |
+
### Common Issues
|
| 163 |
+
|
| 164 |
+
**Hub Not Recognized**
|
| 165 |
+
- Check USB-C connection is secure
|
| 166 |
+
- Try different USB-C port on device
|
| 167 |
+
- Restart connected device
|
| 168 |
+
- Verify device supports USB-C data
|
| 169 |
+
|
| 170 |
+
**HDMI Display Not Working**
|
| 171 |
+
- Check HDMI cable connections
|
| 172 |
+
- Try different HDMI cable
|
| 173 |
+
- Verify display supports resolution
|
| 174 |
+
- Update graphics drivers
|
| 175 |
+
|
| 176 |
+
**USB Devices Not Working**
|
| 177 |
+
- Check USB device compatibility
|
| 178 |
+
- Try connecting device directly to test
|
| 179 |
+
- Verify power requirements
|
| 180 |
+
- Use powered USB hub if needed
|
| 181 |
+
|
| 182 |
+
**Ethernet Not Connecting**
|
| 183 |
+
- Check Ethernet cable connection
|
| 184 |
+
- Verify network settings
|
| 185 |
+
- Try different Ethernet cable
|
| 186 |
+
- Check router/switch status
|
| 187 |
+
|
| 188 |
+
**Charging Not Working**
|
| 189 |
+
- Verify charger wattage is adequate
|
| 190 |
+
- Check USB-C PD charger compatibility
|
| 191 |
+
- Try original device charger
|
| 192 |
+
- Ensure hub PD port is used
|
| 193 |
+
|
| 194 |
+
### Advanced Troubleshooting
|
| 195 |
+
- **Driver Updates**: Install latest USB-C and graphics drivers
|
| 196 |
+
- **Power Management**: Disable USB selective suspend in power settings
|
| 197 |
+
- **Display Settings**: Check resolution and refresh rate settings
|
| 198 |
+
- **Network Configuration**: Verify Ethernet adapter settings
|
| 199 |
+
|
| 200 |
+
## Safety Information
|
| 201 |
+
|
| 202 |
+
### Important Safety Notes
|
| 203 |
+
- **Heat Dissipation**: Hub may become warm during use - this is normal
|
| 204 |
+
- **Water Protection**: Keep hub away from liquids
|
| 205 |
+
- **Ventilation**: Don't block ventilation holes
|
| 206 |
+
- **Cable Stress**: Avoid bending USB-C cable excessively
|
| 207 |
+
|
| 208 |
+
### Certifications
|
| 209 |
+
- **CE Marking**: European Conformity
|
| 210 |
+
- **FCC Certified**: Electromagnetic compatibility
|
| 211 |
+
- **RoHS Compliant**: Restriction of hazardous substances
|
| 212 |
+
|
| 213 |
+
## What's Included
|
| 214 |
+
|
| 215 |
+
### Package Contents
|
| 216 |
+
- 1x TMC 7-in-1 USB-C Hub
|
| 217 |
+
- 1x Quick Start Guide
|
| 218 |
+
- 1x Warranty Registration Card
|
| 219 |
+
- 1x Premium carrying pouch
|
| 220 |
+
|
| 221 |
+
## Warranty & Support
|
| 222 |
+
|
| 223 |
+
### Warranty Coverage
|
| 224 |
+
- **Duration**: 2-year limited warranty
|
| 225 |
+
- **Coverage**: Manufacturing defects and material failures
|
| 226 |
+
- **Exclusions**: Physical damage, misuse, normal wear
|
| 227 |
+
|
| 228 |
+
### Customer Support
|
| 229 |
+
- **Website**: support.toomanycables.com/hub
|
| 230 |
+
- **Email**: support@toomanycables.com
|
| 231 |
+
- **Phone**: 1-800-TMC-HELP
|
| 232 |
+
- **Live Chat**: Available 24/7 on website
|
| 233 |
+
- **Support Hours**: Monday-Friday 8AM-8PM EST
|
| 234 |
+
|
| 235 |
+
### Warranty Registration
|
| 236 |
+
Register your product for:
|
| 237 |
+
- Full warranty coverage
|
| 238 |
+
- Product update notifications
|
| 239 |
+
- Priority technical support
|
| 240 |
+
|
| 241 |
+
---
|
| 242 |
+
|
| 243 |
+
**Model**: TMC-USBC-HUB-7IN1
|
| 244 |
+
**Manual Version**: 1.6
|
| 245 |
+
**Last Updated**: October 2025
|
| 246 |
+
**Document**: TMC-HUB-MANUAL-V1.6
|
knowledge_base/product_manuals/wireless_charging_pad.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Wireless Charging Pad Product Manual
|
| 2 |
+
|
| 3 |
+
## Product Overview
|
| 4 |
+
|
| 5 |
+
Too Many Cables Wireless Charging Pad delivers fast 15W wireless charging for Qi-enabled devices with intelligent charging control, LED status indicators, and a premium non-slip design for safe and efficient wireless power delivery.
|
| 6 |
+
|
| 7 |
+
## Product Specifications
|
| 8 |
+
|
| 9 |
+
### TMC-WIRELESS-15W-QI
|
| 10 |
+
- **Maximum Output**: 15W (fast wireless charging)
|
| 11 |
+
- **Charging Standards**: Qi 1.2.4 (EPP), Qi 1.1 (BPP)
|
| 12 |
+
- **Charging Modes**:
|
| 13 |
+
- 15W (compatible phones in fast mode)
|
| 14 |
+
- 10W (Samsung fast charging)
|
| 15 |
+
- 7.5W (iPhone fast charging)
|
| 16 |
+
- 5W (standard Qi charging)
|
| 17 |
+
- **Input**: USB-C, 5V/3A, 9V/2A (QC 3.0/PD compatible)
|
| 18 |
+
- **Dimensions**: 4.3" diameter x 0.4" height (110mm x 10mm)
|
| 19 |
+
- **Weight**: 4.2 oz (120g)
|
| 20 |
+
- **Material**: Premium aluminum top, ABS plastic base
|
| 21 |
+
- **LED Indicator**: Multi-color status LED
|
| 22 |
+
- **Safety Features**: Over-temperature, over-voltage, foreign object detection
|
| 23 |
+
- **Certifications**: Qi Certified, FCC, CE, RoHS
|
| 24 |
+
|
| 25 |
+
## Compatibility Guide
|
| 26 |
+
|
| 27 |
+
### Fast Charging Compatible (15W)
|
| 28 |
+
**Android Phones:**
|
| 29 |
+
- Samsung Galaxy S21/S22/S23/S24 series
|
| 30 |
+
- Google Pixel 4 and later
|
| 31 |
+
- OnePlus 8 and later
|
| 32 |
+
- LG V30 and later
|
| 33 |
+
- Sony Xperia 1 II and later
|
| 34 |
+
|
| 35 |
+
### iPhone Fast Charging (7.5W)
|
| 36 |
+
- iPhone 8 and later
|
| 37 |
+
- iPhone SE (2nd/3rd generation)
|
| 38 |
+
- iPhone X/XR/XS series
|
| 39 |
+
- iPhone 11/12/13/14/15 series
|
| 40 |
+
|
| 41 |
+
### Standard Charging (5W)
|
| 42 |
+
- Most Qi-enabled devices
|
| 43 |
+
- Older wireless charging phones
|
| 44 |
+
- AirPods with wireless charging case
|
| 45 |
+
- Samsung Galaxy Buds series
|
| 46 |
+
|
| 47 |
+
### Case Compatibility
|
| 48 |
+
- **Compatible**: Cases up to 5mm thick (non-metal)
|
| 49 |
+
- **Remove Cases**: Metal cases, magnetic mounts, credit cards
|
| 50 |
+
- **Recommended**: Official phone cases, thin plastic/silicone cases
|
| 51 |
+
|
| 52 |
+
## Usage Instructions
|
| 53 |
+
|
| 54 |
+
### Initial Setup
|
| 55 |
+
1. Connect USB-C cable to charging pad
|
| 56 |
+
2. Connect other end to QC 3.0 or PD wall adapter (included)
|
| 57 |
+
3. Place charging pad on flat, stable surface
|
| 58 |
+
4. LED should show solid blue (ready to charge)
|
| 59 |
+
|
| 60 |
+
### Charging Your Device
|
| 61 |
+
1. Remove metal cases and objects from phone
|
| 62 |
+
2. Place device center of charging pad
|
| 63 |
+
3. LED changes to indicate charging status
|
| 64 |
+
4. Charging begins automatically
|
| 65 |
+
5. LED shows green when fully charged
|
| 66 |
+
|
| 67 |
+
### Optimal Positioning
|
| 68 |
+
- **Center Alignment**: Place device center of pad for best efficiency
|
| 69 |
+
- **Portrait/Landscape**: Both orientations supported
|
| 70 |
+
- **Multiple Devices**: Charge one device at a time for best performance
|
| 71 |
+
|
| 72 |
+
## LED Status Indicators
|
| 73 |
+
|
| 74 |
+
### LED Color Meanings
|
| 75 |
+
- **Solid Blue**: Ready to charge, no device detected
|
| 76 |
+
- **Solid Green**: Device charging normally
|
| 77 |
+
- **Blinking Green**: Device fully charged
|
| 78 |
+
- **Solid Red**: Error condition (foreign object, overheating)
|
| 79 |
+
- **Blinking Red**: Device not compatible or misaligned
|
| 80 |
+
- **Purple**: Fast charging mode active
|
| 81 |
+
|
| 82 |
+
### Troubleshooting LED Codes
|
| 83 |
+
- **No LED**: Check power connection and adapter
|
| 84 |
+
- **Red Solid**: Remove foreign objects, check alignment
|
| 85 |
+
- **Red Blinking**: Reposition device, remove thick case
|
| 86 |
+
|
| 87 |
+
## Safety Features
|
| 88 |
+
|
| 89 |
+
### Protection Systems
|
| 90 |
+
- **Foreign Object Detection (FOD)**: Stops charging when metal objects detected
|
| 91 |
+
- **Temperature Control**: Automatic power reduction if overheating
|
| 92 |
+
- **Over-Voltage Protection**: Protects against power surges
|
| 93 |
+
- **Over-Current Protection**: Prevents excessive current draw
|
| 94 |
+
- **Short-Circuit Protection**: Immediate shutdown on electrical faults
|
| 95 |
+
|
| 96 |
+
### Thermal Management
|
| 97 |
+
- **Heat Dissipation**: Aluminum surface for efficient heat transfer
|
| 98 |
+
- **Temperature Monitoring**: Real-time thermal sensing
|
| 99 |
+
- **Cooling Periods**: Automatic charging pauses if temperature exceeds limits
|
| 100 |
+
- **Ventilation**: Bottom air channels for passive cooling
|
| 101 |
+
|
| 102 |
+
## Performance Optimization
|
| 103 |
+
|
| 104 |
+
### Maximum Charging Speed
|
| 105 |
+
- **Use Included Adapter**: QC 3.0 18W adapter for best performance
|
| 106 |
+
- **Remove Thick Cases**: Cases over 5mm may reduce charging speed
|
| 107 |
+
- **Proper Alignment**: Center device on pad for optimal efficiency
|
| 108 |
+
- **Room Temperature**: Avoid direct sunlight or heat sources
|
| 109 |
+
|
| 110 |
+
### Efficiency Tips
|
| 111 |
+
- **Clean Surfaces**: Keep charging pad and device clean
|
| 112 |
+
- **Stable Placement**: Avoid vibrating surfaces
|
| 113 |
+
- **No Metal Objects**: Remove coins, keys, magnetic mounts
|
| 114 |
+
- **Update Software**: Ensure device has latest wireless charging firmware
|
| 115 |
+
|
| 116 |
+
## Technical Specifications
|
| 117 |
+
|
| 118 |
+
### Electrical Ratings
|
| 119 |
+
- **Input Voltage**: 5V/3A, 9V/2A
|
| 120 |
+
- **Output Power**: 15W max, 10W, 7.5W, 5W
|
| 121 |
+
- **Charging Efficiency**: >75% at 15W
|
| 122 |
+
- **Standby Power**: <0.5W when no device present
|
| 123 |
+
- **Frequency**: 110-148 kHz (Qi standard)
|
| 124 |
+
|
| 125 |
+
### Environmental Conditions
|
| 126 |
+
- **Operating Temperature**: 32°F to 104°F (0°C to 40°C)
|
| 127 |
+
- **Storage Temperature**: -4°F to 140°F (-20°C to 60°C)
|
| 128 |
+
- **Humidity**: 10% to 90% RH (non-condensing)
|
| 129 |
+
- **Altitude**: Up to 6,600 feet (2,000m)
|
| 130 |
+
|
| 131 |
+
### Physical Specifications
|
| 132 |
+
- **Diameter**: 4.3 inches (110mm)
|
| 133 |
+
- **Thickness**: 0.4 inches (10mm)
|
| 134 |
+
- **Weight**: 4.2 oz (120g)
|
| 135 |
+
- **Top Material**: Premium aluminum
|
| 136 |
+
- **Base Material**: ABS plastic with rubber grips
|
| 137 |
+
|
| 138 |
+
## Troubleshooting
|
| 139 |
+
|
| 140 |
+
### Common Issues
|
| 141 |
+
|
| 142 |
+
**Device Not Charging**
|
| 143 |
+
- Check device Qi compatibility
|
| 144 |
+
- Remove thick or metal cases
|
| 145 |
+
- Center device on charging pad
|
| 146 |
+
- Verify power adapter connection
|
| 147 |
+
|
| 148 |
+
**Slow Charging Speed**
|
| 149 |
+
- Use included QC 3.0 adapter
|
| 150 |
+
- Remove case if thick (>5mm)
|
| 151 |
+
- Ensure proper device alignment
|
| 152 |
+
- Check for overheating
|
| 153 |
+
|
| 154 |
+
**LED Shows Red**
|
| 155 |
+
- Remove metal objects from pad
|
| 156 |
+
- Clean charging surfaces
|
| 157 |
+
- Reposition device
|
| 158 |
+
- Check for foreign objects
|
| 159 |
+
|
| 160 |
+
**Intermittent Charging**
|
| 161 |
+
- Ensure stable placement
|
| 162 |
+
- Check for loose connections
|
| 163 |
+
- Clean charging contacts
|
| 164 |
+
- Verify case compatibility
|
| 165 |
+
|
| 166 |
+
### Advanced Troubleshooting
|
| 167 |
+
- **Firmware Updates**: Some phones may need software updates for optimal charging
|
| 168 |
+
- **Adapter Compatibility**: Use QC 3.0 or PD adapters for fast charging
|
| 169 |
+
- **Environmental Factors**: Avoid extreme temperatures and magnetic fields
|
| 170 |
+
|
| 171 |
+
## Care & Maintenance
|
| 172 |
+
|
| 173 |
+
### Cleaning Instructions
|
| 174 |
+
- **Power Off**: Disconnect from power before cleaning
|
| 175 |
+
- **Dry Cloth**: Use lint-free cloth for surface cleaning
|
| 176 |
+
- **No Liquids**: Do not use water or cleaning solutions
|
| 177 |
+
- **Gentle Cleaning**: Avoid abrasive materials that could scratch aluminum
|
| 178 |
+
|
| 179 |
+
### Storage
|
| 180 |
+
- **Cool, Dry Place**: Store in temperature-controlled environment
|
| 181 |
+
- **Avoid Pressure**: Don't place heavy objects on charging pad
|
| 182 |
+
- **Cable Management**: Coil cable loosely to prevent damage
|
| 183 |
+
|
| 184 |
+
## What's Included
|
| 185 |
+
|
| 186 |
+
### Package Contents
|
| 187 |
+
- 1x TMC Wireless Charging Pad
|
| 188 |
+
- 1x USB-C to USB-A Cable (3ft)
|
| 189 |
+
- 1x QC 3.0 Wall Adapter (18W)
|
| 190 |
+
- 1x Quick Start Guide
|
| 191 |
+
- 1x Warranty Registration Card
|
| 192 |
+
|
| 193 |
+
## Warranty & Support
|
| 194 |
+
|
| 195 |
+
### Warranty Coverage
|
| 196 |
+
- **Duration**: 2-year limited warranty
|
| 197 |
+
- **Coverage**: Manufacturing defects and material failures
|
| 198 |
+
- **Exclusions**: Physical damage, misuse, normal wear
|
| 199 |
+
|
| 200 |
+
### Customer Support
|
| 201 |
+
- **Website**: support.toomanycables.com/wireless
|
| 202 |
+
- **Email**: support@toomanycables.com
|
| 203 |
+
- **Phone**: 1-800-TMC-HELP
|
| 204 |
+
- **Live Chat**: Available 24/7 on website
|
| 205 |
+
|
| 206 |
+
### Warranty Registration
|
| 207 |
+
Register within 30 days for:
|
| 208 |
+
- Full warranty coverage
|
| 209 |
+
- Product notifications
|
| 210 |
+
- Technical support priority
|
| 211 |
+
|
| 212 |
+
---
|
| 213 |
+
|
| 214 |
+
**Model**: TMC-WIRELESS-15W-QI
|
| 215 |
+
**Manual Version**: 1.4
|
| 216 |
+
**Last Updated**: October 2025
|
| 217 |
+
**Document**: TMC-WIRELESS-MANUAL-V1.4
|
requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Flask==2.3.3
|
| 2 |
+
Flask-CORS==4.0.0
|
| 3 |
+
requests==2.31.0
|
| 4 |
+
Werkzeug==2.3.7
|
| 5 |
+
Flask-Limiter==3.1.0
|
| 6 |
+
Flask-WTF==1.1.1
|
| 7 |
+
chromadb==0.4.22
|
| 8 |
+
sentence-transformers==2.7.0
|
| 9 |
+
numpy==1.24.3
|
| 10 |
+
openai>=1.0.0
|
scripts/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Helper scripts for TMC Chatbot
|
| 3 |
+
"""
|
scripts/database.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import os
|
| 3 |
+
import secrets
|
| 4 |
+
import hashlib
|
| 5 |
+
from typing import Optional, Dict, List
|
| 6 |
+
|
| 7 |
+
class DatabaseManager:
|
| 8 |
+
def __init__(self, db_path=None):
|
| 9 |
+
self.db_path = db_path or os.environ.get('DATABASE_PATH', 'tmc_customer_service.db')
|
| 10 |
+
os.makedirs(os.path.dirname(self.db_path) or '.', exist_ok=True)
|
| 11 |
+
self.init_database()
|
| 12 |
+
|
| 13 |
+
def get_connection(self):
|
| 14 |
+
conn = sqlite3.connect(self.db_path)
|
| 15 |
+
conn.row_factory = sqlite3.Row
|
| 16 |
+
return conn
|
| 17 |
+
|
| 18 |
+
def init_database(self):
|
| 19 |
+
with self.get_connection() as conn:
|
| 20 |
+
c = conn.cursor()
|
| 21 |
+
c.execute('''CREATE TABLE IF NOT EXISTS users (
|
| 22 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 23 |
+
email TEXT UNIQUE NOT NULL,
|
| 24 |
+
first_name TEXT NOT NULL,
|
| 25 |
+
last_name TEXT NOT NULL,
|
| 26 |
+
password_hash TEXT NOT NULL,
|
| 27 |
+
salt TEXT NOT NULL,
|
| 28 |
+
phone TEXT,
|
| 29 |
+
company TEXT,
|
| 30 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 31 |
+
is_active BOOLEAN DEFAULT 1,
|
| 32 |
+
role TEXT DEFAULT 'user'
|
| 33 |
+
)''')
|
| 34 |
+
c.execute('''CREATE TABLE IF NOT EXISTS sessions (
|
| 35 |
+
id TEXT PRIMARY KEY,
|
| 36 |
+
user_id INTEGER,
|
| 37 |
+
ip_address TEXT,
|
| 38 |
+
user_agent TEXT,
|
| 39 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 40 |
+
expires_at TIMESTAMP NOT NULL,
|
| 41 |
+
is_active BOOLEAN DEFAULT 1,
|
| 42 |
+
FOREIGN KEY(user_id) REFERENCES users(id)
|
| 43 |
+
)''')
|
| 44 |
+
c.execute('''CREATE TABLE IF NOT EXISTS conversations (
|
| 45 |
+
id TEXT PRIMARY KEY,
|
| 46 |
+
user_id INTEGER,
|
| 47 |
+
session_id TEXT,
|
| 48 |
+
title TEXT,
|
| 49 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 50 |
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 51 |
+
is_active BOOLEAN DEFAULT 1,
|
| 52 |
+
FOREIGN KEY(user_id) REFERENCES users(id)
|
| 53 |
+
)''')
|
| 54 |
+
c.execute('''CREATE TABLE IF NOT EXISTS messages (
|
| 55 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 56 |
+
conversation_id TEXT NOT NULL,
|
| 57 |
+
role TEXT NOT NULL,
|
| 58 |
+
content TEXT NOT NULL,
|
| 59 |
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 60 |
+
model_used TEXT,
|
| 61 |
+
response_time_ms INTEGER,
|
| 62 |
+
FOREIGN KEY(conversation_id) REFERENCES conversations(id)
|
| 63 |
+
)''')
|
| 64 |
+
c.execute('''CREATE TABLE IF NOT EXISTS support_tickets (
|
| 65 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 66 |
+
ticket_number TEXT UNIQUE NOT NULL,
|
| 67 |
+
user_id INTEGER NOT NULL,
|
| 68 |
+
conversation_id TEXT,
|
| 69 |
+
subject TEXT NOT NULL,
|
| 70 |
+
description TEXT NOT NULL,
|
| 71 |
+
category TEXT NOT NULL,
|
| 72 |
+
priority TEXT DEFAULT 'medium',
|
| 73 |
+
status TEXT DEFAULT 'open',
|
| 74 |
+
assigned_agent TEXT,
|
| 75 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 76 |
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 77 |
+
resolved_at TIMESTAMP,
|
| 78 |
+
FOREIGN KEY(user_id) REFERENCES users(id)
|
| 79 |
+
)''')
|
| 80 |
+
c.execute('''CREATE TABLE IF NOT EXISTS ticket_updates (
|
| 81 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 82 |
+
ticket_id INTEGER NOT NULL,
|
| 83 |
+
user_id INTEGER,
|
| 84 |
+
update_type TEXT DEFAULT 'note',
|
| 85 |
+
message TEXT NOT NULL,
|
| 86 |
+
is_internal BOOLEAN DEFAULT 0,
|
| 87 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 88 |
+
FOREIGN KEY(ticket_id) REFERENCES support_tickets(id)
|
| 89 |
+
)''')
|
| 90 |
+
c.execute('''CREATE TABLE IF NOT EXISTS ticket_categories (
|
| 91 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 92 |
+
name TEXT UNIQUE NOT NULL,
|
| 93 |
+
description TEXT,
|
| 94 |
+
default_priority TEXT DEFAULT 'medium',
|
| 95 |
+
is_active BOOLEAN DEFAULT 1
|
| 96 |
+
)''')
|
| 97 |
+
conn.commit()
|
| 98 |
+
c.execute("INSERT OR IGNORE INTO ticket_categories (name, description) VALUES ('General', 'General inquiries')")
|
| 99 |
+
conn.commit()
|
| 100 |
+
|
| 101 |
+
def hash_password(self, password: str):
|
| 102 |
+
salt = secrets.token_hex(32)
|
| 103 |
+
ph = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000).hex()
|
| 104 |
+
return ph, salt
|
| 105 |
+
|
| 106 |
+
def verify_password(self, password, phash, salt):
|
| 107 |
+
return hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000).hex() == phash
|
| 108 |
+
|
| 109 |
+
def create_user(self, email, first_name, last_name, password, phone=None, company=None) -> Optional[int]:
|
| 110 |
+
try:
|
| 111 |
+
phash, salt = self.hash_password(password)
|
| 112 |
+
with self.get_connection() as conn:
|
| 113 |
+
cur = conn.cursor()
|
| 114 |
+
cur.execute('''INSERT INTO users (email, first_name, last_name, password_hash, salt, phone, company)
|
| 115 |
+
VALUES (?,?,?,?,?,?,?)''',
|
| 116 |
+
(email, first_name, last_name, phash, salt, phone, company))
|
| 117 |
+
uid = cur.lastrowid
|
| 118 |
+
conn.commit()
|
| 119 |
+
return uid
|
| 120 |
+
except sqlite3.IntegrityError:
|
| 121 |
+
return None
|
| 122 |
+
|
| 123 |
+
def authenticate_user(self, email, password) -> Optional[Dict]:
|
| 124 |
+
with self.get_connection() as conn:
|
| 125 |
+
cur = conn.cursor()
|
| 126 |
+
cur.execute('SELECT id, email, first_name, last_name, password_hash, salt, is_active FROM users WHERE email = ?', (email,))
|
| 127 |
+
row = cur.fetchone()
|
| 128 |
+
if row and row['is_active'] and self.verify_password(password, row['password_hash'], row['salt']):
|
| 129 |
+
return dict(row)
|
| 130 |
+
return None
|
| 131 |
+
|
| 132 |
+
def create_session(self, user_id, ip, ua, hours=24) -> str:
|
| 133 |
+
sid = secrets.token_urlsafe(32)
|
| 134 |
+
with self.get_connection() as conn:
|
| 135 |
+
conn.execute('INSERT INTO sessions (id, user_id, ip_address, user_agent, expires_at) VALUES (?,?,?,?, datetime("now", "+? hours"))',
|
| 136 |
+
(sid, user_id, ip, ua[:255], hours))
|
| 137 |
+
conn.commit()
|
| 138 |
+
return sid
|
| 139 |
+
|
| 140 |
+
def get_user_by_session(self, session_id) -> Optional[Dict]:
|
| 141 |
+
with self.get_connection() as conn:
|
| 142 |
+
cur = conn.cursor()
|
| 143 |
+
cur.execute('''SELECT u.id, u.email, u.first_name, u.last_name, u.role
|
| 144 |
+
FROM users u JOIN sessions s ON u.id = s.user_id
|
| 145 |
+
WHERE s.id = ? AND s.is_active = 1 AND s.expires_at > CURRENT_TIMESTAMP''', (session_id,))
|
| 146 |
+
row = cur.fetchone()
|
| 147 |
+
return dict(row) if row else None
|
| 148 |
+
|
| 149 |
+
def get_user_role(self, user_id) -> str:
|
| 150 |
+
with self.get_connection() as conn:
|
| 151 |
+
cur = conn.cursor()
|
| 152 |
+
cur.execute('SELECT role FROM users WHERE id = ?', (user_id,))
|
| 153 |
+
row = cur.fetchone()
|
| 154 |
+
return row['role'] if row else 'user'
|
| 155 |
+
|
| 156 |
+
def create_conversation(self, user_id=None, session_id=None, title=None) -> str:
|
| 157 |
+
cid = secrets.token_urlsafe(16)
|
| 158 |
+
with self.get_connection() as conn:
|
| 159 |
+
conn.execute('INSERT INTO conversations (id, user_id, session_id, title) VALUES (?,?,?,?)',
|
| 160 |
+
(cid, user_id, session_id, title))
|
| 161 |
+
conn.commit()
|
| 162 |
+
return cid
|
| 163 |
+
|
| 164 |
+
def add_message(self, conversation_id, role, content, model_used=None, response_time_ms=None):
|
| 165 |
+
with self.get_connection() as conn:
|
| 166 |
+
cur = conn.cursor()
|
| 167 |
+
cur.execute('''INSERT INTO messages (conversation_id, role, content, model_used, response_time_ms)
|
| 168 |
+
VALUES (?,?,?,?,?)''', (conversation_id, role, content, model_used, response_time_ms))
|
| 169 |
+
conn.execute('UPDATE conversations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', (conversation_id,))
|
| 170 |
+
conn.commit()
|
| 171 |
+
return cur.lastrowid
|
| 172 |
+
|
| 173 |
+
def get_conversation_history(self, conversation_id, limit=50) -> List[Dict]:
|
| 174 |
+
with self.get_connection() as conn:
|
| 175 |
+
cur = conn.cursor()
|
| 176 |
+
cur.execute('SELECT is_active FROM conversations WHERE id = ?', (conversation_id,))
|
| 177 |
+
if not cur.fetchone():
|
| 178 |
+
return []
|
| 179 |
+
cur.execute('''SELECT role, content, timestamp, model_used FROM messages
|
| 180 |
+
WHERE conversation_id = ? ORDER BY timestamp LIMIT ?''', (conversation_id, limit))
|
| 181 |
+
return [dict(row) for row in cur.fetchall()]
|
| 182 |
+
|
| 183 |
+
def create_support_ticket(self, user_id, subject, description, category, conversation_id=None, priority='medium') -> str:
|
| 184 |
+
import random, string
|
| 185 |
+
tn = 'TMC-' + ''.join(random.choices(string.digits, k=6))
|
| 186 |
+
with self.get_connection() as conn:
|
| 187 |
+
cur = conn.cursor()
|
| 188 |
+
cur.execute('''INSERT INTO support_tickets (ticket_number, user_id, conversation_id, subject, description, category, priority)
|
| 189 |
+
VALUES (?,?,?,?,?,?,?)''', (tn, user_id, conversation_id, subject, description, category, priority))
|
| 190 |
+
conn.commit()
|
| 191 |
+
return tn
|
| 192 |
+
|
| 193 |
+
def get_ticket_by_number(self, ticket_number) -> Optional[Dict]:
|
| 194 |
+
with self.get_connection() as conn:
|
| 195 |
+
cur = conn.cursor()
|
| 196 |
+
cur.execute('SELECT * FROM support_tickets WHERE ticket_number = ?', (ticket_number,))
|
| 197 |
+
row = cur.fetchone()
|
| 198 |
+
return dict(row) if row else None
|
| 199 |
+
|
| 200 |
+
def get_ticket_updates(self, ticket_id, include_internal=False) -> List[Dict]:
|
| 201 |
+
with self.get_connection() as conn:
|
| 202 |
+
cur = conn.cursor()
|
| 203 |
+
if include_internal:
|
| 204 |
+
cur.execute('SELECT * FROM ticket_updates WHERE ticket_id = ? ORDER BY created_at', (ticket_id,))
|
| 205 |
+
else:
|
| 206 |
+
cur.execute('SELECT * FROM ticket_updates WHERE ticket_id = ? AND is_internal = 0 ORDER BY created_at', (ticket_id,))
|
| 207 |
+
return [dict(row) for row in cur.fetchall()]
|
| 208 |
+
|
| 209 |
+
def add_ticket_update(self, ticket_id, user_id, message, update_type='note', is_internal=False) -> int:
|
| 210 |
+
with self.get_connection() as conn:
|
| 211 |
+
cur = conn.cursor()
|
| 212 |
+
cur.execute('''INSERT INTO ticket_updates (ticket_id, user_id, update_type, message, is_internal)
|
| 213 |
+
VALUES (?,?,?,?,?)''', (ticket_id, user_id, update_type, message, is_internal))
|
| 214 |
+
conn.execute('UPDATE support_tickets SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', (ticket_id,))
|
| 215 |
+
conn.commit()
|
| 216 |
+
return cur.lastrowid
|
| 217 |
+
|
| 218 |
+
def get_user_tickets(self, user_id, limit=20) -> List[Dict]:
|
| 219 |
+
with self.get_connection() as conn:
|
| 220 |
+
cur = conn.cursor()
|
| 221 |
+
cur.execute('SELECT * FROM support_tickets WHERE user_id = ? ORDER BY created_at DESC LIMIT ?', (user_id, limit))
|
| 222 |
+
return [dict(row) for row in cur.fetchall()]
|
| 223 |
+
|
| 224 |
+
def get_tickets_by_status(self, status, limit=50) -> List[Dict]:
|
| 225 |
+
with self.get_connection() as conn:
|
| 226 |
+
cur = conn.cursor()
|
| 227 |
+
if status:
|
| 228 |
+
cur.execute('SELECT * FROM support_tickets WHERE status = ? ORDER BY created_at DESC LIMIT ?', (status, limit))
|
| 229 |
+
else:
|
| 230 |
+
cur.execute('SELECT * FROM support_tickets ORDER BY created_at DESC LIMIT ?', (limit,))
|
| 231 |
+
return [dict(row) for row in cur.fetchall()]
|
| 232 |
+
|
| 233 |
+
# Stubs for additional methods used in original app
|
| 234 |
+
def categorize_ticket_content(self, text):
|
| 235 |
+
return "General"
|
| 236 |
+
|
| 237 |
+
def escalate_ticket(self, ticket_id, reason, user_id=None):
|
| 238 |
+
with self.get_connection() as conn:
|
| 239 |
+
conn.execute("UPDATE support_tickets SET priority = 'high' WHERE id = ?", (ticket_id,))
|
| 240 |
+
conn.commit()
|
| 241 |
+
return True
|
| 242 |
+
|
| 243 |
+
def check_escalation_needed(self, ticket_id):
|
| 244 |
+
return {"needs_escalation": False}
|
scripts/init_database.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from .database import DatabaseManager
|
| 3 |
+
|
| 4 |
+
def init_sample_data():
|
| 5 |
+
db = DatabaseManager()
|
| 6 |
+
admin = db.create_user('admin@toomanycables.com', 'Admin', 'User', 'CHANGE_ME_ADMIN_PASSWORD', company='Too Many Cables')
|
| 7 |
+
if admin:
|
| 8 |
+
with db.get_connection() as conn:
|
| 9 |
+
conn.execute("UPDATE users SET role = 'admin' WHERE id = ?", (admin,))
|
| 10 |
+
conn.commit()
|
| 11 |
+
print("Admin created with password 'CHANGE_ME_ADMIN_PASSWORD'. Change it immediately via UI or DB.")
|
| 12 |
+
db.create_user('customer@example.com', 'John', 'Customer', 'customer123', company='Example Corp')
|
| 13 |
+
print("Sample data initialized.")
|
| 14 |
+
|
| 15 |
+
if __name__ == '__main__':
|
| 16 |
+
init_sample_data()
|
scripts/knowledge_base_manager.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import Dict, List, Optional
|
| 4 |
+
|
| 5 |
+
class KnowledgeBaseManager:
|
| 6 |
+
def __init__(self, knowledge_base_path: str = "knowledge_base"):
|
| 7 |
+
self.kb_path = Path(knowledge_base_path)
|
| 8 |
+
self.documents = {}
|
| 9 |
+
self.kb_path.mkdir(exist_ok=True)
|
| 10 |
+
|
| 11 |
+
def scan_documents(self) -> Dict:
|
| 12 |
+
documents = {}
|
| 13 |
+
categories = {
|
| 14 |
+
'faqs': 'Frequently Asked Questions',
|
| 15 |
+
'policies': 'Company Policies',
|
| 16 |
+
'product_manuals': 'Product Manuals',
|
| 17 |
+
# 'development' intentionally omitted
|
| 18 |
+
}
|
| 19 |
+
for category, desc in categories.items():
|
| 20 |
+
cat_path = self.kb_path / category
|
| 21 |
+
if cat_path.exists():
|
| 22 |
+
docs = []
|
| 23 |
+
for f in cat_path.glob('*.md'):
|
| 24 |
+
docs.append(self._analyze_document(f, category))
|
| 25 |
+
documents[category] = {'description': desc, 'documents': docs}
|
| 26 |
+
self.documents = documents
|
| 27 |
+
return documents
|
| 28 |
+
|
| 29 |
+
def _analyze_document(self, file_path: Path, category: str) -> Dict:
|
| 30 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 31 |
+
content = f.read()
|
| 32 |
+
title = next((line.strip('# ').strip() for line in content.split('\n') if line.startswith('# ')), file_path.stem)
|
| 33 |
+
return {
|
| 34 |
+
'filename': file_path.name,
|
| 35 |
+
'title': title,
|
| 36 |
+
'category': category,
|
| 37 |
+
'path': str(file_path.relative_to(self.kb_path)),
|
| 38 |
+
'size': file_path.stat().st_size,
|
| 39 |
+
'word_count': len(content.split()),
|
| 40 |
+
'char_count': len(content)
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
def load_document_content(self, document_path: str) -> Optional[str]:
|
| 44 |
+
full = self.kb_path / document_path
|
| 45 |
+
if full.exists():
|
| 46 |
+
with open(full, 'r', encoding='utf-8') as f:
|
| 47 |
+
return f.read()
|
| 48 |
+
return None
|
| 49 |
+
|
| 50 |
+
def search_documents(self, query: str, category: Optional[str] = None) -> List[Dict]:
|
| 51 |
+
results = []
|
| 52 |
+
ql = query.lower()
|
| 53 |
+
for cat, info in self.documents.items():
|
| 54 |
+
if category and cat != category:
|
| 55 |
+
continue
|
| 56 |
+
for doc in info['documents']:
|
| 57 |
+
content = self.load_document_content(doc['path'])
|
| 58 |
+
if not content:
|
| 59 |
+
continue
|
| 60 |
+
if ql in content.lower() or ql in doc['title'].lower():
|
| 61 |
+
score = content.lower().count(ql)
|
| 62 |
+
results.append({**doc, 'relevance_score': score})
|
| 63 |
+
results.sort(key=lambda x: x['relevance_score'], reverse=True)
|
| 64 |
+
return results
|
scripts/rag_helper.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from typing import List, Dict
|
| 3 |
+
from .knowledge_base_manager import KnowledgeBaseManager
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
from .vector_rag_manager import VectorRAGManager
|
| 7 |
+
VECTOR_AVAILABLE = True
|
| 8 |
+
except ImportError:
|
| 9 |
+
VECTOR_AVAILABLE = False
|
| 10 |
+
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
class RAGHelper:
|
| 14 |
+
def __init__(self, knowledge_base_path: str = "knowledge_base", use_vector_search: bool = True):
|
| 15 |
+
self.kb_manager = KnowledgeBaseManager(knowledge_base_path)
|
| 16 |
+
self.kb_manager.scan_documents()
|
| 17 |
+
self.use_vector_search = use_vector_search and VECTOR_AVAILABLE
|
| 18 |
+
self.vector_rag = None
|
| 19 |
+
if self.use_vector_search:
|
| 20 |
+
try:
|
| 21 |
+
self.vector_rag = VectorRAGManager(knowledge_base_path, vector_db_path="/app/data/vector_db")
|
| 22 |
+
logger.info("Vector RAG initialized")
|
| 23 |
+
except Exception as e:
|
| 24 |
+
logger.warning(f"Vector RAG failed: {e}")
|
| 25 |
+
self.use_vector_search = False
|
| 26 |
+
self.max_context_docs = 3
|
| 27 |
+
self.similarity_threshold = 0.30
|
| 28 |
+
|
| 29 |
+
def _route_query_to_categories(self, query: str) -> List[str]:
|
| 30 |
+
ql = query.lower()
|
| 31 |
+
cat_keywords = {
|
| 32 |
+
'policies': ['return','refund','warranty','policy','shipping'],
|
| 33 |
+
'faqs': ['how','what','why','help','troubleshoot','problem'],
|
| 34 |
+
'product_manuals': ['spec','manual','guide','connect','cable','hdmi','usb']
|
| 35 |
+
}
|
| 36 |
+
scores = {}
|
| 37 |
+
for cat, kw in cat_keywords.items():
|
| 38 |
+
score = sum(1 for k in kw if k in ql)
|
| 39 |
+
if score:
|
| 40 |
+
scores[cat] = score
|
| 41 |
+
if scores:
|
| 42 |
+
return sorted(scores, key=scores.get, reverse=True)
|
| 43 |
+
return ['policies','faqs','product_manuals']
|
| 44 |
+
|
| 45 |
+
def get_relevant_context(self, query: str) -> str:
|
| 46 |
+
categories = self._route_query_to_categories(query)
|
| 47 |
+
if self.use_vector_search and self.vector_rag:
|
| 48 |
+
try:
|
| 49 |
+
results = self.vector_rag.retrieve_and_rerank_filtered(
|
| 50 |
+
query, target_categories=categories, initial_k=20, final_k=3,
|
| 51 |
+
similarity_threshold=self.similarity_threshold
|
| 52 |
+
)
|
| 53 |
+
return self._build_context_from_results(results)
|
| 54 |
+
except Exception as e:
|
| 55 |
+
logger.warning(f"Vector search failed: {e}, falling back to keyword")
|
| 56 |
+
return self._get_keyword_context(query)
|
| 57 |
+
|
| 58 |
+
def _get_keyword_context(self, query: str) -> str:
|
| 59 |
+
results = self.kb_manager.search_documents(query)
|
| 60 |
+
context = ""
|
| 61 |
+
for r in results[:self.max_context_docs]:
|
| 62 |
+
content = self.kb_manager.load_document_content(r['path'])
|
| 63 |
+
if content:
|
| 64 |
+
context += f"\n--- {r['title']} (Category: {r['category']}) ---\n{content[:1500]}\n"
|
| 65 |
+
return context
|
| 66 |
+
|
| 67 |
+
def _build_context_from_results(self, results: List[Dict]) -> str:
|
| 68 |
+
if not results:
|
| 69 |
+
return ""
|
| 70 |
+
chunks = []
|
| 71 |
+
for res in results:
|
| 72 |
+
doc = res['document']
|
| 73 |
+
meta = res['metadata']
|
| 74 |
+
chunks.append(f"--- {meta['document_title']} (Category: {meta['category']}) ---\n{doc[:1200]}")
|
| 75 |
+
return "\n".join(chunks[:3])
|
| 76 |
+
|
| 77 |
+
def get_knowledge_base_stats(self) -> Dict:
|
| 78 |
+
stats = self.kb_manager.get_stats() if hasattr(self.kb_manager, 'get_stats') else {'total_documents': 0}
|
| 79 |
+
stats['vector_search_available'] = self.use_vector_search
|
| 80 |
+
return stats
|
| 81 |
+
|
| 82 |
+
def ensure_vector_index(self, force_reindex: bool = False):
|
| 83 |
+
if self.vector_rag:
|
| 84 |
+
return self.vector_rag.index_documents(force_reindex=force_reindex)
|
| 85 |
+
return {"error": "Vector not available"}
|
scripts/vector_rag_manager.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import List, Dict
|
| 5 |
+
import chromadb
|
| 6 |
+
from chromadb.config import Settings
|
| 7 |
+
from sentence_transformers import SentenceTransformer
|
| 8 |
+
from .knowledge_base_manager import KnowledgeBaseManager
|
| 9 |
+
|
| 10 |
+
os.environ['ANONYMIZED_TELEMETRY'] = 'False'
|
| 11 |
+
os.environ['CHROMA_TELEMETRY_ENABLED'] = 'false'
|
| 12 |
+
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
class VectorRAGManager:
|
| 16 |
+
def __init__(self, knowledge_base_path: str, vector_db_path: str = "vector_db", embedding_model: str = "all-MiniLM-L6-v2"):
|
| 17 |
+
self.kb_path = Path(knowledge_base_path)
|
| 18 |
+
self.vector_db_path = Path(vector_db_path)
|
| 19 |
+
self.vector_db_path.mkdir(exist_ok=True, parents=True)
|
| 20 |
+
self.kb_manager = KnowledgeBaseManager(knowledge_base_path)
|
| 21 |
+
self.chroma_client = chromadb.PersistentClient(path=str(self.vector_db_path), settings=Settings(anonymized_telemetry=False))
|
| 22 |
+
self.embedding_model = SentenceTransformer(embedding_model)
|
| 23 |
+
self.collection_name = "tmc_documents"
|
| 24 |
+
self._init_collection()
|
| 25 |
+
self.chunk_size = 800
|
| 26 |
+
self.chunk_overlap = 120
|
| 27 |
+
|
| 28 |
+
def _init_collection(self):
|
| 29 |
+
try:
|
| 30 |
+
self.collection = self.chroma_client.get_collection(self.collection_name)
|
| 31 |
+
except:
|
| 32 |
+
self.collection = self.chroma_client.create_collection(self.collection_name)
|
| 33 |
+
|
| 34 |
+
def chunk_text(self, text: str) -> List[str]:
|
| 35 |
+
if len(text) <= self.chunk_size:
|
| 36 |
+
return [text]
|
| 37 |
+
chunks = []
|
| 38 |
+
start = 0
|
| 39 |
+
while start < len(text):
|
| 40 |
+
end = min(start + self.chunk_size, len(text))
|
| 41 |
+
if end < len(text):
|
| 42 |
+
for i in range(end, max(start, end-200), -1):
|
| 43 |
+
if text[i] in '.!?\n':
|
| 44 |
+
end = i+1
|
| 45 |
+
break
|
| 46 |
+
chunks.append(text[start:end].strip())
|
| 47 |
+
start = end - self.chunk_overlap
|
| 48 |
+
return [c for c in chunks if len(c) > 50]
|
| 49 |
+
|
| 50 |
+
def generate_embedding(self, text: str) -> List[float]:
|
| 51 |
+
return self.embedding_model.encode(text).tolist()
|
| 52 |
+
|
| 53 |
+
def index_documents(self, force_reindex: bool = False) -> Dict:
|
| 54 |
+
if force_reindex and self.collection.count() > 0:
|
| 55 |
+
self.chroma_client.delete_collection(self.collection_name)
|
| 56 |
+
self._init_collection()
|
| 57 |
+
self.kb_manager.scan_documents()
|
| 58 |
+
stats = {"documents": 0, "chunks": 0}
|
| 59 |
+
for category, cat_info in self.kb_manager.documents.items():
|
| 60 |
+
if category == 'development':
|
| 61 |
+
logger.info(f"Skipping development folder")
|
| 62 |
+
continue
|
| 63 |
+
for doc in cat_info['documents']:
|
| 64 |
+
content = self.kb_manager.load_document_content(doc['path'])
|
| 65 |
+
if not content:
|
| 66 |
+
continue
|
| 67 |
+
chunks = self.chunk_text(content)
|
| 68 |
+
if not chunks:
|
| 69 |
+
continue
|
| 70 |
+
ids = []
|
| 71 |
+
embeds = []
|
| 72 |
+
texts = []
|
| 73 |
+
metas = []
|
| 74 |
+
for i, chunk in enumerate(chunks):
|
| 75 |
+
cid = f"{doc['filename']}_{i}"
|
| 76 |
+
ids.append(cid)
|
| 77 |
+
texts.append(chunk)
|
| 78 |
+
embeds.append(self.generate_embedding(chunk))
|
| 79 |
+
metas.append({
|
| 80 |
+
"document_title": doc['title'],
|
| 81 |
+
"document_path": doc['path'],
|
| 82 |
+
"category": category,
|
| 83 |
+
"chunk_index": i
|
| 84 |
+
})
|
| 85 |
+
self.collection.add(ids=ids, embeddings=embeds, documents=texts, metadatas=metas)
|
| 86 |
+
stats["documents"] += 1
|
| 87 |
+
stats["chunks"] += len(chunks)
|
| 88 |
+
logger.info(f"Indexed {stats['documents']} docs, {stats['chunks']} chunks")
|
| 89 |
+
return stats
|
| 90 |
+
|
| 91 |
+
def semantic_search(self, query: str, n_results: int = 5) -> List[Dict]:
|
| 92 |
+
if self.collection.count() == 0:
|
| 93 |
+
return []
|
| 94 |
+
q_emb = self.generate_embedding(query)
|
| 95 |
+
results = self.collection.query(query_embeddings=[q_emb], n_results=n_results, include=["documents","metadatas","distances"])
|
| 96 |
+
formatted = []
|
| 97 |
+
if results['documents'] and results['documents'][0]:
|
| 98 |
+
for doc, meta, dist in zip(results['documents'][0], results['metadatas'][0], results['distances'][0]):
|
| 99 |
+
similarity = max(0.0, 1.0 - (dist / 2.0))
|
| 100 |
+
formatted.append({"document": doc, "metadata": meta, "similarity": similarity})
|
| 101 |
+
return formatted
|
| 102 |
+
|
| 103 |
+
def retrieve_and_rerank_filtered(self, query: str, target_categories: List[str], initial_k: int = 20, final_k: int = 3, similarity_threshold: float = 0.30) -> List[Dict]:
|
| 104 |
+
candidates = self.semantic_search(query, n_results=initial_k)
|
| 105 |
+
filtered = [c for c in candidates if c['similarity'] >= similarity_threshold and c['metadata'].get('category') in target_categories]
|
| 106 |
+
return filtered[:final_k]
|
| 107 |
+
|
| 108 |
+
def get_collection_stats(self) -> Dict:
|
| 109 |
+
return {"total_chunks": self.collection.count()}
|
static/chat.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Chat functionality for Too Many Cables customer service
|
| 2 |
+
class ChatInterface {
|
| 3 |
+
constructor() {
|
| 4 |
+
this.chatMessages = document.getElementById('chat-messages');
|
| 5 |
+
this.messageInput = document.getElementById('message-input');
|
| 6 |
+
this.sendButton = document.getElementById('send-button');
|
| 7 |
+
this.chatForm = document.getElementById('chat-form');
|
| 8 |
+
this.clearButton = document.getElementById('clear-chat');
|
| 9 |
+
this.typingIndicator = document.getElementById('typing-indicator');
|
| 10 |
+
this.statusDot = document.getElementById('status-dot');
|
| 11 |
+
this.statusText = document.getElementById('status-text');
|
| 12 |
+
this.agentStatus = document.getElementById('agent-status');
|
| 13 |
+
|
| 14 |
+
this.conversationId = null;
|
| 15 |
+
this.isConnected = false;
|
| 16 |
+
|
| 17 |
+
this.init();
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
init() {
|
| 21 |
+
this.checkConnection();
|
| 22 |
+
this.setupEventListeners();
|
| 23 |
+
this.loadConversationHistory();
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
setupEventListeners() {
|
| 27 |
+
if (this.chatForm) {
|
| 28 |
+
this.chatForm.addEventListener('submit', (e) => {
|
| 29 |
+
e.preventDefault();
|
| 30 |
+
this.sendMessage();
|
| 31 |
+
});
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
if (this.clearButton) {
|
| 35 |
+
this.clearButton.addEventListener('click', () => {
|
| 36 |
+
this.clearConversation();
|
| 37 |
+
});
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
const endConversationButton = document.getElementById('end-conversation-button');
|
| 41 |
+
if (endConversationButton) {
|
| 42 |
+
endConversationButton.addEventListener('click', () => {
|
| 43 |
+
this.endConversation();
|
| 44 |
+
});
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
if (this.messageInput) {
|
| 48 |
+
this.messageInput.addEventListener('input', () => {
|
| 49 |
+
this.autoResizeTextarea();
|
| 50 |
+
this.toggleSendButton();
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
this.messageInput.addEventListener('keydown', (e) => {
|
| 54 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 55 |
+
e.preventDefault();
|
| 56 |
+
this.sendMessage();
|
| 57 |
+
}
|
| 58 |
+
});
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
async checkConnection() {
|
| 63 |
+
try {
|
| 64 |
+
const response = await fetch('/api/health');
|
| 65 |
+
if (response.ok) {
|
| 66 |
+
this.updateConnectionStatus(true);
|
| 67 |
+
} else {
|
| 68 |
+
this.updateConnectionStatus(false);
|
| 69 |
+
}
|
| 70 |
+
} catch (error) {
|
| 71 |
+
console.error('Connection check failed:', error);
|
| 72 |
+
this.updateConnectionStatus(false);
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
updateConnectionStatus(connected) {
|
| 77 |
+
this.isConnected = connected;
|
| 78 |
+
if (this.statusDot && this.statusText) {
|
| 79 |
+
if (connected) {
|
| 80 |
+
this.statusDot.className = 'status-dot connected';
|
| 81 |
+
this.statusText.textContent = 'Connected';
|
| 82 |
+
} else {
|
| 83 |
+
this.statusDot.className = 'status-dot disconnected';
|
| 84 |
+
this.statusText.textContent = 'Connection issues';
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
if (this.agentStatus) {
|
| 88 |
+
this.agentStatus.textContent = connected ? 'Online' : 'Offline';
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
autoResizeTextarea() {
|
| 93 |
+
if (this.messageInput) {
|
| 94 |
+
this.messageInput.style.height = 'auto';
|
| 95 |
+
this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
toggleSendButton() {
|
| 100 |
+
if (this.sendButton && this.messageInput) {
|
| 101 |
+
this.sendButton.disabled = this.messageInput.value.trim().length === 0;
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
async sendMessage() {
|
| 106 |
+
const message = this.messageInput.value.trim();
|
| 107 |
+
if (!message || !this.isConnected) return;
|
| 108 |
+
|
| 109 |
+
this.addMessage(message, 'user');
|
| 110 |
+
this.messageInput.value = '';
|
| 111 |
+
this.messageInput.style.height = 'auto';
|
| 112 |
+
this.toggleSendButton();
|
| 113 |
+
this.showTypingIndicator();
|
| 114 |
+
|
| 115 |
+
try {
|
| 116 |
+
const controller = new AbortController();
|
| 117 |
+
const timeoutId = setTimeout(() => controller.abort(), 120000);
|
| 118 |
+
|
| 119 |
+
// CRITICAL FIX: Add credentials: 'include'
|
| 120 |
+
const response = await fetch('/api/chat', {
|
| 121 |
+
method: 'POST',
|
| 122 |
+
headers: {
|
| 123 |
+
'Content-Type': 'application/json',
|
| 124 |
+
},
|
| 125 |
+
body: JSON.stringify({
|
| 126 |
+
message: message,
|
| 127 |
+
conversation_id: this.conversationId
|
| 128 |
+
}),
|
| 129 |
+
signal: controller.signal,
|
| 130 |
+
cache: 'no-cache',
|
| 131 |
+
mode: 'cors',
|
| 132 |
+
credentials: 'include' // <-- THIS LINE FIXES THE NETWORK ERROR
|
| 133 |
+
});
|
| 134 |
+
|
| 135 |
+
clearTimeout(timeoutId);
|
| 136 |
+
const data = await response.json();
|
| 137 |
+
|
| 138 |
+
if (data.success) {
|
| 139 |
+
this.conversationId = data.conversation_id;
|
| 140 |
+
if (typeof updateConversationId === 'function') {
|
| 141 |
+
updateConversationId(this.conversationId);
|
| 142 |
+
}
|
| 143 |
+
this.addMessage(data.response, 'assistant', {
|
| 144 |
+
responseTime: data.response_time_ms,
|
| 145 |
+
model: data.model_used
|
| 146 |
+
});
|
| 147 |
+
} else {
|
| 148 |
+
const errorMessage = data.error || 'Sorry, I encountered an error. Please try again.';
|
| 149 |
+
this.addMessage(errorMessage, 'assistant', { isError: true });
|
| 150 |
+
}
|
| 151 |
+
} catch (error) {
|
| 152 |
+
console.error('Error sending message:', error);
|
| 153 |
+
this.addMessage('Connection error. Please check your internet connection and try again.', 'assistant', { isError: true });
|
| 154 |
+
} finally {
|
| 155 |
+
this.hideTypingIndicator();
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
addMessage(content, role, metadata = {}) {
|
| 160 |
+
const messageDiv = document.createElement('div');
|
| 161 |
+
messageDiv.className = `message ${role}-message`;
|
| 162 |
+
const timestamp = new Date().toLocaleTimeString();
|
| 163 |
+
let messageHTML = '';
|
| 164 |
+
if (role === 'user') {
|
| 165 |
+
messageHTML = `
|
| 166 |
+
<div class="message-content">
|
| 167 |
+
<div class="message-text">${this.escapeHtml(content)}</div>
|
| 168 |
+
<div class="message-time">${timestamp}</div>
|
| 169 |
+
</div>
|
| 170 |
+
<div class="message-avatar">👤</div>
|
| 171 |
+
`;
|
| 172 |
+
} else {
|
| 173 |
+
const errorClass = metadata.isError ? ' error' : '';
|
| 174 |
+
const responseTimeText = metadata.responseTime ? ` (${metadata.responseTime}ms)` : '';
|
| 175 |
+
messageHTML = `
|
| 176 |
+
<div class="message-avatar">🤖</div>
|
| 177 |
+
<div class="message-content${errorClass}">
|
| 178 |
+
<div class="message-text">${this.escapeHtml(content)}</div>
|
| 179 |
+
<div class="message-time">${timestamp}${responseTimeText}</div>
|
| 180 |
+
</div>
|
| 181 |
+
`;
|
| 182 |
+
}
|
| 183 |
+
messageDiv.innerHTML = messageHTML;
|
| 184 |
+
if (this.chatMessages) {
|
| 185 |
+
this.chatMessages.appendChild(messageDiv);
|
| 186 |
+
this.scrollToBottom();
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
showTypingIndicator() {
|
| 191 |
+
if (this.typingIndicator) {
|
| 192 |
+
this.typingIndicator.style.display = 'flex';
|
| 193 |
+
this.scrollToBottom();
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
hideTypingIndicator() {
|
| 198 |
+
if (this.typingIndicator) {
|
| 199 |
+
this.typingIndicator.style.display = 'none';
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
scrollToBottom() {
|
| 204 |
+
if (this.chatMessages) {
|
| 205 |
+
this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
async clearConversation() {
|
| 210 |
+
if (!this.conversationId) {
|
| 211 |
+
this.clearChatUI();
|
| 212 |
+
return;
|
| 213 |
+
}
|
| 214 |
+
if (confirm('Are you sure you want to clear this conversation?')) {
|
| 215 |
+
try {
|
| 216 |
+
const response = await fetch(`/api/conversation/${this.conversationId}/clear`, {
|
| 217 |
+
method: 'POST'
|
| 218 |
+
});
|
| 219 |
+
if (response.ok) {
|
| 220 |
+
this.clearChatUI();
|
| 221 |
+
this.conversationId = null;
|
| 222 |
+
} else {
|
| 223 |
+
alert('Failed to clear conversation. Please try again.');
|
| 224 |
+
}
|
| 225 |
+
} catch (error) {
|
| 226 |
+
console.error('Error clearing conversation:', error);
|
| 227 |
+
alert('Error clearing conversation. Please try again.');
|
| 228 |
+
}
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
async endConversation() {
|
| 233 |
+
if (!this.conversationId) {
|
| 234 |
+
alert('No active conversation to end.');
|
| 235 |
+
return;
|
| 236 |
+
}
|
| 237 |
+
if (confirm('End this conversation and save a summary to any mentioned tickets?')) {
|
| 238 |
+
try {
|
| 239 |
+
const response = await fetch('/api/conversation/end', {
|
| 240 |
+
method: 'POST',
|
| 241 |
+
headers: { 'Content-Type': 'application/json' },
|
| 242 |
+
body: JSON.stringify({ conversation_id: this.conversationId })
|
| 243 |
+
});
|
| 244 |
+
if (response.ok) {
|
| 245 |
+
const result = await response.json();
|
| 246 |
+
if (result.success) {
|
| 247 |
+
alert(result.message || 'Conversation ended successfully!');
|
| 248 |
+
this.clearChatUI();
|
| 249 |
+
this.conversationId = null;
|
| 250 |
+
} else {
|
| 251 |
+
alert(result.message || 'Failed to end conversation properly.');
|
| 252 |
+
}
|
| 253 |
+
} else {
|
| 254 |
+
alert('Failed to end conversation. Please try again.');
|
| 255 |
+
}
|
| 256 |
+
} catch (error) {
|
| 257 |
+
console.error('Error ending conversation:', error);
|
| 258 |
+
alert('Error ending conversation. Please try again.');
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
clearChatUI() {
|
| 264 |
+
if (this.chatMessages) {
|
| 265 |
+
const welcomeMessage = this.chatMessages.querySelector('.welcome-message');
|
| 266 |
+
this.chatMessages.innerHTML = '';
|
| 267 |
+
if (welcomeMessage) {
|
| 268 |
+
this.chatMessages.appendChild(welcomeMessage);
|
| 269 |
+
}
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
async loadConversationHistory() {
|
| 274 |
+
try {
|
| 275 |
+
const response = await fetch('/api/user');
|
| 276 |
+
if (response.ok) {
|
| 277 |
+
const userData = await response.json();
|
| 278 |
+
if (userData.success) {
|
| 279 |
+
console.log('User authenticated:', userData.user.name);
|
| 280 |
+
}
|
| 281 |
+
}
|
| 282 |
+
} catch (error) {
|
| 283 |
+
console.log('User not authenticated');
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
escapeHtml(text) {
|
| 288 |
+
const div = document.createElement('div');
|
| 289 |
+
div.textContent = text;
|
| 290 |
+
return div.innerHTML;
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 295 |
+
if (document.getElementById('chat-messages')) {
|
| 296 |
+
new ChatInterface();
|
| 297 |
+
}
|
| 298 |
+
});
|
static/script.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Too Many Cables - Main Website JavaScript
|
| 2 |
+
class TMCWebsite {
|
| 3 |
+
constructor() {
|
| 4 |
+
this.currentUser = null;
|
| 5 |
+
this.init();
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
init() {
|
| 9 |
+
this.setupNavigation();
|
| 10 |
+
this.setupUserAuthentication();
|
| 11 |
+
this.checkUserStatus();
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
setupNavigation() {
|
| 15 |
+
const navToggle = document.getElementById('nav-toggle');
|
| 16 |
+
const navMenu = document.getElementById('nav-menu');
|
| 17 |
+
|
| 18 |
+
if (navToggle && navMenu) {
|
| 19 |
+
navToggle.addEventListener('click', () => {
|
| 20 |
+
navMenu.classList.toggle('active');
|
| 21 |
+
navToggle.classList.toggle('active');
|
| 22 |
+
});
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
setupUserAuthentication() {
|
| 27 |
+
// Login/logout functionality is now handled in base.html
|
| 28 |
+
// This method is kept for future authentication-related features
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
showModal(type) {
|
| 32 |
+
const modalId = type === 'login' ? 'login-modal' : 'register-modal';
|
| 33 |
+
const modal = document.getElementById(modalId);
|
| 34 |
+
if (modal) {
|
| 35 |
+
modal.style.display = 'flex';
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
hideModal(modalId) {
|
| 40 |
+
const modal = document.getElementById(modalId);
|
| 41 |
+
if (modal) {
|
| 42 |
+
modal.style.display = 'none';
|
| 43 |
+
const form = modal.querySelector('form');
|
| 44 |
+
if (form) form.reset();
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
async logout() {
|
| 49 |
+
// Logout functionality is now handled in base.html
|
| 50 |
+
// This method is kept for compatibility
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
async checkUserStatus() {
|
| 54 |
+
try {
|
| 55 |
+
const response = await fetch('/api/user');
|
| 56 |
+
const data = await response.json();
|
| 57 |
+
|
| 58 |
+
if (data.success) {
|
| 59 |
+
this.currentUser = data.user;
|
| 60 |
+
this.updateUserUI();
|
| 61 |
+
}
|
| 62 |
+
} catch (error) {
|
| 63 |
+
console.log('User not authenticated');
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
updateUserUI() {
|
| 68 |
+
const authBtn = document.getElementById('auth-btn');
|
| 69 |
+
const userGreeting = document.getElementById('user-greeting');
|
| 70 |
+
const userName = document.getElementById('user-name');
|
| 71 |
+
const ticketsLink = document.getElementById('tickets-link');
|
| 72 |
+
|
| 73 |
+
if (this.currentUser) {
|
| 74 |
+
if (authBtn) {
|
| 75 |
+
authBtn.textContent = 'Logout';
|
| 76 |
+
authBtn.title = 'Click to logout';
|
| 77 |
+
}
|
| 78 |
+
if (userGreeting) userGreeting.style.display = 'inline';
|
| 79 |
+
if (userName) userName.textContent = this.currentUser.name;
|
| 80 |
+
if (ticketsLink) ticketsLink.style.display = 'inline-block';
|
| 81 |
+
} else {
|
| 82 |
+
if (authBtn) {
|
| 83 |
+
authBtn.textContent = 'Login';
|
| 84 |
+
authBtn.title = 'Click to login';
|
| 85 |
+
}
|
| 86 |
+
if (userGreeting) userGreeting.style.display = 'none';
|
| 87 |
+
if (ticketsLink) ticketsLink.style.display = 'none';
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// Initialize website functionality when DOM is loaded
|
| 93 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 94 |
+
new TMCWebsite();
|
| 95 |
+
});
|
static/style.css
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Reset and base styles */
|
| 2 |
+
* {
|
| 3 |
+
margin: 0;
|
| 4 |
+
padding: 0;
|
| 5 |
+
box-sizing: border-box;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
body {
|
| 9 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 10 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 11 |
+
min-height: 100vh;
|
| 12 |
+
line-height: 1.6;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.container {
|
| 16 |
+
max-width: 1200px;
|
| 17 |
+
margin: 0 auto;
|
| 18 |
+
padding: 20px;
|
| 19 |
+
min-height: 100vh;
|
| 20 |
+
display: flex;
|
| 21 |
+
flex-direction: column;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
/* Header */
|
| 25 |
+
header {
|
| 26 |
+
background: rgba(255, 255, 255, 0.95);
|
| 27 |
+
backdrop-filter: blur(10px);
|
| 28 |
+
border-radius: 15px;
|
| 29 |
+
padding: 20px 30px;
|
| 30 |
+
margin-bottom: 20px;
|
| 31 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
| 32 |
+
display: flex;
|
| 33 |
+
justify-content: space-between;
|
| 34 |
+
align-items: center;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
header h1 {
|
| 38 |
+
color: #333;
|
| 39 |
+
font-size: 2.2em;
|
| 40 |
+
font-weight: 700;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.status-indicator {
|
| 44 |
+
display: flex;
|
| 45 |
+
align-items: center;
|
| 46 |
+
gap: 10px;
|
| 47 |
+
font-weight: 500;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.status-dot {
|
| 51 |
+
width: 12px;
|
| 52 |
+
height: 12px;
|
| 53 |
+
border-radius: 50%;
|
| 54 |
+
background: #ffa500;
|
| 55 |
+
animation: pulse 2s infinite;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.status-dot.connected {
|
| 59 |
+
background: #4caf50;
|
| 60 |
+
animation: none;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
.status-dot.error {
|
| 64 |
+
background: #f44336;
|
| 65 |
+
animation: none;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
@keyframes pulse {
|
| 69 |
+
0% { opacity: 1; }
|
| 70 |
+
50% { opacity: 0.5; }
|
| 71 |
+
100% { opacity: 1; }
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/* Main content */
|
| 75 |
+
.main-content {
|
| 76 |
+
display: grid;
|
| 77 |
+
grid-template-columns: 300px 1fr;
|
| 78 |
+
gap: 20px;
|
| 79 |
+
flex: 1;
|
| 80 |
+
min-height: 0;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/* Agent panel */
|
| 84 |
+
.agent-panel {
|
| 85 |
+
background: rgba(255, 255, 255, 0.95);
|
| 86 |
+
backdrop-filter: blur(10px);
|
| 87 |
+
border-radius: 15px;
|
| 88 |
+
padding: 25px;
|
| 89 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
| 90 |
+
height: fit-content;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.agent-panel h3 {
|
| 94 |
+
color: #333;
|
| 95 |
+
margin-bottom: 15px;
|
| 96 |
+
font-size: 1.3em;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.agent-info {
|
| 100 |
+
display: flex;
|
| 101 |
+
flex-direction: column;
|
| 102 |
+
gap: 10px;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
.agent-info p {
|
| 106 |
+
color: #666;
|
| 107 |
+
font-size: 14px;
|
| 108 |
+
margin: 5px 0;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.agent-info p strong {
|
| 112 |
+
color: #333;
|
| 113 |
+
min-width: 80px;
|
| 114 |
+
display: inline-block;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.agent-description {
|
| 118 |
+
margin-top: 15px;
|
| 119 |
+
padding: 15px;
|
| 120 |
+
background: #f8f9ff;
|
| 121 |
+
border-radius: 10px;
|
| 122 |
+
border-left: 4px solid #667eea;
|
| 123 |
+
font-style: italic;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
#agent-status {
|
| 127 |
+
font-weight: 500;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
#agent-status.ready {
|
| 131 |
+
color: #28a745;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
#agent-status.error {
|
| 135 |
+
color: #dc3545;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
#agent-status.connecting {
|
| 139 |
+
color: #ffc107;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
/* Chat container */
|
| 143 |
+
.chat-container {
|
| 144 |
+
background: rgba(255, 255, 255, 0.95);
|
| 145 |
+
backdrop-filter: blur(10px);
|
| 146 |
+
border-radius: 15px;
|
| 147 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
| 148 |
+
display: flex;
|
| 149 |
+
flex-direction: column;
|
| 150 |
+
overflow: hidden;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
.chat-header {
|
| 154 |
+
padding: 20px 25px;
|
| 155 |
+
border-bottom: 1px solid #e0e0e0;
|
| 156 |
+
display: flex;
|
| 157 |
+
justify-content: space-between;
|
| 158 |
+
align-items: center;
|
| 159 |
+
background: rgba(255, 255, 255, 0.8);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.chat-header h3 {
|
| 163 |
+
color: #333;
|
| 164 |
+
font-size: 1.3em;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.clear-btn {
|
| 168 |
+
padding: 8px 15px;
|
| 169 |
+
background: #f44336;
|
| 170 |
+
color: white;
|
| 171 |
+
border: none;
|
| 172 |
+
border-radius: 8px;
|
| 173 |
+
cursor: pointer;
|
| 174 |
+
font-size: 14px;
|
| 175 |
+
transition: all 0.3s ease;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.clear-btn:hover {
|
| 179 |
+
background: #d32f2f;
|
| 180 |
+
transform: translateY(-2px);
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
/* Chat messages */
|
| 184 |
+
.chat-messages {
|
| 185 |
+
flex: 1;
|
| 186 |
+
padding: 20px;
|
| 187 |
+
overflow-y: auto;
|
| 188 |
+
max-height: 500px;
|
| 189 |
+
min-height: 400px;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.welcome-message {
|
| 193 |
+
text-align: center;
|
| 194 |
+
color: #666;
|
| 195 |
+
margin-top: 50px;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.welcome-message h4 {
|
| 199 |
+
color: #333;
|
| 200 |
+
margin-bottom: 15px;
|
| 201 |
+
font-size: 1.4em;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.welcome-message ul {
|
| 205 |
+
text-align: left;
|
| 206 |
+
max-width: 400px;
|
| 207 |
+
margin: 20px auto 0;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
.welcome-message li {
|
| 211 |
+
margin: 8px 0;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
.message {
|
| 215 |
+
margin: 15px 0;
|
| 216 |
+
display: flex;
|
| 217 |
+
align-items: flex-start;
|
| 218 |
+
gap: 12px;
|
| 219 |
+
opacity: 0;
|
| 220 |
+
animation: messageSlideIn 0.3s ease forwards;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
@keyframes messageSlideIn {
|
| 224 |
+
from {
|
| 225 |
+
opacity: 0;
|
| 226 |
+
transform: translateY(20px);
|
| 227 |
+
}
|
| 228 |
+
to {
|
| 229 |
+
opacity: 1;
|
| 230 |
+
transform: translateY(0);
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.message.user {
|
| 235 |
+
flex-direction: row-reverse;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
.message-avatar {
|
| 239 |
+
width: 40px;
|
| 240 |
+
height: 40px;
|
| 241 |
+
border-radius: 50%;
|
| 242 |
+
display: flex;
|
| 243 |
+
align-items: center;
|
| 244 |
+
justify-content: center;
|
| 245 |
+
font-size: 18px;
|
| 246 |
+
flex-shrink: 0;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.message.user .message-avatar {
|
| 250 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 251 |
+
color: white;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.message.bot .message-avatar {
|
| 255 |
+
background: linear-gradient(135deg, #4caf50, #45a049);
|
| 256 |
+
color: white;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
.message-content {
|
| 260 |
+
max-width: 70%;
|
| 261 |
+
padding: 15px 20px;
|
| 262 |
+
border-radius: 20px;
|
| 263 |
+
position: relative;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
.message.user .message-content {
|
| 267 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 268 |
+
color: white;
|
| 269 |
+
border-bottom-right-radius: 5px;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
.message.bot .message-content {
|
| 273 |
+
background: #f8f9ff;
|
| 274 |
+
border: 1px solid #e0e0e0;
|
| 275 |
+
color: #333;
|
| 276 |
+
border-bottom-left-radius: 5px;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
.message-text {
|
| 280 |
+
margin: 0;
|
| 281 |
+
word-wrap: break-word;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
.message-time {
|
| 285 |
+
font-size: 11px;
|
| 286 |
+
opacity: 0.7;
|
| 287 |
+
margin-top: 5px;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
/* Chat input */
|
| 291 |
+
.chat-input-container {
|
| 292 |
+
padding: 20px;
|
| 293 |
+
border-top: 1px solid #e0e0e0;
|
| 294 |
+
background: rgba(255, 255, 255, 0.8);
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
.input-group {
|
| 298 |
+
display: flex;
|
| 299 |
+
gap: 15px;
|
| 300 |
+
align-items: flex-end;
|
| 301 |
+
flex-wrap: wrap;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
#message-input {
|
| 305 |
+
flex: 1;
|
| 306 |
+
padding: 15px 20px;
|
| 307 |
+
border: 2px solid #e0e0e0;
|
| 308 |
+
border-radius: 25px;
|
| 309 |
+
font-size: 14px;
|
| 310 |
+
font-family: inherit;
|
| 311 |
+
resize: none;
|
| 312 |
+
transition: all 0.3s ease;
|
| 313 |
+
background: white;
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
#message-input:focus {
|
| 317 |
+
outline: none;
|
| 318 |
+
border-color: #667eea;
|
| 319 |
+
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
#message-input:disabled {
|
| 323 |
+
background: #f5f5f5;
|
| 324 |
+
color: #999;
|
| 325 |
+
cursor: not-allowed;
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
#send-btn {
|
| 329 |
+
padding: 15px 25px;
|
| 330 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 331 |
+
color: white;
|
| 332 |
+
border: none;
|
| 333 |
+
border-radius: 25px;
|
| 334 |
+
cursor: pointer;
|
| 335 |
+
font-size: 14px;
|
| 336 |
+
font-weight: 600;
|
| 337 |
+
transition: all 0.3s ease;
|
| 338 |
+
display: flex;
|
| 339 |
+
align-items: center;
|
| 340 |
+
gap: 8px;
|
| 341 |
+
min-width: 100px;
|
| 342 |
+
justify-content: center;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
#send-btn:hover:not(:disabled) {
|
| 346 |
+
transform: translateY(-2px);
|
| 347 |
+
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
#send-btn:disabled {
|
| 351 |
+
background: #ccc;
|
| 352 |
+
cursor: not-allowed;
|
| 353 |
+
transform: none;
|
| 354 |
+
box-shadow: none;
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
.action-button {
|
| 358 |
+
padding: 10px 15px;
|
| 359 |
+
border: 2px solid #667eea;
|
| 360 |
+
background: transparent;
|
| 361 |
+
color: #667eea;
|
| 362 |
+
border-radius: 20px;
|
| 363 |
+
cursor: pointer;
|
| 364 |
+
font-size: 12px;
|
| 365 |
+
font-weight: 600;
|
| 366 |
+
transition: all 0.3s ease;
|
| 367 |
+
margin-left: 10px;
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
.action-button:hover {
|
| 371 |
+
background: #667eea;
|
| 372 |
+
color: white;
|
| 373 |
+
transform: translateY(-1px);
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
.action-button.end-conversation {
|
| 377 |
+
border-color: #e74c3c;
|
| 378 |
+
color: #e74c3c;
|
| 379 |
+
background: #ffebee;
|
| 380 |
+
font-weight: bold;
|
| 381 |
+
padding: 12px 20px;
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
.action-button.end-conversation:hover {
|
| 385 |
+
background: #e74c3c;
|
| 386 |
+
color: white;
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
.send-icon {
|
| 390 |
+
font-size: 16px;
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
.input-info {
|
| 394 |
+
display: flex;
|
| 395 |
+
justify-content: space-between;
|
| 396 |
+
align-items: center;
|
| 397 |
+
margin-top: 10px;
|
| 398 |
+
font-size: 12px;
|
| 399 |
+
color: #666;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.divider {
|
| 403 |
+
margin: 0 10px;
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
/* Loading overlay */
|
| 407 |
+
.loading-overlay {
|
| 408 |
+
position: fixed;
|
| 409 |
+
top: 0;
|
| 410 |
+
left: 0;
|
| 411 |
+
right: 0;
|
| 412 |
+
bottom: 0;
|
| 413 |
+
background: rgba(0, 0, 0, 0.5);
|
| 414 |
+
display: flex;
|
| 415 |
+
align-items: center;
|
| 416 |
+
justify-content: center;
|
| 417 |
+
z-index: 1000;
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
.loading-overlay.hidden {
|
| 421 |
+
display: none;
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
.loading-spinner {
|
| 425 |
+
background: white;
|
| 426 |
+
padding: 30px;
|
| 427 |
+
border-radius: 15px;
|
| 428 |
+
text-align: center;
|
| 429 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
.spinner {
|
| 433 |
+
width: 40px;
|
| 434 |
+
height: 40px;
|
| 435 |
+
border: 4px solid #f3f3f3;
|
| 436 |
+
border-top: 4px solid #667eea;
|
| 437 |
+
border-radius: 50%;
|
| 438 |
+
animation: spin 1s linear infinite;
|
| 439 |
+
margin: 0 auto 15px;
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
@keyframes spin {
|
| 443 |
+
0% { transform: rotate(0deg); }
|
| 444 |
+
100% { transform: rotate(360deg); }
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
/* Responsive design */
|
| 448 |
+
@media (max-width: 768px) {
|
| 449 |
+
.container {
|
| 450 |
+
padding: 10px;
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
.main-content {
|
| 454 |
+
grid-template-columns: 1fr;
|
| 455 |
+
gap: 15px;
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
.agent-panel {
|
| 459 |
+
order: 2;
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
.chat-container {
|
| 463 |
+
order: 1;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
header {
|
| 467 |
+
padding: 15px 20px;
|
| 468 |
+
flex-direction: column;
|
| 469 |
+
gap: 15px;
|
| 470 |
+
text-align: center;
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
header h1 {
|
| 474 |
+
font-size: 1.8em;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
.chat-messages {
|
| 478 |
+
max-height: 300px;
|
| 479 |
+
min-height: 250px;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
.message-content {
|
| 483 |
+
max-width: 85%;
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
.input-group {
|
| 487 |
+
flex-direction: column;
|
| 488 |
+
gap: 10px;
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
#send-btn {
|
| 492 |
+
align-self: flex-end;
|
| 493 |
+
min-width: 120px;
|
| 494 |
+
}
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
/* Scrollbar styling */
|
| 498 |
+
.chat-messages::-webkit-scrollbar {
|
| 499 |
+
width: 6px;
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
.chat-messages::-webkit-scrollbar-track {
|
| 503 |
+
background: #f1f1f1;
|
| 504 |
+
border-radius: 3px;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
.chat-messages::-webkit-scrollbar-thumb {
|
| 508 |
+
background: #c1c1c1;
|
| 509 |
+
border-radius: 3px;
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
.chat-messages::-webkit-scrollbar-thumb:hover {
|
| 513 |
+
background: #a8a8a8;
|
| 514 |
+
}
|
templates/about.html
ADDED
|
File without changes
|
templates/admin_tickets.html
ADDED
|
@@ -0,0 +1,888 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Admin - Ticket Management - Too Many Cables{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block description %}Administrative interface for managing customer support tickets and monitoring system performance.{% endblock %}
|
| 6 |
+
|
| 7 |
+
{% block content %}
|
| 8 |
+
<div class="admin-container">
|
| 9 |
+
<div class="page-header">
|
| 10 |
+
<h1 class="page-title">Admin - Ticket Management</h1>
|
| 11 |
+
<p class="page-subtitle">Manage customer support tickets and monitor system performance</p>
|
| 12 |
+
</div>
|
| 13 |
+
|
| 14 |
+
<!-- Statistics Dashboard -->
|
| 15 |
+
<div class="stats-grid">
|
| 16 |
+
<div class="stat-card">
|
| 17 |
+
<div class="stat-icon">📊</div>
|
| 18 |
+
<div class="stat-content">
|
| 19 |
+
<h3 id="total-tickets">-</h3>
|
| 20 |
+
<p>Total Tickets</p>
|
| 21 |
+
</div>
|
| 22 |
+
</div>
|
| 23 |
+
|
| 24 |
+
<div class="stat-card">
|
| 25 |
+
<div class="stat-icon">🟢</div>
|
| 26 |
+
<div class="stat-content">
|
| 27 |
+
<h3 id="open-tickets">-</h3>
|
| 28 |
+
<p>Open Tickets</p>
|
| 29 |
+
</div>
|
| 30 |
+
</div>
|
| 31 |
+
|
| 32 |
+
<div class="stat-card">
|
| 33 |
+
<div class="stat-icon">🔵</div>
|
| 34 |
+
<div class="stat-content">
|
| 35 |
+
<h3 id="in-progress-tickets">-</h3>
|
| 36 |
+
<p>In Progress</p>
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<div class="stat-card">
|
| 41 |
+
<div class="stat-icon">✅</div>
|
| 42 |
+
<div class="stat-content">
|
| 43 |
+
<h3 id="resolved-tickets">-</h3>
|
| 44 |
+
<p>Resolved</p>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
</div>
|
| 48 |
+
|
| 49 |
+
<!-- Filters and Controls -->
|
| 50 |
+
<div class="glass-card filters-section">
|
| 51 |
+
<div class="filters-header">
|
| 52 |
+
<h3>Ticket Filters</h3>
|
| 53 |
+
<button class="btn btn-primary" onclick="refreshTickets()">Refresh</button>
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<div class="filters-row">
|
| 57 |
+
<div class="filter-group">
|
| 58 |
+
<label for="status-filter">Status:</label>
|
| 59 |
+
<select id="status-filter" onchange="applyFilters()">
|
| 60 |
+
<option value="">All Status</option>
|
| 61 |
+
<option value="open">Open</option>
|
| 62 |
+
<option value="in_progress">In Progress</option>
|
| 63 |
+
<option value="resolved">Resolved</option>
|
| 64 |
+
<option value="closed">Closed</option>
|
| 65 |
+
</select>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
<div class="filter-group">
|
| 69 |
+
<label for="priority-filter">Priority:</label>
|
| 70 |
+
<select id="priority-filter" onchange="applyFilters()">
|
| 71 |
+
<option value="">All Priority</option>
|
| 72 |
+
<option value="low">Low</option>
|
| 73 |
+
<option value="medium">Medium</option>
|
| 74 |
+
<option value="high">High</option>
|
| 75 |
+
<option value="urgent">Urgent</option>
|
| 76 |
+
</select>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
<div class="filter-group">
|
| 80 |
+
<label for="category-filter">Category:</label>
|
| 81 |
+
<select id="category-filter" onchange="applyFilters()">
|
| 82 |
+
<option value="">All Categories</option>
|
| 83 |
+
</select>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
|
| 88 |
+
<!-- Tickets Table -->
|
| 89 |
+
<div class="glass-card tickets-table-section">
|
| 90 |
+
<div class="table-header">
|
| 91 |
+
<h3>All Tickets</h3>
|
| 92 |
+
<div class="pagination-info">
|
| 93 |
+
<span id="pagination-text">Loading...</span>
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
|
| 97 |
+
<div id="tickets-loading" class="loading-state">
|
| 98 |
+
<div class="spinner"></div>
|
| 99 |
+
<p>Loading tickets...</p>
|
| 100 |
+
</div>
|
| 101 |
+
|
| 102 |
+
<div id="tickets-table-container" style="display: none;">
|
| 103 |
+
<table id="tickets-table" class="admin-table">
|
| 104 |
+
<thead>
|
| 105 |
+
<tr>
|
| 106 |
+
<th>Ticket #</th>
|
| 107 |
+
<th>Customer</th>
|
| 108 |
+
<th>Subject</th>
|
| 109 |
+
<th>Category</th>
|
| 110 |
+
<th>Priority</th>
|
| 111 |
+
<th>Status</th>
|
| 112 |
+
<th>Assigned To</th>
|
| 113 |
+
<th>Created</th>
|
| 114 |
+
<th>Actions</th>
|
| 115 |
+
</tr>
|
| 116 |
+
</thead>
|
| 117 |
+
<tbody id="tickets-table-body">
|
| 118 |
+
</tbody>
|
| 119 |
+
</table>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
<!-- Pagination -->
|
| 123 |
+
<div class="pagination-container">
|
| 124 |
+
<button id="prev-page" class="btn btn-outline" onclick="changePage(-1)" disabled>Previous</button>
|
| 125 |
+
<span id="page-info">Page 1 of 1</span>
|
| 126 |
+
<button id="next-page" class="btn btn-outline" onclick="changePage(1)" disabled>Next</button>
|
| 127 |
+
</div>
|
| 128 |
+
</div>
|
| 129 |
+
</div>
|
| 130 |
+
|
| 131 |
+
<!-- Ticket Detail Modal -->
|
| 132 |
+
<div id="ticket-detail-modal" class="modal" style="display: none;">
|
| 133 |
+
<div class="modal-content large-modal glass-card">
|
| 134 |
+
<div class="modal-header">
|
| 135 |
+
<h3 id="modal-ticket-title">Ticket Details</h3>
|
| 136 |
+
<button class="modal-close" onclick="closeTicketModal()">×</button>
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<div class="modal-body">
|
| 140 |
+
<div id="ticket-detail-content"></div>
|
| 141 |
+
|
| 142 |
+
<!-- Admin Actions -->
|
| 143 |
+
<div class="admin-actions">
|
| 144 |
+
<h4>Admin Actions</h4>
|
| 145 |
+
|
| 146 |
+
<div class="action-row">
|
| 147 |
+
<div class="action-group">
|
| 148 |
+
<label for="assign-agent">Assign to Agent:</label>
|
| 149 |
+
<input type="text" id="assign-agent" placeholder="Agent name">
|
| 150 |
+
<button class="btn btn-secondary" onclick="assignTicket()">Assign</button>
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
<div class="action-group">
|
| 154 |
+
<label for="update-status">Update Status:</label>
|
| 155 |
+
<select id="update-status">
|
| 156 |
+
<option value="open">Open</option>
|
| 157 |
+
<option value="in_progress">In Progress</option>
|
| 158 |
+
<option value="resolved">Resolved</option>
|
| 159 |
+
<option value="closed">Closed</option>
|
| 160 |
+
</select>
|
| 161 |
+
<button class="btn btn-secondary" onclick="updateStatus()">Update</button>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
|
| 165 |
+
<div class="admin-reply-section">
|
| 166 |
+
<h5>Add Admin Reply</h5>
|
| 167 |
+
<textarea id="admin-reply" placeholder="Type your reply..." rows="3"></textarea>
|
| 168 |
+
<div class="reply-options">
|
| 169 |
+
<label>
|
| 170 |
+
<input type="checkbox" id="internal-note"> Internal note (not visible to customer)
|
| 171 |
+
</label>
|
| 172 |
+
<button class="btn btn-primary" onclick="addAdminReply()">Send Reply</button>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
</div>
|
| 177 |
+
</div>
|
| 178 |
+
</div>
|
| 179 |
+
|
| 180 |
+
<style>
|
| 181 |
+
.admin-container {
|
| 182 |
+
max-width: 1400px;
|
| 183 |
+
margin: 0 auto;
|
| 184 |
+
padding: 2rem;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
.page-header {
|
| 188 |
+
text-align: center;
|
| 189 |
+
margin-bottom: 3rem;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.page-title {
|
| 193 |
+
font-size: 2.5rem;
|
| 194 |
+
font-weight: 700;
|
| 195 |
+
color: white;
|
| 196 |
+
text-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
| 197 |
+
margin-bottom: 0.5rem;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
.page-subtitle {
|
| 201 |
+
font-size: 1.1rem;
|
| 202 |
+
color: rgba(255,255,255,0.9);
|
| 203 |
+
font-weight: 300;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
.stats-grid {
|
| 207 |
+
display: grid;
|
| 208 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 209 |
+
gap: 1.5rem;
|
| 210 |
+
margin-bottom: 2rem;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.stat-card {
|
| 214 |
+
background: rgba(255, 255, 255, 0.15);
|
| 215 |
+
backdrop-filter: blur(25px);
|
| 216 |
+
border-radius: 15px;
|
| 217 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 218 |
+
padding: 1.5rem;
|
| 219 |
+
display: flex;
|
| 220 |
+
align-items: center;
|
| 221 |
+
gap: 1rem;
|
| 222 |
+
transition: transform 0.3s ease;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.stat-card:hover {
|
| 226 |
+
transform: translateY(-2px);
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
.stat-icon {
|
| 230 |
+
font-size: 2rem;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.stat-content h3 {
|
| 234 |
+
font-size: 2rem;
|
| 235 |
+
font-weight: 700;
|
| 236 |
+
color: white;
|
| 237 |
+
margin: 0;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.stat-content p {
|
| 241 |
+
color: rgba(255,255,255,0.8);
|
| 242 |
+
margin: 0;
|
| 243 |
+
font-size: 0.9rem;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.glass-card {
|
| 247 |
+
background: rgba(255, 255, 255, 0.15);
|
| 248 |
+
backdrop-filter: blur(25px);
|
| 249 |
+
-webkit-backdrop-filter: blur(25px);
|
| 250 |
+
border-radius: 20px;
|
| 251 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 252 |
+
padding: 2rem;
|
| 253 |
+
margin-bottom: 2rem;
|
| 254 |
+
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.filters-header {
|
| 258 |
+
display: flex;
|
| 259 |
+
justify-content: space-between;
|
| 260 |
+
align-items: center;
|
| 261 |
+
margin-bottom: 1.5rem;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.filters-header h3 {
|
| 265 |
+
color: white;
|
| 266 |
+
margin: 0;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.filters-row {
|
| 270 |
+
display: grid;
|
| 271 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 272 |
+
gap: 1rem;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
.filter-group label {
|
| 276 |
+
display: block;
|
| 277 |
+
color: rgba(255,255,255,0.9);
|
| 278 |
+
margin-bottom: 0.5rem;
|
| 279 |
+
font-weight: 500;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.filter-group select,
|
| 283 |
+
.filter-group input {
|
| 284 |
+
width: 100%;
|
| 285 |
+
padding: 0.5rem;
|
| 286 |
+
border: 1px solid rgba(255,255,255,0.3);
|
| 287 |
+
border-radius: 8px;
|
| 288 |
+
background: rgba(255,255,255,0.1);
|
| 289 |
+
color: white;
|
| 290 |
+
font-size: 0.9rem;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
.table-header {
|
| 294 |
+
display: flex;
|
| 295 |
+
justify-content: space-between;
|
| 296 |
+
align-items: center;
|
| 297 |
+
margin-bottom: 1.5rem;
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
.table-header h3 {
|
| 301 |
+
color: white;
|
| 302 |
+
margin: 0;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.admin-table {
|
| 306 |
+
width: 100%;
|
| 307 |
+
border-collapse: collapse;
|
| 308 |
+
background: rgba(255,255,255,0.1);
|
| 309 |
+
border-radius: 10px;
|
| 310 |
+
overflow: hidden;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
.admin-table th,
|
| 314 |
+
.admin-table td {
|
| 315 |
+
padding: 1rem;
|
| 316 |
+
text-align: left;
|
| 317 |
+
border-bottom: 1px solid rgba(255,255,255,0.1);
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
.admin-table th {
|
| 321 |
+
background: rgba(255,255,255,0.2);
|
| 322 |
+
color: white;
|
| 323 |
+
font-weight: 600;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
.admin-table td {
|
| 327 |
+
color: rgba(255,255,255,0.9);
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
.admin-table tr:hover {
|
| 331 |
+
background: rgba(255,255,255,0.1);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
.ticket-status-badge {
|
| 335 |
+
padding: 0.25rem 0.75rem;
|
| 336 |
+
border-radius: 20px;
|
| 337 |
+
font-size: 0.8rem;
|
| 338 |
+
font-weight: 500;
|
| 339 |
+
text-transform: uppercase;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
.priority-badge {
|
| 343 |
+
padding: 0.25rem 0.5rem;
|
| 344 |
+
border-radius: 15px;
|
| 345 |
+
font-size: 0.75rem;
|
| 346 |
+
font-weight: 500;
|
| 347 |
+
text-transform: uppercase;
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
.priority-low { background: #95a5a6; color: white; }
|
| 351 |
+
.priority-medium { background: #3498db; color: white; }
|
| 352 |
+
.priority-high { background: #f39c12; color: white; }
|
| 353 |
+
.priority-urgent { background: #e74c3c; color: white; }
|
| 354 |
+
|
| 355 |
+
.btn {
|
| 356 |
+
padding: 0.5rem 1rem;
|
| 357 |
+
border: none;
|
| 358 |
+
border-radius: 8px;
|
| 359 |
+
font-weight: 600;
|
| 360 |
+
text-decoration: none;
|
| 361 |
+
display: inline-block;
|
| 362 |
+
transition: all 0.3s ease;
|
| 363 |
+
cursor: pointer;
|
| 364 |
+
font-size: 0.9rem;
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
.btn-primary {
|
| 368 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 369 |
+
color: white;
|
| 370 |
+
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
.btn-secondary {
|
| 374 |
+
background: rgba(255,255,255,0.2);
|
| 375 |
+
color: white;
|
| 376 |
+
border: 1px solid rgba(255,255,255,0.3);
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.btn-outline {
|
| 380 |
+
background: transparent;
|
| 381 |
+
color: white;
|
| 382 |
+
border: 1px solid rgba(255,255,255,0.3);
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
.btn:hover {
|
| 386 |
+
transform: translateY(-1px);
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
.btn:disabled {
|
| 390 |
+
opacity: 0.5;
|
| 391 |
+
cursor: not-allowed;
|
| 392 |
+
transform: none;
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
.pagination-container {
|
| 396 |
+
display: flex;
|
| 397 |
+
justify-content: space-between;
|
| 398 |
+
align-items: center;
|
| 399 |
+
margin-top: 1rem;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.loading-state {
|
| 403 |
+
text-align: center;
|
| 404 |
+
padding: 2rem;
|
| 405 |
+
color: rgba(255,255,255,0.8);
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
.spinner {
|
| 409 |
+
border: 3px solid rgba(255,255,255,0.3);
|
| 410 |
+
border-top: 3px solid white;
|
| 411 |
+
border-radius: 50%;
|
| 412 |
+
width: 40px;
|
| 413 |
+
height: 40px;
|
| 414 |
+
animation: spin 1s linear infinite;
|
| 415 |
+
margin: 0 auto 1rem;
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
@keyframes spin {
|
| 419 |
+
0% { transform: rotate(0deg); }
|
| 420 |
+
100% { transform: rotate(360deg); }
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
.modal {
|
| 424 |
+
position: fixed;
|
| 425 |
+
top: 0;
|
| 426 |
+
left: 0;
|
| 427 |
+
right: 0;
|
| 428 |
+
bottom: 0;
|
| 429 |
+
background: rgba(0,0,0,0.8);
|
| 430 |
+
display: flex;
|
| 431 |
+
justify-content: center;
|
| 432 |
+
align-items: center;
|
| 433 |
+
z-index: 2000;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
.large-modal {
|
| 437 |
+
max-width: 900px;
|
| 438 |
+
width: 95%;
|
| 439 |
+
max-height: 90vh;
|
| 440 |
+
overflow-y: auto;
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
.modal-header {
|
| 444 |
+
display: flex;
|
| 445 |
+
justify-content: space-between;
|
| 446 |
+
align-items: center;
|
| 447 |
+
margin-bottom: 1.5rem;
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
.modal-close {
|
| 451 |
+
background: none;
|
| 452 |
+
border: none;
|
| 453 |
+
color: white;
|
| 454 |
+
font-size: 2rem;
|
| 455 |
+
cursor: pointer;
|
| 456 |
+
opacity: 0.7;
|
| 457 |
+
transition: opacity 0.3s ease;
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
.modal-close:hover {
|
| 461 |
+
opacity: 1;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
.admin-actions {
|
| 465 |
+
margin-top: 2rem;
|
| 466 |
+
padding-top: 2rem;
|
| 467 |
+
border-top: 1px solid rgba(255,255,255,0.2);
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
.admin-actions h4,
|
| 471 |
+
.admin-actions h5 {
|
| 472 |
+
color: white;
|
| 473 |
+
margin-bottom: 1rem;
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.action-row {
|
| 477 |
+
display: grid;
|
| 478 |
+
grid-template-columns: 1fr 1fr;
|
| 479 |
+
gap: 2rem;
|
| 480 |
+
margin-bottom: 2rem;
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
.action-group {
|
| 484 |
+
display: flex;
|
| 485 |
+
flex-direction: column;
|
| 486 |
+
gap: 0.5rem;
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
.action-group label {
|
| 490 |
+
color: rgba(255,255,255,0.9);
|
| 491 |
+
font-weight: 500;
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
.action-group input,
|
| 495 |
+
.action-group select {
|
| 496 |
+
padding: 0.5rem;
|
| 497 |
+
border: 1px solid rgba(255,255,255,0.3);
|
| 498 |
+
border-radius: 8px;
|
| 499 |
+
background: rgba(255,255,255,0.1);
|
| 500 |
+
color: white;
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
.admin-reply-section textarea {
|
| 504 |
+
width: 100%;
|
| 505 |
+
padding: 0.75rem;
|
| 506 |
+
border: 1px solid rgba(255,255,255,0.3);
|
| 507 |
+
border-radius: 8px;
|
| 508 |
+
background: rgba(255,255,255,0.1);
|
| 509 |
+
color: white;
|
| 510 |
+
resize: vertical;
|
| 511 |
+
margin-bottom: 1rem;
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
.reply-options {
|
| 515 |
+
display: flex;
|
| 516 |
+
justify-content: space-between;
|
| 517 |
+
align-items: center;
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
.reply-options label {
|
| 521 |
+
color: rgba(255,255,255,0.8);
|
| 522 |
+
display: flex;
|
| 523 |
+
align-items: center;
|
| 524 |
+
gap: 0.5rem;
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
@media (max-width: 768px) {
|
| 528 |
+
.stats-grid {
|
| 529 |
+
grid-template-columns: 1fr;
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
.filters-row {
|
| 533 |
+
grid-template-columns: 1fr;
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
.action-row {
|
| 537 |
+
grid-template-columns: 1fr;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
.table-header {
|
| 541 |
+
flex-direction: column;
|
| 542 |
+
gap: 1rem;
|
| 543 |
+
text-align: center;
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
.admin-table {
|
| 547 |
+
font-size: 0.85rem;
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
.admin-table th,
|
| 551 |
+
.admin-table td {
|
| 552 |
+
padding: 0.5rem;
|
| 553 |
+
}
|
| 554 |
+
}
|
| 555 |
+
</style>
|
| 556 |
+
|
| 557 |
+
<script>
|
| 558 |
+
// Admin ticket management functionality
|
| 559 |
+
let currentPage = 1;
|
| 560 |
+
let totalPages = 1;
|
| 561 |
+
let currentFilters = {};
|
| 562 |
+
let currentTicketId = null;
|
| 563 |
+
|
| 564 |
+
// Initialize page
|
| 565 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 566 |
+
loadStats();
|
| 567 |
+
loadCategories();
|
| 568 |
+
loadTickets();
|
| 569 |
+
});
|
| 570 |
+
|
| 571 |
+
function loadStats() {
|
| 572 |
+
fetch('/api/admin/tickets/stats')
|
| 573 |
+
.then(response => response.json())
|
| 574 |
+
.then(data => {
|
| 575 |
+
if (data.success) {
|
| 576 |
+
const stats = data.stats.overall;
|
| 577 |
+
document.getElementById('total-tickets').textContent = stats.total_tickets;
|
| 578 |
+
document.getElementById('open-tickets').textContent = stats.open_tickets;
|
| 579 |
+
document.getElementById('in-progress-tickets').textContent = stats.in_progress_tickets;
|
| 580 |
+
document.getElementById('resolved-tickets').textContent = stats.resolved_tickets;
|
| 581 |
+
}
|
| 582 |
+
})
|
| 583 |
+
.catch(error => console.error('Error loading stats:', error));
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
function loadCategories() {
|
| 587 |
+
fetch('/api/tickets/categories')
|
| 588 |
+
.then(response => response.json())
|
| 589 |
+
.then(data => {
|
| 590 |
+
if (data.success) {
|
| 591 |
+
const categorySelect = document.getElementById('category-filter');
|
| 592 |
+
data.categories.forEach(category => {
|
| 593 |
+
const option = document.createElement('option');
|
| 594 |
+
option.value = category.name;
|
| 595 |
+
option.textContent = category.name;
|
| 596 |
+
categorySelect.appendChild(option);
|
| 597 |
+
});
|
| 598 |
+
}
|
| 599 |
+
})
|
| 600 |
+
.catch(error => console.error('Failed to load categories:', error));
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
function loadTickets(page = 1) {
|
| 604 |
+
currentPage = page;
|
| 605 |
+
document.getElementById('tickets-loading').style.display = 'block';
|
| 606 |
+
document.getElementById('tickets-table-container').style.display = 'none';
|
| 607 |
+
|
| 608 |
+
// Build query parameters
|
| 609 |
+
const params = new URLSearchParams({
|
| 610 |
+
page: page,
|
| 611 |
+
limit: 20,
|
| 612 |
+
...currentFilters
|
| 613 |
+
});
|
| 614 |
+
|
| 615 |
+
fetch(`/api/admin/tickets?${params}`)
|
| 616 |
+
.then(response => response.json())
|
| 617 |
+
.then(data => {
|
| 618 |
+
document.getElementById('tickets-loading').style.display = 'none';
|
| 619 |
+
|
| 620 |
+
if (data.success) {
|
| 621 |
+
displayTickets(data.tickets);
|
| 622 |
+
updatePagination(data.pagination);
|
| 623 |
+
document.getElementById('tickets-table-container').style.display = 'block';
|
| 624 |
+
} else {
|
| 625 |
+
console.error('Failed to load tickets:', data.error);
|
| 626 |
+
}
|
| 627 |
+
})
|
| 628 |
+
.catch(error => {
|
| 629 |
+
document.getElementById('tickets-loading').style.display = 'none';
|
| 630 |
+
console.error('Error loading tickets:', error);
|
| 631 |
+
});
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
function displayTickets(tickets) {
|
| 635 |
+
const tbody = document.getElementById('tickets-table-body');
|
| 636 |
+
tbody.innerHTML = '';
|
| 637 |
+
|
| 638 |
+
tickets.forEach(ticket => {
|
| 639 |
+
const row = createTicketRow(ticket);
|
| 640 |
+
tbody.appendChild(row);
|
| 641 |
+
});
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
function createTicketRow(ticket) {
|
| 645 |
+
const tr = document.createElement('tr');
|
| 646 |
+
const createdDate = new Date(ticket.created_at).toLocaleDateString();
|
| 647 |
+
|
| 648 |
+
tr.innerHTML = `
|
| 649 |
+
<td><strong>${ticket.ticket_number}</strong></td>
|
| 650 |
+
<td>${ticket.first_name} ${ticket.last_name}<br><small>${ticket.email}</small></td>
|
| 651 |
+
<td>${ticket.subject}</td>
|
| 652 |
+
<td>${ticket.category}</td>
|
| 653 |
+
<td><span class="priority-badge priority-${ticket.priority}">${ticket.priority}</span></td>
|
| 654 |
+
<td><span class="ticket-status-badge status-${ticket.status}">${ticket.status.replace('_', ' ')}</span></td>
|
| 655 |
+
<td>${ticket.assigned_agent || 'Unassigned'}</td>
|
| 656 |
+
<td>${createdDate}</td>
|
| 657 |
+
<td>
|
| 658 |
+
<button class="btn btn-secondary" onclick="viewTicket('${ticket.ticket_number}')">View</button>
|
| 659 |
+
</td>
|
| 660 |
+
`;
|
| 661 |
+
|
| 662 |
+
return tr;
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
function updatePagination(pagination) {
|
| 666 |
+
totalPages = pagination.pages;
|
| 667 |
+
document.getElementById('page-info').textContent = `Page ${pagination.page} of ${pagination.pages}`;
|
| 668 |
+
document.getElementById('pagination-text').textContent =
|
| 669 |
+
`Showing ${((pagination.page - 1) * pagination.limit) + 1}-${Math.min(pagination.page * pagination.limit, pagination.total)} of ${pagination.total} tickets`;
|
| 670 |
+
|
| 671 |
+
document.getElementById('prev-page').disabled = pagination.page <= 1;
|
| 672 |
+
document.getElementById('next-page').disabled = pagination.page >= pagination.pages;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
function changePage(direction) {
|
| 676 |
+
const newPage = currentPage + direction;
|
| 677 |
+
if (newPage >= 1 && newPage <= totalPages) {
|
| 678 |
+
loadTickets(newPage);
|
| 679 |
+
}
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
function applyFilters() {
|
| 683 |
+
currentFilters = {
|
| 684 |
+
status: document.getElementById('status-filter').value,
|
| 685 |
+
priority: document.getElementById('priority-filter').value,
|
| 686 |
+
category: document.getElementById('category-filter').value
|
| 687 |
+
};
|
| 688 |
+
|
| 689 |
+
// Remove empty filters
|
| 690 |
+
Object.keys(currentFilters).forEach(key => {
|
| 691 |
+
if (!currentFilters[key]) {
|
| 692 |
+
delete currentFilters[key];
|
| 693 |
+
}
|
| 694 |
+
});
|
| 695 |
+
|
| 696 |
+
loadTickets(1); // Reset to page 1 when filtering
|
| 697 |
+
}
|
| 698 |
+
|
| 699 |
+
function refreshTickets() {
|
| 700 |
+
loadStats();
|
| 701 |
+
loadTickets(currentPage);
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
function viewTicket(ticketNumber) {
|
| 705 |
+
fetch(`/api/tickets/${ticketNumber}`)
|
| 706 |
+
.then(response => response.json())
|
| 707 |
+
.then(data => {
|
| 708 |
+
if (data.success) {
|
| 709 |
+
showTicketDetails(data.ticket, data.updates);
|
| 710 |
+
currentTicketId = data.ticket.id;
|
| 711 |
+
document.getElementById('ticket-detail-modal').style.display = 'flex';
|
| 712 |
+
} else {
|
| 713 |
+
alert('Failed to load ticket details: ' + data.error);
|
| 714 |
+
}
|
| 715 |
+
})
|
| 716 |
+
.catch(error => {
|
| 717 |
+
console.error('Error loading ticket:', error);
|
| 718 |
+
alert('Error loading ticket details.');
|
| 719 |
+
});
|
| 720 |
+
}
|
| 721 |
+
|
| 722 |
+
function showTicketDetails(ticket, updates) {
|
| 723 |
+
const container = document.getElementById('ticket-detail-content');
|
| 724 |
+
const createdDate = new Date(ticket.created_at).toLocaleDateString();
|
| 725 |
+
|
| 726 |
+
let updatesHtml = '';
|
| 727 |
+
if (updates && updates.length > 0) {
|
| 728 |
+
updatesHtml = '<h4 style="color: white; margin-top: 2rem;">Updates:</h4>';
|
| 729 |
+
updates.forEach(update => {
|
| 730 |
+
const updateDate = new Date(update.created_at).toLocaleDateString();
|
| 731 |
+
const isInternal = update.is_internal ? ' (Internal)' : '';
|
| 732 |
+
updatesHtml += `
|
| 733 |
+
<div style="background: rgba(255,255,255,0.1); padding: 1rem; margin: 0.5rem 0; border-radius: 8px;">
|
| 734 |
+
<div style="color: rgba(255,255,255,0.8); font-size: 0.9rem; margin-bottom: 0.5rem;">
|
| 735 |
+
${updateDate} - ${update.update_type}${isInternal}
|
| 736 |
+
</div>
|
| 737 |
+
<div style="color: white;">${update.message}</div>
|
| 738 |
+
</div>
|
| 739 |
+
`;
|
| 740 |
+
});
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
container.innerHTML = `
|
| 744 |
+
<div style="color: white;">
|
| 745 |
+
<h4>Ticket ${ticket.ticket_number}</h4>
|
| 746 |
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin: 1rem 0;">
|
| 747 |
+
<div>
|
| 748 |
+
<p><strong>Customer:</strong> ${ticket.first_name} ${ticket.last_name}</p>
|
| 749 |
+
<p><strong>Email:</strong> ${ticket.email}</p>
|
| 750 |
+
<p><strong>Status:</strong> <span class="ticket-status-badge status-${ticket.status}">${ticket.status.replace('_', ' ')}</span></p>
|
| 751 |
+
<p><strong>Priority:</strong> <span class="priority-badge priority-${ticket.priority}">${ticket.priority}</span></p>
|
| 752 |
+
</div>
|
| 753 |
+
<div>
|
| 754 |
+
<p><strong>Category:</strong> ${ticket.category}</p>
|
| 755 |
+
<p><strong>Assigned Agent:</strong> ${ticket.assigned_agent || 'Unassigned'}</p>
|
| 756 |
+
<p><strong>Created:</strong> ${createdDate}</p>
|
| 757 |
+
<p><strong>Escalation Level:</strong> ${ticket.escalation_level || 0}</p>
|
| 758 |
+
</div>
|
| 759 |
+
</div>
|
| 760 |
+
<h5 style="margin-top: 1.5rem;">Subject:</h5>
|
| 761 |
+
<p>${ticket.subject}</p>
|
| 762 |
+
<h5 style="margin-top: 1.5rem;">Description:</h5>
|
| 763 |
+
<p style="white-space: pre-wrap;">${ticket.description}</p>
|
| 764 |
+
${updatesHtml}
|
| 765 |
+
</div>
|
| 766 |
+
`;
|
| 767 |
+
|
| 768 |
+
// Set current values in form fields
|
| 769 |
+
document.getElementById('assign-agent').value = ticket.assigned_agent || '';
|
| 770 |
+
document.getElementById('update-status').value = ticket.status;
|
| 771 |
+
|
| 772 |
+
document.getElementById('modal-ticket-title').textContent = `Ticket ${ticket.ticket_number}`;
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
function assignTicket() {
|
| 776 |
+
if (!currentTicketId) return;
|
| 777 |
+
|
| 778 |
+
const agentName = document.getElementById('assign-agent').value.trim();
|
| 779 |
+
if (!agentName) {
|
| 780 |
+
alert('Please enter an agent name');
|
| 781 |
+
return;
|
| 782 |
+
}
|
| 783 |
+
|
| 784 |
+
fetch(`/api/admin/tickets/${currentTicketId}/assign`, {
|
| 785 |
+
method: 'PUT',
|
| 786 |
+
headers: { 'Content-Type': 'application/json' },
|
| 787 |
+
body: JSON.stringify({ assigned_agent: agentName })
|
| 788 |
+
})
|
| 789 |
+
.then(response => response.json())
|
| 790 |
+
.then(data => {
|
| 791 |
+
if (data.success) {
|
| 792 |
+
alert('Ticket assigned successfully!');
|
| 793 |
+
refreshTickets();
|
| 794 |
+
closeTicketModal();
|
| 795 |
+
} else {
|
| 796 |
+
alert('Failed to assign ticket: ' + data.error);
|
| 797 |
+
}
|
| 798 |
+
})
|
| 799 |
+
.catch(error => {
|
| 800 |
+
console.error('Error assigning ticket:', error);
|
| 801 |
+
alert('Error assigning ticket. Please try again.');
|
| 802 |
+
});
|
| 803 |
+
}
|
| 804 |
+
|
| 805 |
+
function updateStatus() {
|
| 806 |
+
if (!currentTicketId) return;
|
| 807 |
+
|
| 808 |
+
const newStatus = document.getElementById('update-status').value;
|
| 809 |
+
const resolutionNotes = newStatus === 'resolved' ?
|
| 810 |
+
prompt('Resolution notes (optional):') || '' : '';
|
| 811 |
+
|
| 812 |
+
fetch(`/api/admin/tickets/${currentTicketId}/status`, {
|
| 813 |
+
method: 'PUT',
|
| 814 |
+
headers: { 'Content-Type': 'application/json' },
|
| 815 |
+
body: JSON.stringify({
|
| 816 |
+
status: newStatus,
|
| 817 |
+
resolution_notes: resolutionNotes
|
| 818 |
+
})
|
| 819 |
+
})
|
| 820 |
+
.then(response => response.json())
|
| 821 |
+
.then(data => {
|
| 822 |
+
if (data.success) {
|
| 823 |
+
alert('Status updated successfully!');
|
| 824 |
+
refreshTickets();
|
| 825 |
+
closeTicketModal();
|
| 826 |
+
} else {
|
| 827 |
+
alert('Failed to update status: ' + data.error);
|
| 828 |
+
}
|
| 829 |
+
})
|
| 830 |
+
.catch(error => {
|
| 831 |
+
console.error('Error updating status:', error);
|
| 832 |
+
alert('Error updating status. Please try again.');
|
| 833 |
+
});
|
| 834 |
+
}
|
| 835 |
+
|
| 836 |
+
function addAdminReply() {
|
| 837 |
+
if (!currentTicketId) return;
|
| 838 |
+
|
| 839 |
+
const message = document.getElementById('admin-reply').value.trim();
|
| 840 |
+
const isInternal = document.getElementById('internal-note').checked;
|
| 841 |
+
|
| 842 |
+
if (!message) {
|
| 843 |
+
alert('Please enter a message');
|
| 844 |
+
return;
|
| 845 |
+
}
|
| 846 |
+
|
| 847 |
+
fetch(`/api/admin/tickets/${currentTicketId}/reply`, {
|
| 848 |
+
method: 'POST',
|
| 849 |
+
headers: { 'Content-Type': 'application/json' },
|
| 850 |
+
body: JSON.stringify({
|
| 851 |
+
message: message,
|
| 852 |
+
is_internal: isInternal
|
| 853 |
+
})
|
| 854 |
+
})
|
| 855 |
+
.then(response => response.json())
|
| 856 |
+
.then(data => {
|
| 857 |
+
if (data.success) {
|
| 858 |
+
alert('Reply added successfully!');
|
| 859 |
+
document.getElementById('admin-reply').value = '';
|
| 860 |
+
document.getElementById('internal-note').checked = false;
|
| 861 |
+
// Reload ticket details
|
| 862 |
+
const ticketNumber = document.getElementById('modal-ticket-title').textContent.replace('Ticket ', '');
|
| 863 |
+
viewTicket(ticketNumber);
|
| 864 |
+
} else {
|
| 865 |
+
alert('Failed to add reply: ' + data.error);
|
| 866 |
+
}
|
| 867 |
+
})
|
| 868 |
+
.catch(error => {
|
| 869 |
+
console.error('Error adding reply:', error);
|
| 870 |
+
alert('Error adding reply. Please try again.');
|
| 871 |
+
});
|
| 872 |
+
}
|
| 873 |
+
|
| 874 |
+
function closeTicketModal() {
|
| 875 |
+
document.getElementById('ticket-detail-modal').style.display = 'none';
|
| 876 |
+
document.getElementById('admin-reply').value = '';
|
| 877 |
+
document.getElementById('internal-note').checked = false;
|
| 878 |
+
currentTicketId = null;
|
| 879 |
+
}
|
| 880 |
+
|
| 881 |
+
// Close modal on outside click
|
| 882 |
+
document.getElementById('ticket-detail-modal').addEventListener('click', function(e) {
|
| 883 |
+
if (e.target === this) {
|
| 884 |
+
closeTicketModal();
|
| 885 |
+
}
|
| 886 |
+
});
|
| 887 |
+
</script>
|
| 888 |
+
{% endblock %}
|
templates/base.html
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
templates/chat.html
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Live Chat - Too Many Cables{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<!-- Page Header -->
|
| 7 |
+
<section class="page-header">
|
| 8 |
+
<div class="container">
|
| 9 |
+
<div class="header-card">
|
| 10 |
+
<div class="page-header-content">
|
| 11 |
+
<h1>Live Chat Support</h1>
|
| 12 |
+
<p>Get instant help from our AI-powered customer service agent</p>
|
| 13 |
+
<div class="status-indicator">
|
| 14 |
+
<div id="status-dot" class="status-dot online"></div>
|
| 15 |
+
<span id="status-text">Agent Online</span>
|
| 16 |
+
</div>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
</div>
|
| 20 |
+
</section>
|
| 21 |
+
|
| 22 |
+
<!-- Chat Interface -->
|
| 23 |
+
<section class="chat-section">
|
| 24 |
+
<div class="container">
|
| 25 |
+
<div class="chat-layout">
|
| 26 |
+
<!-- Main Chat Container -->
|
| 27 |
+
<div class="glassmorphism-container chat-main">
|
| 28 |
+
<div class="chat-container">
|
| 29 |
+
<div id="chat-messages" class="chat-messages">
|
| 30 |
+
<div class="welcome-message">
|
| 31 |
+
<div class="agent-avatar">🤖</div>
|
| 32 |
+
<div class="message-content">
|
| 33 |
+
<h4>Hi! I'm TMCBot</h4>
|
| 34 |
+
<p>I'm here to help you find the perfect cables and accessories. Ask me about products, compatibility, or any technical questions!</p>
|
| 35 |
+
<div id="welcome-tickets-info" style="display: none;">
|
| 36 |
+
<p><strong>I can see you have active support tickets. Feel free to ask me about them!</strong></p>
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
</div>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
<div class="typing-indicator" id="typing-indicator" style="display: none;">
|
| 43 |
+
<div class="typing-dots">
|
| 44 |
+
<span></span>
|
| 45 |
+
<span></span>
|
| 46 |
+
<span></span>
|
| 47 |
+
</div>
|
| 48 |
+
<span>AI agent is typing...</span>
|
| 49 |
+
</div>
|
| 50 |
+
|
| 51 |
+
<form id="chat-form" class="chat-input-form" onsubmit="event.preventDefault(); return false;" novalidate>
|
| 52 |
+
<div class="input-group">
|
| 53 |
+
<textarea
|
| 54 |
+
id="message-input"
|
| 55 |
+
placeholder="Type your message here..."
|
| 56 |
+
rows="1"
|
| 57 |
+
maxlength="1000"
|
| 58 |
+
></textarea>
|
| 59 |
+
<button type="submit" id="send-button" class="send-button" disabled>
|
| 60 |
+
<span class="send-icon">➤</span>
|
| 61 |
+
</button>
|
| 62 |
+
<button type="button" id="clear-chat" class="action-button clear-chat" title="Clear Chat History">
|
| 63 |
+
Clear
|
| 64 |
+
</button>
|
| 65 |
+
<button type="button" id="create-ticket-button" class="action-button" title="Create Support Ticket">
|
| 66 |
+
Create Ticket
|
| 67 |
+
</button>
|
| 68 |
+
<button type="button" id="end-conversation-button" class="action-button end-conversation" title="End Conversation & Save Summary">
|
| 69 |
+
End Chat
|
| 70 |
+
</button>
|
| 71 |
+
</div>
|
| 72 |
+
</form>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<!-- Ticket Sidebar -->
|
| 77 |
+
<div class="glassmorphism-container ticket-sidebar" id="ticket-sidebar">
|
| 78 |
+
<div class="sidebar-header">
|
| 79 |
+
<h3>Your Tickets</h3>
|
| 80 |
+
<button id="toggle-sidebar" class="toggle-button">↔</button>
|
| 81 |
+
</div>
|
| 82 |
+
|
| 83 |
+
<div id="user-tickets-loading" class="loading-state" style="display: none;">
|
| 84 |
+
<div class="spinner-small"></div>
|
| 85 |
+
<p>Loading tickets...</p>
|
| 86 |
+
</div>
|
| 87 |
+
|
| 88 |
+
<div id="no-tickets" class="no-tickets-state">
|
| 89 |
+
<p>No active tickets</p>
|
| 90 |
+
<button class="btn btn-primary" onclick="showCreateTicketModal()">Create Ticket</button>
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
<div id="user-tickets-list" class="tickets-list"></div>
|
| 94 |
+
|
| 95 |
+
<div class="sidebar-actions">
|
| 96 |
+
<button class="btn btn-outline" onclick="refreshUserTickets()">Refresh</button>
|
| 97 |
+
<button class="btn btn-primary" onclick="showCreateTicketModal()">New Ticket</button>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
</section>
|
| 103 |
+
|
| 104 |
+
<!-- Create Ticket Modal -->
|
| 105 |
+
<div id="create-ticket-modal" class="modal" style="display: none;">
|
| 106 |
+
<div class="modal-content glassmorphism-container">
|
| 107 |
+
<div class="modal-header">
|
| 108 |
+
<h3>Create Support Ticket</h3>
|
| 109 |
+
<button class="modal-close" onclick="closeCreateTicketModal()">×</button>
|
| 110 |
+
</div>
|
| 111 |
+
|
| 112 |
+
<form id="create-ticket-form" class="modal-body">
|
| 113 |
+
<div class="form-group">
|
| 114 |
+
<label for="ticket-subject">Subject *</label>
|
| 115 |
+
<input type="text" id="ticket-subject" placeholder="Brief description of your issue" required>
|
| 116 |
+
</div>
|
| 117 |
+
|
| 118 |
+
<div class="form-group">
|
| 119 |
+
<label for="ticket-category">Category</label>
|
| 120 |
+
<select id="ticket-category">
|
| 121 |
+
<option value="General">General Support</option>
|
| 122 |
+
<option value="Technical">Technical Issue</option>
|
| 123 |
+
<option value="Product">Product Question</option>
|
| 124 |
+
<option value="Billing">Billing</option>
|
| 125 |
+
<option value="Shipping">Shipping & Delivery</option>
|
| 126 |
+
</select>
|
| 127 |
+
</div>
|
| 128 |
+
|
| 129 |
+
<div class="form-group">
|
| 130 |
+
<label for="ticket-priority">Priority</label>
|
| 131 |
+
<select id="ticket-priority">
|
| 132 |
+
<option value="low">Low</option>
|
| 133 |
+
<option value="medium" selected>Medium</option>
|
| 134 |
+
<option value="high">High</option>
|
| 135 |
+
<option value="urgent">Urgent</option>
|
| 136 |
+
</select>
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<div class="form-group">
|
| 140 |
+
<label for="ticket-description">Description *</label>
|
| 141 |
+
<textarea id="ticket-description" rows="4" placeholder="Please describe your issue in detail..." required></textarea>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<div class="form-group">
|
| 145 |
+
<label>
|
| 146 |
+
<input type="checkbox" id="include-chat-context" checked>
|
| 147 |
+
Include recent chat conversation in ticket
|
| 148 |
+
</label>
|
| 149 |
+
</div>
|
| 150 |
+
|
| 151 |
+
<div class="modal-actions">
|
| 152 |
+
<button type="button" class="btn btn-outline" onclick="closeCreateTicketModal()">Cancel</button>
|
| 153 |
+
<button type="submit" class="btn btn-primary">Create Ticket</button>
|
| 154 |
+
</div>
|
| 155 |
+
</form>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
{% endblock %}
|
| 159 |
+
|
| 160 |
+
{% block extra_styles %}
|
| 161 |
+
<style>
|
| 162 |
+
/* Chat Layout with Sidebar */
|
| 163 |
+
.chat-layout {
|
| 164 |
+
display: grid;
|
| 165 |
+
grid-template-columns: 1fr 320px;
|
| 166 |
+
gap: 2rem;
|
| 167 |
+
max-width: 1400px;
|
| 168 |
+
margin: 0 auto;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
.chat-main {
|
| 172 |
+
min-height: 600px;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
.ticket-sidebar {
|
| 176 |
+
background: rgba(255, 255, 255, 0.12);
|
| 177 |
+
padding: 1.5rem;
|
| 178 |
+
height: fit-content;
|
| 179 |
+
max-height: 80vh;
|
| 180 |
+
overflow-y: auto;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
.sidebar-header {
|
| 184 |
+
display: flex;
|
| 185 |
+
justify-content: space-between;
|
| 186 |
+
align-items: center;
|
| 187 |
+
margin-bottom: 1rem;
|
| 188 |
+
padding-bottom: 0.5rem;
|
| 189 |
+
border-bottom: 1px solid rgba(255,255,255,0.2);
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.sidebar-header h3 {
|
| 193 |
+
color: white;
|
| 194 |
+
margin: 0;
|
| 195 |
+
font-size: 1.2rem;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.toggle-button {
|
| 199 |
+
background: rgba(255,255,255,0.2);
|
| 200 |
+
border: none;
|
| 201 |
+
color: white;
|
| 202 |
+
padding: 0.25rem 0.5rem;
|
| 203 |
+
border-radius: 4px;
|
| 204 |
+
cursor: pointer;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.loading-state {
|
| 208 |
+
text-align: center;
|
| 209 |
+
padding: 2rem;
|
| 210 |
+
color: rgba(255,255,255,0.8);
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.spinner-small {
|
| 214 |
+
border: 2px solid rgba(255,255,255,0.3);
|
| 215 |
+
border-top: 2px solid white;
|
| 216 |
+
border-radius: 50%;
|
| 217 |
+
width: 20px;
|
| 218 |
+
height: 20px;
|
| 219 |
+
animation: spin 1s linear infinite;
|
| 220 |
+
margin: 0 auto 0.5rem;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
.no-tickets-state {
|
| 224 |
+
text-align: center;
|
| 225 |
+
padding: 2rem 1rem;
|
| 226 |
+
color: rgba(255,255,255,0.8);
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
.tickets-list {
|
| 230 |
+
max-height: 400px;
|
| 231 |
+
overflow-y: auto;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.ticket-item {
|
| 235 |
+
background: rgba(255,255,255,0.1);
|
| 236 |
+
border: 1px solid rgba(255,255,255,0.2);
|
| 237 |
+
border-radius: 8px;
|
| 238 |
+
padding: 1rem;
|
| 239 |
+
margin-bottom: 0.5rem;
|
| 240 |
+
cursor: pointer;
|
| 241 |
+
transition: all 0.3s ease;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
.ticket-item:hover {
|
| 245 |
+
background: rgba(255,255,255,0.15);
|
| 246 |
+
transform: translateY(-1px);
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.ticket-item h4 {
|
| 250 |
+
color: white;
|
| 251 |
+
font-size: 0.9rem;
|
| 252 |
+
margin: 0 0 0.5rem 0;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
.ticket-item p {
|
| 256 |
+
color: rgba(255,255,255,0.8);
|
| 257 |
+
font-size: 0.8rem;
|
| 258 |
+
margin: 0.25rem 0;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
.ticket-status {
|
| 262 |
+
display: inline-block;
|
| 263 |
+
padding: 0.2rem 0.5rem;
|
| 264 |
+
border-radius: 10px;
|
| 265 |
+
font-size: 0.7rem;
|
| 266 |
+
font-weight: 500;
|
| 267 |
+
text-transform: uppercase;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
.status-open { background: rgba(52, 152, 219, 0.8); color: white; }
|
| 271 |
+
.status-in_progress { background: rgba(241, 196, 15, 0.8); color: white; }
|
| 272 |
+
.status-resolved { background: rgba(46, 204, 113, 0.8); color: white; }
|
| 273 |
+
|
| 274 |
+
.sidebar-actions {
|
| 275 |
+
margin-top: 1rem;
|
| 276 |
+
padding-top: 1rem;
|
| 277 |
+
border-top: 1px solid rgba(255,255,255,0.2);
|
| 278 |
+
display: flex;
|
| 279 |
+
gap: 0.5rem;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.sidebar-actions .btn {
|
| 283 |
+
flex: 1;
|
| 284 |
+
padding: 0.5rem;
|
| 285 |
+
font-size: 0.8rem;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
/* Enhanced Input Group */
|
| 289 |
+
.input-group {
|
| 290 |
+
position: relative;
|
| 291 |
+
display: flex;
|
| 292 |
+
align-items: flex-end;
|
| 293 |
+
gap: 0.5rem;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.action-button {
|
| 297 |
+
background: rgba(255, 255, 255, 0.2);
|
| 298 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 299 |
+
color: white;
|
| 300 |
+
padding: 0.75rem;
|
| 301 |
+
border-radius: 10px;
|
| 302 |
+
cursor: pointer;
|
| 303 |
+
font-size: 1.1rem;
|
| 304 |
+
transition: all 0.3s ease;
|
| 305 |
+
min-width: 44px;
|
| 306 |
+
height: 44px;
|
| 307 |
+
display: flex;
|
| 308 |
+
align-items: center;
|
| 309 |
+
justify-content: center;
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
.action-button:hover {
|
| 313 |
+
background: rgba(255, 255, 255, 0.3);
|
| 314 |
+
transform: translateY(-1px);
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
/* Modal Styles */
|
| 318 |
+
.modal {
|
| 319 |
+
position: fixed;
|
| 320 |
+
top: 0;
|
| 321 |
+
left: 0;
|
| 322 |
+
right: 0;
|
| 323 |
+
bottom: 0;
|
| 324 |
+
background: rgba(0,0,0,0.8);
|
| 325 |
+
display: flex;
|
| 326 |
+
justify-content: center;
|
| 327 |
+
align-items: center;
|
| 328 |
+
z-index: 2000;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
.modal-content {
|
| 332 |
+
max-width: 500px;
|
| 333 |
+
width: 95%;
|
| 334 |
+
max-height: 80vh;
|
| 335 |
+
overflow-y: auto;
|
| 336 |
+
padding: 2rem;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
.modal-header {
|
| 340 |
+
display: flex;
|
| 341 |
+
justify-content: space-between;
|
| 342 |
+
align-items: center;
|
| 343 |
+
margin-bottom: 1.5rem;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
.modal-header h3 {
|
| 347 |
+
color: white;
|
| 348 |
+
margin: 0;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.modal-close {
|
| 352 |
+
background: none;
|
| 353 |
+
border: none;
|
| 354 |
+
color: white;
|
| 355 |
+
font-size: 1.5rem;
|
| 356 |
+
cursor: pointer;
|
| 357 |
+
opacity: 0.7;
|
| 358 |
+
transition: opacity 0.3s ease;
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
.modal-close:hover {
|
| 362 |
+
opacity: 1;
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
.form-group {
|
| 366 |
+
margin-bottom: 1rem;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
.form-group label {
|
| 370 |
+
display: block;
|
| 371 |
+
color: rgba(255,255,255,0.9);
|
| 372 |
+
margin-bottom: 0.5rem;
|
| 373 |
+
font-weight: 500;
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
.form-group input,
|
| 377 |
+
.form-group select,
|
| 378 |
+
.form-group textarea {
|
| 379 |
+
width: 100%;
|
| 380 |
+
padding: 0.75rem;
|
| 381 |
+
border: 1px solid rgba(255,255,255,0.3);
|
| 382 |
+
border-radius: 8px;
|
| 383 |
+
background: rgba(255,255,255,0.1);
|
| 384 |
+
color: white;
|
| 385 |
+
font-size: 0.9rem;
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
.form-group input::placeholder,
|
| 389 |
+
.form-group textarea::placeholder {
|
| 390 |
+
color: rgba(255,255,255,0.6);
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
.form-group input[type="checkbox"] {
|
| 394 |
+
width: auto;
|
| 395 |
+
margin-right: 0.5rem;
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
.modal-actions {
|
| 399 |
+
display: flex;
|
| 400 |
+
gap: 1rem;
|
| 401 |
+
justify-content: flex-end;
|
| 402 |
+
margin-top: 2rem;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
.btn {
|
| 406 |
+
padding: 0.75rem 1.5rem;
|
| 407 |
+
border: none;
|
| 408 |
+
border-radius: 8px;
|
| 409 |
+
font-weight: 600;
|
| 410 |
+
cursor: pointer;
|
| 411 |
+
transition: all 0.3s ease;
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
.btn-primary {
|
| 415 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 416 |
+
color: white;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
.btn-outline {
|
| 420 |
+
background: transparent;
|
| 421 |
+
color: white;
|
| 422 |
+
border: 1px solid rgba(255,255,255,0.3);
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
.btn:hover {
|
| 426 |
+
transform: translateY(-1px);
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
/* Responsive Design */
|
| 430 |
+
@media (max-width: 768px) {
|
| 431 |
+
.chat-layout {
|
| 432 |
+
grid-template-columns: 1fr;
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
.ticket-sidebar {
|
| 436 |
+
order: -1;
|
| 437 |
+
max-height: 300px;
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
.sidebar-actions {
|
| 441 |
+
flex-direction: column;
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
.modal-content {
|
| 445 |
+
margin: 1rem;
|
| 446 |
+
padding: 1.5rem;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.modal-actions {
|
| 450 |
+
flex-direction: column;
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
/* Sidebar collapsed state */
|
| 455 |
+
.ticket-sidebar.collapsed {
|
| 456 |
+
width: 60px;
|
| 457 |
+
padding: 1rem 0.5rem;
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
.ticket-sidebar.collapsed .sidebar-header h3,
|
| 461 |
+
.ticket-sidebar.collapsed .tickets-list,
|
| 462 |
+
.ticket-sidebar.collapsed .sidebar-actions,
|
| 463 |
+
.ticket-sidebar.collapsed .no-tickets-state {
|
| 464 |
+
display: none;
|
| 465 |
+
}
|
| 466 |
+
</style>
|
| 467 |
+
{% endblock %}
|
| 468 |
+
|
| 469 |
+
{% block extra_scripts %}
|
| 470 |
+
<script src="{{ url_for('static', filename='chat.js') }}?v={{ cache_bust }}"></script>
|
| 471 |
+
<script>
|
| 472 |
+
// Global variables for ticket integration
|
| 473 |
+
let currentConversationId = null;
|
| 474 |
+
let userTickets = [];
|
| 475 |
+
|
| 476 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 477 |
+
// Auto-resize textarea
|
| 478 |
+
const messageInput = document.getElementById('message-input');
|
| 479 |
+
messageInput.addEventListener('input', function() {
|
| 480 |
+
this.style.height = 'auto';
|
| 481 |
+
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
|
| 482 |
+
|
| 483 |
+
// Enable/disable send button
|
| 484 |
+
const sendButton = document.getElementById('send-button');
|
| 485 |
+
sendButton.disabled = this.value.trim().length === 0;
|
| 486 |
+
});
|
| 487 |
+
|
| 488 |
+
// Load user tickets if logged in
|
| 489 |
+
loadUserTickets();
|
| 490 |
+
|
| 491 |
+
// Set up create ticket form
|
| 492 |
+
setupCreateTicketModal();
|
| 493 |
+
});
|
| 494 |
+
|
| 495 |
+
function loadUserTickets() {
|
| 496 |
+
const loading = document.getElementById('user-tickets-loading');
|
| 497 |
+
const noTickets = document.getElementById('no-tickets');
|
| 498 |
+
const ticketsList = document.getElementById('user-tickets-list');
|
| 499 |
+
|
| 500 |
+
loading.style.display = 'block';
|
| 501 |
+
noTickets.style.display = 'none';
|
| 502 |
+
ticketsList.innerHTML = '';
|
| 503 |
+
|
| 504 |
+
fetch('/api/chat/user-tickets')
|
| 505 |
+
.then(response => response.json())
|
| 506 |
+
.then(data => {
|
| 507 |
+
loading.style.display = 'none';
|
| 508 |
+
|
| 509 |
+
if (data.success && data.tickets.length > 0) {
|
| 510 |
+
userTickets = data.tickets;
|
| 511 |
+
displayUserTickets(data.tickets);
|
| 512 |
+
|
| 513 |
+
// Show ticket info in welcome message
|
| 514 |
+
const welcomeTicketsInfo = document.getElementById('welcome-tickets-info');
|
| 515 |
+
if (welcomeTicketsInfo) {
|
| 516 |
+
welcomeTicketsInfo.style.display = 'block';
|
| 517 |
+
}
|
| 518 |
+
} else {
|
| 519 |
+
noTickets.style.display = 'block';
|
| 520 |
+
}
|
| 521 |
+
})
|
| 522 |
+
.catch(error => {
|
| 523 |
+
loading.style.display = 'none';
|
| 524 |
+
noTickets.style.display = 'block';
|
| 525 |
+
console.error('Error loading user tickets:', error);
|
| 526 |
+
});
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
function displayUserTickets(tickets) {
|
| 530 |
+
const ticketsList = document.getElementById('user-tickets-list');
|
| 531 |
+
ticketsList.innerHTML = '';
|
| 532 |
+
|
| 533 |
+
tickets.forEach(ticket => {
|
| 534 |
+
const ticketElement = createTicketElement(ticket);
|
| 535 |
+
ticketsList.appendChild(ticketElement);
|
| 536 |
+
});
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
function createTicketElement(ticket) {
|
| 540 |
+
const div = document.createElement('div');
|
| 541 |
+
div.className = 'ticket-item';
|
| 542 |
+
div.onclick = () => viewTicketInChat(ticket);
|
| 543 |
+
|
| 544 |
+
const createdDate = new Date(ticket.created_at).toLocaleDateString();
|
| 545 |
+
|
| 546 |
+
div.innerHTML = `
|
| 547 |
+
<h4>#${ticket.number}: ${ticket.subject}</h4>
|
| 548 |
+
<p><span class="ticket-status status-${ticket.status}">${ticket.status.replace('_', ' ')}</span></p>
|
| 549 |
+
<p><strong>Category:</strong> ${ticket.category}</p>
|
| 550 |
+
<p><strong>Priority:</strong> ${ticket.priority}</p>
|
| 551 |
+
<p><strong>Created:</strong> ${createdDate}</p>
|
| 552 |
+
${ticket.assigned_agent ? `<p><strong>Agent:</strong> ${ticket.assigned_agent}</p>` : ''}
|
| 553 |
+
`;
|
| 554 |
+
|
| 555 |
+
return div;
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
function viewTicketInChat(ticket) {
|
| 559 |
+
// Add ticket info to chat
|
| 560 |
+
const ticketMessage = `I'd like to discuss my ticket #${ticket.number}: "${ticket.subject}". Status: ${ticket.status}, Priority: ${ticket.priority}`;
|
| 561 |
+
|
| 562 |
+
const messageInput = document.getElementById('message-input');
|
| 563 |
+
messageInput.value = ticketMessage;
|
| 564 |
+
messageInput.focus();
|
| 565 |
+
|
| 566 |
+
// Trigger input event to enable send button
|
| 567 |
+
messageInput.dispatchEvent(new Event('input'));
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
function refreshUserTickets() {
|
| 571 |
+
loadUserTickets();
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
function showCreateTicketModal() {
|
| 575 |
+
document.getElementById('create-ticket-modal').style.display = 'flex';
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
function closeCreateTicketModal() {
|
| 579 |
+
document.getElementById('create-ticket-modal').style.display = 'none';
|
| 580 |
+
document.getElementById('create-ticket-form').reset();
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
function setupCreateTicketModal() {
|
| 584 |
+
const form = document.getElementById('create-ticket-form');
|
| 585 |
+
|
| 586 |
+
form.addEventListener('submit', function(e) {
|
| 587 |
+
e.preventDefault();
|
| 588 |
+
|
| 589 |
+
const subject = document.getElementById('ticket-subject').value.trim();
|
| 590 |
+
const description = document.getElementById('ticket-description').value.trim();
|
| 591 |
+
const category = document.getElementById('ticket-category').value;
|
| 592 |
+
const priority = document.getElementById('ticket-priority').value;
|
| 593 |
+
const includeChat = document.getElementById('include-chat-context').checked;
|
| 594 |
+
|
| 595 |
+
if (!subject || !description) {
|
| 596 |
+
alert('Please fill in all required fields');
|
| 597 |
+
return;
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
const ticketData = {
|
| 601 |
+
subject,
|
| 602 |
+
description,
|
| 603 |
+
category,
|
| 604 |
+
priority,
|
| 605 |
+
conversation_id: includeChat ? currentConversationId : null
|
| 606 |
+
};
|
| 607 |
+
|
| 608 |
+
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
| 609 |
+
|
| 610 |
+
fetch('/api/chat/create-ticket', {
|
| 611 |
+
method: 'POST',
|
| 612 |
+
headers: {
|
| 613 |
+
'Content-Type': 'application/json',
|
| 614 |
+
'X-CSRFToken': csrfToken
|
| 615 |
+
},
|
| 616 |
+
body: JSON.stringify(ticketData)
|
| 617 |
+
})
|
| 618 |
+
.then(response => response.json())
|
| 619 |
+
.then(data => {
|
| 620 |
+
if (data.success) {
|
| 621 |
+
alert(data.message);
|
| 622 |
+
closeCreateTicketModal();
|
| 623 |
+
loadUserTickets(); // Refresh ticket list
|
| 624 |
+
|
| 625 |
+
// Add success message to chat
|
| 626 |
+
addMessageToChat('system', `✅ ${data.message}`);
|
| 627 |
+
} else {
|
| 628 |
+
alert('Failed to create ticket: ' + data.error);
|
| 629 |
+
}
|
| 630 |
+
})
|
| 631 |
+
.catch(error => {
|
| 632 |
+
console.error('Error creating ticket:', error);
|
| 633 |
+
alert('Error creating ticket. Please try again.');
|
| 634 |
+
});
|
| 635 |
+
});
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
// Update conversation ID when chat starts
|
| 639 |
+
function updateConversationId(conversationId) {
|
| 640 |
+
currentConversationId = conversationId;
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
// Add message to chat (utility function)
|
| 644 |
+
function addMessageToChat(role, content) {
|
| 645 |
+
const chatMessages = document.getElementById('chat-messages');
|
| 646 |
+
const messageDiv = document.createElement('div');
|
| 647 |
+
messageDiv.className = `message ${role}-message`;
|
| 648 |
+
|
| 649 |
+
if (role === 'system') {
|
| 650 |
+
messageDiv.innerHTML = `
|
| 651 |
+
<div class="system-message">
|
| 652 |
+
<div class="message-content">
|
| 653 |
+
${content}
|
| 654 |
+
</div>
|
| 655 |
+
</div>
|
| 656 |
+
`;
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
chatMessages.appendChild(messageDiv);
|
| 660 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
// Toggle sidebar
|
| 664 |
+
document.getElementById('toggle-sidebar').addEventListener('click', function() {
|
| 665 |
+
const sidebar = document.getElementById('ticket-sidebar');
|
| 666 |
+
sidebar.classList.toggle('collapsed');
|
| 667 |
+
});
|
| 668 |
+
|
| 669 |
+
// Close modal on outside click
|
| 670 |
+
document.getElementById('create-ticket-modal').addEventListener('click', function(e) {
|
| 671 |
+
if (e.target === this) {
|
| 672 |
+
closeCreateTicketModal();
|
| 673 |
+
}
|
| 674 |
+
});
|
| 675 |
+
</script>
|
| 676 |
+
{% endblock %}
|
templates/contact.html
ADDED
|
File without changes
|
templates/homepage.html
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Too Many Cables - Premium Cables & Tech Accessories{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<!-- Hero Section -->
|
| 7 |
+
<section class="hero">
|
| 8 |
+
<div class="hero-container">
|
| 9 |
+
<div class="hero-content">
|
| 10 |
+
<h1 class="hero-title">Premium Cables for Every Connection</h1>
|
| 11 |
+
<p class="hero-subtitle">From USB-C to HDMI, we've got the high-quality cables and accessories you need. Fast shipping, lifetime warranty, and 24/7 support.</p>
|
| 12 |
+
<div class="hero-actions">
|
| 13 |
+
<a href="{{ url_for('products') }}" class="btn btn-primary btn-large">Shop Now</a>
|
| 14 |
+
<a href="#" class="btn btn-outline btn-large chat-trigger-btn">Get Help 💬</a>
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
</div>
|
| 18 |
+
</section>
|
| 19 |
+
|
| 20 |
+
<!-- Features Section -->
|
| 21 |
+
<section class="features">
|
| 22 |
+
<div class="container">
|
| 23 |
+
<h2 class="section-title">Why Choose Too Many Cables?</h2>
|
| 24 |
+
<div class="features-grid">
|
| 25 |
+
<div class="feature-card">
|
| 26 |
+
<div class="feature-icon">⚡</div>
|
| 27 |
+
<h3>Fast & Reliable</h3>
|
| 28 |
+
<p>High-speed data transfer and charging with premium materials and gold-plated connectors.</p>
|
| 29 |
+
</div>
|
| 30 |
+
<div class="feature-card">
|
| 31 |
+
<div class="feature-icon">🛡️</div>
|
| 32 |
+
<h3>Lifetime Warranty</h3>
|
| 33 |
+
<p>We stand behind our products with a comprehensive lifetime warranty on all cables.</p>
|
| 34 |
+
</div>
|
| 35 |
+
<div class="feature-card">
|
| 36 |
+
<div class="feature-icon">🚚</div>
|
| 37 |
+
<h3>Fast Shipping</h3>
|
| 38 |
+
<p>Free shipping on orders over $25. Most orders ship same day and arrive within 2-3 business days.</p>
|
| 39 |
+
</div>
|
| 40 |
+
<div class="feature-card">
|
| 41 |
+
<div class="feature-icon">💬</div>
|
| 42 |
+
<h3>24/7 Support</h3>
|
| 43 |
+
<p>Get instant help with our AI-powered chat support, available 24/7 for all your questions.</p>
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
</section>
|
| 48 |
+
|
| 49 |
+
<!-- Popular Products Section -->
|
| 50 |
+
<section class="popular-products">
|
| 51 |
+
<div class="container">
|
| 52 |
+
<h2 class="section-title">Popular Products</h2>
|
| 53 |
+
<div class="products-grid">
|
| 54 |
+
<div class="product-card">
|
| 55 |
+
<div class="product-image">🔌</div>
|
| 56 |
+
<h3>USB-C to USB-C Cable</h3>
|
| 57 |
+
<p class="product-description">60W fast charging, 10Gbps data transfer, 6ft length</p>
|
| 58 |
+
<div class="product-price">$19.99</div>
|
| 59 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (1,247 reviews)</div>
|
| 60 |
+
<button class="btn btn-primary" onclick="showProductDetails('usb-c-cable')">More Details</button>
|
| 61 |
+
</div>
|
| 62 |
+
<div class="product-card">
|
| 63 |
+
<div class="product-image">📺</div>
|
| 64 |
+
<h3>4K HDMI Cable</h3>
|
| 65 |
+
<p class="product-description">8K@60Hz, 4K@120Hz, HDR10+, 10ft premium cable</p>
|
| 66 |
+
<div class="product-price">$24.99</div>
|
| 67 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (856 reviews)</div>
|
| 68 |
+
<button class="btn btn-primary" onclick="showProductDetails('4k-hdmi-cable')">More Details</button>
|
| 69 |
+
</div>
|
| 70 |
+
<div class="product-card">
|
| 71 |
+
<div class="product-image">🔋</div>
|
| 72 |
+
<h3>Multi-Port Charging Hub</h3>
|
| 73 |
+
<p class="product-description">6 ports, 100W total, USB-C PD, smart charging</p>
|
| 74 |
+
<div class="product-price">$49.99</div>
|
| 75 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (692 reviews)</div>
|
| 76 |
+
<button class="btn btn-primary" onclick="showProductDetails('charging-hub')">More Details</button>
|
| 77 |
+
</div>
|
| 78 |
+
<div class="product-card">
|
| 79 |
+
<div class="product-image">🔗</div>
|
| 80 |
+
<h3>USB-C Hub Adapter</h3>
|
| 81 |
+
<p class="product-description">7-in-1 hub with HDMI, USB-A, SD card, Ethernet</p>
|
| 82 |
+
<div class="product-price">$34.99</div>
|
| 83 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (1,104 reviews)</div>
|
| 84 |
+
<button class="btn btn-primary" onclick="showProductDetails('usb-c-hub')">More Details</button>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
<div class="section-cta">
|
| 88 |
+
<a href="{{ url_for('products') }}" class="btn btn-outline">View All Products</a>
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
</section>
|
| 92 |
+
|
| 93 |
+
<!-- Customer Support CTA -->
|
| 94 |
+
<section class="support-cta">
|
| 95 |
+
<div class="container">
|
| 96 |
+
<div class="cta-content">
|
| 97 |
+
<h2>Need Help Choosing the Right Cable?</h2>
|
| 98 |
+
<p>Our AI-powered customer service is available 24/7 to help you find the perfect solution for your needs.</p>
|
| 99 |
+
<div class="cta-actions">
|
| 100 |
+
<a href="#" class="btn btn-primary btn-large chat-trigger-btn">Start Live Chat 💬</a>
|
| 101 |
+
<a href="{{ url_for('products') }}" class="btn btn-outline btn-large">Browse Products</a>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
<div class="cta-features">
|
| 105 |
+
<div class="cta-feature">
|
| 106 |
+
<span class="feature-icon">🤖</span>
|
| 107 |
+
<span>AI-Powered Support</span>
|
| 108 |
+
</div>
|
| 109 |
+
<div class="cta-feature">
|
| 110 |
+
<span class="feature-icon">⚡</span>
|
| 111 |
+
<span>Instant Responses</span>
|
| 112 |
+
</div>
|
| 113 |
+
<div class="cta-feature">
|
| 114 |
+
<span class="feature-icon">🎯</span>
|
| 115 |
+
<span>Personalized Recommendations</span>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
</section>
|
| 120 |
+
|
| 121 |
+
<!-- Customer Reviews Section -->
|
| 122 |
+
<section class="reviews">
|
| 123 |
+
<div class="container">
|
| 124 |
+
<h2 class="section-title">What Our Customers Say</h2>
|
| 125 |
+
<div class="reviews-grid">
|
| 126 |
+
<div class="review-card">
|
| 127 |
+
<div class="review-rating">⭐⭐⭐⭐⭐</div>
|
| 128 |
+
<p class="review-text">"Amazing quality cables! My USB-C cable has been working perfectly for over a year now. The customer service chat helped me choose the right one."</p>
|
| 129 |
+
<div class="review-author">- Sarah M., Software Developer</div>
|
| 130 |
+
</div>
|
| 131 |
+
<div class="review-card">
|
| 132 |
+
<div class="review-rating">⭐⭐⭐⭐⭐</div>
|
| 133 |
+
<p class="review-text">"Fast shipping and great prices. The HDMI cable works flawlessly with my 4K monitor. Will definitely order again!"</p>
|
| 134 |
+
<div class="review-author">- Mike R., Graphic Designer</div>
|
| 135 |
+
</div>
|
| 136 |
+
<div class="review-card">
|
| 137 |
+
<div class="review-rating">⭐⭐⭐⭐⭐</div>
|
| 138 |
+
<p class="review-text">"The AI chat support is incredible! Got help immediately at 2AM when I needed to find a specific adapter. Highly recommend!"</p>
|
| 139 |
+
<div class="review-author">- Jennifer L., Marketing Manager</div>
|
| 140 |
+
</div>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
</section>
|
| 144 |
+
|
| 145 |
+
<!-- Product Details Modal -->
|
| 146 |
+
<div id="product-details-modal" class="modal" style="display: none;">
|
| 147 |
+
<div class="modal-content">
|
| 148 |
+
<div class="modal-header">
|
| 149 |
+
<h2 id="product-modal-title">Product Details</h2>
|
| 150 |
+
<span class="close-modal" onclick="closeProductModal()">×</span>
|
| 151 |
+
</div>
|
| 152 |
+
<div class="modal-body">
|
| 153 |
+
<div id="product-modal-content" class="product-details-content">
|
| 154 |
+
<div class="loading">Loading product specifications...</div>
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
|
| 160 |
+
<style>
|
| 161 |
+
.product-details-content {
|
| 162 |
+
max-height: 70vh;
|
| 163 |
+
overflow-y: auto;
|
| 164 |
+
line-height: 1.6;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.product-details-content h3 {
|
| 168 |
+
color: #2c3e50;
|
| 169 |
+
margin-top: 20px;
|
| 170 |
+
margin-bottom: 10px;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.product-details-content ul {
|
| 174 |
+
margin: 10px 0;
|
| 175 |
+
padding-left: 20px;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.product-details-content li {
|
| 179 |
+
margin: 5px 0;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
.product-details-content strong {
|
| 183 |
+
color: #2980b9;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
.loading {
|
| 187 |
+
text-align: center;
|
| 188 |
+
padding: 40px;
|
| 189 |
+
color: #7f8c8d;
|
| 190 |
+
}
|
| 191 |
+
</style>
|
| 192 |
+
|
| 193 |
+
<script>
|
| 194 |
+
async function showProductDetails(productName) {
|
| 195 |
+
const modal = document.getElementById('product-details-modal');
|
| 196 |
+
const title = document.getElementById('product-modal-title');
|
| 197 |
+
const content = document.getElementById('product-modal-content');
|
| 198 |
+
|
| 199 |
+
// Show modal with loading state
|
| 200 |
+
modal.style.display = 'flex';
|
| 201 |
+
title.textContent = 'Loading Product Details...';
|
| 202 |
+
content.innerHTML = '<div class="loading">Loading product specifications...</div>';
|
| 203 |
+
|
| 204 |
+
try {
|
| 205 |
+
const response = await fetch(`/api/product/${productName}`);
|
| 206 |
+
const data = await response.json();
|
| 207 |
+
|
| 208 |
+
if (data.success) {
|
| 209 |
+
// Set product title
|
| 210 |
+
const productTitles = {
|
| 211 |
+
'usb-c-cable': 'USB-C to USB-C Cable',
|
| 212 |
+
'4k-hdmi-cable': '4K HDMI Cable',
|
| 213 |
+
'charging-hub': 'Multi-Port Charging Hub',
|
| 214 |
+
'usb-c-hub': 'USB-C Hub Adapter'
|
| 215 |
+
};
|
| 216 |
+
|
| 217 |
+
title.textContent = productTitles[productName] || 'Product Details';
|
| 218 |
+
|
| 219 |
+
// Format and display specifications
|
| 220 |
+
const specs = data.specifications;
|
| 221 |
+
const formattedSpecs = formatProductSpecs(specs);
|
| 222 |
+
content.innerHTML = formattedSpecs;
|
| 223 |
+
} else {
|
| 224 |
+
content.innerHTML = '<div class="error">Failed to load product specifications. Please try again.</div>';
|
| 225 |
+
}
|
| 226 |
+
} catch (error) {
|
| 227 |
+
console.error('Error fetching product details:', error);
|
| 228 |
+
content.innerHTML = '<div class="error">Failed to load product specifications. Please try again.</div>';
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
function formatProductSpecs(specs) {
|
| 233 |
+
// Convert the raw specifications text into formatted HTML
|
| 234 |
+
let formatted = specs
|
| 235 |
+
.replace(/\n\n/g, '</p><p>')
|
| 236 |
+
.replace(/\n/g, '<br>')
|
| 237 |
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
| 238 |
+
.replace(/^# (.*?)$/gm, '<h3>$1</h3>')
|
| 239 |
+
.replace(/^## (.*?)$/gm, '<h4>$1</h4>')
|
| 240 |
+
.replace(/^### (.*?)$/gm, '<h5>$1</h5>');
|
| 241 |
+
|
| 242 |
+
return `<div class="specs-content"><p>${formatted}</p></div>`;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
function closeProductModal() {
|
| 246 |
+
const modal = document.getElementById('product-details-modal');
|
| 247 |
+
modal.style.display = 'none';
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
// Close modal when clicking outside
|
| 251 |
+
document.addEventListener('click', function(e) {
|
| 252 |
+
const modal = document.getElementById('product-details-modal');
|
| 253 |
+
if (e.target === modal) {
|
| 254 |
+
closeProductModal();
|
| 255 |
+
}
|
| 256 |
+
});
|
| 257 |
+
</script>
|
| 258 |
+
{% endblock %}
|
templates/index.html
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Customer Service - Talk to Agent</title>
|
| 7 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="container">
|
| 11 |
+
<header>
|
| 12 |
+
<h1>Customer Service Chat</h1>
|
| 13 |
+
<div class="status-indicator">
|
| 14 |
+
<div id="status-dot" class="status-dot"></div>
|
| 15 |
+
<span id="status-text">Checking connection...</span>
|
| 16 |
+
</div>
|
| 17 |
+
</header>
|
| 18 |
+
|
| 19 |
+
<div class="main-content">
|
| 20 |
+
<!-- Agent Info Panel -->
|
| 21 |
+
<div class="agent-panel">
|
| 22 |
+
<h3>Customer Service Agent</h3>
|
| 23 |
+
<div class="agent-info">
|
| 24 |
+
<p><strong>AI Agent:</strong> <span id="configured-model">{{ configured_model or 'Loading...' }}</span></p>
|
| 25 |
+
<p><strong>Status:</strong> <span id="agent-status">Connecting...</span></p>
|
| 26 |
+
<p class="agent-description">Your AI-powered customer service assistant is ready to help with product questions, technical support, and general inquiries.</p>
|
| 27 |
+
</div>
|
| 28 |
+
</div>
|
| 29 |
+
|
| 30 |
+
<!-- Chat Interface -->
|
| 31 |
+
<div class="chat-container">
|
| 32 |
+
<div class="chat-header">
|
| 33 |
+
<h3>Chat</h3>
|
| 34 |
+
<button id="clear-chat" class="clear-btn" title="Clear conversation">Clear</button>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<div id="chat-messages" class="chat-messages">
|
| 38 |
+
<div class="welcome-message">
|
| 39 |
+
<h4>Welcome to Customer Service!</h4>
|
| 40 |
+
<p>Hello! I'm your AI customer service assistant. How can I help you today?</p>
|
| 41 |
+
<ul>
|
| 42 |
+
<li>Ask questions about products and services</li>
|
| 43 |
+
<li>Get technical support and troubleshooting help</li>
|
| 44 |
+
<li>Your conversation history will be preserved</li>
|
| 45 |
+
<li>I can escalate complex issues to human agents</li>
|
| 46 |
+
</ul>
|
| 47 |
+
</div>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div class="chat-input-container">
|
| 51 |
+
<div class="input-group">
|
| 52 |
+
<textarea
|
| 53 |
+
id="message-input"
|
| 54 |
+
placeholder="Type your message here..."
|
| 55 |
+
rows="3"
|
| 56 |
+
disabled
|
| 57 |
+
></textarea>
|
| 58 |
+
<button id="send-btn" disabled>
|
| 59 |
+
<span class="send-icon">➤</span>
|
| 60 |
+
Send
|
| 61 |
+
</button>
|
| 62 |
+
<button type="button" id="end-conversation-button" class="action-button end-conversation" title="End Conversation & Save Summary">
|
| 63 |
+
End Chat
|
| 64 |
+
</button>
|
| 65 |
+
</div>
|
| 66 |
+
<div class="input-info">
|
| 67 |
+
<span id="char-count">0 characters</span>
|
| 68 |
+
<span class="divider">•</span>
|
| 69 |
+
<span>Press Ctrl+Enter to send</span>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
|
| 75 |
+
<!-- Loading overlay -->
|
| 76 |
+
<div id="loading-overlay" class="loading-overlay hidden">
|
| 77 |
+
<div class="loading-spinner">
|
| 78 |
+
<div class="spinner"></div>
|
| 79 |
+
<p>Thinking...</p>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
|
| 84 |
+
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
| 85 |
+
</body>
|
| 86 |
+
</html>
|
templates/products.html
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Products - Too Many Cables{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<!-- Page Header -->
|
| 7 |
+
<section class="page-header">
|
| 8 |
+
<div class="header-container">
|
| 9 |
+
<div class="header-card">
|
| 10 |
+
<h1>Our Products</h1>
|
| 11 |
+
<p>Premium cables and accessories for all your connectivity needs</p>
|
| 12 |
+
</div>
|
| 13 |
+
</div>
|
| 14 |
+
</section>
|
| 15 |
+
|
| 16 |
+
<!-- Products Grid -->
|
| 17 |
+
<section class="products-section">
|
| 18 |
+
<div class="container">
|
| 19 |
+
<div class="products-grid" id="products-grid">
|
| 20 |
+
<!-- USB-C Cables -->
|
| 21 |
+
<div class="product-card" data-category="usb-c" data-price="24.99">
|
| 22 |
+
<div class="product-image">🔌</div>
|
| 23 |
+
<h3>USB-C Premium Cable (100W)</h3>
|
| 24 |
+
<p class="product-description">100W fast charging, 10Gbps data transfer, 6ft length with braided nylon protection</p>
|
| 25 |
+
<div class="product-specs">
|
| 26 |
+
<span class="spec">100W Power</span>
|
| 27 |
+
<span class="spec">10Gbps Speed</span>
|
| 28 |
+
<span class="spec">6ft Length</span>
|
| 29 |
+
</div>
|
| 30 |
+
<div class="product-price">$24.99</div>
|
| 31 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (1,247 reviews)</div>
|
| 32 |
+
<button class="btn btn-primary" onclick="showProductDetails('usb-c-cable')">More Details</button>
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<div class="product-card" data-category="usb-c" data-price="19.99">
|
| 36 |
+
<div class="product-image">🔌</div>
|
| 37 |
+
<h3>USB-C Standard Cable (60W)</h3>
|
| 38 |
+
<p class="product-description">60W fast charging, 5Gbps data transfer, 3ft compact design with PVC jacket</p>
|
| 39 |
+
<div class="product-specs">
|
| 40 |
+
<span class="spec">60W Power</span>
|
| 41 |
+
<span class="spec">5Gbps Speed</span>
|
| 42 |
+
<span class="spec">3ft Length</span>
|
| 43 |
+
</div>
|
| 44 |
+
<div class="product-price">$19.99</div>
|
| 45 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (892 reviews)</div>
|
| 46 |
+
<button class="btn btn-primary" onclick="showProductDetails('usb-c-standard')">More Details</button>
|
| 47 |
+
</div>
|
| 48 |
+
|
| 49 |
+
<div class="product-card" data-category="usb-c" data-price="16.99">
|
| 50 |
+
<div class="product-image">🔌</div>
|
| 51 |
+
<h3>USB-A to USB-C Cable</h3>
|
| 52 |
+
<p class="product-description">Universal compatibility, 18W fast charging, 6ft braided design</p>
|
| 53 |
+
<div class="product-specs">
|
| 54 |
+
<span class="spec">18W Charging</span>
|
| 55 |
+
<span class="spec">5Gbps Speed</span>
|
| 56 |
+
<span class="spec">6ft Length</span>
|
| 57 |
+
</div>
|
| 58 |
+
<div class="product-price">$16.99</div>
|
| 59 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (734 reviews)</div>
|
| 60 |
+
<button class="btn btn-primary" onclick="showProductDetails('usb-c-to-usb-a')">More Details</button>
|
| 61 |
+
</div>
|
| 62 |
+
|
| 63 |
+
<!-- HDMI Cables -->
|
| 64 |
+
<div class="product-card" data-category="hdmi" data-price="34.99">
|
| 65 |
+
<div class="product-image">📺</div>
|
| 66 |
+
<h3>8K HDMI Cable (Ultra High Speed)</h3>
|
| 67 |
+
<p class="product-description">8K@60Hz, 4K@120Hz, HDR10+, Dolby Vision, premium 10ft cable with gold-plated connectors</p>
|
| 68 |
+
<div class="product-specs">
|
| 69 |
+
<span class="spec">8K@60Hz</span>
|
| 70 |
+
<span class="spec">48 Gbps</span>
|
| 71 |
+
<span class="spec">10ft Length</span>
|
| 72 |
+
</div>
|
| 73 |
+
<div class="product-price">$34.99</div>
|
| 74 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (856 reviews)</div>
|
| 75 |
+
<button class="btn btn-primary" onclick="showProductDetails('4k-hdmi-cable')">More Details</button>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
<div class="product-card" data-category="hdmi" data-price="24.99">
|
| 79 |
+
<div class="product-image">📺</div>
|
| 80 |
+
<h3>4K HDMI Cable (High Speed)</h3>
|
| 81 |
+
<p class="product-description">4K@60Hz, 1440p@144Hz, HDR10+, ARC audio, 6ft cable with durable PVC jacket</p>
|
| 82 |
+
<div class="product-specs">
|
| 83 |
+
<span class="spec">4K@60Hz</span>
|
| 84 |
+
<span class="spec">18 Gbps</span>
|
| 85 |
+
<span class="spec">6ft Length</span>
|
| 86 |
+
</div>
|
| 87 |
+
<div class="product-price">$24.99</div>
|
| 88 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (634 reviews)</div>
|
| 89 |
+
<button class="btn btn-primary" onclick="showProductDetails('hdmi-standard')">More Details</button>
|
| 90 |
+
</div>
|
| 91 |
+
|
| 92 |
+
<div class="product-card" data-category="hdmi" data-price="18.99">
|
| 93 |
+
<div class="product-image">📺</div>
|
| 94 |
+
<h3>HDMI to USB-C Cable</h3>
|
| 95 |
+
<p class="product-description">Direct connection from laptop to monitor, 4K@60Hz, 6ft cable</p>
|
| 96 |
+
<div class="product-specs">
|
| 97 |
+
<span class="spec">4K@60Hz</span>
|
| 98 |
+
<span class="spec">Plug & Play</span>
|
| 99 |
+
<span class="spec">6ft Length</span>
|
| 100 |
+
</div>
|
| 101 |
+
<div class="product-price">$18.99</div>
|
| 102 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (445 reviews)</div>
|
| 103 |
+
<button class="btn btn-primary" onclick="showProductDetails('hdmi-usb-c-cable')">More Details</button>
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
+
<div class="product-card" data-category="hdmi" data-price="14.99">
|
| 107 |
+
<div class="product-image">📺</div>
|
| 108 |
+
<h3>Mini HDMI to HDMI Cable</h3>
|
| 109 |
+
<p class="product-description">Connect tablets and cameras to displays, 4K@30Hz, 3ft flexible design</p>
|
| 110 |
+
<div class="product-specs">
|
| 111 |
+
<span class="spec">4K@30Hz</span>
|
| 112 |
+
<span class="spec">Mini HDMI</span>
|
| 113 |
+
<span class="spec">3ft Length</span>
|
| 114 |
+
</div>
|
| 115 |
+
<div class="product-price">$14.99</div>
|
| 116 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (312 reviews)</div>
|
| 117 |
+
<button class="btn btn-primary" onclick="showProductDetails('mini-hdmi-cable')">More Details</button>
|
| 118 |
+
</div>
|
| 119 |
+
|
| 120 |
+
<div class="product-card" data-category="hdmi" data-price="14.99">
|
| 121 |
+
<div class="product-image">📺</div>
|
| 122 |
+
<h3>Micro HDMI to HDMI Cable</h3>
|
| 123 |
+
<p class="product-description">Connect action cameras and devices to displays, 4K@30Hz, 3ft ultra-flexible</p>
|
| 124 |
+
<div class="product-specs">
|
| 125 |
+
<span class="spec">4K@30Hz</span>
|
| 126 |
+
<span class="spec">Micro HDMI</span>
|
| 127 |
+
<span class="spec">3ft Length</span>
|
| 128 |
+
</div>
|
| 129 |
+
<div class="product-price">$14.99</div>
|
| 130 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (284 reviews)</div>
|
| 131 |
+
<button class="btn btn-primary" onclick="showProductDetails('micro-hdmi-cable')">More Details</button>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<!-- Lightning Cables -->
|
| 135 |
+
<div class="product-card" data-category="lightning" data-price="19.99">
|
| 136 |
+
<div class="product-image">⚡</div>
|
| 137 |
+
<h3>Lightning to USB-A Cable (MFi)</h3>
|
| 138 |
+
<p class="product-description">Apple MFi certified, 2.4A fast charging, 6ft durable cable for iPhone and iPad</p>
|
| 139 |
+
<div class="product-specs">
|
| 140 |
+
<span class="spec">MFi Certified</span>
|
| 141 |
+
<span class="spec">2.4A Charging</span>
|
| 142 |
+
<span class="spec">6ft Length</span>
|
| 143 |
+
</div>
|
| 144 |
+
<div class="product-price">$19.99</div>
|
| 145 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (1,542 reviews)</div>
|
| 146 |
+
<button class="btn btn-primary" onclick="showProductDetails('lightning-cable')">More Details</button>
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
<!-- Charging Cables -->
|
| 150 |
+
<div class="product-card" data-category="charging" data-price="49.99">
|
| 151 |
+
<div class="product-image">🔋</div>
|
| 152 |
+
<h3>Multi-Port Charging Hub</h3>
|
| 153 |
+
<p class="product-description">6 ports, 100W total power, USB-C PD, smart charging technology</p>
|
| 154 |
+
<div class="product-specs">
|
| 155 |
+
<span class="spec">100W Total</span>
|
| 156 |
+
<span class="spec">6 Ports</span>
|
| 157 |
+
<span class="spec">Smart Charging</span>
|
| 158 |
+
</div>
|
| 159 |
+
<div class="product-price">$49.99</div>
|
| 160 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (692 reviews)</div>
|
| 161 |
+
<button class="btn btn-primary" onclick="showProductDetails('charging-hub')">More Details</button>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<div class="product-card" data-category="charging" data-price="29.99">
|
| 165 |
+
<div class="product-image">⚡</div>
|
| 166 |
+
<h3>Wireless Charging Pad</h3>
|
| 167 |
+
<p class="product-description">15W fast wireless charging, Qi-certified, LED indicator, non-slip design</p>
|
| 168 |
+
<div class="product-specs">
|
| 169 |
+
<span class="spec">15W Fast</span>
|
| 170 |
+
<span class="spec">Qi Certified</span>
|
| 171 |
+
<span class="spec">LED Status</span>
|
| 172 |
+
</div>
|
| 173 |
+
<div class="product-price">$29.99</div>
|
| 174 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (445 reviews)</div>
|
| 175 |
+
<button class="btn btn-primary" onclick="showProductDetails('wireless-charging')">More Details</button>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
<!-- Adapters & Hubs -->
|
| 179 |
+
<div class="product-card" data-category="adapters" data-price="34.99">
|
| 180 |
+
<div class="product-image">🔗</div>
|
| 181 |
+
<h3>USB-C Hub Adapter</h3>
|
| 182 |
+
<p class="product-description">7-in-1 hub with HDMI, USB-A, SD card, Ethernet, compact aluminum design</p>
|
| 183 |
+
<div class="product-specs">
|
| 184 |
+
<span class="spec">7-in-1 Hub</span>
|
| 185 |
+
<span class="spec">4K HDMI</span>
|
| 186 |
+
<span class="spec">Gigabit Ethernet</span>
|
| 187 |
+
</div>
|
| 188 |
+
<div class="product-price">$34.99</div>
|
| 189 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (1,104 reviews)</div>
|
| 190 |
+
<button class="btn btn-primary" onclick="showProductDetails('usb-c-hub')">More Details</button>
|
| 191 |
+
</div>
|
| 192 |
+
|
| 193 |
+
<div class="product-card" data-category="adapters" data-price="12.99">
|
| 194 |
+
<div class="product-image">🔄</div>
|
| 195 |
+
<h3>USB-C to HDMI Adapter</h3>
|
| 196 |
+
<p class="product-description">4K@30Hz video output, plug and play, ultra-compact design</p>
|
| 197 |
+
<div class="product-specs">
|
| 198 |
+
<span class="spec">4K@30Hz</span>
|
| 199 |
+
<span class="spec">Plug & Play</span>
|
| 200 |
+
<span class="spec">Compact</span>
|
| 201 |
+
</div>
|
| 202 |
+
<div class="product-price">$12.99</div>
|
| 203 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (789 reviews)</div>
|
| 204 |
+
<button class="btn btn-primary" onclick="showProductDetails('usb-c-hdmi-adapter')">More Details</button>
|
| 205 |
+
</div>
|
| 206 |
+
|
| 207 |
+
<!-- Audio Cables -->
|
| 208 |
+
<div class="product-card" data-category="audio" data-price="16.99">
|
| 209 |
+
<div class="product-image">🎧</div>
|
| 210 |
+
<h3>3.5mm Audio Cable</h3>
|
| 211 |
+
<p class="product-description">Premium gold-plated connectors, tangle-free design, 6ft length</p>
|
| 212 |
+
<div class="product-specs">
|
| 213 |
+
<span class="spec">Gold Plated</span>
|
| 214 |
+
<span class="spec">Tangle Free</span>
|
| 215 |
+
<span class="spec">6ft Length</span>
|
| 216 |
+
</div>
|
| 217 |
+
<div class="product-price">$16.99</div>
|
| 218 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (567 reviews)</div>
|
| 219 |
+
<button class="btn btn-primary" onclick="showProductDetails('audio-cable')">More Details</button>
|
| 220 |
+
</div>
|
| 221 |
+
|
| 222 |
+
<div class="product-card" data-category="audio" data-price="22.99">
|
| 223 |
+
<div class="product-image">🎵</div>
|
| 224 |
+
<h3>USB-C to 3.5mm Adapter</h3>
|
| 225 |
+
<p class="product-description">High-resolution audio, 32-bit DAC, compact aluminum housing</p>
|
| 226 |
+
<div class="product-specs">
|
| 227 |
+
<span class="spec">Hi-Res Audio</span>
|
| 228 |
+
<span class="spec">32-bit DAC</span>
|
| 229 |
+
<span class="spec">Aluminum</span>
|
| 230 |
+
</div>
|
| 231 |
+
<div class="product-price">$22.99</div>
|
| 232 |
+
<div class="product-rating">⭐⭐⭐⭐⭐ (423 reviews)</div>
|
| 233 |
+
<button class="btn btn-primary" onclick="showProductDetails('usb-c-audio-adapter')">More Details</button>
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
</div>
|
| 237 |
+
</section>
|
| 238 |
+
|
| 239 |
+
<!-- Product Support CTA -->
|
| 240 |
+
<section class="product-support-cta">
|
| 241 |
+
<div class="container">
|
| 242 |
+
<div class="cta-content">
|
| 243 |
+
<h2>Need Help Choosing?</h2>
|
| 244 |
+
<p>Our AI assistant can help you find the perfect cable for your specific needs.</p>
|
| 245 |
+
<a href="#" class="btn btn-primary btn-large chat-trigger-btn">Get Product Recommendations 💬</a>
|
| 246 |
+
</div>
|
| 247 |
+
</div>
|
| 248 |
+
</section>
|
| 249 |
+
|
| 250 |
+
<!-- Product Details Modal -->
|
| 251 |
+
<div id="product-details-modal" class="modal" style="display: none;">
|
| 252 |
+
<div class="modal-content">
|
| 253 |
+
<div class="modal-header">
|
| 254 |
+
<h2 id="product-modal-title">Product Details</h2>
|
| 255 |
+
<span class="close-modal" onclick="closeProductModal()">×</span>
|
| 256 |
+
</div>
|
| 257 |
+
<div class="modal-body">
|
| 258 |
+
<div id="product-modal-content" class="product-details-content">
|
| 259 |
+
<div class="loading">Loading product specifications...</div>
|
| 260 |
+
</div>
|
| 261 |
+
</div>
|
| 262 |
+
</div>
|
| 263 |
+
</div>
|
| 264 |
+
|
| 265 |
+
<style>
|
| 266 |
+
.product-details-content {
|
| 267 |
+
max-height: 70vh;
|
| 268 |
+
overflow-y: auto;
|
| 269 |
+
line-height: 1.6;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
.product-details-content h3 {
|
| 273 |
+
color: #2c3e50;
|
| 274 |
+
margin-top: 20px;
|
| 275 |
+
margin-bottom: 10px;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
.product-details-content h4 {
|
| 279 |
+
color: #34495e;
|
| 280 |
+
margin-top: 15px;
|
| 281 |
+
margin-bottom: 8px;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
.product-details-content h5 {
|
| 285 |
+
color: #5d6d7e;
|
| 286 |
+
margin-top: 12px;
|
| 287 |
+
margin-bottom: 6px;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.product-details-content ul {
|
| 291 |
+
margin: 10px 0;
|
| 292 |
+
padding-left: 20px;
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
.product-details-content li {
|
| 296 |
+
margin: 5px 0;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
.product-details-content strong {
|
| 300 |
+
color: #2980b9;
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
.loading {
|
| 304 |
+
text-align: center;
|
| 305 |
+
padding: 40px;
|
| 306 |
+
color: #7f8c8d;
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
.error {
|
| 310 |
+
text-align: center;
|
| 311 |
+
padding: 40px;
|
| 312 |
+
color: #e74c3c;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
.specs-content {
|
| 316 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 317 |
+
}
|
| 318 |
+
</style>
|
| 319 |
+
{% endblock %}
|
| 320 |
+
|
| 321 |
+
{% block extra_scripts %}
|
| 322 |
+
<script>
|
| 323 |
+
// Product filtering functionality
|
| 324 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 325 |
+
const categoryFilter = document.getElementById('category-filter');
|
| 326 |
+
const priceFilter = document.getElementById('price-filter');
|
| 327 |
+
const productsGrid = document.getElementById('products-grid');
|
| 328 |
+
const products = Array.from(productsGrid.querySelectorAll('.product-card'));
|
| 329 |
+
|
| 330 |
+
function filterProducts() {
|
| 331 |
+
const selectedCategory = categoryFilter.value;
|
| 332 |
+
const selectedPriceRange = priceFilter.value;
|
| 333 |
+
|
| 334 |
+
products.forEach(product => {
|
| 335 |
+
const category = product.dataset.category;
|
| 336 |
+
const price = parseFloat(product.dataset.price);
|
| 337 |
+
|
| 338 |
+
let showProduct = true;
|
| 339 |
+
|
| 340 |
+
// Category filter
|
| 341 |
+
if (selectedCategory && category !== selectedCategory) {
|
| 342 |
+
showProduct = false;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
// Price filter
|
| 346 |
+
if (selectedPriceRange) {
|
| 347 |
+
const [min, max] = selectedPriceRange.split('-').map(p => p === '+' ? Infinity : parseFloat(p));
|
| 348 |
+
if (selectedPriceRange.includes('+')) {
|
| 349 |
+
if (price < min) showProduct = false;
|
| 350 |
+
} else {
|
| 351 |
+
if (price < min || price > max) showProduct = false;
|
| 352 |
+
}
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
product.style.display = showProduct ? 'block' : 'none';
|
| 356 |
+
});
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
// Event listeners
|
| 360 |
+
categoryFilter.addEventListener('change', filterProducts);
|
| 361 |
+
priceFilter.addEventListener('change', filterProducts);
|
| 362 |
+
});
|
| 363 |
+
|
| 364 |
+
// Product details functionality
|
| 365 |
+
async function showProductDetails(productName) {
|
| 366 |
+
const modal = document.getElementById('product-details-modal');
|
| 367 |
+
const title = document.getElementById('product-modal-title');
|
| 368 |
+
const content = document.getElementById('product-modal-content');
|
| 369 |
+
|
| 370 |
+
// Show modal with loading state
|
| 371 |
+
modal.style.display = 'flex';
|
| 372 |
+
title.textContent = 'Loading Product Details...';
|
| 373 |
+
content.innerHTML = '<div class="loading">Loading product specifications...</div>';
|
| 374 |
+
|
| 375 |
+
try {
|
| 376 |
+
const response = await fetch(`/api/product/${productName}`);
|
| 377 |
+
const data = await response.json();
|
| 378 |
+
|
| 379 |
+
if (data.success) {
|
| 380 |
+
// Set product title
|
| 381 |
+
const productTitles = {
|
| 382 |
+
'usb-c-cable': 'USB-C to USB-C Cable',
|
| 383 |
+
'usb-c-to-usb-a': 'USB-C to USB-A Cable',
|
| 384 |
+
'4k-hdmi-cable': '4K HDMI Cable',
|
| 385 |
+
'hdmi-usb-c-cable': 'HDMI to USB-C Cable',
|
| 386 |
+
'charging-hub': 'Multi-Port Charging Hub',
|
| 387 |
+
'wireless-charging': 'Wireless Charging Pad',
|
| 388 |
+
'usb-c-hub': 'USB-C Hub Adapter',
|
| 389 |
+
'usb-c-hdmi-adapter': 'USB-C to HDMI Adapter',
|
| 390 |
+
'audio-cable': '3.5mm Audio Cable',
|
| 391 |
+
'usb-c-audio-adapter': 'USB-C to 3.5mm Adapter'
|
| 392 |
+
};
|
| 393 |
+
|
| 394 |
+
title.textContent = productTitles[productName] || 'Product Details';
|
| 395 |
+
|
| 396 |
+
// Format and display specifications
|
| 397 |
+
const specs = data.specifications;
|
| 398 |
+
const formattedSpecs = formatProductSpecs(specs);
|
| 399 |
+
content.innerHTML = formattedSpecs;
|
| 400 |
+
} else {
|
| 401 |
+
content.innerHTML = '<div class="error">Failed to load product specifications. Please try again.</div>';
|
| 402 |
+
}
|
| 403 |
+
} catch (error) {
|
| 404 |
+
console.error('Error fetching product details:', error);
|
| 405 |
+
content.innerHTML = '<div class="error">Failed to load product specifications. Please try again.</div>';
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
function formatProductSpecs(specs) {
|
| 410 |
+
// Convert the raw specifications text into formatted HTML
|
| 411 |
+
let formatted = specs
|
| 412 |
+
.replace(/\n\n/g, '</p><p>')
|
| 413 |
+
.replace(/\n/g, '<br>')
|
| 414 |
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
| 415 |
+
.replace(/^# (.*?)$/gm, '<h3>$1</h3>')
|
| 416 |
+
.replace(/^## (.*?)$/gm, '<h4>$1</h4>')
|
| 417 |
+
.replace(/^### (.*?)$/gm, '<h5>$1</h5>');
|
| 418 |
+
|
| 419 |
+
return `<div class="specs-content"><p>${formatted}</p></div>`;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
function closeProductModal() {
|
| 423 |
+
const modal = document.getElementById('product-details-modal');
|
| 424 |
+
modal.style.display = 'none';
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
// Close modal when clicking outside
|
| 428 |
+
document.addEventListener('click', function(e) {
|
| 429 |
+
const modal = document.getElementById('product-details-modal');
|
| 430 |
+
if (e.target === modal) {
|
| 431 |
+
closeProductModal();
|
| 432 |
+
}
|
| 433 |
+
});
|
| 434 |
+
</script>
|
| 435 |
+
{% endblock %}
|
templates/support.html
ADDED
|
File without changes
|
templates/tickets.html
ADDED
|
@@ -0,0 +1,940 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block title %}My Support Tickets - Too Many Cables{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block description %}Manage your support tickets and track the status of your requests with Too Many Cables customer service.{% endblock %}
|
| 6 |
+
|
| 7 |
+
{% block content %}
|
| 8 |
+
<div class="tickets-container">
|
| 9 |
+
<div class="page-header">
|
| 10 |
+
<h1 class="page-title">My Support Tickets</h1>
|
| 11 |
+
<p class="page-subtitle">Track and manage your support requests</p>
|
| 12 |
+
</div>
|
| 13 |
+
|
| 14 |
+
<!-- User must be logged in to view tickets -->
|
| 15 |
+
<div id="auth-required" class="auth-message" style="display: none;">
|
| 16 |
+
<div class="glass-card">
|
| 17 |
+
<h3>Login Required</h3>
|
| 18 |
+
<p>Please log in to view and manage your support tickets.</p>
|
| 19 |
+
<button class="btn btn-primary" onclick="showLoginModal()">Login</button>
|
| 20 |
+
</div>
|
| 21 |
+
</div>
|
| 22 |
+
|
| 23 |
+
<!-- Main tickets interface -->
|
| 24 |
+
<div id="tickets-main" style="display: none;">
|
| 25 |
+
<!-- Create New Ticket Section -->
|
| 26 |
+
<div class="glass-card ticket-creation">
|
| 27 |
+
<h3>Create New Ticket</h3>
|
| 28 |
+
<form id="create-ticket-form">
|
| 29 |
+
<div class="form-group">
|
| 30 |
+
<label for="ticket-subject">Subject</label>
|
| 31 |
+
<input type="text" id="ticket-subject" name="subject" required
|
| 32 |
+
placeholder="Brief description of your issue">
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<div class="form-group">
|
| 36 |
+
<label for="ticket-description">Description</label>
|
| 37 |
+
<textarea id="ticket-description" name="description" required
|
| 38 |
+
placeholder="Please provide detailed information about your issue"
|
| 39 |
+
rows="4"></textarea>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
<div class="form-row">
|
| 43 |
+
<div class="form-group">
|
| 44 |
+
<label for="ticket-priority">Priority</label>
|
| 45 |
+
<select id="ticket-priority" name="priority">
|
| 46 |
+
<option value="low">Low</option>
|
| 47 |
+
<option value="medium" selected>Medium</option>
|
| 48 |
+
<option value="high">High</option>
|
| 49 |
+
<option value="urgent">Urgent</option>
|
| 50 |
+
</select>
|
| 51 |
+
</div>
|
| 52 |
+
|
| 53 |
+
<div class="form-group">
|
| 54 |
+
<label for="ticket-category">Category</label>
|
| 55 |
+
<select id="ticket-category" name="category">
|
| 56 |
+
<option value="">Auto-detect</option>
|
| 57 |
+
</select>
|
| 58 |
+
</div>
|
| 59 |
+
</div>
|
| 60 |
+
|
| 61 |
+
<button type="submit" class="btn btn-primary">Create Ticket</button>
|
| 62 |
+
</form>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<!-- Tickets List -->
|
| 66 |
+
<div class="glass-card tickets-list">
|
| 67 |
+
<div class="tickets-header">
|
| 68 |
+
<h3>Your Tickets</h3>
|
| 69 |
+
<div class="tickets-filters">
|
| 70 |
+
<select id="status-filter">
|
| 71 |
+
<option value="">All Status</option>
|
| 72 |
+
<option value="open">Open</option>
|
| 73 |
+
<option value="in_progress">In Progress</option>
|
| 74 |
+
<option value="resolved">Resolved</option>
|
| 75 |
+
<option value="closed">Closed</option>
|
| 76 |
+
</select>
|
| 77 |
+
</div>
|
| 78 |
+
</div>
|
| 79 |
+
|
| 80 |
+
<div id="tickets-loading" class="loading-state">
|
| 81 |
+
<div class="spinner"></div>
|
| 82 |
+
<p>Loading your tickets...</p>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<div id="tickets-empty" class="empty-state" style="display: none;">
|
| 86 |
+
<h4>No tickets yet</h4>
|
| 87 |
+
<p>When you create support tickets, they'll appear here.</p>
|
| 88 |
+
</div>
|
| 89 |
+
|
| 90 |
+
<div id="tickets-container"></div>
|
| 91 |
+
</div>
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
<!-- Ticket Detail Modal -->
|
| 96 |
+
<div id="ticket-modal" class="modal" style="display: none;">
|
| 97 |
+
<div class="modal-content glass-card">
|
| 98 |
+
<div class="modal-header">
|
| 99 |
+
<h3 id="ticket-modal-title">Ticket Details</h3>
|
| 100 |
+
<button class="modal-close" onclick="closeTicketModal()">×</button>
|
| 101 |
+
</div>
|
| 102 |
+
|
| 103 |
+
<div class="modal-body">
|
| 104 |
+
<div id="ticket-details"></div>
|
| 105 |
+
|
| 106 |
+
<!-- SLA and Escalation Status -->
|
| 107 |
+
<div id="sla-status" class="sla-section" style="display: none;"></div>
|
| 108 |
+
|
| 109 |
+
<!-- Escalation Actions -->
|
| 110 |
+
<div id="escalation-actions" class="escalation-section" style="display: none;">
|
| 111 |
+
<h4>Escalation</h4>
|
| 112 |
+
<button class="btn btn-warning" onclick="escalateTicket()">Request Escalation</button>
|
| 113 |
+
<p style="font-size: 0.9rem; color: rgba(0,0,0,0.7); margin-top: 0.5rem;">
|
| 114 |
+
Request priority escalation if your issue needs immediate attention.
|
| 115 |
+
</p>
|
| 116 |
+
</div>
|
| 117 |
+
|
| 118 |
+
<!-- Add Update Form -->
|
| 119 |
+
<div class="ticket-update-form">
|
| 120 |
+
<h4>Add Update</h4>
|
| 121 |
+
<form id="update-ticket-form">
|
| 122 |
+
<input type="hidden" id="update-ticket-id">
|
| 123 |
+
<div class="form-group">
|
| 124 |
+
<textarea id="update-message" placeholder="Add a comment or update..."
|
| 125 |
+
rows="3" required></textarea>
|
| 126 |
+
</div>
|
| 127 |
+
<button type="submit" class="btn btn-primary">Add Update</button>
|
| 128 |
+
</form>
|
| 129 |
+
</div>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<style>
|
| 135 |
+
.tickets-container {
|
| 136 |
+
max-width: 1200px;
|
| 137 |
+
margin: 0 auto;
|
| 138 |
+
padding: 2rem;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
.page-header {
|
| 142 |
+
text-align: center;
|
| 143 |
+
margin-bottom: 3rem;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.page-title {
|
| 147 |
+
font-size: 2.5rem;
|
| 148 |
+
font-weight: 700;
|
| 149 |
+
color: white;
|
| 150 |
+
text-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
| 151 |
+
margin-bottom: 0.5rem;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.page-subtitle {
|
| 155 |
+
font-size: 1.1rem;
|
| 156 |
+
color: rgba(255,255,255,0.9);
|
| 157 |
+
font-weight: 300;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.glass-card {
|
| 161 |
+
background: rgba(255, 255, 255, 0.15);
|
| 162 |
+
backdrop-filter: blur(25px);
|
| 163 |
+
-webkit-backdrop-filter: blur(25px);
|
| 164 |
+
border-radius: 20px;
|
| 165 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 166 |
+
padding: 2rem;
|
| 167 |
+
margin-bottom: 2rem;
|
| 168 |
+
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
.ticket-creation h3,
|
| 172 |
+
.tickets-list h3 {
|
| 173 |
+
color: white;
|
| 174 |
+
margin-bottom: 1.5rem;
|
| 175 |
+
font-weight: 600;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.form-group {
|
| 179 |
+
margin-bottom: 1.5rem;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
.form-row {
|
| 183 |
+
display: grid;
|
| 184 |
+
grid-template-columns: 1fr 1fr;
|
| 185 |
+
gap: 1rem;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.form-group label {
|
| 189 |
+
display: block;
|
| 190 |
+
color: rgba(255,255,255,0.9);
|
| 191 |
+
margin-bottom: 0.5rem;
|
| 192 |
+
font-weight: 500;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
.form-group input,
|
| 196 |
+
.form-group textarea,
|
| 197 |
+
.form-group select {
|
| 198 |
+
width: 100%;
|
| 199 |
+
padding: 0.75rem 1rem;
|
| 200 |
+
border: 1px solid rgba(255,255,255,0.3);
|
| 201 |
+
border-radius: 10px;
|
| 202 |
+
background: rgba(255,255,255,0.1);
|
| 203 |
+
color: white;
|
| 204 |
+
font-size: 1rem;
|
| 205 |
+
transition: all 0.3s ease;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.form-group input:focus,
|
| 209 |
+
.form-group textarea:focus,
|
| 210 |
+
.form-group select:focus {
|
| 211 |
+
outline: none;
|
| 212 |
+
border-color: rgba(255,255,255,0.6);
|
| 213 |
+
background: rgba(255,255,255,0.2);
|
| 214 |
+
box-shadow: 0 0 0 3px rgba(255,255,255,0.1);
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
.form-group input::placeholder,
|
| 218 |
+
.form-group textarea::placeholder {
|
| 219 |
+
color: rgba(255,255,255,0.6);
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.btn {
|
| 223 |
+
padding: 0.75rem 1.5rem;
|
| 224 |
+
border: none;
|
| 225 |
+
border-radius: 10px;
|
| 226 |
+
font-weight: 600;
|
| 227 |
+
text-decoration: none;
|
| 228 |
+
display: inline-block;
|
| 229 |
+
transition: all 0.3s ease;
|
| 230 |
+
cursor: pointer;
|
| 231 |
+
font-size: 1rem;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.btn-primary {
|
| 235 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 236 |
+
color: white;
|
| 237 |
+
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.btn-primary:hover {
|
| 241 |
+
transform: translateY(-2px);
|
| 242 |
+
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.6);
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
.tickets-header {
|
| 246 |
+
display: flex;
|
| 247 |
+
justify-content: space-between;
|
| 248 |
+
align-items: center;
|
| 249 |
+
margin-bottom: 1.5rem;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.tickets-filters select {
|
| 253 |
+
padding: 0.5rem 1rem;
|
| 254 |
+
border: 1px solid rgba(255,255,255,0.3);
|
| 255 |
+
border-radius: 8px;
|
| 256 |
+
background: rgba(255,255,255,0.1);
|
| 257 |
+
color: white;
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
.ticket-item {
|
| 261 |
+
background: rgba(255,255,255,0.1);
|
| 262 |
+
border-radius: 10px;
|
| 263 |
+
padding: 1.5rem;
|
| 264 |
+
margin-bottom: 1rem;
|
| 265 |
+
border: 1px solid rgba(255,255,255,0.2);
|
| 266 |
+
cursor: pointer;
|
| 267 |
+
transition: all 0.3s ease;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
.ticket-item:hover {
|
| 271 |
+
background: rgba(255,255,255,0.2);
|
| 272 |
+
transform: translateY(-2px);
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
.ticket-header {
|
| 276 |
+
display: flex;
|
| 277 |
+
justify-content: space-between;
|
| 278 |
+
align-items: start;
|
| 279 |
+
margin-bottom: 0.5rem;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.ticket-number {
|
| 283 |
+
font-weight: 600;
|
| 284 |
+
color: white;
|
| 285 |
+
font-size: 1.1rem;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
.ticket-status {
|
| 289 |
+
padding: 0.25rem 0.75rem;
|
| 290 |
+
border-radius: 20px;
|
| 291 |
+
font-size: 0.85rem;
|
| 292 |
+
font-weight: 500;
|
| 293 |
+
text-transform: uppercase;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.status-open { background: #f39c12; color: white; }
|
| 297 |
+
.status-in_progress { background: #3498db; color: white; }
|
| 298 |
+
.status-resolved { background: #27ae60; color: white; }
|
| 299 |
+
.status-closed { background: #95a5a6; color: white; }
|
| 300 |
+
|
| 301 |
+
.ticket-subject {
|
| 302 |
+
color: white;
|
| 303 |
+
font-size: 1.1rem;
|
| 304 |
+
margin-bottom: 0.5rem;
|
| 305 |
+
font-weight: 500;
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
.ticket-meta {
|
| 309 |
+
display: flex;
|
| 310 |
+
justify-content: space-between;
|
| 311 |
+
align-items: center;
|
| 312 |
+
color: rgba(255,255,255,0.7);
|
| 313 |
+
font-size: 0.9rem;
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
.ticket-category {
|
| 317 |
+
background: rgba(255,255,255,0.2);
|
| 318 |
+
padding: 0.25rem 0.5rem;
|
| 319 |
+
border-radius: 15px;
|
| 320 |
+
font-size: 0.8rem;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
.loading-state,
|
| 324 |
+
.empty-state {
|
| 325 |
+
text-align: center;
|
| 326 |
+
padding: 2rem;
|
| 327 |
+
color: rgba(255,255,255,0.8);
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
.spinner {
|
| 331 |
+
border: 3px solid rgba(255,255,255,0.3);
|
| 332 |
+
border-top: 3px solid white;
|
| 333 |
+
border-radius: 50%;
|
| 334 |
+
width: 40px;
|
| 335 |
+
height: 40px;
|
| 336 |
+
animation: spin 1s linear infinite;
|
| 337 |
+
margin: 0 auto 1rem;
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
@keyframes spin {
|
| 341 |
+
0% { transform: rotate(0deg); }
|
| 342 |
+
100% { transform: rotate(360deg); }
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
.modal {
|
| 346 |
+
position: fixed;
|
| 347 |
+
top: 0;
|
| 348 |
+
left: 0;
|
| 349 |
+
right: 0;
|
| 350 |
+
bottom: 0;
|
| 351 |
+
background: rgba(0,0,0,0.8);
|
| 352 |
+
display: flex;
|
| 353 |
+
justify-content: center;
|
| 354 |
+
align-items: center;
|
| 355 |
+
z-index: 2000;
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
.modal-content {
|
| 359 |
+
max-width: 600px;
|
| 360 |
+
width: 90%;
|
| 361 |
+
max-height: 80vh;
|
| 362 |
+
overflow-y: auto;
|
| 363 |
+
margin: 2rem;
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
.modal-header {
|
| 367 |
+
display: flex;
|
| 368 |
+
justify-content: space-between;
|
| 369 |
+
align-items: center;
|
| 370 |
+
margin-bottom: 1.5rem;
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
.modal-close {
|
| 374 |
+
background: none;
|
| 375 |
+
border: none;
|
| 376 |
+
color: #1a202c; /* dark close button for contrast on light modal */
|
| 377 |
+
font-size: 2rem;
|
| 378 |
+
cursor: pointer;
|
| 379 |
+
opacity: 0.8;
|
| 380 |
+
transition: opacity 0.3s ease;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
.modal-close:hover {
|
| 384 |
+
opacity: 1;
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
/* Ensure modal header and title are dark for readability */
|
| 388 |
+
.modal-header h3, .modal-content h3 {
|
| 389 |
+
color: #1a202c !important;
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
.auth-message {
|
| 393 |
+
text-align: center;
|
| 394 |
+
padding: 3rem 2rem;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
.auth-message h3 {
|
| 398 |
+
color: white;
|
| 399 |
+
margin-bottom: 1rem;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.auth-message p {
|
| 403 |
+
color: rgba(255,255,255,0.8);
|
| 404 |
+
margin-bottom: 2rem;
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
.ticket-update-form {
|
| 408 |
+
margin-top: 2rem;
|
| 409 |
+
padding-top: 2rem;
|
| 410 |
+
border-top: 1px solid rgba(255,255,255,0.2);
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
.ticket-update-form h4 {
|
| 414 |
+
color: white;
|
| 415 |
+
margin-bottom: 1rem;
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
.escalation-badge {
|
| 419 |
+
background: #e74c3c;
|
| 420 |
+
color: white;
|
| 421 |
+
padding: 0.25rem 0.5rem;
|
| 422 |
+
border-radius: 12px;
|
| 423 |
+
font-size: 0.75rem;
|
| 424 |
+
font-weight: 600;
|
| 425 |
+
margin-left: 0.5rem;
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
/* Ensure ticket modal content is readable: light card with dark text */
|
| 429 |
+
.modal-content {
|
| 430 |
+
background: rgba(255,255,255,0.98) !important;
|
| 431 |
+
color: #1a202c !important;
|
| 432 |
+
border-radius: 12px;
|
| 433 |
+
padding: 1.5rem;
|
| 434 |
+
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
/* Strong override: force dark text for modal and all children to avoid inheritance issues */
|
| 438 |
+
.modal-content.glass-card,
|
| 439 |
+
.modal-content.glass-card * {
|
| 440 |
+
color: #1a202c !important;
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
/* Ensure headings and paragraphs within modal are readable */
|
| 444 |
+
.modal-content h1,
|
| 445 |
+
.modal-content h2,
|
| 446 |
+
.modal-content h3,
|
| 447 |
+
.modal-content h4,
|
| 448 |
+
.modal-content p,
|
| 449 |
+
.modal-content label,
|
| 450 |
+
.modal-content span,
|
| 451 |
+
.modal-content div {
|
| 452 |
+
color: #1a202c !important;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
.sla-section {
|
| 456 |
+
background: rgba(255,255,255,0.1);
|
| 457 |
+
border-radius: 10px;
|
| 458 |
+
padding: 1.5rem;
|
| 459 |
+
margin: 1.5rem 0;
|
| 460 |
+
border-left: 4px solid #3498db;
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
.sla-section h4 {
|
| 464 |
+
color: white;
|
| 465 |
+
margin-bottom: 1rem;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
.sla-metric {
|
| 469 |
+
display: flex;
|
| 470 |
+
justify-content: space-between;
|
| 471 |
+
align-items: center;
|
| 472 |
+
margin-bottom: 0.5rem;
|
| 473 |
+
color: rgba(255,255,255,0.9);
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.sla-status {
|
| 477 |
+
padding: 0.25rem 0.75rem;
|
| 478 |
+
border-radius: 15px;
|
| 479 |
+
font-size: 0.85rem;
|
| 480 |
+
font-weight: 500;
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
.sla-met { background: #27ae60; color: white; }
|
| 484 |
+
.sla-warning { background: #f39c12; color: white; }
|
| 485 |
+
.sla-breach { background: #e74c3c; color: white; }
|
| 486 |
+
|
| 487 |
+
.escalation-section {
|
| 488 |
+
background: rgba(255,255,255,0.1);
|
| 489 |
+
border-radius: 10px;
|
| 490 |
+
padding: 1.5rem;
|
| 491 |
+
margin: 1.5rem 0;
|
| 492 |
+
border-left: 4px solid #e74c3c;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
.escalation-section h4 {
|
| 496 |
+
color: white;
|
| 497 |
+
margin-bottom: 1rem;
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
.btn-warning {
|
| 501 |
+
background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%);
|
| 502 |
+
color: white;
|
| 503 |
+
box-shadow: 0 4px 15px rgba(243, 156, 18, 0.4);
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
.btn-warning:hover {
|
| 507 |
+
transform: translateY(-2px);
|
| 508 |
+
box-shadow: 0 8px 25px rgba(243, 156, 18, 0.6);
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
@media (max-width: 768px) {
|
| 512 |
+
.form-row {
|
| 513 |
+
grid-template-columns: 1fr;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
.tickets-header {
|
| 517 |
+
flex-direction: column;
|
| 518 |
+
gap: 1rem;
|
| 519 |
+
align-items: stretch;
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
.ticket-header {
|
| 523 |
+
flex-direction: column;
|
| 524 |
+
gap: 0.5rem;
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
.ticket-meta {
|
| 528 |
+
flex-direction: column;
|
| 529 |
+
gap: 0.5rem;
|
| 530 |
+
align-items: start;
|
| 531 |
+
}
|
| 532 |
+
}
|
| 533 |
+
</style>
|
| 534 |
+
|
| 535 |
+
<script>
|
| 536 |
+
// Ticket management functionality
|
| 537 |
+
let currentUser = null;
|
| 538 |
+
let tickets = [];
|
| 539 |
+
let categories = [];
|
| 540 |
+
|
| 541 |
+
// Initialize page
|
| 542 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 543 |
+
checkAuthentication();
|
| 544 |
+
loadCategories();
|
| 545 |
+
});
|
| 546 |
+
|
| 547 |
+
function checkAuthentication() {
|
| 548 |
+
fetch('/api/user')
|
| 549 |
+
.then(response => response.json())
|
| 550 |
+
.then(data => {
|
| 551 |
+
if (data.success && data.user) {
|
| 552 |
+
currentUser = data.user;
|
| 553 |
+
document.getElementById('auth-required').style.display = 'none';
|
| 554 |
+
document.getElementById('tickets-main').style.display = 'block';
|
| 555 |
+
loadTickets();
|
| 556 |
+
|
| 557 |
+
// Show tickets link in navigation
|
| 558 |
+
const ticketsLink = document.getElementById('tickets-link');
|
| 559 |
+
if (ticketsLink) {
|
| 560 |
+
ticketsLink.style.display = 'inline-block';
|
| 561 |
+
}
|
| 562 |
+
} else {
|
| 563 |
+
document.getElementById('auth-required').style.display = 'block';
|
| 564 |
+
document.getElementById('tickets-main').style.display = 'none';
|
| 565 |
+
}
|
| 566 |
+
})
|
| 567 |
+
.catch(error => {
|
| 568 |
+
console.error('Auth check failed:', error);
|
| 569 |
+
document.getElementById('auth-required').style.display = 'block';
|
| 570 |
+
});
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
function loadCategories() {
|
| 574 |
+
fetch('/api/tickets/categories')
|
| 575 |
+
.then(response => response.json())
|
| 576 |
+
.then(data => {
|
| 577 |
+
if (data.success) {
|
| 578 |
+
categories = data.categories;
|
| 579 |
+
const categorySelect = document.getElementById('ticket-category');
|
| 580 |
+
categorySelect.innerHTML = '<option value="">Auto-detect</option>';
|
| 581 |
+
|
| 582 |
+
categories.forEach(category => {
|
| 583 |
+
const option = document.createElement('option');
|
| 584 |
+
option.value = category.name;
|
| 585 |
+
option.textContent = category.name;
|
| 586 |
+
categorySelect.appendChild(option);
|
| 587 |
+
});
|
| 588 |
+
}
|
| 589 |
+
})
|
| 590 |
+
.catch(error => console.error('Failed to load categories:', error));
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
function loadTickets() {
|
| 594 |
+
document.getElementById('tickets-loading').style.display = 'block';
|
| 595 |
+
document.getElementById('tickets-empty').style.display = 'none';
|
| 596 |
+
document.getElementById('tickets-container').innerHTML = '';
|
| 597 |
+
|
| 598 |
+
fetch('/api/tickets/user')
|
| 599 |
+
.then(response => response.json())
|
| 600 |
+
.then(data => {
|
| 601 |
+
document.getElementById('tickets-loading').style.display = 'none';
|
| 602 |
+
|
| 603 |
+
if (data.success) {
|
| 604 |
+
tickets = data.tickets;
|
| 605 |
+
if (tickets.length === 0) {
|
| 606 |
+
document.getElementById('tickets-empty').style.display = 'block';
|
| 607 |
+
} else {
|
| 608 |
+
displayTickets(tickets);
|
| 609 |
+
}
|
| 610 |
+
} else {
|
| 611 |
+
console.error('Failed to load tickets:', data.error);
|
| 612 |
+
}
|
| 613 |
+
})
|
| 614 |
+
.catch(error => {
|
| 615 |
+
document.getElementById('tickets-loading').style.display = 'none';
|
| 616 |
+
console.error('Error loading tickets:', error);
|
| 617 |
+
});
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
function displayTickets(ticketsToShow) {
|
| 621 |
+
const container = document.getElementById('tickets-container');
|
| 622 |
+
container.innerHTML = '';
|
| 623 |
+
|
| 624 |
+
ticketsToShow.forEach(ticket => {
|
| 625 |
+
const ticketElement = createTicketElement(ticket);
|
| 626 |
+
container.appendChild(ticketElement);
|
| 627 |
+
});
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
function createTicketElement(ticket) {
|
| 631 |
+
const div = document.createElement('div');
|
| 632 |
+
div.className = 'ticket-item';
|
| 633 |
+
div.onclick = () => openTicketModal(ticket.ticket_number);
|
| 634 |
+
|
| 635 |
+
const createdDate = new Date(ticket.created_at).toLocaleDateString();
|
| 636 |
+
|
| 637 |
+
div.innerHTML = `
|
| 638 |
+
<div class="ticket-header">
|
| 639 |
+
<div class="ticket-number">${ticket.ticket_number}</div>
|
| 640 |
+
<div class="ticket-status status-${ticket.status}">${ticket.status.replace('_', ' ')}</div>
|
| 641 |
+
</div>
|
| 642 |
+
<div class="ticket-subject">${ticket.subject}</div>
|
| 643 |
+
<div class="ticket-meta">
|
| 644 |
+
<div>
|
| 645 |
+
<span class="ticket-category">${ticket.category}</span>
|
| 646 |
+
Priority: ${ticket.priority}
|
| 647 |
+
${ticket.escalation_level > 0 ? `<span class="escalation-badge">Escalated L${ticket.escalation_level}</span>` : ''}
|
| 648 |
+
</div>
|
| 649 |
+
<div>Created: ${createdDate}</div>
|
| 650 |
+
</div>
|
| 651 |
+
`;
|
| 652 |
+
|
| 653 |
+
return div;
|
| 654 |
+
}
|
| 655 |
+
|
| 656 |
+
// Form submission
|
| 657 |
+
document.getElementById('create-ticket-form').addEventListener('submit', function(e) {
|
| 658 |
+
e.preventDefault();
|
| 659 |
+
|
| 660 |
+
const formData = new FormData(e.target);
|
| 661 |
+
const ticketData = {
|
| 662 |
+
subject: formData.get('subject'),
|
| 663 |
+
description: formData.get('description'),
|
| 664 |
+
priority: formData.get('priority'),
|
| 665 |
+
category: formData.get('category')
|
| 666 |
+
};
|
| 667 |
+
|
| 668 |
+
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
| 669 |
+
|
| 670 |
+
fetch('/api/tickets/create', {
|
| 671 |
+
method: 'POST',
|
| 672 |
+
headers: {
|
| 673 |
+
'Content-Type': 'application/json',
|
| 674 |
+
'X-CSRFToken': csrfToken
|
| 675 |
+
},
|
| 676 |
+
body: JSON.stringify(ticketData)
|
| 677 |
+
})
|
| 678 |
+
.then(response => response.json())
|
| 679 |
+
.then(data => {
|
| 680 |
+
if (data.success) {
|
| 681 |
+
alert(`Ticket created successfully! Ticket number: ${data.ticket_number}`);
|
| 682 |
+
e.target.reset();
|
| 683 |
+
loadTickets(); // Refresh the tickets list
|
| 684 |
+
} else {
|
| 685 |
+
alert('Failed to create ticket: ' + data.error);
|
| 686 |
+
}
|
| 687 |
+
})
|
| 688 |
+
.catch(error => {
|
| 689 |
+
console.error('Error creating ticket:', error);
|
| 690 |
+
alert('Error creating ticket. Please try again.');
|
| 691 |
+
});
|
| 692 |
+
});
|
| 693 |
+
|
| 694 |
+
// Status filter
|
| 695 |
+
document.getElementById('status-filter').addEventListener('change', function(e) {
|
| 696 |
+
const status = e.target.value;
|
| 697 |
+
if (status === '') {
|
| 698 |
+
displayTickets(tickets);
|
| 699 |
+
} else {
|
| 700 |
+
const filtered = tickets.filter(ticket => ticket.status === status);
|
| 701 |
+
displayTickets(filtered);
|
| 702 |
+
}
|
| 703 |
+
});
|
| 704 |
+
|
| 705 |
+
function openTicketModal(ticketNumber) {
|
| 706 |
+
fetch(`/api/tickets/${ticketNumber}`)
|
| 707 |
+
.then(response => response.json())
|
| 708 |
+
.then(data => {
|
| 709 |
+
if (data.success) {
|
| 710 |
+
showTicketDetails(data.ticket, data.updates);
|
| 711 |
+
document.getElementById('update-ticket-id').value = data.ticket.id;
|
| 712 |
+
|
| 713 |
+
// Load SLA and escalation info
|
| 714 |
+
loadSLAInfo(data.ticket.id);
|
| 715 |
+
checkEscalationStatus(data.ticket.id);
|
| 716 |
+
|
| 717 |
+
document.getElementById('ticket-modal').style.display = 'flex';
|
| 718 |
+
} else {
|
| 719 |
+
alert('Failed to load ticket details: ' + data.error);
|
| 720 |
+
}
|
| 721 |
+
})
|
| 722 |
+
.catch(error => {
|
| 723 |
+
console.error('Error loading ticket:', error);
|
| 724 |
+
alert('Error loading ticket details.');
|
| 725 |
+
});
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
function loadSLAInfo(ticketId) {
|
| 729 |
+
fetch(`/api/tickets/${ticketId}/sla`)
|
| 730 |
+
.then(response => response.json())
|
| 731 |
+
.then(data => {
|
| 732 |
+
if (data.success && data.sla_metrics) {
|
| 733 |
+
showSLAStatus(data.sla_metrics);
|
| 734 |
+
}
|
| 735 |
+
})
|
| 736 |
+
.catch(error => console.error('Error loading SLA info:', error));
|
| 737 |
+
}
|
| 738 |
+
|
| 739 |
+
function showSLAStatus(sla) {
|
| 740 |
+
const slaSection = document.getElementById('sla-status');
|
| 741 |
+
let statusClass = 'sla-met';
|
| 742 |
+
let statusText = 'Within SLA';
|
| 743 |
+
|
| 744 |
+
if (!sla.sla_met) {
|
| 745 |
+
if (sla.sla_breach_hours > 0) {
|
| 746 |
+
statusClass = 'sla-breach';
|
| 747 |
+
statusText = `SLA Breached (${sla.sla_breach_hours.toFixed(1)}h over)`;
|
| 748 |
+
} else {
|
| 749 |
+
statusClass = 'sla-warning';
|
| 750 |
+
statusText = 'SLA at Risk';
|
| 751 |
+
}
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
+
slaSection.innerHTML = `
|
| 755 |
+
<h4>Service Level Agreement</h4>
|
| 756 |
+
<div class="sla-metric">
|
| 757 |
+
<span>Target Response Time:</span>
|
| 758 |
+
<span>${sla.sla_target_hours}h</span>
|
| 759 |
+
</div>
|
| 760 |
+
<div class="sla-metric">
|
| 761 |
+
<span>Hours Open:</span>
|
| 762 |
+
<span>${sla.hours_open.toFixed(1)}h</span>
|
| 763 |
+
</div>
|
| 764 |
+
<div class="sla-metric">
|
| 765 |
+
<span>Status:</span>
|
| 766 |
+
<span class="sla-status ${statusClass}">${statusText}</span>
|
| 767 |
+
</div>
|
| 768 |
+
${sla.resolution_hours ? `
|
| 769 |
+
<div class="sla-metric">
|
| 770 |
+
<span>Resolution Time:</span>
|
| 771 |
+
<span>${sla.resolution_hours.toFixed(1)}h</span>
|
| 772 |
+
</div>` : ''}
|
| 773 |
+
`;
|
| 774 |
+
slaSection.style.display = 'block';
|
| 775 |
+
}
|
| 776 |
+
|
| 777 |
+
function checkEscalationStatus(ticketId) {
|
| 778 |
+
fetch(`/api/tickets/${ticketId}/escalation-check`)
|
| 779 |
+
.then(response => response.json())
|
| 780 |
+
.then(data => {
|
| 781 |
+
if (data.success) {
|
| 782 |
+
const escalationSection = document.getElementById('escalation-actions');
|
| 783 |
+
if (data.escalation_check.needs_escalation || data.escalation_check.status !== 'resolved') {
|
| 784 |
+
escalationSection.style.display = 'block';
|
| 785 |
+
window.currentTicketId = ticketId; // Store for escalation function
|
| 786 |
+
}
|
| 787 |
+
}
|
| 788 |
+
})
|
| 789 |
+
.catch(error => console.error('Error checking escalation:', error));
|
| 790 |
+
}
|
| 791 |
+
|
| 792 |
+
function escalateTicket() {
|
| 793 |
+
if (!window.currentTicketId) {
|
| 794 |
+
alert('No ticket selected for escalation');
|
| 795 |
+
return;
|
| 796 |
+
}
|
| 797 |
+
|
| 798 |
+
const reason = prompt('Please provide a reason for escalation:');
|
| 799 |
+
if (!reason) return;
|
| 800 |
+
|
| 801 |
+
fetch(`/api/tickets/${window.currentTicketId}/escalate`, {
|
| 802 |
+
method: 'POST',
|
| 803 |
+
headers: {
|
| 804 |
+
'Content-Type': 'application/json',
|
| 805 |
+
},
|
| 806 |
+
body: JSON.stringify({
|
| 807 |
+
reason: reason
|
| 808 |
+
})
|
| 809 |
+
})
|
| 810 |
+
.then(response => response.json())
|
| 811 |
+
.then(data => {
|
| 812 |
+
if (data.success) {
|
| 813 |
+
alert('Ticket escalated successfully! Our team will prioritize your request.');
|
| 814 |
+
closeTicketModal();
|
| 815 |
+
loadTickets(); // Refresh the tickets list
|
| 816 |
+
} else {
|
| 817 |
+
alert('Failed to escalate ticket: ' + data.error);
|
| 818 |
+
}
|
| 819 |
+
})
|
| 820 |
+
.catch(error => {
|
| 821 |
+
console.error('Error escalating ticket:', error);
|
| 822 |
+
alert('Error escalating ticket. Please try again.');
|
| 823 |
+
});
|
| 824 |
+
}
|
| 825 |
+
|
| 826 |
+
function showTicketDetails(ticket, updates) {
|
| 827 |
+
const detailsContainer = document.getElementById('ticket-details');
|
| 828 |
+
const createdDate = new Date(ticket.created_at).toLocaleDateString();
|
| 829 |
+
|
| 830 |
+
let updatesHtml = '';
|
| 831 |
+
if (updates && updates.length > 0) {
|
| 832 |
+
updatesHtml = '<h4 style="margin-top: 2rem;">Updates:</h4>';
|
| 833 |
+
updates.forEach(update => {
|
| 834 |
+
const updateDate = new Date(update.created_at).toLocaleDateString();
|
| 835 |
+
updatesHtml += `
|
| 836 |
+
<div style="background: rgba(255,255,255,0.1); padding: 1rem; margin: 0.5rem 0; border-radius: 8px;">
|
| 837 |
+
<div style="font-size: 0.9rem; margin-bottom: 0.5rem;">
|
| 838 |
+
${updateDate} - ${update.update_type}
|
| 839 |
+
</div>
|
| 840 |
+
<div>${update.message}</div>
|
| 841 |
+
</div>
|
| 842 |
+
`;
|
| 843 |
+
});
|
| 844 |
+
}
|
| 845 |
+
|
| 846 |
+
detailsContainer.innerHTML = `
|
| 847 |
+
<div>
|
| 848 |
+
<h4>Ticket ${ticket.ticket_number}</h4>
|
| 849 |
+
<p><strong>Status:</strong> <span class="ticket-status status-${ticket.status}">${ticket.status.replace('_', ' ')}</span></p>
|
| 850 |
+
<p><strong>Category:</strong> ${ticket.category}</p>
|
| 851 |
+
<p><strong>Priority:</strong> ${ticket.priority}</p>
|
| 852 |
+
<p><strong>Created:</strong> ${createdDate}</p>
|
| 853 |
+
<h5 style="margin-top: 1.5rem;">Subject:</h5>
|
| 854 |
+
<p>${ticket.subject}</p>
|
| 855 |
+
<h5 style="margin-top: 1.5rem;">Description:</h5>
|
| 856 |
+
<p style="white-space: pre-wrap;">${ticket.description}</p>
|
| 857 |
+
${updatesHtml}
|
| 858 |
+
</div>
|
| 859 |
+
`;
|
| 860 |
+
|
| 861 |
+
document.getElementById('ticket-modal-title').textContent = `Ticket ${ticket.ticket_number}`;
|
| 862 |
+
|
| 863 |
+
// Force inline styles on modal content to avoid external CSS conflicts
|
| 864 |
+
const modalContent = document.querySelector('#ticket-modal .modal-content');
|
| 865 |
+
if (modalContent) {
|
| 866 |
+
modalContent.style.backgroundColor = 'rgba(255,255,255,0.98)';
|
| 867 |
+
modalContent.style.color = '#1a202c';
|
| 868 |
+
}
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
function closeTicketModal() {
|
| 872 |
+
document.getElementById('ticket-modal').style.display = 'none';
|
| 873 |
+
document.getElementById('update-message').value = '';
|
| 874 |
+
}
|
| 875 |
+
|
| 876 |
+
// Update form submission
|
| 877 |
+
document.getElementById('update-ticket-form').addEventListener('submit', function(e) {
|
| 878 |
+
e.preventDefault();
|
| 879 |
+
|
| 880 |
+
const ticketId = document.getElementById('update-ticket-id').value;
|
| 881 |
+
const message = document.getElementById('update-message').value;
|
| 882 |
+
|
| 883 |
+
fetch(`/api/tickets/${ticketId}/update`, {
|
| 884 |
+
method: 'POST',
|
| 885 |
+
headers: {
|
| 886 |
+
'Content-Type': 'application/json',
|
| 887 |
+
},
|
| 888 |
+
body: JSON.stringify({
|
| 889 |
+
message: message,
|
| 890 |
+
update_type: 'note'
|
| 891 |
+
})
|
| 892 |
+
})
|
| 893 |
+
.then(response => {
|
| 894 |
+
console.log('Response received, status:', response.status);
|
| 895 |
+
// Parse JSON regardless of status
|
| 896 |
+
return response.json().then(data => ({ data, status: response.status, ok: response.ok }));
|
| 897 |
+
})
|
| 898 |
+
.then(({ data, status, ok }) => {
|
| 899 |
+
console.log('JSON parsed, data:', data);
|
| 900 |
+
|
| 901 |
+
if (!ok) {
|
| 902 |
+
const errorMsg = data.error || `Server error: ${status}`;
|
| 903 |
+
alert('Failed to add update: ' + errorMsg);
|
| 904 |
+
return;
|
| 905 |
+
}
|
| 906 |
+
|
| 907 |
+
if (data.success) {
|
| 908 |
+
alert('Update added successfully!');
|
| 909 |
+
document.getElementById('update-message').value = '';
|
| 910 |
+
// Reload the ticket details
|
| 911 |
+
const ticketNumber = document.getElementById('ticket-modal-title').textContent.replace('Ticket ', '');
|
| 912 |
+
openTicketModal(ticketNumber);
|
| 913 |
+
} else {
|
| 914 |
+
alert('Failed to add update: ' + (data.error || 'Unknown error'));
|
| 915 |
+
}
|
| 916 |
+
})
|
| 917 |
+
.catch(error => {
|
| 918 |
+
console.error('Error adding update:', error);
|
| 919 |
+
console.error('Error details:', error.message);
|
| 920 |
+
alert('Error adding update: ' + error.message);
|
| 921 |
+
});
|
| 922 |
+
});
|
| 923 |
+
|
| 924 |
+
// Function to show login modal (assuming it exists in base template)
|
| 925 |
+
function showLoginModal() {
|
| 926 |
+
// This should trigger the login modal from the base template
|
| 927 |
+
const authBtn = document.getElementById('auth-btn');
|
| 928 |
+
if (authBtn && authBtn.textContent === 'Login') {
|
| 929 |
+
authBtn.click();
|
| 930 |
+
}
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
// Close modal on outside click
|
| 934 |
+
document.getElementById('ticket-modal').addEventListener('click', function(e) {
|
| 935 |
+
if (e.target === this) {
|
| 936 |
+
closeTicketModal();
|
| 937 |
+
}
|
| 938 |
+
});
|
| 939 |
+
</script>
|
| 940 |
+
{% endblock %}
|